Bài 11.716 phút đọc
strcmp
Sau bài này bạn sẽ làm được
- Đọc đúng ý nghĩa giá trị trả về của strcmp
- Không bao giờ so sánh chuỗi bằng toán tử bằng
- So sánh không phân biệt hoa thường một cách di động
- Dùng strcmp làm hàm so sánh cho qsort
So sánh chuỗi là nơi C khác các ngôn ngữ khác nhiều nhất về mặt cú pháp, và cũng là nơi người mới mắc lỗi thường xuyên nhất. Toán tử bằng kép hoạt động trên chuỗi C, nhưng nó không so sánh nội dung.
#Cách dùng và giá trị trả về
#include <string.h>
int strcmp(const char *a, const char *b);| Giá trị trả về | Nghĩa là | Ví dụ |
|---|---|---|
| Bằng 0 | Hai chuỗi giống hệt nhau | strcmp("abc", "abc") |
| Âm | a đứng trước b theo thứ tự từ điển | strcmp("abc", "abd") |
| Dương | a đứng sau b | strcmp("abd", "abc") |
strcmp.c
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("%d\n", strcmp("abc", "abc")); /* 0 */
printf("%d\n", strcmp("abc", "abd")); /* âm, thường là -1 */
printf("%d\n", strcmp("abd", "abc")); /* dương, thường là 1 */
printf("%d\n", strcmp("abc", "abcd")); /* âm, chuỗi ngắn hơn đứng trước */
printf("%d\n", strcmp("", "a")); /* âm */
printf("%d\n", strcmp("", "")); /* 0 */
printf("%d\n", strcmp("Z", "a")); /* âm: 'Z' là 90, 'a' là 97 */
return 0;
}#Không so sánh chuỗi bằng dấu bằng
So sánh con trỏ
char a[] = "hello";
char b[] = "hello";
if (a == b) /* so hai ĐỊA CHỈ, luôn khác nhau */
printf("giong nhau\n"); /* không bao giờ chạy */So sánh nội dung
char a[] = "hello";
char b[] = "hello";
if (strcmp(a, b) == 0)
printf("giong nhau\n"); /* chạy đúng */| Phép so sánh | So cái gì | Khi nào dùng |
|---|---|---|
| a == b | Hai con trỏ, tức hai địa chỉ | Kiểm tra hai con trỏ có trỏ cùng chỗ không |
| strcmp(a, b) == 0 | Nội dung hai chuỗi | So sánh chuỗi, gần như luôn là cái bạn cần |
| memcmp(a, b, n) | n byte đầu, không quan tâm byte 0 | So dữ liệu nhị phân có độ dài biết trước |
| strncmp(a, b, n) | Nhiều nhất n ký tự, dừng ở byte 0 | So tiền tố |
#Thứ tự từ điển
Hàm strcmp so từng byte một cho tới khi gặp cặp khác nhau, hoặc tới khi cả hai cùng kết thúc.
/* Gần đúng với cài đặt thật */
int strcmp(const char *a, const char *b)
{
while (*a != '\0' && *a == *b) {
++a;
++b;
}
return (unsigned char)*a - (unsigned char)*b;
}| So sánh | Byte khác nhau đầu tiên | Kết quả | Vì sao |
|---|---|---|---|
| "abc" và "abd" | 'c' là 99, 'd' là 100 | âm | 99 nhỏ hơn 100 |
| "abc" và "abcd" | byte 0 và 'd' là 100 | âm | 0 nhỏ hơn 100 |
| "Zebra" và "apple" | 'Z' là 90, 'a' là 97 | âm | Chữ hoa đứng trước chữ thường |
| "10" và "9" | '1' là 49, '9' là 57 | âm | So theo ký tự, không theo giá trị số |
strncmp cho so sánh tiền tố
int strncmp(const char *a, const char *b, size_t n);/* Kiểm tra chuỗi có bắt đầu bằng một tiền tố không */
if (strncmp(dong, "GET ", 4) == 0)
xu_ly_get(dong + 4);
/* Dùng sizeof để không phải đếm tay */
#define BAT_DAU_BANG(s, t) (strncmp((s), (t), sizeof (t) - 1) == 0)
if (BAT_DAU_BANG(dong, "GET "))
xu_ly_get(dong + sizeof "GET " - 1);Hàm này dừng khi đã so đủ n ký tự, hoặc khi gặp byte kết thúc ở một trong hai chuỗi. Nhờ vậy nó an toàn ngay cả khi chuỗi ngắn hơn n.
#So sánh không phân biệt hoa thường
Chuẩn C không có hàm nào cho việc này. Có hai hàm phổ biến nhưng cả hai đều không thuộc chuẩn.
| Hàm | Có ở đâu | Cần gì |
|---|---|---|
| strcasecmp | POSIX, tức Linux và macOS | #include <strings.h> |
| _stricmp | Windows, thư viện của Microsoft | #include <string.h> |
di-dong.c
#include <ctype.h>
/* Bản tự viết, chạy ở mọi nơi. */
int so_sanh_khong_phan_biet(const char *a, const char *b)
{
while (*a != '\0' && *b != '\0') {
int x = tolower((unsigned char)*a);
int y = tolower((unsigned char)*b);
if (x != y)
return x - y;
++a;
++b;
}
return (unsigned char)*a - (unsigned char)*b;
}#Sắp xếp mảng chuỗi
sap-xep.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Mảng con trỏ chuỗi: mỗi phần tử là một địa chỉ. */
static int so_sanh_chuoi(const void *x, const void *y)
{
const char *const *a = x;
const char *const *b = y;
return strcmp(*a, *b);
}
int main(void)
{
const char *ten[] = { "Cuong", "An", "Binh", "Dung" };
size_t n = sizeof ten / sizeof ten[0];
qsort(ten, n, sizeof ten[0], so_sanh_chuoi);
for (size_t i = 0; i < n; ++i)
printf("%s ", ten[i]);
printf("\n");
return 0;
}terminal
./sap-xep
An Binh Cuong Dung
Sắp xếp mảng hai chiều thì khác
char ten[4][16] = { "Cuong", "An", "Binh", "Dung" };
/* Ở đây mỗi phần tử là một MẢNG 16 ký tự, không phải con trỏ.
Địa chỉ phần tử chính là địa chỉ chuỗi, nên không cần bỏ tầng nào. */
static int so_sanh_mang(const void *x, const void *y)
{
return strcmp(x, y);
}
qsort(ten, 4, sizeof ten[0], so_sanh_mang);| Cách lưu | Kiểu phần tử | Hàm so sánh |
|---|---|---|
| const char *ten[4] | con trỏ | strcmp(*a, *b) |
| char ten[4][16] | mảng 16 ký tự | strcmp(x, y) |
Tự làm thử
- In kết quả của bảy lời gọi
strcmptrong bài, giải thích dấu của từng cái. - So sánh hai mảng ký tự cùng nội dung bằng dấu bằng và bằng
strcmp, so kết quả. - So sánh hai hằng chuỗi giống nhau bằng dấu bằng với
-O0và-O2, ghi lại chênh lệch. - Viết hàm so sánh không phân biệt hoa thường và kiểm thử với chuỗi trộn hoa thường.
- Sắp xếp mảng con trỏ chuỗi và mảng hai chiều bằng
qsort, viết hai hàm so sánh khác nhau. - Đo thời gian sắp xếp một trăm nghìn chuỗi bằng hai cách lưu, so sánh kết quả.
- Viết chương trình đọc danh sách từ vào rồi in ra theo thứ tự từ điển, có tùy chọn phân biệt hoa thường hay không.
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
strcmptrả về 0 khi bằng, âm khi chuỗi thứ nhất đứng trước, và chỉ dấu mới có ý nghĩa.- Không bao giờ so sánh chuỗi bằng
==. Nó so hai địa chỉ, và đôi khi cho kết quả đúng do trùng hợp. - So sánh theo bảng mã, nên mọi chữ hoa đứng trước mọi chữ thường, và chuỗi số so theo ký tự chứ không theo giá trị.
- Chuẩn C không có hàm so sánh không phân biệt hoa thường. Tự viết bằng
tolower, và luôn ép vềunsigned char. - Sắp mảng con trỏ chuỗi cần hàm so sánh bỏ một tầng con trỏ, khác với sắp mảng hai chiều.