Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 4.411 phút đọc

Toán tử gán

Sau bài này bạn sẽ làm được

  • Dùng thành thạo các dạng gán rút gọn
  • Giải thích vì sao phép gán là biểu thức có giá trị
  • Nhận ra bẫy ép kiểu ngầm khi dùng gán rút gọn

Phép gán trong C không chỉ là một câu lệnh. Nó là một biểu thức có giá trị, và chính đặc tính đó tạo ra vài thành ngữ rất phổ biến, đồng thời tạo ra lỗi if (x = 5) mà bạn đã gặp ở bài trước.

#Các dạng gán

Toán tửTương đươngVí dụ
a = bgán trực tiếpx = 10
a += ba = a + btong += diem
a -= ba = a - bcon_lai -= da_dung
a *= ba = a * btich *= i
a /= ba = a / bn /= 10
a %= ba = a % bvi_tri %= kich_thuoc
a &= ba = a & bco &= ~MAT_NA
a |= ba = a | bco |= CO_DOC
a ^= ba = a ^ bco ^= CO_AN
a <<= ba = a << bx <<= 2
a >>= ba = a >> bx >>= 1
int x = 10;

x += 5;      /* 15 */
x *= 2;      /* 30 */
x -= 8;      /* 22 */
x /= 2;      /* 11 */
x %= 7;      /* 4  */

#Gán là biểu thức

Phép gán trả về giá trị vừa được gán. Điều này cho phép vài cách viết mà các ngôn ngữ khác không có.

int a, b, c;

a = b = c = 5;              /* cả ba đều bằng 5 */

printf("%d\n", a = 10);    /* in 10, đồng thời gán 10 vào a */

int x = (a = 3) + (b = 4);  /* x bằng 7, a bằng 3, b bằng 4 */

Thành ngữ đọc dữ liệu

Ứng dụng phổ biến nhất của đặc tính này là gộp việc gán và việc kiểm tra vào một biểu thức.

/* Đọc từng ký tự tới hết luồng */
int c;

while ((c = getchar()) != EOF) {
    putchar(c);
}

/* Đọc từng dòng */
char dong[256];

while (fgets(dong, sizeof dong, stdin) != NULL) {
    xu_ly(dong);
}

/* Kiểm tra cấp phát ngay khi gán */
int *p;

if ((p = malloc(n * sizeof *p)) == NULL) {
    return -1;
}

#Bẫy ép kiểu ngầm

Phép gán tự động chuyển giá trị sang kiểu của vế trái. Việc này thường tiện, nhưng đôi khi làm mất dữ liệu một cách âm thầm.

int i = 10;

i += 0.7;

printf("%d\n", i);
/* In 10, không phải 10.7 và cũng không phải 11.
   Quá trình: 10 + 0.7 = 10.7, rồi ép về int thành 10. */
Phép gánKết quảĐiều gì xảy ra
int i = 3.993Cắt phần thập phân
int i = -3.99-3Cắt về phía số 0
char c = 30044Chỉ giữ 8 bit thấp
unsigned u = -14294967295Diễn giải lại các bit
float f = 1.0 / 30.3333333Mất độ chính xác
int i = 1e20không xác địnhVượt miền của int
Không nên
int tong = 0;
double gia[] = {10.5, 20.3, 30.7};

for (int i = 0; i < 3; ++i) {
    tong += gia[i];      /* mỗi lần đều cắt phần thập phân */
}

printf("%d\n", tong);   /* 60, đáng lẽ phải là 61.5 */
Nên
double tong = 0;
double gia[] = {10.5, 20.3, 30.7};

for (int i = 0; i < 3; ++i) {
    tong += gia[i];
}

printf("%.1f\n", tong);   /* 61.5 */

#Chiều kết hợp

Toán tử gán kết hợp từ phải sang trái, khác với hầu hết toán tử khác vốn kết hợp từ trái sang phải.

a = b = c = 5;

/* Được hiểu là: */
a = (b = (c = 5));

/* Thứ tự thực hiện:
   c nhận 5, biểu thức (c = 5) có giá trị 5
   b nhận 5, biểu thức (b = 5) có giá trị 5
   a nhận 5                                   */

Gán không phải lúc nào cũng làm được

int a[5] = {1, 2, 3, 4, 5};
int b[5];

b = a;                     /* LỖI: không gán được mảng */
memcpy(b, a, sizeof a);    /* đúng */

const int x = 5;
x = 10;                    /* LỖI: không gán được cho hằng */

struct Diem p1 = {1, 2};
struct Diem p2;

p2 = p1;                   /* ĐƯỢC: struct gán được, sao chép từng byte */

Tự làm thử

  1. Viết chương trình đếm số ký tự nhập vào bằng thành ngữ while ((c = getchar()) != EOF).
  2. Bỏ cặp ngoặc trong biểu thức trên rồi chạy lại, giải thích vì sao chương trình chạy sai.
  3. Viết chương trình chứng minh i += 0.7 không làm i thay đổi khi iint.
  4. Biên dịch một bài tập cũ của bạn với -Wconversion và sửa hết cảnh báo.
  5. Cho int i; double d; i = d = 3.7;, đoán giá trị hai biến rồi chạy để kiểm chứng.
  6. Viết đoạn code trong đó a[f()] += 1 a[f()] = a[f()] + 1 cho kết quả khác nhau.

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

  • Gán rút gọn chỉ đánh giá vế trái một lần, dạng đầy đủ thì đánh giá hai lần.
  • Phép gán là biểu thức có giá trị, nên viết được a = b = c = 5.
  • Thành ngữ while ((c = getchar()) != EOF) cần ngoặc vì gán có độ ưu tiên rất thấp.
  • Phép gán tự ép kiểu về kiểu vế trái, có thể làm mất dữ liệu âm thầm.
  • Toán tử gán kết hợp từ phải sang trái, khác với đa số toán tử khác.
  • Mảng không gán được bằng dấu bằng, phải dùng memcpy. Struct thì gán được.