Warum gibt diese Funktion die richtige Länge eines Strings zurück? (Inkrementieren eines Zeichenzeigers)


12

Dies ist eine Funktion, die die Anzahl der Zeichen in einer Zeichenfolge zählt:

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

Warum gibt dies die richtige Länge zurück?

Angenommen, ich rufe diese Funktion mit einem einfachen String auf "a". Dann swird in der while-Schleife inkrementiert, daher sind der Wert von sund ibeide 0.

Antworten:


10

Der Wert von s++ist der ursprüngliche Wert von s, bevor das Inkrement zu einem nicht angegebenen Zeitpunkt vor dem nächsten Sequenzpunkt auftritt.

Daher *s++und *(s++)sind äquivalent: Beide dereferenzieren den ursprünglichen Wert von s. Ein anderer äquivalenter Ausdruck ist *(0, s++)und nicht für schwache Nerven wie dieser:0[s++]

Beachten Sie jedoch, dass Ihre Funktion type size_tfor iund seinen Rückgabetyp verwenden sollte:

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

Hier ist eine potenziell effizientere Version mit einem einzelnen Inkrement pro Schleife:

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

Für diejenigen, die sich über die seltsamen Ausdrücke im zweiten Absatz wundern:

  • 0, s++ist eine Instanz des Kommaoperators ,, der seinen linken Teil und dann seinen rechten Teil, der seinen Wert ausmacht, auswertet. daher(0, s++) ist äquivalent zu (s++).

  • 0[s++]ist äquivalent zu (s++)[0]und *(0 + s++)oder *(s++ + 0)die vereinfachen als *(s++). Das Transponieren des Zeigers und der Indexausdrücke in []Ausdrücken ist weder sehr häufig noch besonders nützlich, entspricht jedoch dem C-Standard.


Klar hoffe der Komma-Operator war klar. Nehmen Sie das weg , s++und schlimme Dinge werden passieren:)
David C. Rankin

6

Angenommen, ich rufe diese Funktion mit einem einfachen String "a" auf. Dann wird s in der while-Schleife inkrementiert, daher ist der Wert von s 0 und i ist ebenfalls 0.

In diesem Beispiel szeigt auf das 'a'in "a". Dann wird es inkrementiert und iauch inkrementiert. Zeigen Sie nun sauf den Nullterminator und iist 1. Also im nächsten Durchlauf durch die Schleife *(s++)ist '\0'(was ist 0), also endet die Schleife und der aktuelle Wert von i(das ist)1 ) wird zurückgegeben.

Im Allgemeinen wird die Schleife für jedes Zeichen in der Zeichenfolge einmal ausgeführt und stoppt dann am Nullterminator. Auf diese Weise werden die Zeichen gezählt.


Da s in Klammern steht, dachte ich, dass es zuerst inkrementiert wird (also zeigt es jetzt auf '/ 0'). Daher ist die while-Schleife falsch und i wird niemals inkrementiert.
lor

2
@lor, denken Sie daran , was die Postinkrement Betreiber: Es wertet, was auch immer sgehalten , bevor erhöht wird . Was Sie beschreiben, ist das Verhalten von ++s(das tatsächlich um eins unterzählt und UB aufruft, wenn eine leere Zeichenfolge übergeben wird).
Toby Speight

2

Es macht vollkommen Sinn:

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

"Aber ssteht in Klammern. Deshalb dachte ich, es würde zuerst erhöht werden."

Genau aus diesem Grund wird der Zeiger inkrementiert und nicht das Zeichen. Nehmen wir an (*s)++, in diesem Fall wird das Zeichen inkrementiert und nicht der Zeiger. Die Dereferenzierung bedeutet, dass Sie jetzt mit dem Wert arbeiten, auf den sich der Zeiger bezieht, und nicht mit dem Zeiger selbst.

Da beide Operatoren die gleiche Priorität haben, aber die Assoziativität von rechts nach links, können Sie *s++den Zeiger sogar einfach ohne Klammern inkrementieren.


Aber s steht in Klammern. Deshalb dachte ich, dass es zuerst erhöht werden würde. (Wenn wir einen einfachen String wie "a" haben, würde s jetzt auf "/ 0" zeigen). Da die Bedingung jetzt while (0) ist, wird die while-Schleife niemals eingegeben.
lor

2

Der Post-Inkrement-Operator erhöht den Wert des Operanden um 1, aber der Wert des Ausdrucks ist der ursprüngliche Wert des Operanden vor der Inkrement-Operation.

Angenommen, das übergebene Argument str_len()ist "a". In str_len()zeigt der Zeiger sauf das erste Zeichen der Zeichenfolge "a". In demwhile Schleife:

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

Das swird zwar inkrementiert, aber der Wert von sim Ausdruck ist ein Zeiger auf das Zeichen, auf das er vor dem Inkrement zeigt, das ein Zeiger auf das erste Zeichen ist 'a'. Wenn der Zeiger sdereferenziert wird, gibt er Zeichen 'a'. In der nächsten Iteration zeigt der sZeiger auf das nächste Zeichen, das das Nullzeichen ist \0. Wenn sdereferenziert wird, gibt es0 und die Schleife wird beendet. Beachten Sie, dass sjetzt auf ein Element nach dem Nullzeichen der Zeichenfolge verwiesen wird "a".

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.