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
Mainchỉ đó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:
voidKế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
refkhi 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
itrong vòng lặpforkhô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
classnhư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
Mainchỉ đóng vai trò “nhạc trưởng” gọi các hàm trên.
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ề
doublevà kiểm tra mẫu số khác 0.
Bước 3: Viết hàm
MainKhai báo biến
a,b.Gọi
NhapSođể lấy giá trị choavàb.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
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ếutruethì in rai.
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:
truenếu là SNT,falsenế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ềtruethì in ra.
2. Hướng dẫn từng bước
Bước 1: Viết hàm
KiemTraSNTTham số:
int n.Kiểu trả về:
bool.Logic:
Nếu
n < 2->return false.Dùng vòng lặp
forchạy từ 2 đến căn bậc 2 củan.Nếu
nchia hết cho bất kỳ số nào (n % i == 0) ->return falsengay 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
MainDùng vòng lặp
forchạyitừ 1 đến 100.Trong vòng lặp, gọi
if (KiemTraSNT(i))thì inira.
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
avàb.Nếu không dùng từ khóa
ref, hàm chỉ đổi chỗ 2 bản sao (copy), biến gốc ởMainvẫ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
MainKhai 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óarefkhi 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ọiTinhTB()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,diemVancầ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ọiTinhTBđể 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 Programnhư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
NhapDiemGá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
TinhTBSử 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
XepLoaiKhai báo biến
dtb = TinhTB().Dùng
if...elseđể in ra Giỏi/Khá/TB dựa trêndtb.
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();
}
}