Як перевірити, чи рядок починається з іншого рядка в C?


83

Чи є щось подібне startsWith(str_a, str_b)до стандартної бібліотеки C?

Потрібно взяти покажчики на два рядки, що закінчуються нульбайтами, і сказати мені, чи перший також повністю з’являється на початку другого.

Приклади:

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

3
Я думаю, що ваш 3-й приклад повинен мати справжній результат.
Michael Burr

можливо дублікат stackoverflow.com/questions/15515088 / ...
vacing

Відповіді:


73

Очевидно, для цього не існує стандартної функції C. Тому:

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;
}

Зауважте, що вищесказане є приємним і зрозумілим, але якщо ви робите це в щільному циклі або працюєте з дуже великими струнами, це не найкраще працює, оскільки сканує всю довжину обох рядків спереду ( strlen). Рішення, такі як wj32 або Christoph, можуть запропонувати кращу продуктивність (хоча цей коментар щодо векторизації виходить за рамки мого сенсу C). Також зверніть увагу на рішення Фреда Foo в який уникає strlenна str(він має рацію, це НЕ потрібно , якщо ви використовуєте strncmpзамість memcmp). Має значення лише для (дуже) великих рядків або багаторазового використання в щільних циклах, але коли це важливо, це має значення.


5
Слід зазначити, що звичайним є те, що рядок буде першим параметром, а префікс - другим. Але я дотримувався їх, як зазначено вище, тому що, здавалося, саме так формулювалося ваше запитання ... Порядок залежить виключно від вас, але я справді мав би зробити це навпаки - більшість рядкових функцій приймають повний рядок як перший аргумент, підрядок як другий.
TJ Crowder,

1
Це елегантне рішення, але воно має деякі проблеми з продуктивністю. Оптимізована реалізація ніколи не буде розглядати більше ніж min (strlen (pre), strlen (str)) символів з кожного рядка, і ніколи не буде виглядати далі першого невідповідності. Якби струни були довгими, але ранні невідповідності були поширеними, це було б дуже легко. Але оскільки ця реалізація займає всю довжину обох рядків прямо вперед, це призводить до найгіршого виконання, навіть якщо рядки відрізняються в самому першому символі. Чи це насправді залежить від обставин, але це потенційна проблема.
Tom Karzes

1
@TomKarzes Ви можете замінити memcmpна strncmpтут , і це швидше. UB немає, оскільки відомо, що обидва рядки мають принаймні lenpreбайти. strncmpперевіряє кожен байт обох рядків на наявність NUL, але strlenдзвінки вже гарантували, що їх немає. (Але в ньому все ще є хіт продуктивності, про який ви згадали, коли preабо strперевищують фактичну загальну початкову послідовність.)
Джим Балтер,

1
@JimBalter - Дуже хороший момент! Оскільки використання memcmpвищезазначеного не відповідає іншій відповіді тут, я продовжив і змінив його у відповіді.
TJ Crowder,

1
PS Це (зараз) може бути найшвидшою відповіддю на деяких машинах з деякими рядками, тому що strlenі memcmpможе бути реалізовано за допомогою дуже швидких апаратних вказівок, і strlens може вставляти рядки в кеш, уникаючи подвійного потрапляння в пам'ять. На таких машинах це strncmpможе бути реалізовано як два strlens і memcmpподібні до цього, але для бібліотечного письменника це буде ризиковано, оскільки це може зайняти набагато більше часу на довгих рядках із короткими загальними префіксами. Тут цей удар є явним, і strlens виконуються лише один раз для кожного (Fred Foo's strlen+ strncmpзробить 3).
Джим Балтер

156

Для цього не існує стандартної функції, але ви можете визначити

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

Нам не потрібно турбуватися про strте, що ми коротші, ніж preтому, що відповідно до стандарту С (7.21.4.4/2):

strncmpФункція порівнює не більше ніж nсимволи (символи , які слідують нульовий символ не порівнювати) з масиву , на який вказує s1на масив , на який вказує s2«.


12
Чому відповідь «ні»? Очевидно, що відповідь так, це називається strncmp.
Джаспер

6
^ Має бути очевидним, чому відповідь - ні. Алгоритм, який використовує strncmpі strlenне "називається strncmp".
Джим Балтер

33

Я б, мабуть, пішов strncmp(), але просто для розваги, сире імплементація:

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

    return 1;
}

6
Мені це найбільше подобається - немає причин сканувати будь-яку з рядків на довжину.
Michael Burr

1
Я б, мабуть, теж пішов із strlen + strncmp, але хоча це насправді працює, всі суперечки щодо його розпливчастого визначення мене відволікають. Тож я скористаюся цим, дякую.
Сем Уоткінс

4
Це, швидше за все, буде повільніше, ніж strncmp, якщо ваш компілятор не дуже добре векторизує, тому що glibc-автори впевнені :-)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

3
Ця версія повинна бути швидшою за версію strlen + strncmp, якщо префікс не збігається, особливо якщо в перших кількох символах вже є відмінності.
dpi

1
^ Ця оптимізація застосовуватиметься лише в тому випадку, якщо функція вбудована.
Джим Балтер

5

Я не фахівець у написанні елегантного коду, але ...

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

Використовуйте strstr()функцію. Stra == strstr(stra, strb)


3
це здається дещо відсталим способом - ви пройдете цілі стра, хоча це повинно бути зрозуміло з дуже короткого початкового сегмента, якщо strb є префіксом чи ні.
StasM

1
Передчасна оптимізація - корінь усього зла. Я думаю, що це найкраще рішення, якщо це не критично важливий час код або довгі рядки.
Frank Buss

1
@ilw Це відомий вислів відомих інформатиків - погугл. Це часто неправильно застосовується (як тут) ... див. Joshbarczak.com/blog/?p=580
Джим Балтер

2

Оптимізовано (v.2. - виправлено):

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
негативне голосування: startsWith("\2", "\1")повертає 1, startsWith("\1", "\1")також повертає 1
2015 року

Це рішення не використовуватиме оптимізацію в дзвінках, оскільки не використовуватиме інстинкти.
socketpair

Внутрішнє значення тут не допомагає, особливо якщо цільовий рядок набагато довший за префікс.
Джим Балтер

1

Оскільки я запустив прийняту версію і мав проблему з дуже довгим str, мені довелося додати таку логіку:

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

Або поєднання двох підходів:

_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));
    }  
}

EDIT: Наведений нижче код НЕ працює, оскільки якщо strncmp повертає 0, невідомо, чи було досягнуто кінцеве значення 0 або довжина (block_size).

Додатковою ідеєю є порівняння за блоками. Якщо блок не рівний, порівняйте цей блок із вихідною функцією:

_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;
    }
}

Константи 13, 64, 4096, а також експоненціація block_sizeлише здогадки. Його потрібно було б вибрати для використовуваних вхідних даних та обладнання.


Це хороші ідеї. Зверніть увагу, що перший з них є технічно невизначеною поведінкою, якщо префікс менший за 12 байт (13, включаючи NUL), оскільки мовний стандарт не визначає результат обчислення адреси поза рядком, крім безпосередньо наступного байта.
Джим Балтер

@JimBalter: Не могли б ви додати посилання? Якщо вказівник розмежовується і знаходиться після закінчення 0, тоді визначене значення вказівника не визначено. Але чому сама адреса не має бути визначеною? Це просто розрахунок.
shpc

Однак виникла загальна помилка: block_sizeприріст повинен бути після збільшення вказівника. Зараз виправлено
shpc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.