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

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

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

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

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

Ответы [ 4 ]

10 голосов
/ 14 января 2020

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

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

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

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

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

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.

6 голосов
/ 14 января 2020

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

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

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

2 голосов
/ 14 января 2020

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

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

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

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

2 голосов
/ 14 января 2020

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

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++ без скобок для увеличения указателя.

...