Bỏ qua điều hướng, tới nội dung chính
Học C
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 0Hai chuỗi giống hệt nhaustrcmp("abc", "abc")
Âma đứng trước b theo thứ tự từ điểnstrcmp("abc", "abd")
Dươnga đứng sau bstrcmp("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ánhSo cái gìKhi nào dùng
a == bHai con trỏ, tức hai địa chỉKiểm tra hai con trỏ có trỏ cùng chỗ không
strcmp(a, b) == 0Nội dung hai chuỗiSo 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 0So 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 0So 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ánhByte khác nhau đầu tiênKết quảVì sao
"abc" và "abd"'c' là 99, 'd' là 100âm99 nhỏ hơn 100
"abc" và "abcd"byte 0 và 'd' là 100âm0 nhỏ hơn 100
"Zebra" và "apple"'Z' là 90, 'a' là 97âmChữ hoa đứng trước chữ thường
"10" và "9"'1' là 49, '9' là 57âmSo 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àmCó ở đâuCần gì
strcasecmpPOSIX, tức Linux và macOS#include <strings.h>
_stricmpWindows, 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ưuKiể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ử

  1. In kết quả của bảy lời gọi strcmp trong bài, giải thích dấu của từng cái.
  2. 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ả.
  3. So sánh hai hằng chuỗi giống nhau bằng dấu bằng với -O0 -O2, ghi lại chênh lệch.
  4. 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.
  5. 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.
  6. Đ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ả.
  7. 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

  • strcmp trả 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.