BÀI 6: HÀM, THỦ TỤC VÀ TỔ CHỨC CHƯƠNG TRÌNH (METHOD)

6.1. Khái niệm Chương trình con (Subprograms)

6.1.1. Vấn đề thực tế

Hãy tưởng tượng bạn viết một phần mềm quản lý bán hàng dài 5000 dòng code.

  • Nếu viết tất cả trong một hàm Main, code sẽ cực kỳ rối rắm.

  • Nếu cần tính “Tổng tiền” ở 10 nơi khác nhau, bạn phải copy-paste công thức 10 lần. Khi công thức đổi, bạn phải sửa 10 chỗ.

6.1.2. Giải pháp: Chia để trị

Chúng ta chia chương trình lớn thành các mảnh nhỏ, mỗi mảnh làm một nhiệm vụ cụ thể. Các mảnh này gọi là Chương trình con.

  • Trong Pascal gọi là Procedure/Function.

  • Trong C# gọi chung là Phương thức (Method).

Lợi ích:

  • Tái sử dụng (Reuse): Viết 1 lần, gọi dùng nhiều lần.

  • Dễ bảo trì: Sửa lỗi ở 1 chỗ, cập nhật cho toàn bộ chương trình.

  • Code gọn gàng: Hàm Main chỉ đóng vai trò điều phối, giống như Tổng giám đốc chỉ đạo các Trưởng phòng.

6.2.1. Cấu trúc Hàm và Thủ tục trong C# (Methods)

Trong C#, chúng ta định nghĩa Method bên trong class Program (nhưng bên ngoài Main).

Cú pháp tổng quát:

C#

 
[Phạm_vi] static [Kiểu_trả_về] [Tên_hàm] ( [Danh_sách_tham_số] )
{
    // Thân hàm: Các lệnh xử lý
    // return [giá_trị]; (nếu có kiểu trả về)
}

6.2.2. “Thủ tục” (Void Method – Không trả về giá trị)

Dùng khi muốn thực hiện một hành động (In menu, Xóa màn hình, Lưu file) mà không cần lấy lại kết quả.

  • Từ khóa: void

  • Kết thúc: Hàm tự kết thúc khi chạy hết lệnh hoặc gặp return; (không có giá trị).

  • Ví dụ:

    C#
    // Định nghĩa thủ tục in Menu
    static void HienThiMenu()
    {
        Console.WriteLine("=== MENU ===");
        Console.WriteLine("1. Đăng nhập");
        Console.WriteLine("2. Thoát");
    }
    
    // Cách gọi trong Main:
    HienThiMenu();
    

6.2.2. “Hàm” (Value-Returning Method – Có trả về giá trị)

Dùng khi muốn tính toán và nhận lại kết quả để dùng tiếp (Tính tổng, Kiểm tra đúng sai, Tìm Max).

  • Từ khóa: int, double, string, bool… (tùy kết quả muốn nhận).

  • Bắt buộc: Phải có lệnh return giá_trị; ở cuối.

  • Ví dụ:

    C#
    // Định nghĩa hàm tính bình phương
    static int TinhBinhPhuong(int so)
    {
        int ketQua = so * so;
        return ketQua; // Trả con số 25 về cho người gọi
    }
    
    // Cách gọi trong Main:
    int x = 5;
    int kp = TinhBinhPhuong(x); // kp sẽ nhận giá trị 25
    Console.WriteLine(kp);

6.3. Tham trị và Tham biến (Parameters)

Đây là phần khó nhất và quan trọng nhất của chương.

6.3.1. Tham trị (Pass by Value) – Mặc định

  • Cơ chế: Copy giá trị.

  • Khi truyền biến vào hàm, máy tính tạo ra một bản sao của biến đó.

  • Mọi thay đổi bên trong hàm chỉ tác động lên bản sao, bản gốc không đổi.

  • Ví dụ (Không hoán đổi được):

    C#
    static void HoanVi_ThamTri(int a, int b)
    {
        int temp = a; a = b; b = temp;
        // Bên trong này a, b đã đổi chỗ
    }
    // Main:
    int x = 1, y = 2;
    HoanVi_ThamTri(x, y);
    // Kết quả: x vẫn là 1, y vẫn là 2 (Vì hàm chỉ đổi bản sao)
    

6.3.2. Tham biến (Pass by Reference) – Dùng ref

  • Cơ chế: Truyền địa chỉ (Trao chìa khóa nhà).

  • Hàm nhận trực tiếp địa chỉ ô nhớ của biến gốc. Mọi thay đổi bên trong hàm làm thay đổi luôn biến gốc.

  • Cú pháp: Thêm từ khóa ref khi khai báo và khi gọi.

  • Ví dụ (Hoán đổi thành công):

    C#
    static void HoanVi_ThamBien(ref int a, ref int b)
    {
        int temp = a; a = b; b = temp;
    }
    // Main:
    int x = 1, y = 2;
    HoanVi_ThamBien(ref x, ref y);
    // Kết quả: x thành 2, y thành 1.

6.4. Biến toàn cục và Biến địa phương (Scope)

6.4.1. Biến địa phương (Local Variable)

  • Khai báo: Bên trong một hàm (hoặc trong vòng lặp, if).

  • Phạm vi: Chỉ sống và được nhìn thấy bên trong hàm đó.

  • Ra khỏi hàm: Biến bị hủy, giải phóng bộ nhớ.

  • Ví dụ: Biến i trong vòng lặp for không thể được gọi ở ngoài vòng lặp.

6.4.2. Biến toàn cục (Global Variable / Class Field)

  • Khai báo: Bên trong class nhưng bên ngoài tất cả các hàm (thường đặt đầu class).

  • Trong C# Console: Phải có từ khóa static.

  • Phạm vi: Tất cả các hàm trong chương trình đều có thể nhìn thấy và sửa đổi nó.

  • Lưu ý: Hạn chế dùng biến toàn cục vì khó kiểm soát lỗi (hàm này sửa làm sai hàm kia).

Bài tập thực hành

Bài 6.1: Máy tính Module hóa (Refactoring)

Mục tiêu: Rèn luyện kỹ năng viết hàm trả về giá trị (return) và tái sử dụng code.

  • Yêu cầu: Viết lại chương trình máy tính (+, -, *, /).

  • Cấu trúc:

    • Hàm NhapSo(string thongBao): Trả về số nguyên người dùng nhập.

    • Hàm TinhTong(int a, int b): Trả về a + b.

    • Hàm TinhHieu, TinhTich, TinhThuong.

    • Hàm Main: Chỉ gọi các hàm trên, không viết logic tính toán.

1. Phân tích đề bài

Thay vì viết tất cả lệnh nhập, cộng, trừ, nhân, chia trong Main, ta sẽ tách chúng ra:

    • Input: Cần một hàm để nhập số (vì ta phải nhập 2 lần cho số a và số b, viết 1 hàm dùng 2 lần sẽ gọn hơn).

    • Process: Mỗi phép tính (+, -, *, /) là một hàm riêng biệt trả về kết quả.

    • Output: Hàm Main chỉ đóng vai trò “nhạc trưởng” gọi các hàm trên.

Tham khảo Getty Images

2. Hướng dẫn từng bước

  • Bước 1: Viết hàm Nhập liệu (NhapSo)

    • Hàm cần nhận vào một câu thông báo (ví dụ: “Nhập số a: “).

    • Hàm trả về số nguyên (int) người dùng nhập.

  • Bước 2: Viết các hàm Tính toán

    • Viết 4 hàm: Tong, Hieu, Tich, Thuong.

    • Mỗi hàm nhận 2 tham số (int a, int b).

    • Trả về kết quả tương ứng. Lưu ý: Phép chia nên trả về double và kiểm tra mẫu số khác 0.

  • Bước 3: Viết hàm Main

    • Khai báo biến a, b.

    • Gọi NhapSo để lấy giá trị cho ab.

    • Gọi các hàm tính toán và in kết quả ra màn hình.

3. Mã nguồn mẫu

C#
 
using System;

class Program
{
    // Bước 1: Hàm nhập số (Tái sử dụng code nhập liệu)
    static int NhapSo(string thongBao)
    {
        Console.Write(thongBao);
        return int.Parse(Console.ReadLine());
    }

    // Bước 2: Các hàm tính toán
    static int TinhTong(int a, int b)
    {
        return a + b;
    }

    static int TinhHieu(int a, int b)
    {
        return a - b;
    }

    static double TinhThuong(int a, int b)
    {
        if (b == 0) return 0; // Tránh lỗi chia cho 0
        return (double)a / b; // Ép kiểu để ra số thực
    }

    // Bước 3: Hàm Main điều phối
    static void Main(string[] args)
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8;
        
        // Gọi hàm nhập 2 lần
        int so1 = NhapSo("Nhập số thứ nhất: ");
        int so2 = NhapSo("Nhập số thứ hai: ");

        // Gọi hàm tính toán và in kết quả
        Console.WriteLine("Tổng: " + TinhTong(so1, so2));
        Console.WriteLine("Hiệu: " + TinhHieu(so1, so2));
        Console.WriteLine("Thương: " + TinhThuong(so1, so2));

        Console.ReadKey();
    }
}

Bài 6.2: Kiểm tra Số nguyên tố (Hàm trả về bool)

Mục tiêu: Hiểu cách sử dụng kiểu trả về bool (True/False) để kiểm tra logic.

  • Yêu cầu: Viết hàm bool KiemTraSNT(int n).

  • Trong Main: Duyệt từ 1 đến 100, gọi hàm KiemTraSNT(i), nếu true thì in ra i.

Hướng dẫn làm bài

1. Phân tích đề bài

  • Input: Một số nguyên n.

  • Logic: Số nguyên tố là số > 1 và chỉ chia hết cho 1 và chính nó.

  • Output: true nếu là SNT, false nếu không phải.

  • Ứng dụng: Trong Main, ta duyệt các số từ 1 đến 100, số nào mà hàm trả về true thì in ra.

2. Hướng dẫn từng bước

  • Bước 1: Viết hàm KiemTraSNT

    • Tham số: int n.

    • Kiểu trả về: bool.

    • Logic:

      • Nếu n < 2 -> return false.

      • Dùng vòng lặp for chạy từ 2 đến căn bậc 2 của n.

      • Nếu n chia hết cho bất kỳ số nào (n % i == 0) -> return false ngay lập tức.

      • Nếu chạy hết vòng lặp mà không chia hết cho ai -> return true.

  • Bước 2: Viết hàm Main

    • Dùng vòng lặp for chạy i từ 1 đến 100.

    • Trong vòng lặp, gọi if (KiemTraSNT(i)) thì in i ra.

3. Mã nguồn mẫu

C#

    // Hàm kiểm tra logic
    static bool KiemTraSNT(int n)
    {
        if (n < 2) return false; // SNT phải lớn hơn 1
        
        for (int i = 2; i <= Math.Sqrt(n); i++)
        {
            if (n % i == 0) return false; // Tìm thấy ước số -> Không phải SNT
        }
        
        return true; // Chạy hết vòng lặp mà không có ước số -> Là SNT
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Các số nguyên tố từ 1 đến 100:");
        for (int i = 1; i <= 100; i++)
        {
            // Gọi hàm trong điều kiện IF
            if (KiemTraSNT(i) == true)
            {
                Console.Write(i + " ");
            }
        }
        Console.ReadKey();
    }

Bài 6.3: Hoán vị (Tham biến ref)

Mục tiêu: Phân biệt rõ ràng sự khác nhau giữa Tham trị (giá trị không đổi sau khi ra khỏi hàm) và Tham biến (giá trị thay đổi).

  • Yêu cầu: Viết hàm Swap(ref int a, ref int b). Nhập 2 số từ bàn phím, in ra trước và sau khi gọi hàm Swap để chứng minh giá trị đã thay đổi.

Hướng dẫn làm bài

1. Phân tích đề bài

  • Ta cần viết một hàm để đổi chỗ giá trị của 2 biến ab.

  • Nếu không dùng từ khóa ref, hàm chỉ đổi chỗ 2 bản sao (copy), biến gốc ở Main vẫn y nguyên.

  • Bắt buộc dùng ref để hàm truy cập trực tiếp vào ô nhớ của biến gốc.

2. Hướng dẫn từng bước

  • Bước 1: Viết hàm Swap (Hoán vị)

    • Tham số: ref int a, ref int b.

    • Logic: Dùng một biến trung gian temp.

      • temp = a;

      • a = b;

      • b = temp;

  • Bước 2: Viết hàm Main

    • Khai báo x = 10, y = 20.

    • In giá trị trước khi hoán vị.

    • Gọi hàm: Swap(ref x, ref y); (Lưu ý phải có từ khóa ref khi gọi).

    • In giá trị sau khi hoán vị để kiểm chứng.

3. Mã nguồn mẫu

C#

    // Hàm nhận vào THAM BIẾN (ref)
    static void Swap(ref int a, ref int b)
    {
        int temp = a; // Cất a vào biến tạm
        a = b;        // Gán b cho a
        b = temp;     // Gán biến tạm (a cũ) cho b
    }

    static void Main(string[] args)
    {
        int x = 10;
        int y = 20;

        Console.WriteLine("Trước khi Swap: x = {0}, y = {1}", x, y);

        // Gọi hàm có ref
        Swap(ref x, ref y);

        Console.WriteLine("Sau khi Swap:  x = {0}, y = {1}", x, y);
        // Kết quả x phải là 20, y phải là 10
        
        Console.ReadKey();
    }

Bài 6.4: Quản lý điểm (Biến toàn cục & Hàm)

Mục tiêu: Hiểu về phạm vi của biến (Scope). Sử dụng biến toàn cục để chia sẻ dữ liệu giữa các hàm mà không cần truyền tham số quá nhiều.

  • Yêu cầu:

    • Khai báo biến toàn cục static double diemToan, diemVan;

    • Hàm void NhapDiem(): Cho người dùng nhập và gán vào biến toàn cục.

    • Hàm double TinhTB(): Trả về (Toan + Van) / 2.

    • Hàm void XepLoai(): Gọi TinhTB() và in ra Giỏi/Khá/TB.

    • Hàm Main: Gọi lần lượt 3 hàm trên.

Hướng dẫn làm bài

1. Phân tích đề bài

  • Dữ liệu: diemToan, diemVan cần được dùng chung bởi cả hàm Nhập, hàm Tính và hàm Hiển thị. -> Khai báo chúng là Biến toàn cục (Global variables).

  • Các hàm:

    • NhapDiem(): Chỉ thực hiện nhập, không trả về (void).

    • TinhTB(): Tính toán và trả về số thực (double).

    • XepLoai(): Gọi TinhTB để lấy kết quả rồi in ra màn hình.

2. Hướng dẫn từng bước

  • Bước 1: Khai báo biến toàn cục

    • Đặt bên trong class Program nhưng bên ngoài các hàm.

    • Phải có từ khóa static (trong Console App).

  • Bước 2: Viết hàm NhapDiem

    • Gán giá trị nhập từ bàn phím thẳng vào các biến toàn cục diemToan, diemVan.

  • Bước 3: Viết hàm TinhTB

    • Sử dụng biến toàn cục để tính: (diemToan + diemVan) / 2.

    • Trả về kết quả.

  • Bước 4: Viết hàm XepLoai

    • Khai báo biến dtb = TinhTB().

    • Dùng if...else để in ra Giỏi/Khá/TB dựa trên dtb.

3. Mã nguồn mẫu

C#

class Program
{
    // 1. BIẾN TOÀN CỤC (Sống trong suốt chương trình)
    static double diemToan;
    static double diemVan;

    // 2. Hàm Nhập (Thay đổi giá trị biến toàn cục)
    static void NhapDiem()
    {
        Console.Write("Nhập điểm Toán: ");
        diemToan = double.Parse(Console.ReadLine());

        Console.Write("Nhập điểm Văn: ");
        diemVan = double.Parse(Console.ReadLine());
    }

    // 3. Hàm Tính (Sử dụng biến toàn cục)
    static double TinhTB()
    {
        return (diemToan + diemVan) / 2;
    }

    // 4. Hàm Xếp loại
    static void XepLoai()
    {
        // Gọi hàm TinhTB để lấy số liệu
        double dtb = TinhTB();
        
        Console.WriteLine("\n--- KẾT QUẢ ---");
        Console.WriteLine("Điểm trung bình: " + dtb);

        if (dtb >= 8.0) Console.WriteLine("Xếp loại: GIỎI");
        else if (dtb >= 6.5) Console.WriteLine("Xếp loại: KHÁ");
        else Console.WriteLine("Xếp loại: TRUNG BÌNH");
    }

    // 5. Hàm Main (Rất gọn)
    static void Main(string[] args)
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8;
        
        NhapDiem(); // Bước 1: Nhập
        XepLoai();  // Bước 2: Tính và In
        
        Console.ReadKey();
    }
}