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 đương | Ví dụ |
|---|---|---|
| a = b | gán trực tiếp | x = 10 |
| a += b | a = a + b | tong += diem |
| a -= b | a = a - b | con_lai -= da_dung |
| a *= b | a = a * b | tich *= i |
| a /= b | a = a / b | n /= 10 |
| a %= b | a = a % b | vi_tri %= kich_thuoc |
| a &= b | a = a & b | co &= ~MAT_NA |
| a |= b | a = a | b | co |= CO_DOC |
| a ^= b | a = a ^ b | co ^= CO_AN |
| a <<= b | a = a << b | x <<= 2 |
| a >>= b | a = a >> b | x >>= 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án | Kết quả | Điều gì xảy ra |
|---|---|---|
| int i = 3.99 | 3 | Cắt phần thập phân |
| int i = -3.99 | -3 | Cắt về phía số 0 |
| char c = 300 | 44 | Chỉ giữ 8 bit thấp |
| unsigned u = -1 | 4294967295 | Diễn giải lại các bit |
| float f = 1.0 / 3 | 0.3333333 | Mất độ chính xác |
| int i = 1e20 | không xác định | Vượ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ử
- Viết chương trình đếm số ký tự nhập vào bằng thành ngữ
while ((c = getchar()) != EOF). - 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.
- Viết chương trình chứng minh
i += 0.7không làmithay đổi khiilàint. - Biên dịch một bài tập cũ của bạn với
-Wconversionvà sửa hết cảnh báo. - Cho
int i; double d; i = d = 3.7;, đoán giá trị hai biến rồi chạy để kiểm chứng. - Viết đoạn code trong đó
a[f()] += 1vàa[f()] = a[f()] + 1cho 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.