Почему эта функция возвращает правильную длину строки? (Увеличение указателя на символ)


12

Это функция, которая считает количество символов в строке:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) {
        i++;
    }
    return i;
}

Почему это возвращает правильную длину?

Допустим, я вызываю эту функцию с простой строкой "a". Затем sувеличивается в цикле while, поэтому значения sи iравны 0.

Ответы:


10

Значение s++является исходным значением s, до увеличения, приращение происходит в неуказанное время до следующей точки последовательности.

Следовательно *s++и *(s++)эквивалентны: они оба разыменовывают исходное значение s. Другое эквивалентное выражение, *(0, s++)и, не для слабонервных, таково это:0[s++]

Обратите внимание, что ваша функция должна использовать тип size_tfor iи тип возвращаемого значения:

size_t str_len(const char *s) {
    size_t i = 0;
    while (*s++) {
        i++;
    }
    /* s points after the null terminator */
    return i;
}

Вот потенциально более эффективная версия с одним шагом на цикл:

size_t str_len(const char *s) {
    const char *s0 = s;
    while (*s++) {
        /* nothing */
    }
    return s - 1 - s0;
}

Для тех, кто интересуется странными выражениями во втором абзаце:

  • 0, s++является экземпляром оператора запятой, ,который оценивает его левую часть, а затем правую часть, которая составляет его значение. следовательно (0, s++)эквивалентно (s++).

  • 0[s++]эквивалентно (s++)[0]и *(0 + s++)или или *(s++ + 0)которые упрощают как *(s++). Транспонирование указателя и индексных выражений в []выражениях не очень распространено и не особенно полезно, но соответствует стандарту C.


Надеемся, что запятая была ясна. Забери , s++и плохие вещи случатся:)
Дэвид С. Ранкин

6

Допустим, я называю эту функцию простой строкой «а». Затем s увеличивается в цикле while, поэтому значение s равно 0, a i также равно 0.

В этом примере sуказывает на 'a'в "a". Затем он увеличивается, а iтакже увеличивается. Теперь sукажите на нулевой терминатор и iесть 1. Таким образом, при следующем запуске цикла *(s++)будет '\0'(что есть 0), так что цикл заканчивается, и возвращается текущее значение i(это 1).

Как правило, цикл запускается один раз для каждого символа в строке, а затем останавливается на нулевом терминаторе, поэтому он подсчитывает символы.


Поскольку s в скобках, я подумал, что он будет увеличиваться первым (теперь он указывает на '/ 0'). Поэтому цикл while имеет значение false, и я никогда не увеличивается.
лор

2
@lor, помните, что операторы postincrement: он оценивает все, что sхранится до увеличения. То, что вы описываете, это поведение ++s(которое действительно будет недооцениваться на единицу и вызывать UB, если передается пустая строка).
Тоби Спейт

2

Это имеет смысл:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) { //<-- increments the pointer to char till the end of the string
                    //till it finds '\0', that is, if s = "a" then s is 'a'
                    // followed by '\0' so it increments one time
        i++; //counts the number of times the pointer moves forward
    }
    return i;
}

«Но sэто в скобках. Вот почему я думал, что это будет увеличиваться в первую очередь»

Именно поэтому указатель увеличивается, а не символ, скажем, у вас (*s)++, в этом случае будет увеличиваться символ, а не указатель. Разыменование означает, что вы теперь работаете со значением, на которое ссылается указатель, а не с самим указателем.

Поскольку оба оператора имеют одинаковый приоритет, но ассоциативность справа налево, вы даже можете использовать просто *s++без скобок для увеличения указателя.


Но с в скобках. Вот почему я думал, что это будет увеличиваться в первую очередь. (Если бы у нас была простая строка типа «a», то теперь она указывала бы на «/ 0»). Поскольку условие теперь while (0), цикл while никогда не вводится.
лор

2

Оператор постинкрементного увеличения значения операнда на 1, но значение выражения является исходным значением операнда до операции приращения.

Предположим, что аргумент передан str_len()is "a". В str_len(), указатель sуказывает на первый символ строки "a". В whileцикле:

while(*(s++)) {
.....
.....

хотя значение sбудет увеличиваться, но значение sв выражении будет указателем на символ, на который оно указывает перед увеличением, то есть указателем на первый символ 'a'. Когда указатель sразыменовывается, он будет давать символ 'a'. На следующей итерации sуказатель будет указывать на следующий символ, который является нулевым символом \0. Когда sразыменовывается, это даст, 0и цикл будет выход. Обратите внимание, что sтеперь будет указывать на один элемент после нулевого символа строки "a".

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.