Toán tử so sánh
Sau bài này bạn sẽ làm được
- Giải thích vì sao kết quả so sánh trong C là kiểu int
- Tránh năm lỗi so sánh kinh điển
- So sánh số thực và chuỗi đúng cách
- Giải thích vì sao không được trộn signed với unsigned
Sáu toán tử so sánh của C rất đơn giản, nhưng chúng đứng sau năm lỗi kinh điển mà gần như người học C nào cũng mắc ít nhất một lần. Bài này đi qua từng lỗi, giải thích nguyên nhân và cách phòng.
#Sáu toán tử
| Toán tử | Ví dụ | Kết quả | Ý nghĩa |
|---|---|---|---|
| == | 5 == 5 | 1 | Bằng |
| != | 5 != 3 | 1 | Khác |
| > | 5 > 3 | 1 | Lớn hơn |
| < | 5 < 3 | 0 | Nhỏ hơn |
| >= | 5 >= 5 | 1 | Lớn hơn hoặc bằng |
| <= | 3 <= 5 | 1 | Nhỏ hơn hoặc bằng |
#1. Dùng dấu bằng đơn
Lỗi phổ biến nhất trong toàn bộ ngôn ngữ C.
if (x = 5) {
printf("x bằng 5");
}
/* Đây là phép GÁN, không phải so sánh.
Nó gán 5 vào x, rồi lấy giá trị 5 làm điều kiện.
5 khác 0 nên điều kiện LUÔN đúng.
Và x đã bị thay đổi. */if (x == 5) {
printf("x bằng 5");
}Lỗi này biên dịch được vì trong C, phép gán là một biểu thức có giá trị, và mọi biểu thức đều dùng làm điều kiện được.
#2. So sánh số thực
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b) {
printf("bằng nhau"); /* KHÔNG BAO GIỜ chạy */
}Bài 2.3 đã giải thích nguyên nhân: số thực lưu ở hệ nhị phân nên 0,1 và 0,2 đều chỉ là giá trị gần đúng. Tổng của hai giá trị gần đúng không trùng khít với giá trị gần đúng của 0,3.
#include <math.h>
/* Cách 1: sai số tuyệt đối, đủ dùng khi các số cùng cỡ */
if (fabs(a - b) < 1e-9) {
printf("coi như bằng nhau");
}
/* Cách 2: sai số tương đối, đúng cả với số rất lớn hoặc rất nhỏ */
int gan_bang(double x, double y, double eps)
{
double lon_hon = fmax(fabs(x), fabs(y));
return fabs(x - y) <= eps * lon_hon;
}Riêng các phép so sánh lớn hơn và nhỏ hơn thì vẫn dùng bình thường được với số thực. Chỉ có phép bằng và khác là có vấn đề.
#3. So sánh chuỗi
char a[10] = "abc";
char b[10] = "abc";
if (a == b) {
printf("bằng nhau"); /* KHÔNG chạy */
}
/* a và b là hai mảng khác nhau, nằm ở hai địa chỉ khác nhau.
Phép so sánh này so ĐỊA CHỈ, không so nội dung. */#include <string.h>
char a[10] = "abc";
char b[10] = "abc";
if (strcmp(a, b) == 0) {
printf("bằng nhau"); /* chạy */
}#4. Trộn signed và unsigned
Đây là lỗi tinh vi nhất trong năm lỗi, vì đoạn code trông hoàn toàn hợp lý.
#include <stdio.h>
int main(void)
{
int a = -1;
unsigned b = 1;
if (a < b) {
printf("a nhỏ hơn b\n");
} else {
printf("a KHÔNG nhỏ hơn b\n"); /* dòng này chạy */
}
return 0;
}Trường hợp hay gặp nhất trong thực tế là so sánh với kết quả của sizeof hoặc strlen, vì cả hai đều trả về size_t không dấu.
if (-1 < sizeof(int)) {
printf("không bao giờ in ra\n");
}
int n = -1;
if (n < strlen(s)) {
printf("cũng không bao giờ in ra\n");
}
/* Vòng lặp vô hạn kinh điển */
for (size_t i = n - 1; i >= 0; --i) { }#5. Nối chuỗi so sánh
Trong toán học, 0 < x < 10 nghĩa là x nằm giữa 0 và 10. Trong C, biểu thức đó hợp lệ về cú pháp nhưng có nghĩa hoàn toàn khác.
if (0 < x && x < 10) { } /* đúng */
if (0 < x < 10) { }
/* C hiểu là: ((0 < x) < 10)
Phần trong ngoặc cho ra 0 hoặc 1.
Cả 0 và 1 đều nhỏ hơn 10.
Điều kiện LUÔN đúng với mọi x. */if (0 < x && x < 10) {
printf("x nằm trong khoảng");
}
/* Hoặc tách ra cho rõ ràng */
int trong_khoang = (x > 0) && (x < 10);
if (trong_khoang) { }Bảng tổng kết năm lỗi
| Lỗi | Cách viết sai | Cách viết đúng |
|---|---|---|
| Dấu bằng đơn | if (x = 5) | if (x == 5) |
| So sánh số thực | if (a == b) | if (fabs(a - b) < eps) |
| So sánh chuỗi | if (s1 == s2) | if (strcmp(s1, s2) == 0) |
| Trộn dấu | if (n < strlen(s)) | if (n >= 0 && (size_t)n < strlen(s)) |
| Nối chuỗi so sánh | if (0 < x < 10) | if (0 < x && x < 10) |
Tự làm thử
- Viết hàm kiểm tra năm nhuận theo công thức
(y % 4 == 0 && y % 100 != 0) || y % 400 == 0, kiểm thử với 1900, 2000, 2024, 2023. - Nhập ba cạnh, kiểm tra có tạo thành tam giác không và nếu có thì thuộc loại nào.
- Kiểm tra một ký tự thuộc loại nào: chữ hoa, chữ thường, chữ số, khoảng trắng, hay ký tự đặc biệt. Không dùng
<ctype.h>. - Tái hiện lỗi trộn dấu: so sánh một biến
intâm vớisizeof(int), rồi biên dịch với-Wextravà đọc cảnh báo. - Viết hàm
gan_bangso sánh số thực theo sai số tương đối, kiểm thử với cặp số cỡ một tỷ và cặp số cỡ một phần tỷ. - Tìm và sửa năm lỗi trong một đoạn code do bạn tự viết dựa trên bảng tổng kết ở trên.
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
- Kết quả so sánh trong C là int, bằng 1 nếu đúng và 0 nếu sai.
if (x = 5)là phép gán và luôn đúng. Bật-Wallđể bắt lỗi này.- Không so sánh số thực bằng dấu bằng kép, hãy so theo sai số tương đối.
- So sánh chuỗi phải dùng
strcmp. Dấu bằng kép so địa chỉ chứ không so nội dung. - Trộn signed với unsigned làm số có dấu bị đổi thành số không dấu rất lớn.
0 < x < 10luôn đúng, phải viết0 < x && x < 10.