Tại sao hàm này trả về độ dài chính xác của chuỗi? (Tăng một con trỏ char)


12

Đây là hàm đếm số lượng ký tự trong chuỗi:

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

Tại sao điều này trả về độ dài chính xác?

Giả sử tôi gọi hàm này bằng một Chuỗi đơn giản "a". Sau đó sđược tăng lên trong vòng lặp while, do đó giá trị của siđều bằng 0.

Câu trả lời:


10

Giá trị của s++là giá trị ban đầu của s, trước khi tăng, mức tăng xảy ra tại một thời điểm không xác định trước điểm chuỗi tiếp theo.

Do đó *s++*(s++)là tương đương: cả hai đều quy định giá trị ban đầu của s. Một biểu hiện tương đương khác là *(0, s++)và không dành cho người yếu tim, đây là biểu hiện:0[s++]

Tuy nhiên, lưu ý rằng chức năng của bạn nên sử dụng loại size_tcho ivà loại trả về của nó:

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

Đây là một phiên bản có khả năng hiệu quả hơn với một mức tăng trên mỗi vòng lặp:

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

Đối với những người thắc mắc về các biểu thức kỳ lạ trong đoạn thứ hai:

  • 0, s++là một thể hiện của toán tử dấu phẩy ,đánh giá phần bên trái của nó, sau đó phần bên phải của nó tạo thành giá trị của nó. do đó (0, s++)tương đương với (s++).

  • 0[s++]tương đương (s++)[0]*(0 + s++)hoặc *(s++ + 0)đơn giản hóa như *(s++). Chuyển đổi con trỏ và biểu thức chỉ mục trong []biểu thức không phổ biến và cũng không đặc biệt hữu ích nhưng phù hợp với tiêu chuẩn C.


Chắc chắn hy vọng các nhà điều hành dấu phẩy đã rõ ràng. Bỏ đi , s++và những điều tồi tệ sẽ xảy ra:)
David C. Rankin

6

Giả sử tôi gọi hàm này bằng một chuỗi đơn giản là "a". Sau đó s được tăng lên trong vòng lặp while do đó giá trị của s là 0 và i cũng bằng 0.

Trong ví dụ đó, schỉ vào 'a'trong "a". Sau đó, nó được tăng lên và icũng được tăng lên. Bây giờ schỉ đến terminator null, và i1. Vì vậy, trong lần chạy tiếp theo qua vòng lặp, *(s++)'\0'(đó là 0), do đó vòng lặp kết thúc và giá trị hiện tại của i(đó 1) được trả về.

Nói chung, vòng lặp chạy một lần cho mỗi ký tự trong chuỗi và sau đó dừng lại ở dấu kết thúc null, vì vậy đó là cách nó đếm các ký tự.


Vì s nằm trong ngoặc, tôi nghĩ rằng nó sẽ được tăng lên trước (vì vậy bây giờ nó trỏ đến '/ 0'). Do đó, vòng lặp while là sai và tôi không bao giờ tăng.
Lor

2
@lor, hãy nhớ những gì các toán tử postincrement: nó đánh giá bất cứ thứ gì sđược giữ trước khi tăng. Những gì bạn đang mô tả là hành vi của ++s(mà thực sự sẽ bị đánh giá thấp bởi một người và gọi UB nếu thông qua một chuỗi trống).
Toby Speight

2

Nó có ý nghĩa hoàn hảo:

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

"Nhưng s là trong ngoặc. Đó là lý do tại sao tôi nghĩ rằng nó sẽ được tăng lên đầu tiên"

Đó chính xác là lý do tại sao con trỏ được tăng lên chứ không phải ký tự, giả sử bạn có (*s)++, trong trường hợp này, ký tự sẽ được tăng lên chứ không phải con trỏ. Việc hủy bỏ hội thảo có nghĩa là bạn hiện đang làm việc với giá trị được giới thiệu bởi con trỏ, chứ không phải chính con trỏ.

Vì cả hai toán tử đều có cùng mức độ ưu tiên nhưng tính kết hợp từ phải sang trái, bạn thậm chí có thể sử dụng đơn giản *s++mà không có dấu ngoặc để tăng con trỏ.


Nhưng s là trong ngoặc. Đó là lý do tại sao tôi nghĩ rằng nó sẽ được tăng lên đầu tiên. (Nếu chúng ta có một Chuỗi đơn giản như "a" bây giờ sẽ trỏ đến "/ 0"). Bởi vì điều kiện bây giờ là while (0), vòng lặp while không bao giờ được nhập.
Lor

2

Toán tử gia tăng bài tăng giá trị của toán hạng lên 1 nhưng giá trị của biểu thức là giá trị ban đầu của toán hạng trước thao tác tăng.

Giả sử đối số được truyền vào str_len()"a". Trong str_len(), con trỏ sđang trỏ đến ký tự đầu tiên của chuỗi "a". Trong whilevòng lặp:

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

mặc dù ssẽ được tăng lên nhưng giá trị của strong biểu hiện sẽ là con trỏ đến các nhân vật nó được trỏ đến trước khi tăng, đó là con trỏ tới ký tự đầu tiên 'a'. Khi con trỏ sđược hủy đăng ký, nó sẽ cho ký tự 'a'. Trong lần lặp lại tiếp theo, scon trỏ sẽ trỏ đến ký tự tiếp theo là ký tự null \0. Khi sđược hủy đăng ký, nó sẽ cho 0và vòng lặp sẽ được thoát. Lưu ý rằng, sbây giờ sẽ trỏ đến một phần tử qua ký tự null của chuỗi "a".

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.