Menu

Biến trong C: khai báo, khởi tạo và gán giá trị

Cách khai báo biến trong C, khác biệt giữa khai báo và khởi tạo, quy tắc đặt tên, khai báo nhiều biến cùng lúc, và vì sao biến chưa khởi tạo là bug phổ biến nhất của người mới họ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.

Biến (variable) là một vùng nhớ có tên, chứa một giá trị mà chương trình có thể đọc và thay đổi. Trong C, bạn luôn phải nói trước biến chứa loại giá trị gì, và lựa chọn đó cố định suốt vòng đời của biến.

Khai báo biến

Cú pháp là kiểu, tên, dấu chấm phẩy:

int age;
double price;
char grade;

Mỗi dòng dành đủ bộ nhớ cho kiểu dữ liệu đó và gắn cho nó một cái tên. Lúc này chưa có gì được lưu: vùng nhớ đã có, nhưng nội dung là bất cứ thứ gì mà đoạn code chạy trước để lại.

Bạn có thể gán giá trị ngay lúc khai báo, việc này gọi là khởi tạo:

Để ý các format specifier: %d cho int, %f cho double, %c cho char. printf không kiểm tra các giá trị được truyền vào mà tin hoàn toàn vào chuỗi định dạng của bạn, nên nếu không khớp thì nó in ra kết quả vô nghĩa chứ không báo lỗi.

Gán giá trị sau khi khai báo

Khi biến đã tồn tại, bạn thay đổi nó bằng =:

= trong C là phép gán, không phải so sánh bằng. score = score + 10 đọc là "lấy giá trị trong score, cộng thêm 10, rồi đặt kết quả lại vào score". Toán tử so sánh là ==, và nhầm lẫn giữa hai toán tử này phổ biến đến mức có hẳn một mục riêng trong trang boolean.

Cách viết tắt score += 10 làm điều tương tự mà gọn hơn; các toán tử gán kết hợp có đủ cho mọi phép toán số học.

Cái bẫy biến chưa khởi tạo

Đây là bug phổ biến nhất của người mới học C, và nó không hề gây ra lỗi:

Chạy thử đi. Bạn có thể thấy 15, là kết quả đúng. Bạn cũng có thể thấy một số ngẫu nhiên rất lớn. Lần chạy thứ hai hoặc trên máy khác lại có thể ra kết quả khác nữa. Tất cả đều là hành vi "đúng", vì đọc một biến cục bộ chưa khởi tạo là undefined behavior: chuẩn C không đặt ra yêu cầu nào về chuyện gì sẽ xảy ra.

Khác với nhiều ngôn ngữ khác, C không tự gán 0 cho biến cục bộ. Biến cục bộ là một phần của stack, và stack chứa đầy dữ liệu còn sót lại từ các lần gọi hàm trước. Đôi khi phần còn sót đó tình cờ bằng 0, và chính điều đó khiến bug này nguy hiểm: nó thường chạy đúng khi test rồi lại hỏng khi lên production.

Cách sửa chỉ cần đúng một ký tự:

Hãy tập thói quen: khởi tạo mọi biến ngay tại chỗ khai báo. Nếu thật sự chưa có giá trị hợp lý nào, 0 hoặc NULL vẫn tốt hơn giá trị rác. Và biên dịch với -Wall, cờ này bắt được phần lớn các trường hợp:

warning: 'total' is used uninitialized in this function

Biến toàn cục và biến static là ngoại lệ: chúng được tự động khởi tạo bằng 0. Dựa vào điều đó cũng không sao, nhưng viết thêm = 0 giúp thể hiện rõ ý định của bạn.

Khai báo nhiều biến cùng lúc

Các biến cùng kiểu có thể dùng chung một khai báo, có hoặc không có giá trị khởi tạo:

Cách này hợp lệ và hay gặp với các giá trị liên quan chặt chẽ như x, y, z. Đây cũng là chỗ dễ đọc nhầm kinh điển:

int a = 0, b = 0, c = 0;   /* all three are 0 */
int d, e, f = 0;           /* only f is 0 - d and e are garbage */

Giá trị khởi tạo gắn với một tên, không phải với cả dòng. Khi các giá trị mang ý nghĩa khác nhau, hãy cho mỗi biến một dòng riêng: dễ đọc hơn và dễ so sánh thay đổi hơn.

Khai báo con trỏ trên cùng một dòng cũng có cái bẫy tương tự, còn nguy hiểm hơn:

int *p, q;   /* p is a pointer to int; q is a plain int */

Dấu * gắn với tên biến, không phải với kiểu. Hãy khai báo mỗi con trỏ trên một dòng.

Quy tắc và quy ước đặt tên

Quy tắc của trình biên dịch:

  • Chỉ dùng chữ cái, chữ số và dấu gạch dưới.
  • Không được bắt đầu bằng chữ số: total2 hợp lệ, 2total thì không.
  • Không được trùng từ khóa của C: không có biến nào tên là int, return, for, double.
  • Phân biệt chữ hoa chữ thường: count, Count và COUNT là ba biến riêng biệt.
  • Tên bắt đầu bằng dấu gạch dưới, hoặc chứa 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ạo những tên như vậy.
int itemCount;       /* legal */
int item_count;      /* legal - the usual C style */
int _count;          /* legal but reserved - avoid */
int item-count;      /* error: '-' is the minus operator */
int 3rd_place;       /* error: starts with a digit */
int float;           /* error: keyword */

Quy ước mà phần lớn code C tuân theo: snake_case cho biến và hàm, UPPER_CASE cho hằng số macro, và tên biến nói lên ý nghĩa của giá trị chứ không phải kiểu của nó. elapsed_ms tốt hơn t; customer_count tốt hơn n2.

Tên ngắn vẫn ổn khi phạm vi rất nhỏ: i làm biến đếm vòng lặp thì ai cũng hiểu, và chẳng ai muốn viết for (int loop_iteration_index = 0; ...).

Biến nằm ở đâu

Chỗ bạn khai báo biến quyết định ai nhìn thấy nó và nó tồn tại bao lâu. Đó là phạm vi (scope), và tóm tắt ngắn gọn là:

Một biến có thể thấy được từ chỗ khai báo cho đến dấu ngoặc nhọn đóng của khối lệnh chứa nó. Biến toàn cục tồn tại suốt chương trình và có thể thấy ở mọi nơi. Nghe thì tiện, nhưng đó cũng là lý do chúng gây rắc rối: hàm nào cũng có thể thay đổi chúng, nên muốn tìm ra hàm nào đã làm vậy thì phải lục cả tệp. Hãy ưu tiên biến cục bộ và truyền giá trị cho hàm qua tham số.

Khai báo ở bất kỳ đâu (C99 trở lên)

Trong chuẩn C89 ban đầu, mọi khai báo phải nằm trước mọi câu lệnh trong một khối:

int main(void) {
    int i, sum = 0;      /* all declarations first */
    printf("Starting\n");
    for (i = 0; i < 10; i++) { sum += i; }
    return 0;
}

Từ C99, bạn có thể khai báo ngay tại chỗ cần dùng giá trị lần đầu, kể cả trong phần đầu của vòng lặp for:

Đây là phong cách tốt hơn: khai báo nằm ngay cạnh lần dùng đầu tiên, và i không thể lọt ra phần còn lại của hàm. Biên dịch với -std=c17 (hoặc bất kỳ thiết lập C99 trở lên nào) là chạy được ở mọi nơi.

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

Khai báo biến trong C như thế nào?

Viết kiểu dữ liệu, rồi tên biến, rồi dấu chấm phẩy: int age;. Bạn có thể gán giá trị ngay lúc đó, int age = 30;, gọi là khởi tạo, và gần như lúc nào bạn cũng nên làm vậy.

Khai báo và khởi tạo biến khác nhau thế nào?

Khai báo là dành sẵn vùng nhớ và đặt tên cho nó (int count;). Khởi tạo là đặt một giá trị vào ngay lúc biến được tạo ra (int count = 0;). Một biến cục bộ đã khai báo nhưng chưa khởi tạo chứa những byte nào đang sẵn có ở địa chỉ đó, và đọc nó là undefined behavior.

Quy tắc đặt tên biến trong C là gì?

Chỉ dùng chữ cái, chữ số và dấu gạch dưới; không được bắt đầu bằng chữ số; không được trùng từ khóa như int hay for; và có phân biệt chữ hoa chữ thường, nên total và Total là hai biến khác nhau. Tên bắt đầu bằng dấu gạch dưới đã được dành riêng, nên tránh dùng.

Có thể khai báo biến ở bất kỳ đâu trong hàm C không?

Từ C99 thì có: khai báo mỗi biến ngay tại chỗ bạn cần dùng nó lần đầu. Trong chuẩn C89 ban đầu, mọi khai báo phải nằm ở đầu khối lệnh, vì vậy code cũ thường có cả một loạt khai báo trước khi bắt đầu làm việc thật sự.

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