Menu

Hằng số trong C: const, #define và enum

Ba cách đặt tên cho một giá trị không bao giờ thay đổi trong C: từ khóa const, macro #define và hằng enum. Mỗi cách thực chất là gì và khi nào nên dùng cách nào.

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.

Hằng số (constant) là một giá trị có tên kèm lời hứa rằng nó sẽ không thay đổi. C cho bạn ba cơ chế để làm điều đó, và bên dưới chúng thực sự là những thứ khác nhau: một là biến, một là thay thế văn bản, và một là kiểu số nguyên.

Vì sao phải đặt tên cho giá trị

So sánh hai dòng sau:

double area = 3.14159 * r * r;
double area = PI * r * r;

Dòng thứ hai tốt hơn vì ba lý do mà sau này đều sẽ lộ ra. Nó nói rõ con số mang ý nghĩa gì. Nó chỉ xuất hiện ở đúng một chỗ, nên đổi độ chính xác chỉ cần sửa một lần thay vì đi tìm khắp nơi. Và gõ nhầm PI là một lỗi biên dịch, còn gõ nhầm 3.14195 là một kết quả sai.

Những con số không tên rải rác trong code được gọi là magic number, và loại bỏ chúng là một trong những cải tiến rẻ nhất bạn có thể làm cho một chương trình C.

const

Từ khóa const đánh dấu một biến là chỉ đọc:

Bỏ chú thích ở lệnh gán và trình biên dịch sẽ từ chối: assignment of read-only variable 'PI'. Lỗi đó xuất hiện ngay lúc build, và đó chính là mục đích.

Một biến const là một biến thật. Nó có kiểu, nên trình biên dịch kiểm tra cách bạn dùng nó. Nó tuân theo phạm vi, nên một const bên trong hàm không lọt ra ngoài. Nó có địa chỉ, nên bạn có thể lấy con trỏ tới nó. Và trình gỡ lỗi có thể hiển thị tên và giá trị của nó.

const phải được khởi tạo ngay tại chỗ khai báo, không có cơ hội thứ hai:

const int limit;       /* error (or garbage forever): nothing can assign to it later */
const int limit = 50;  /* correct */

#define

Cách thay thế bằng tiền xử lý là một macro:

Đây không phải là biến. Trước khi trình biên dịch chạy, bộ tiền xử lý thay mọi chỗ xuất hiện PI trong mã nguồn bằng các ký tự 3.14159265358979. Trình biên dịch không bao giờ thấy cái tên đó, và đó chính là lý do #define không có kiểu và không xuất hiện trong trình gỡ lỗi.

Lưu ý là không có dấu chấm phẩy và không có =. Macro là văn bản thô, nên lỗi phổ biến này:

#define MAX_USERS 100;        /* note the semicolon */

int limit = MAX_USERS + 1;    /* expands to: int limit = 100; + 1;  -> error */

gây ra lỗi ở một dòng trông chẳng liên quan gì đến định nghĩa. Quy ước đặt tên macro bằng chữ in hoa tồn tại chính là để người đọc biết đang có một phép thay thế diễn ra.

Theo quy ước, macro được viết dạng UPPER_CASE; các biến const cũng thường theo cách này khi chúng đại diện cho cùng một ý tưởng.

Hằng enum

Dạng thứ ba cho bạn một tập hợp số nguyên có tên:

Giá trị bắt đầu từ 0 và tăng dần một đơn vị nếu bạn không tự gán, nên MON là 0 và FRI là 4. Một enum { ... } không tên là cách viết quen thuộc cho các hằng số nguyên đơn giản.

Hằng enum có một lợi thế thật sự so với cả hai cách còn lại: chúng là hằng số nguyên lúc biên dịch mà trình biên dịch hiểu, và trình gỡ lỗi nhìn thấy được chúng. Toàn bộ chi tiết có trong bài enum trong C.

Nên dùng cách nào

Nhu cầuDùng
Hằng có kiểu, có phạm vi, gỡ lỗi đượcconst
Kích thước mảng, nhãn case, độ rộng bitfieldenum hoặc #define
Hằng chuỗi#define hoặc const char *
Hằng số thựcconst double
Một nhóm giá trị nguyên liên quanenum
Bất cứ thứ gì bộ tiền xử lý phải thấy (#if, include guard)#define

Mặc định nên là const. Dùng #define khi giá trị phải tồn tại trước khi biên dịch, và enum khi bạn cần một số nguyên lúc biên dịch hoặc một nhóm có tên.

Chỗ duy nhất điều này thực sự gây rắc rối là kích thước mảng:

Trong C, một const int là một biến chỉ đọc, không phải biểu thức hằng lúc biên dịch. Bên trong hàm, C99 chấp nhận int local[const_size] bằng cách biến nó thành mảng có độ dài thay đổi; ở phạm vi tệp, trong một struct hoặc làm nhãn case thì nó bị từ chối thẳng. enum và #define dùng được ở mọi nơi.

(Đây là một trong những khác biệt thật sự so với C++, nơi const int là một biểu thức hằng. Code chuyển qua lại giữa hai ngôn ngữ hay vấp phải điểm này.)

const với con trỏ

const và con trỏ kết hợp theo ba cách, và ý nghĩa của chúng không thay thế cho nhau được. Hãy đọc khai báo từ tên biến ra ngoài:

const char *p;          /* p points at chars that must not be modified */
char * const q;         /* q itself cannot be re-pointed; the chars can change */
const char * const r;   /* neither the pointer nor the chars can change */

Dạng const char * là dạng bạn sẽ viết nhiều nhất, vì đó là kiểu đúng cho một tham số hàm chỉ đọc chuỗi mà không thay đổi nó:

Thêm const vào một tham số chỉ đọc không tốn gì và ghi rõ cam kết theo cách mà trình biên dịch buộc tuân theo. Nên làm điều này ở mọi nơi.

Chuỗi ký tự literal thực chất là const

Một cái bẫy tinh vi mà const sinh ra để bắt:

char *s = "hello";   /* legal C, but s points into read-only memory */
s[0] = 'H';          /* undefined behavior - usually a crash */

Chuỗi literal nằm trong một vùng chỉ đọc của tệp thực thi. Ghi qua con trỏ đó thường gây segfault. Khai báo con trỏ là const char *s = "hello"; khiến trình biên dịch từ chối lệnh gán thay vì để chương trình sập lúc chạy.

Nếu cần một chuỗi có thể sửa, hãy sao chép nó vào một mảng:

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

Khai báo hằng số trong C như thế nào?

Thêm const vào khai báo và gán giá trị: const double PI = 3.14159;. Từ đó trình biên dịch sẽ từ chối mọi lệnh gán vào nó. Cách cũ hơn là một macro tiền xử lý, #define PI 3.14159, cách này thay thế văn bản thay vì tạo biến.

const và #define trong C khác nhau thế nào?

const tạo ra một biến thật có kiểu mà trình biên dịch biết, nên nó được kiểm tra kiểu, hiện trong trình gỡ lỗi và tuân theo phạm vi. #define chỉ là thay thế văn bản trước khi biên dịch: không có kiểu, không có phạm vi và không xem được khi gỡ lỗi. Hãy ưu tiên const, trừ khi bạn cần giá trị ở nơi đòi hỏi hằng số lúc biên dịch.

Có dùng biến const làm kích thước mảng trong C được không?

Không, với mảng kích thước cố định ở phạm vi tệp. const int N = 10; int arr[N]; là một mảng có độ dài thay đổi trong C99 (được phép trong hàm, không được ở phạm vi tệp và không được trong struct). Để có kích thước thật sự lúc biên dịch, hãy dùng #define N 10 hoặc enum { N = 10 };.

const char *p nghĩa là gì?

Đó là con trỏ tới một char hằng: bạn có thể đổi chỗ p trỏ tới, nhưng không được đổi ký tự mà nó trỏ tới. char * const p thì ngược lại: một con trỏ hằng tới một char có thể sửa. const char * const p khóa cả hai. Hãy đọc khai báo từ tên biến ra ngoà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