Dlaczego ta funkcja zwraca prawidłową długość łańcucha? (Zwiększanie wskaźnika char)

12

Ta funkcja zlicza liczbę znaków w ciągu:

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

Dlaczego to zwraca prawidłową długość?

Powiedzmy, że nazywam tę funkcję prostym ciągiem "a". Następnie sjest zwiększana w pętli while, dlatego wartość si ioba wynoszą 0.

lor
źródło

Odpowiedzi:

10

Wartość s++jest pierwotną wartością sprzyrostu, przed przyrostem, występuje on w nieokreślonym czasie przed następnym punktem sekwencji.

Stąd *s++i *(s++)są równoważne: obaj odwołują się do pierwotnej wartości s. Innym równoważnym wyrażeniem jest *(0, s++)i, nie dla osób o słabym sercu, takie jest:0[s++]

Zauważ jednak, że twoja funkcja powinna używać type size_tfor ii jej typu zwracanego:

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

Oto potencjalnie bardziej wydajna wersja z jednym przyrostem na pętlę:

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

Dla tych, którzy zastanawiają się nad dziwnymi wyrażeniami w drugim akapicie:

  • 0, s++jest przykładem operatora przecinka, ,który ocenia jego lewą część, a następnie jej prawą część, która stanowi jej wartość. stąd (0, s++)jest równoważne z (s++).

  • 0[s++]jest równoważne (s++)[0]i *(0 + s++)lub *(s++ + 0)które upraszczają jako *(s++). Przeniesienie wskaźnika i wyrażeń indeksowych w []wyrażeniach nie jest zbyt powszechne ani szczególnie przydatne, ale jest zgodne ze standardem C.

chqrlie
źródło
Mam nadzieję, że operator przecinka był czysty. Zabierz , s++i złe rzeczy się zdarzą:)
David C. Rankin
6

Powiedzmy, że nazywam tę funkcję prostym ciągiem „a”. Następnie s jest zwiększane w pętli while, dlatego wartość s wynosi 0, a i również wynosi 0.

W tym przykładzie swskazuje na 'a'in "a". Następnie jest zwiększany, a itakże zwiększany. Teraz swskaż na terminator zerowy i ijest 1. Tak więc w następnym przebiegu przez pętlę *(s++)jest '\0'(która jest 0), więc pętla kończy się i zwracana jest bieżąca wartość i(to 1).

Zasadniczo pętla jest uruchamiana raz dla każdego znaku w ciągu, a następnie zatrzymuje się na zakończeniu zerowym, więc w ten sposób liczy znaki.

Płomień
źródło
Ponieważ s jest w nawiasach, pomyślałem, że najpierw będzie zwiększany (więc teraz wskazuje na „/ 0”). Dlatego pętla while jest fałszem i nigdy nie jest zwiększana.
lub
2
@lor, pamiętaj, co operatorzy poinkrementacji: ocenia to, co sutrzymywało przed inkrementacją. To, co opisujesz, to zachowanie ++s(które w rzeczywistości byłoby niedoszacowane o jeden i wywołanie UB, jeśli przejdzie pusty ciąg).
Toby Speight
2

Ma to sens:

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

„Ale sjest w nawiasach. Dlatego pomyślałem, że najpierw zostanie zwiększony”

Właśnie dlatego wskaźnik jest zwiększany, a nie znak, powiedzmy, że masz (*s)++, w tym przypadku znak będzie zwiększany, a nie wskaźnik. Dereferencje oznaczają, że pracujesz teraz z wartością wskazywaną przez wskaźnik, a nie sam wskaźnik.

Ponieważ obaj operatorzy mają tę samą przewagę, ale skojarzenie od prawej do lewej, możesz nawet użyć po prostu *s++bez nawiasów, aby zwiększyć wskaźnik.

anastaciu
źródło
Ale s jest w nawiasach. Właśnie dlatego pomyślałem, że najpierw wzrośnie. (Jeśli mamy prosty ciąg, taki jak „a” wskazuje teraz na „/ 0”). Ponieważ warunkiem jest teraz while (0), pętla while nigdy nie jest wprowadzana.
lub
2

Operator postkrementacji zwiększa wartość operandu o 1, ale wartość wyrażenia jest pierwotną wartością operandu przed operacją inkrementacji.

Załóżmy, że przekazany argument str_len()to "a". W str_len(), wskaźnik sjest skierowany do pierwszego znaku łańcucha "a". W whilepętli:

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

chociaż wartość sbędzie zwiększana, ale wartość sw wyrażeniu będzie wskaźnikiem do znaku, na który wskazuje przed przyrostem, czyli wskaźnika do pierwszego znaku 'a'. Po sodsunięciu wskaźnika nada charakter 'a'. W następnej iteracji swskaźnik będzie wskazywał na następny znak, który jest znakiem pustym \0. Gdy szostanie odwołany, poda się, 0a pętla zostanie zakończona. Zauważ, sże teraz będzie wskazywał jeden element poza pustym znakiem łańcucha "a".

HS
źródło