Чому ця функція повертає правильну довжину рядка? (Збільшення покажчика символів)


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_tдля 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

Скажімо, я називаю цю функцію простим рядком "a". Тоді s збільшується в циклі while, тому значення s дорівнює 0 a i також дорівнює 0.

У цьому прикладі, sвказує на 'a'ін "a". Потім він збільшується і iтакож збільшується. Тепер sвкажіть на нульовий термінатор і iє 1. Отже, при наступному пробігу через цикл *(s++)є '\0'(що є 0), тому цикл закінчується, і поточне значення i(that 1) повертається.

Як правило, цикл працює один раз для кожного символу в рядку, а потім зупиняється на нульовому термінаторі, тож саме так він рахує символи.


Оскільки s знаходиться в дужках, я думав, що спочатку його збільшать (тому тепер він вказує на "/ 0"). Тому цикл while неправдивий, і я ніколи не збільшується.
лор

2
@lor, пам’ятай, що виконують оператори післязростання: він оцінює те, що sвідбулося перед збільшенням. Те, що ви описуєте, - це поведінка ++s(яка б насправді була недооцінена на одиницю, і викликати UB, якщо він пропустив порожню рядок).
Toby Speight

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++без дужок для збільшення покажчика.


Але s в дужках. Тому я подумав, що спочатку це зросте. (Якщо у нас є простий рядок типу "a", то зараз би вказував на "/ 0"). Оскільки умова зараз є (0), цикл while ніколи не вводиться.
лор

2

Оператор після збільшення приросту збільшує значення операнда на 1, але значення виразу є початковим значенням операнду перед операцією збільшення.

Припустимо, аргумент переданий str_len()є "a". У полі str_len()вказівник sвказує на перший символ рядка "a". У whileциклі:

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

хоча sволя буде нарощена, але значення sв виразі буде вказувати на символ, на який він вказує перед збільшенням, який вказує на перший символ 'a'. Коли вказівник sбуде відмеженим, він надасть характер 'a'. У наступній ітерації sвказівник буде вказувати на наступний символ, який є нульовим символом \0. Коли sdereferenced, він видасть 0і цикл буде виходом. Зауважте, що sтепер буде вказувати на один елемент повз нульовий символ рядка "a".

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