Cấu trúc chương trình C
Sau bài này bạn sẽ làm được
- Kể được năm thành phần của một file nguồn C và thứ tự của chúng
- Phân biệt biểu thức, câu lệnh và khối lệnh
- Biết chính xác chỗ nào có dấu chấm phẩy và chỗ nào không
- Nhận ra bẫy dấu chấm phẩy thừa sau if và for
- Đọc được tham số dòng lệnh qua argc và argv
Một file nguồn C không phải là danh sách lệnh viết tự do. Nó có trật tự rõ ràng, và trình biên dịch đọc từ trên xuống đúng một lượt. Hiểu trật tự này giúp bạn tránh được nhóm lỗi phổ biến nhất của người mới: dùng thứ gì đó trước khi khai báo nó.
#Năm thành phần
/* 1. Chỉ thị tiền xử lý */
#include <stdio.h>
#define MAX 100
/* 2. Khai báo kiểu và biến toàn cục */
int bien_toan_cuc = 0;
/* 3. Nguyên mẫu hàm */
int cong(int a, int b);
/* 4. Hàm main */
int main(void)
{
int ket_qua = cong(3, 4);
printf("%d\n", ket_qua);
return 0;
}
/* 5. Định nghĩa hàm */
int cong(int a, int b)
{
return a + b;
}Thứ tự này không bắt buộc tuyệt đối, nhưng có một luật thì bắt buộc: mọi thứ phải được khai báo trước khi dùng. Trình biên dịch không quay ngược lại. Nếu bạn gọi cong ở dòng 13 mà định nghĩa của nó nằm ở dòng 21, thì phải có nguyên mẫu ở dòng 9 để báo trước.
#Biểu thức và câu lệnh
Hai khái niệm này nghe giống nhau nhưng khác hẳn, và sự khác biệt sẽ quay lại làm phiền bạn ở nhiều bài sau.
3 + 4 có giá trị 7, x có giá trị của biến x, cong(1, 2) có giá trị 3. Ngay cả phép gán x = 5 cũng là biểu thức, và giá trị của nó là 5.3 + 4 /* biểu thức, có giá trị 7 */
3 + 4; /* câu lệnh: tính 7 rồi vứt đi, hợp lệ nhưng vô nghĩa */
x = 5 /* biểu thức, có giá trị 5 */
x = 5; /* câu lệnh: gán 5 vào x */
printf("hi") /* biểu thức, có giá trị là số ký tự đã in */
printf("hi"); /* câu lệnh */
; /* câu lệnh rỗng, hợp lệ, và là nguồn của nhiều lỗi */Việc phép gán là biểu thức có giá trị chính là lý do C cho phép viết a = b = c = 0; và cũng là lý do if (x = 5) biên dịch được mà không báo lỗi. Chi tiết ở Bài 4.4.
#Khối lệnh và phạm vi
Cặp ngoặc nhọn gom nhiều câu lệnh thành một đơn vị. Nhưng nó làm thêm một việc quan trọng nữa: tạo ra một phạm vi mới.
int main(void)
{
int x = 1;
{
int x = 2; /* biến MỚI, che khuất x bên ngoài */
printf("%d\n", x); /* in 2 */
} /* x bên trong chết ở đây */
printf("%d\n", x); /* in 1 */
return 0;
}Biến khai báo trong một khối chỉ sống trong khối đó. Khi ra khỏi ngoặc nhọn đóng, nó biến mất hoàn toàn. Đây là nền tảng của cơ chế ngăn xếp mà bạn sẽ gặp lại ở Phần 3 và Phần 6.
#Luật dấu chấm phẩy
Đây là bảng nên in ra dán cạnh màn hình trong tuần đầu. Sai dấu chấm phẩy là lỗi số một của người mới học C.
| Vị trí | Có dấu chấm phẩy? |
|---|---|
| Sau câu lệnh: x = 5 | Có |
| Sau #include, #define | Không |
| Sau } của hàm | Không |
| Sau } của if, for, while | Không |
| Sau } của struct, union, enum | Có |
| Sau do { ... } while (...) | Có |
| Sau nguyên mẫu hàm | Có |
| Sau nhãn của goto và case | Không |
Bẫy dấu chấm phẩy thừa
Dấu chấm phẩy đứng một mình là một câu lệnh hợp lệ: câu lệnh rỗng. Vì hợp lệ nên trình biên dịch không báo lỗi, và chương trình chạy sai hoàn toàn khác ý bạn.
if (x > 0);
printf("dương");
/* printf LUÔN chạy, kể cả khi x âm.
Thân của if chính là dấu chấm phẩy rỗng. */
for (int i = 0; i < 10; ++i);
printf("lặp?");
/* printf chỉ chạy MỘT lần, sau khi vòng lặp rỗng kết thúc. */if (x > 0) {
printf("dương");
}
for (int i = 0; i < 10; ++i) {
printf("lặp");
}
/* Luôn dùng ngoặc nhọn, kể cả khi thân chỉ có một câu lệnh. */#Chú thích
/* Chú thích nhiều dòng, có từ C89.
Không lồng nhau được. */
// Chú thích một dòng, có từ C99.Quy tắc viết chú thích tốt: giải thích tại sao, không giải thích cái gì. Cái gì thì đọc code là biết, tại sao thì chỉ người viết mới biết.
i++; /* tăng i lên 1 */
/* vòng lặp for */
for (int i = 0; i < n; ++i) { }i++; /* bỏ qua dấu '=' ở giữa cặp key=value */
/* Duyệt ngược vì hàm xóa làm dồn các phần tử
phía sau lên, duyệt xuôi sẽ bỏ sót. */
for (int i = n - 1; i >= 0; --i) { }#Tham số dòng lệnh
Dạng thứ hai của main cho phép chương trình nhận dữ liệu ngay từ dòng lệnh, trước khi chạy.
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Số tham số: %d\n", argc);
for (int i = 0; i < argc; ++i) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}Số tham số: 3 argv[0] = ./thamso argv[1] = hello argv[2] = 123
argclà số lượng tham số, tính cả tên chương trình.argv[0]luôn là tên chương trình như người dùng đã gõ.- Tham số thật bắt đầu từ
argv[1]. argv[argc]luôn là con trỏ rỗng, dùng làm dấu kết thúc.- Mọi tham số đều là chuỗi. Muốn dùng như số thì phải chuyển đổi, xem Bài 3.4.
Tự làm thử
- Viết chương trình có một hàm phụ tên
binh_phuong, đủ cả năm thành phần cấu trúc đã học, và có nguyên mẫu đặt trướcmain. - Xóa nguyên mẫu hàm ở bài trên rồi biên dịch. Ghi lại nguyên văn thông báo lỗi của trình biên dịch.
- Viết chương trình in mọi tham số dòng lệnh theo thứ tự ngược, và báo lỗi rõ ràng khi không có tham số nào.
- Đoán kết quả trước khi chạy:
int x = 5; { int x = 10; { int x = 20; } }in ra gì ở từng tầng. - Cố tình thêm dấu chấm phẩy sau
if (x > 0)rồi biên dịch với-Wextra. Trình biên dịch nói gì?
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Một file nguồn có năm thành phần, và luật bắt buộc là khai báo trước khi dùng.
- Biểu thức có giá trị, câu lệnh là việc cần làm và kết thúc bằng dấu chấm phẩy.
- Ngoặc nhọn vừa gom câu lệnh vừa tạo phạm vi mới cho biến.
- Dấu chấm phẩy đứng một mình là câu lệnh rỗng hợp lệ, nên
if (x);không báo lỗi mà chạy sai. - Chú thích nên giải thích tại sao, không giải thích cái gì.
argv[0]là tên chương trình, tham số thật bắt đầu từargv[1], và tất cả đều là chuỗi.