Làm thế nào để kiểm tra xem một chuỗi có bắt đầu bằng một chuỗi khác trong C hay không?


83

Có một cái gì đó giống như startsWith(str_a, str_b)trong thư viện C tiêu chuẩn?

Nó sẽ đưa con trỏ đến hai chuỗi kết thúc bằng nullbyte và cho tôi biết liệu chuỗi đầu tiên có xuất hiện hoàn toàn ở đầu chuỗi thứ hai hay không.

Ví dụ:

"abc", "abcdef" -> true
"abcdef", "abc" -> false
"abd", "abdcef" -> true
"abc", "abc"    -> true

3
Tôi nghĩ ví dụ thứ 3 của bạn nên có kết quả đúng.
Michael Burr

Câu trả lời:


73

Rõ ràng là không có chức năng C tiêu chuẩn cho việc này. Vì thế:

bool startsWith(const char *pre, const char *str)
{
    size_t lenpre = strlen(pre),
           lenstr = strlen(str);
    return lenstr < lenpre ? false : memcmp(pre, str, lenpre) == 0;
}

Lưu ý rằng phần trên rất hay và rõ ràng, nhưng nếu bạn đang thực hiện nó trong một vòng lặp chặt chẽ hoặc làm việc với các chuỗi rất lớn, nó không mang lại hiệu suất tốt nhất vì nó quét toàn bộ chiều dài của cả hai chuỗi phía trước ( strlen). Các giải pháp như wj32's hoặc Christoph's có thể mang lại hiệu suất tốt hơn (mặc dù nhận xét về vectơ hóa này nằm ngoài tầm ken của tôi về C). Cũng lưu ý giải pháp của Fred Foo tránh strlenbật str(anh ấy nói đúng, nó không cần thiết nếu bạn sử dụng strncmpthay thế memcmp). Chỉ quan trọng đối với các chuỗi (rất) lớn hoặc sử dụng lặp đi lặp lại trong các vòng lặp chặt chẽ, nhưng khi nó quan trọng, điều đó quan trọng.


5
Tôi nên đề cập rằng điều thông thường sẽ là chuỗi là tham số đầu tiên và tiền tố là tham số thứ hai. Nhưng tôi đã giữ chúng như trên bởi vì dường như câu hỏi của bạn được đóng khung như thế nào ... Thứ tự là hoàn toàn tùy thuộc vào bạn, nhưng tôi thực sự nên làm theo cách khác - hầu hết các hàm chuỗi lấy chuỗi đầy đủ làm đối số đầu tiên, chuỗi con là đối số thứ hai.
TJ Crowder

1
Đây là một giải pháp thanh lịch, nhưng nó có một số vấn đề về hiệu suất. Một triển khai được tối ưu hóa sẽ không bao giờ xem xét nhiều hơn các ký tự min (strlen (pre), strlen (str)) từ mỗi chuỗi, cũng như không bao giờ nhìn ra ngoài sự không khớp đầu tiên. Nếu các dây dài, nhưng thường xảy ra tình trạng không khớp sớm, thì nó sẽ rất nhẹ. Nhưng vì việc triển khai này chiếm toàn bộ chiều dài của cả hai chuỗi ngay trước, nó buộc thực hiện trong trường hợp xấu nhất, ngay cả khi các chuỗi khác nhau ở ký tự đầu tiên. Vấn đề này có thực sự phụ thuộc vào hoàn cảnh hay không, nhưng đó là một vấn đề tiềm ẩn.
Tom Karzes

1
@TomKarzes Bạn có thể thay thế memcmpcho strncmpđây và nó nhanh hơn. Không có UB vì cả hai chuỗi được biết là có ít nhất lenprebyte. strncmpkiểm tra từng byte của cả hai chuỗi cho NUL, nhưng các strlenlệnh gọi đã được đảm bảo rằng không có bất kỳ chuỗi nào. (Nhưng nó vẫn có hiệu suất hit mà bạn đã đề cập, khi prehoặc strlâu hơn trình tự ban đầu phổ biến thực tế.)
Jim Balter

1
@JimBalter - Điểm rất tốt! Vì việc sử dụng memcmpở trên sẽ không chiếm dụng từ một câu trả lời khác ở đây, tôi đã tiếp tục và thay đổi nó trong câu trả lời.
TJ Crowder

1
PS Đây (bây giờ) có thể là câu trả lời nhanh nhất trên một số máy có một số chuỗi, bởi vì strlenmemcmpcó thể được thực hiện với các hướng dẫn phần cứng rất nhanh, và các strlens có thể đưa các chuỗi vào bộ nhớ đệm, tránh bộ nhớ kép. Trên những máy như vậy, strncmpcó thể được thực hiện dưới dạng hai strlens và một memcmpnhư thế này, nhưng sẽ rất rủi ro cho người viết thư viện làm như vậy, vì điều đó có thể mất nhiều thời gian hơn trên các chuỗi dài với tiền tố chung ngắn. Ở đây, lần truy cập đó là rõ ràng và các strlenlần đánh chỉ được thực hiện một lần mỗi lần ( strlen+ của Fred Foo's strncmpsẽ thực hiện 3 lần).
Jim Balter

156

Không có chức năng tiêu chuẩn cho việc này, nhưng bạn có thể xác định

bool prefix(const char *pre, const char *str)
{
    return strncmp(pre, str, strlen(pre)) == 0;
}

Chúng tôi không phải lo lắng về str việc ngắn hơn prevì theo tiêu chuẩn C (7.21.4.4/2):

Các strncmpchức năng so sánh không hơn nký tự (nhân vật mà theo một ký tự null không so) từ mảng trỏ đến bởi s1để mảng trỏ đến bởi s2".


12
Tại sao câu trả lời là không? Rõ ràng, câu trả lời là có, nó được gọi là strncmp.
Jasper

6
^ Rõ ràng là tại sao câu trả lời là không. Một thuật toán sử dụng strncmpstrlenkhông được gọi là "strncmp".
Jim Balter

33

Tôi có thể sẽ đi với strncmp(), nhưng chỉ cho vui là một triển khai thô:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    while(*prefix)
    {
        if(*prefix++ != *string++)
            return 0;
    }

    return 1;
}

6
Tôi thích điều này nhất - không có lý do gì để quét một trong hai chuỗi để tìm độ dài.
Michael Burr

1
Tôi có lẽ cũng sẽ sử dụng strlen + strncmp, nhưng mặc dù nó hoạt động trên thực tế, tất cả những tranh cãi về định nghĩa mơ hồ của nó đang khiến tôi khó chịu. Vì vậy, tôi sẽ sử dụng cái này, cảm ơn.
Sam Watkins

4
Điều này có thể sẽ chậm hơn strncmp, trừ khi trình biên dịch của bạn thực sự giỏi vectơ hóa, bởi vì người viết glibc chắc chắn là :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

3
Phiên bản này sẽ nhanh hơn phiên bản strlen + strncmp nếu tiền tố không khớp, đặc biệt nếu đã có sự khác biệt trong một vài ký tự đầu tiên.
dpi

1
^ Tối ưu hóa đó sẽ chỉ áp dụng nếu hàm được nội tuyến.
Jim Balter

5

Tôi không phải là chuyên gia viết mã thanh lịch, nhưng ...

int prefix(const char *pre, const char *str)
{
    char cp;
    char cs;

    if (!*pre)
        return 1;

    while ((cp = *pre++) && (cs = *str++))
    {
        if (cp != cs)
            return 0;
    }

    if (!cs)
        return 0;

    return 1;
}

5

Sử dụng strstr()chức năng. Stra == strstr(stra, strb)


3
đó có vẻ là cách làm hơi ngược - bạn sẽ đi qua toàn bộ chuỗi mặc dù nó phải rõ ràng từ phân đoạn ban đầu rất ngắn nếu strb có phải là tiền tố hay không.
StasM

1
Tối ưu hóa sớm là gốc rễ của mọi điều xấu. Tôi nghĩ rằng đây là giải pháp tốt nhất, nếu nó không phải là mã thời gian quan trọng hoặc chuỗi dài.
Frank Buss

1
@ilw Đó là một câu nói nổi tiếng của các nhà khoa học máy tính nổi tiếng - hãy google nó. Nó thường được áp dụng sai (như nó ở đây) ... xem joshbarczak.com/blog/?p=580
Jim Balter

2

Tối ưu hóa (v.2. - đã sửa):

uint32 startsWith( const void* prefix_, const void* str_ ) {
    uint8 _cp, _cs;
    const uint8* _pr = (uint8*) prefix_;
    const uint8* _str = (uint8*) str_;
    while ( ( _cs = *_str++ ) & ( _cp = *_pr++ ) ) {
        if ( _cp != _cs ) return 0;
    }
    return !_cp;
}

2
bỏ phiếu tiêu cực: startsWith("\2", "\1")trả về 1, startsWith("\1", "\1")cũng trả về 1
thejh

Quyết định này sẽ không sử dụng các tối ưu trong tiếng kêu, vì không sử dụng các instrisinc.
socketpair

^ bản chất không giúp ích gì ở đây, đặc biệt nếu chuỗi mục tiêu dài hơn nhiều so với tiền tố.
Jim Balter

1

Vì tôi đã chạy phiên bản được chấp nhận và gặp sự cố với một chuỗi rất dài, tôi phải thêm vào logic sau:

bool longEnough(const char *str, int min_length) {
    int length = 0;
    while (str[length] && length < min_length)
        length++;
    if (length == min_length)
        return true;
    return false;
}

bool startsWith(const char *pre, const char *str) {
    size_t lenpre = strlen(pre);
    return longEnough(str, lenpre) ? strncmp(str, pre, lenpre) == 0 : false;
}

1

Hoặc sự kết hợp của hai cách tiếp cận:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    char * const restrict prefix_end = prefix + 13;
    while (1)
    {
        if ( 0 == *prefix  )
            return 1;   
        if ( *prefix++ != *string++)
            return 0;
        if ( prefix_end <= prefix  )
            return 0 == strncmp(prefix, string, strlen(prefix));
    }  
}

CHỈNH SỬA: Đoạn mã dưới đây KHÔNG hoạt động bởi vì nếu strncmp trả về 0, nó sẽ không biết liệu đã đạt đến kết thúc 0 hay độ dài (block_size) hay không.

Một ý tưởng bổ sung là so sánh khối khôn ngoan. Nếu khối không bằng nhau, hãy so sánh khối đó với hàm ban đầu:

_Bool starts_with_big(const char *restrict string, const char *restrict prefix)
{
    size_t block_size = 64;
    while (1)
    {
        if ( 0 != strncmp( string, prefix, block_size ) )
          return starts_with( string, prefix);
        string += block_size;
        prefix += block_size;
        if ( block_size < 4096 )
          block_size *= 2;
    }
}

Các hằng số 13, 64, 4096, cũng như các lũy thừa của block_sizechỉ là phỏng đoán. Nó sẽ phải được chọn cho dữ liệu đầu vào và phần cứng đã sử dụng.


Đây là những ý tưởng tốt. Tuy nhiên, lưu ý rằng hành vi đầu tiên là hành vi không xác định về mặt kỹ thuật nếu tiền tố ngắn hơn 12 byte (13 bao gồm cả NUL) vì tiêu chuẩn ngôn ngữ không xác định kết quả của việc tính toán một địa chỉ bên ngoài chuỗi khác với byte ngay sau đó.
Jim Balter

@JimBalter: Bạn có thể thêm tài liệu tham khảo không? Nếu con trỏ được tham chiếu đến và ở sau 0 kết thúc thì giá trị con trỏ được tham chiếu là không xác định. Nhưng tại sao địa chỉ không được xác định? Nó chỉ là một phép tính.
shpc

Tuy nhiên, có một lỗi chung: Mức block_sizetăng phải sau bước tăng của con trỏ. Hiện đã được sửa.
shpc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.