ทำไมฟังก์ชั่นนี้คืนความยาวที่ถูกต้องของสตริง? (การเพิ่มตัวชี้ถ่าน)


12

นี่คือฟังก์ชั่นที่นับจำนวนตัวอักษรในสตริง:

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

ทำไมสิ่งนี้ถึงส่งคืนความยาวที่ถูกต้อง?

"a"สมมติว่าผมเรียกฟังก์ชั่นนี้ด้วยเชือกที่เรียบง่าย จากนั้นsจะเพิ่มขึ้นในขณะที่ลูปดังนั้นค่าของsและiเป็นทั้ง 0

คำตอบ:


10

ค่าของs++เป็นค่าเดิมของsก่อนที่จะเพิ่มขึ้นการเพิ่มขึ้นที่เกิดขึ้นในเวลาที่ไม่ได้ระบุก่อนที่จุดลำดับถัดไป

ดังนั้น*s++และ*(s++)เทียบเท่า: พวกเขาทั้งสอง dereference sค่าเดิมของ การแสดงออกที่เท่าเทียมกันอีกประการหนึ่งคือ*(0, s++)และไม่ใช่สำหรับลมใจเช่นนี้0[s++]

โปรดทราบว่าฟังก์ชั่นของคุณควรใช้ type size_tfor iและ return type:

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 จะเพิ่มขึ้นในขณะที่ลูปดังนั้นค่าของ s คือ 0 และฉันก็คือ 0

ในตัวอย่างที่sชี้ไปใน'a' "a"จากนั้นจะเพิ่มขึ้นและiเพิ่มขึ้นอีก ตอนนี้sชี้ไปที่เทอร์มิโมฆะและเป็นi 1ดังนั้นในการวิ่งผ่านลูปถัดไป*(s++)คือ'\0'(ซึ่งก็คือ0) ดังนั้นลูปจึงสิ้นสุดและค่าปัจจุบันของi (นั่นคือ1)

โดยทั่วไปการวนซ้ำจะทำงานหนึ่งครั้งสำหรับอักขระแต่ละตัวในสตริงแล้วหยุดที่ตัวปิดเทอร์มินัล null ซึ่งเป็นวิธีการนับตัวอักษร


เนื่องจาก s อยู่ในวงเล็บฉันคิดว่ามันจะเพิ่มขึ้นก่อน (ดังนั้นตอนนี้ก็ชี้ไปที่ '/ 0') ดังนั้นขณะที่ห่วงเป็นเท็จและฉันไม่เคยเพิ่มขึ้น
Lor

2
@ เพิ่มเติมจำสิ่งที่ผู้ประกอบการ postincrement: มันประเมินสิ่งที่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" s ตอนนี้จะชี้ไปที่ "/ 0") เนื่องจากเงื่อนไขอยู่ในขณะที่ (0) จึงไม่มีการป้อนลูปในขณะนั้น
Lor

2

ตัวดำเนินการที่เพิ่มขึ้นภายหลังเพิ่มค่าของตัวถูกดำเนินการ 1 แต่ค่าของนิพจน์เป็นค่าดั้งเดิมของตัวถูกดำเนินการก่อนการดำเนินการเพิ่ม

สมมติอาร์กิวเมนต์ที่ผ่านมามีstr_len() "a"ในstr_len()ตัวชี้จะชี้ไปที่ตัวอักษรตัวแรกของสตริงs "a"ในwhileวง:

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

แม้sจะเพิ่มขึ้น แต่ค่าของsในการแสดงออก'a'จะเป็นตัวชี้ไปยังตัวละครมันจะชี้ไปที่เพิ่มขึ้นมาก่อนซึ่งเป็นตัวชี้ไปยังตัวอักษรตัวแรก เมื่อตัวชี้sเป็น dereferenced 'a'ก็จะให้ตัวละคร ในการย้ำถัดไปชี้จะชี้ไปที่ตัวอักษรถัดไปซึ่งเป็นตัวละครที่เป็นโมฆะs \0เมื่อsยกเลิกการลงทะเบียนแล้วจะให้0และลูปจะถูกออก โปรดทราบว่าsตอนนี้จะชี้ไปที่องค์ประกอบหนึ่งที่ผ่านมาอักขระ null "a"ของสตริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.