CHƯƠNG TRÌNH CON TRONG C++
Chào các bạn học sinh và lập trình viên! Khi giải quyết các bài toán lập trình quy mô lớn hoặc tham gia các cuộc thi Học sinh giỏi Tin học trên hệ thống Sen Code Online Judge!, mã nguồn chương trình của chúng ta sẽ ngày một dài hơn. Nếu tất cả các câu lệnh đều viết chung vào hàm main, chương trình sẽ trở nên cực kỳ rối rắm, khó phát hiện lỗi và gặp khó khăn khi cần tái sử dụng một đoạn code nhiều lần.
Để giải quyết vấn đề này, các ngôn ngữ lập trình cung cấp khái niệm Chương trình con (Subprogram). Trong C++, chương trình con được triển khai dưới dạng Hàm (Function) và Thủ tục (Procedure). Bài viết này sẽ hướng dẫn chi tiết cú pháp và cách áp dụng cụ thể.
1. Khái niệm chương trình con
Chương trình con là một khối lệnh thực hiện một nhiệm vụ xác định, được đặt tên và có thể được gọi để thực thi từ nhiều vị trí khác nhau trong chương trình chính.
Lợi ích của chương trình con:
- Tránh trùng lặp code: Viết một lần, gọi dùng nhiều lần.
- Dễ quản lý và gỡ lỗi: Chia nhỏ một bài toán lớn phức tạp thành các khối chức năng nhỏ, độc lập, giúp mã nguồn sáng sủa, dễ đọc.
- Hỗ trợ lập trình theo nhóm: Các thành viên có thể viết các chương trình con khác nhau độc lập rồi ghép nối lại.
2. Hàm trong C++ (Function)
Định nghĩa:
Hàm là một chương trình con nhận vào các tham số đầu vào, thực hiện tính toán và trả về một giá trị duy nhất có kiểu dữ liệu xác định (như int, double, string, bool...) cho nơi gọi nó thông qua câu lệnh return.
Cú pháp khai báo:
kiểu_trả_về tên_hàm(danh_sách_tham_số) {
// Khối lệnh xử lý bên trong
return giá_trị_trả_về; // Giá trị trả về phải có kiểu trùng với kiểu_trả_về
}
Ví dụ minh họa:
Viết hàm kiểm tra xem một số nguyên dương N có phải số nguyên tố hay không.
#include <bits/stdc++.h>
using namespace std;
// Hàm kiểm tra số nguyên tố, trả về kiểu bool (true hoặc false)
bool laNguyenTo(int n) {
if (n <= 1) return false;
for (int i = 2; i <= sqrt(n); ++i) {
if (n % i == 0) {
return false; // Trả về false ngay lập tức và thoát khỏi hàm
}
}
return true; // Trả về true nếu không tìm thấy ước nào
}
int main() {
int x;
cin >> x;
// Gọi hàm laNguyenTo trong câu lệnh điều kiện
if (laNguyenTo(x)) {
cout << x << " la so nguyen to.\n";
} else {
cout << x << " khong phai so nguyen to.\n";
}
return 0;
}
3. Thủ tục trong C++ (Procedure)
Định nghĩa:
C++ không có từ khóa riêng biệt để khai báo thủ tục giống như Pascal (procedure). Trong C++, thủ tục thực chất là một hàm có kiểu trả về đặc biệt là void (rỗng).
Thủ tục thực thi một chuỗi các hành động (như in ấn kết quả, tráo đổi phần tử...) và không trả về bất kỳ giá trị nào cho nơi gọi.
Cú pháp khai báo:
void tên_thủ_tục(danh_sách_tham_số) {
// Khối lệnh thực thi
// Không cần dùng lệnh return để trả về giá trị (có thể dùng 'return;' để thoát sớm khỏi thủ tục)
}
Ví dụ minh họa:
Viết một thủ tục nhận vào một mảng số nguyên và thực hiện in mảng đó ra màn hình.
#include <bits/stdc++.h>
using namespace std;
// Thủ tục in mảng, không trả về giá trị (kiểu void)
void inMang(int A[], int N) {
for (int i = 0; i < N; ++i) {
cout << A[i] << (i == N - 1 ? "" : " ");
}
cout << "\n";
}
int main() {
int A[] = {3, 5, 8, 2, 9};
int N = 5;
// Gọi thủ tục inMang
inMang(A, N);
return 0;
}
4. Lời gọi chương trình con và Tham số truyền vào
Khi gọi một chương trình con, ta truyền các giá trị cụ thể (gọi là Tham số thực sự) vào các biến đại diện của hàm (gọi là Tham số hình thức). C++ hỗ trợ hai cơ chế truyền tham số chính:
a. Truyền tham trị (Pass-by-value):
- Cơ chế: Hệ thống chỉ sao chép giá trị của biến gốc và nạp vào hàm. Mọi biến đổi đối với tham số bên trong chương trình con chỉ tác động lên bản sao đó, không làm thay đổi giá trị của biến gốc ban đầu ngoài chương trình chính.
- Nhận biết: Khai báo danh sách tham số thông thường (ví dụ:
void thayDoi(int x)).
b. Truyền tham chiếu (Pass-by-reference):
- Cơ chế: Hàm không tạo bản sao mà sử dụng trực tiếp địa chỉ vùng nhớ của biến gốc. Mọi thay đổi đối với tham số bên trong chương trình con sẽ tác động trực tiếp và làm thay đổi giá trị của biến gốc ban đầu.
- Nhận biết: Thêm ký tự và (
&) trước tên tham số hình thức (ví dụ:void thayDoi(int &x)).
Ví dụ so sánh Tham trị và Tham chiếu:
Viết chương trình tráo đổi giá trị của hai số nguyên A và B.
#include <bits/stdc++.h>
using namespace std;
// Thủ tục hoán vị sử dụng Tham chiếu (&) để thay đổi trực tiếp biến gốc
void hoanVi(int &x, int &y) {
int temp = x;
x = y;
y = temp;
}
// Thủ tục thử thay đổi giá trị sử dụng Tham trị (Không có &)
void thuThayDoi(int x) {
x = 999;
}
int main() {
int a = 5, b = 10;
cout << "Ban dau: a = " << a << ", b = " << b << "\n";
// Thử dùng tham trị
thuThayDoi(a);
cout << "Sau thuThayDoi (tham tri): a = " << a << " (khong thay doi)\n";
// Dùng tham chiếu
hoanVi(a, b);
cout << "Sau hoanVi (tham chieu): a = " << a << ", b = " << b << " (da hoan vi)\n";
return 0;
}
5. ĐỀ XUẤT 10 BÀI TẬP THỰC HÀNH THỰC TẾ (CHƯƠNG TRÌNH CON)
Hãy áp dụng kỹ năng xây dựng chương trình con để thực hiện đọc/ghi dữ liệu bằng tệp thông qua freopen đối với 10 bài tập thực tế sau:
Bài 1: Tính diện tích nông trại phức tạp
- Ngữ cảnh: Một nông trại hình đa giác được chia thành N mảnh đất nhỏ hình tam giác. Người quản lý cần biết tổng diện tích đất bằng cách tính diện tích từng tam giác rồi cộng lại.
- Yêu cầu: Viết một hàm
double tinhDienTich(double a, double b, double c)tính diện tích tam giác theo công thức Heron khi biết độ dài 3 cạnh:- p = (a + b + c) / 2
- diệntích = cănbậc_hai(p * (p - a) * (p - b) * (p - c))
- Áp dụng hàm để tính tổng diện tích mảnh đất từ tệp
farm.inpghi ra tệpfarm.out.
- Đầu vào (Input - tệp
farm.inp):- Dòng 1 chứa số nguyên dương N (1 <= N <= 1000).
- N dòng tiếp theo, mỗi dòng chứa 3 số thực là độ dài 3 cạnh của một tam giác.
- Đầu ra (Output - tệp
farm.out): Một số thực duy nhất là tổng diện tích nông trại (lấy chính xác 2 chữ số thập phân). Ví dụ:
Input:
2 3.0 4.0 5.0 6.0 8.0 10.0Output:
30.00(Diện tích tam giác 1 là 6.0, tam giác 2 là 24.0 -> Tổng = 30.0)
Bài 2: Chuẩn hóa tự động mã vạch hàng hóa
- Ngữ cảnh: Trong hệ thống quản lý kho, các mã vạch quét về chứa nhiều ký tự lỗi không hợp lệ. Quy tắc chuẩn hóa: Chỉ giữ lại các chữ số và viết hoa tất cả chữ cái thường.
- Yêu cầu: Viết một thủ tục
void chuanHoa(string &s)để chuẩn hóa trực tiếp chuỗi tham chiếus. Đọc dữ liệu từ tệpbarcode.inpghi ra tệpbarcode.out. - Đầu vào (Input - tệp
barcode.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 100).
- N dòng tiếp theo, mỗi dòng chứa một chuỗi ký tự mã vạch S (độ dài không quá 100 ký tự).
- Đầu ra (Output - tệp
barcode.out): N dòng là các mã vạch sau khi đã được chuẩn hóa. Ví dụ:
Input:
2 abc-123-XYZ 12aB34#Output:
ABC123XYZ 12AB34
Bài 3: Tìm kiếm số siêu nguyên tố bảo mật
- Ngữ cảnh: Số siêu nguyên tố là số nguyên tố mà khi ta cắt bỏ các chữ số ở phía bên phải của nó, các số còn lại cũng là số nguyên tố. Ví dụ số
373là số siêu nguyên tố vì373,37,3đều là các số nguyên tố. - Yêu cầu: Viết hàm
bool laNguyenTo(int n). Áp dụng hàm này để viết chương trình tìm tất cả các số siêu nguyên tố có K chữ số. Đọc K từ tệpsprime.inpghi ra tệpsprime.out. - Đầu vào (Input - tệp
sprime.inp): Một số nguyên duy nhất K (1 <= K <= 8). - Đầu ra (Output - tệp
sprime.out): In ra tất cả các số siêu nguyên tố có K chữ số theo thứ tự tăng dần, cách nhau bởi khoảng trắng. - Ví dụ:
- Input:
2 - Output:
23 29 31 37 53 59 71 73 79
- Input:
Bài 4: Tối giản phân số số học
- Ngữ cảnh: Một ứng dụng học tập toán học trực tuyến cần hiển thị kết quả phân số ở dạng tối giản nhất.
- Yêu cầu: Viết hàm
int timUCLN(int a, int b)và thủ tụcvoid toiGian(int &tu, int &mau)truyền tham chiếu để rút gọn phân số. Đọc tử và mẫu từ tệpfraction.inpghi kết quả ra tệpfraction.out. - Đầu vào (Input - tệp
fraction.inp): Một dòng chứa hai số nguyên dương là Tử số và Mẫu số (1 <= Tử, Mẫu <= 109). - Đầu ra (Output - tệp
fraction.out): Tử số và mẫu số của phân số tối giản cách nhau bởi dấu cách. - Ví dụ:
- Input:
12 18 - Output:
2 3
- Input:
Bài 5: Số Fibonacci lớn nhất nhỏ hơn N
- Ngữ cảnh: Trong nghiên cứu sinh học cấu trúc hoa hướng dương, ta cần tìm số lượng hạt lớn nhất thuộc dãy Fibonacci nhưng phải nhỏ hơn một ngưỡng giới hạn N cho trước.
- Yêu cầu: Viết hàm
long long fibo(int k)trả về số Fibonacci thứ k. Dùng hàm này để tìm số Fibonacci lớn nhất nhưng nhỏ hơn N từ tệptargetfibo.inpghi ra tệptargetfibo.out. - Đầu vào (Input - tệp
targetfibo.inp): Một số nguyên dương duy nhất N (2 <= N <= 1018). - Đầu ra (Output - tệp
targetfibo.out): Một số nguyên duy nhất thỏa mãn yêu cầu. - Ví dụ:
- Input:
10 - Output:
8(Dãy Fibonacci gồm: 1, 1, 2, 3, 5, 8, 13,... Số lớn nhất nhỏ hơn 10 là 8)
- Input:
Bài 6: Sắp xếp điểm số bằng thủ tục hoán vị tự viết
- Ngữ cảnh: Hệ thống cần sắp xếp danh sách N điểm số theo thứ tự giảm dần. Bạn không được dùng hàm
std::swaphaystd::sortcó sẵn, mà phải tự viết hàm đổi chỗ. - Yêu cầu: Viết thủ tục
void hoanVi(double &x, double &y)sử dụng tham chiếu để đổi chỗ hai số thực. Cài đặt thuật toán sắp xếp nổi bọt (Bubble Sort) sử dụng thủ tục này. Đọc điểm số từ tệpswapsort.inpghi kết quả ra tệpswapsort.out. - Đầu vào (Input - tệp
swapsort.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- Dòng 2 chứa N số thực điểm số.
- Đầu ra (Output - tệp
swapsort.out): Dãy điểm số sau khi đã sắp xếp giảm dần cách nhau bởi dấu cách (lấy 1 chữ số thập phân). Ví dụ:
Input:
4 8.2 9.5 7.0 8.5Output:
9.5 8.5 8.2 7.0
Bài 7: Số lượng số đối xứng trong khoảng
- Ngữ cảnh: Một nhà toán học cần đếm xem trong khoảng từ số nguyên dương A đến số nguyên dương B có bao nhiêu số đối xứng (số đọc xuôi hay ngược đều giống nhau, ví dụ: 121, 55, 7887).
- Yêu cầu: Viết hàm
bool laDoiXung(long long n). Áp dụng hàm để đếm số lượng số đối xứng trong đoạn [A, B] từ tệppalincount.inpghi ra tệppalincount.out. - Đầu vào (Input - tệp
palincount.inp): Một dòng gồm hai số nguyên dương A và B (1 <= A <= B <= 105). - Đầu ra (Output - tệp
palincount.out): Một số nguyên duy nhất là số lượng số đối xứng tìm được. - Ví dụ:
- Input:
10 100 - Output:
9(Các số đối xứng là 11, 22, 33, 44, 55, 66, 77, 88, 99)
- Input:
Bài 8: Bộ chuyển đổi hệ cơ số
- Ngữ cảnh: Trong kỹ thuật mạng và phần cứng máy tính, ta cần chuyển số nguyên dương N hệ thập phân sang hệ cơ số B bất kỳ từ 2 đến 16.
- Yêu cầu: Viết hàm
string doiCoSo(long long N, int B)nhận vào N và cơ số B, trả về xâu ký tự là kết quả biểu diễn của N ở hệ cơ số B (sử dụng các ký tựAđếnFcho các chữ số từ 10 đến 15). Đọc dữ liệu từ tệpbaseconv.inpghi kết quả ra tệpbaseconv.out. - Đầu vào (Input - tệp
baseconv.inp): Một dòng chứa hai số nguyên N và B (1 <= N <= 1012, 2 <= B <= 16). - Đầu ra (Output - tệp
baseconv.out): Chuỗi ký tự kết quả biểu diễn số. - Ví dụ:
- Input:
255 16 - Output:
FF
- Input:
Bài 9: Biểu diễn phân tích thừa số nguyên tố viết tay
- Ngữ cảnh: Trong giảng dạy toán học cơ sở, học sinh cần viết phân tích thừa số nguyên tố của số N dưới dạng tích các số cách nhau bởi dấu nhân (ví dụ:
20phân tích thành2 * 2 * 5). - Yêu cầu: Viết thủ tục
void phanTichNguyenTo(int n)thực hiện tính toán và in ra màn hình dạng phân tích của N theo định dạng trên. Đọc dữ liệu từ tệpprimefact.inpghi kết quả ra tệpprimefact.out. - Đầu vào (Input - tệp
primefact.inp): Một số nguyên dương duy nhất N (2 <= N <= 108). - Đầu ra (Output - tệp
primefact.out): Chuỗi ký tự biểu thị tích các thừa số nguyên tố. - Ví dụ:
- Input:
60 - Output:
2 * 2 * 3 * 5
- Input:
Bài 10: Truy vấn kỳ hạn gửi tiền lãi kép
- Ngữ cảnh: Công ty tài chính có Q đối tác muốn gửi tiền tiết kiệm với lãi suất lũy kế R% mỗi tháng. Mỗi đối tác có số tiền gốc ban đầu là M đồng và số tiền mục tiêu cần đạt là T đồng.
- Yêu cầu: Viết hàm
int tinhSoThang(double M, double R, double T)nhận các thông tin và trả về số tháng tối thiểu cần gửi. Áp dụng hàm để giải quyết Q câu hỏi truy vấn của đối tác từ tệpfinance.inpghi kết quả ra tệpfinance.out. - Đầu vào (Input - tệp
finance.inp):- Dòng 1 chứa số nguyên dương Q là số lượng đối tác (1 <= Q <= 1000).
- Q dòng tiếp theo, mỗi dòng chứa 3 số thực: Số tiền gốc M, lãi suất R%, số tiền mục tiêu T (1 <= M <= T <= 109, 0.1 <= R <= 10.0).
- Đầu ra (Output - tệp
finance.out): Q số nguyên tương ứng là số tháng tối thiểu cần gửi của từng đối tác cách nhau bởi khoảng trắng. Ví dụ:
Input:
2 100.0 5.0 120.0 500.0 2.0 550.0Output:
4 5
Lời kết
Chương trình con (Hàm và Thủ tục) là công cụ tuyệt vời nhất để tổ chức mã nguồn của bạn một cách khoa học, chuyên nghiệp và tối ưu. Hãy lưu ý phân biệt rõ ràng khi nào cần dùng Tham trị (truyền giá trị) và khi nào cần dùng Tham chiếu (truyền địa chỉ vùng nhớ dùng dấu &) để tránh các lỗi logic đáng tiếc khi làm bài.
Hãy lập trình và gửi lời giải của 10 bài tập chương trình con trên tại hệ thống Sen Code Online Judge! nhé! Chúc các bạn học tập thật tốt!
Bình luận
Test comments!