Menu

Cú pháp C: cấu trúc một chương trình C cơ bản

Mọi chương trình C đều có cùng một bộ khung: các lệnh include, hàm main, các câu lệnh kết thúc bằng dấu chấm phẩy và các khối lệnh trong cặp ngoặc nhọn. Đây là vai trò của từng phần và những quy tắc trình biên dịch thật sự bắt buộc.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

C có rất ít cú pháp (syntax) cần học: khoảng 32 từ khóa và một vài quy tắc về dấu câu. Hầu như mọi chương trình bạn sẽ viết đều có cùng một hình dạng bên ngoài, nên rất đáng để tháo rời hình dạng đó một lần thật cẩn thận.

Chương trình hoàn chỉnh nhỏ nhất

Đoạn này biên dịch được, chạy được, và không làm gì cả:

Không include, không đầu ra, không lớp hay module rập khuôn nào. main là điểm bắt đầu: khi hệ điều hành khởi chạy chương trình, nó gọi main. Khi main trả về, chương trình kết thúc, và giá trị nó trả về là mã thoát.

Thêm phần in ra màn hình thì bạn cần một lệnh include:

Chỉ thị tiền xử lý

Các dòng bắt đầu bằng # được bộ tiền xử lý xử lý trước khi biên dịch thật sự. Chúng không phải câu lệnh C, và không có dấu chấm phẩy.

#include <stdio.h>    /* standard library header - angle brackets */
#include "myutils.h"  /* your own header - quotes, searched locally first */
#define MAX 100       /* a macro - MAX is replaced by 100 everywhere below */

#include là thao tác dán văn bản theo đúng nghĩa đen: toàn bộ nội dung của tệp được chỉ định được chèn vào đúng chỗ đó. stdio.h chứa khai báo của printf, nhờ vậy trình biên dịch biết printf nhận những đối số nào. Bỏ lệnh include đi thì bạn sẽ gặp implicit declaration of function 'printf'.

Khác biệt giữa ngoặc nhọn và dấu nháy kép nằm ở nơi trình biên dịch tìm tệp: <...> nghĩa là các thư mục include của hệ thống, "..." nghĩa là tìm cạnh tệp hiện tại trước.

Hàm main

int main(void) {
    return 0;
}

Bốn phần, phần nào cũng có ý nghĩa:

  • int: kiểu trả về của main. Giá trị này được gửi tới hệ điều hành làm mã thoát, trong đó 0 nghĩa là thành công.
  • main: cái tên dành riêng mà môi trường chạy sẽ gọi. Mỗi chương trình C chạy trên hệ điều hành có đúng một hàm như vậy.
  • (void): danh sách tham số. void ở đây nghĩa là "không nhận đối số nào". Viết main() thay vào đó vẫn hợp lệ nhưng yếu hơn: cặp ngoặc rỗng trong C nghĩa là "tham số không xác định", và điều đó tắt việc kiểm tra đối số.
  • { ... }: thân hàm.

Dạng chuẩn còn lại nhận đối số dòng lệnh:

int main(int argc, char *argv[]) { ... }

Hãy dùng int main(void) cho đến khi bạn cần tới chúng.

Câu lệnh và dấu chấm phẩy

Một câu lệnh (statement) là một chỉ dẫn đơn lẻ, và nó kết thúc bằng dấu chấm phẩy.

Bốn câu lệnh, bốn dấu chấm phẩy. Dấu chấm phẩy là dấu kết thúc chứ không phải dấu phân cách: câu lệnh cuối cùng trong một khối cũng cần có.

Những thứ không cần dấu chấm phẩy:

#include <stdio.h>        /* preprocessor line - no semicolon */

int add(int a, int b) {   /* function definition header - no semicolon */
    return a + b;
}                         /* closing brace of a function - no semicolon */

if (x > 0) {
    ...
}                         /* closing brace of a control block - no semicolon */

Trường hợp ai cũng từng vấp là một dấu chấm phẩy nằm ở chỗ đáng lẽ là thân lệnh:

if (score > 90);              /* BUG: this ; is the entire if body */
    printf("Excellent!\n");   /* always runs, whatever score is */

Đó là code C hoàn toàn hợp lệ. if điều khiển một câu lệnh rỗng, còn printf chỉ là dòng tiếp theo theo thứ tự. -Wall sẽ cảnh báo; trình biên dịch sẽ không ngăn bạn lại.

Khối lệnh và ngoặc nhọn

Cặp ngoặc nhọn gom các câu lệnh thành một khối lệnh (block), được tính là một câu lệnh ở bất cứ đâu cần một câu lệnh.

Ngoặc nhọn là tùy chọn khi thân chỉ có một câu lệnh, nhưng bỏ chúng đi chính là nguồn gốc của những bug nổi tiếng: thêm một dòng thứ hai về sau và nó âm thầm nằm ngoài if. Hãy luôn dùng ngoặc nhọn.

Khối lệnh cũng tạo ra phạm vi: một biến được khai báo bên trong { ... } sẽ không còn tồn tại sau dấu ngoặc nhọn đóng.

Phân biệt hoa thường và khoảng trắng

C phân biệt chữ hoa chữ thường ở mọi nơi. total, Total và TOTAL là ba cái tên riêng biệt. Mọi từ khóa đều viết thường, nên Int, Return và Printf đều là lỗi, và thông báo lỗi cho Printf khá khó hiểu, vì trình biên dịch cho rằng bạn muốn gọi một hàm nào đó nó chưa từng biết.

Ngược lại, khoảng trắng gần như hoàn toàn tự do. Trình biên dịch coi mọi chuỗi liên tiếp gồm dấu cách, tab và xuống dòng là một dấu phân cách. Đoạn này hợp lệ:

int    main ( void )
{
    printf
    ( "Hello\n" ) ;
    return 0 ;
}

Đoạn này cũng vậy:

int main(void){printf("Hello\n");return 0;}

Cả hai biên dịch ra kết quả giống hệt nhau. Vì trình biên dịch không quan tâm, việc định dạng hoàn toàn là cho con người, và cách trình bày quen thuộc (mỗi dòng một câu lệnh, mỗi cấp lồng nhau thụt bốn dấu cách hoặc một tab, ngoặc nhọn mở nằm cùng dòng) rất đáng theo, vì mọi codebase C bạn đọc đều dùng cách gần giống như vậy.

Có hai chỗ khoảng trắng thật sự quan trọng: bạn không thể tách một từ khóa hay định danh (in t không phải int), và không thể xuống dòng bên trong một chuỗi ký tự thông thường.

Định danh: quy tắc đặt tên

Định danh (identifier) là bất kỳ cái tên nào bạn tự đặt: biến, hàm, kiểu. Các quy tắc:

  • Gồm chữ cái, chữ số và dấu gạch dưới.
  • Không được bắt đầu bằng chữ số. total2 thì được, 2total thì không.
  • Không được trùng từ khóa: không có biến nào tên int, for hay return.
  • Phân biệt chữ hoa chữ thường.
  • Những tên bắt đầu bằng dấu gạch dưới, và mọi tên có hai dấu gạch dưới liền nhau, được dành riêng cho trình biên dịch và thư viện chuẩn: đừng tự đặt những tên như vậy.
int score;        /* good */
int total_items;  /* good - snake_case is the C convention */
int _hidden;      /* legal but reserved - avoid */
int 2fast;        /* error */
int float;        /* error - keyword */

Một chương trình có chú thích đầy đủ

Tất cả những điều trên, gom vào một chỗ:

Thứ tự đọc ở đây rất quan trọng: with_tax được định nghĩa trước khi main dùng nó. C đọc tệp từ trên xuống dưới, nên một hàm phải được khai báo trước khi được gọi. Hãy định nghĩa nó sớm hơn, hoặc viết một nguyên mẫu hàm ở đầu tệp rồi định nghĩa sau.

Câu hỏi thường gặp

Cấu trúc cơ bản của một chương trình C là gì?

Đầu tiên là các chỉ thị tiền xử lý (#include <stdio.h>), sau đó là các định nghĩa hàm, trong đó bắt buộc phải có một hàm main. Bên trong hàm, mỗi câu lệnh kết thúc bằng dấu chấm phẩy và các khối lệnh được bọc trong cặp ngoặc nhọn. Một chương trình tối thiểu gồm #include <stdio.h> cùng int main(void) { return 0; }.

Vì sao viết int main(void) mà không chỉ là main()?

int là kiểu trả về của main, tức mã thoát (exit status) mà chương trình trả lại cho hệ điều hành. (void) nói rõ rằng main không nhận đối số nào. Viết main() với cặp ngoặc rỗng là phong cách cũ, không cho trình biên dịch biết gì về tham số, nên nó kiểm tra ít hơn.

Mọi câu lệnh C đều cần dấu chấm phẩy?

Mọi câu lệnh đều cần, nhưng không phải mọi dòng. Chỉ thị tiền xử lý (#include, #define), định nghĩa hàm và dấu ngoặc nhọn đóng của một khối lệnh hay của if/for/while thì không. Một dấu chấm phẩy thừa sau if (x) vẫn hợp lệ trong C và âm thầm khiến thân lệnh luôn được chạy.

C có phân biệt chữ hoa chữ thường không?

Có, hoàn toàn. count, Count và COUNT là ba định danh khác nhau, và Int không phải từ khóa int. Mọi từ khóa của C đều viết thường, nên một chữ hoa trong Return hay Printf sẽ gây lỗi.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu