นี่คือคำอธิบายโดยละเอียดซึ่งฉันหวังว่าจะเป็นประโยชน์ เริ่มต้นด้วยโปรแกรมของคุณเพราะมันง่ายที่สุดที่จะอธิบาย
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
คำสั่งแรก:
const char* p = "Hello";
ประกาศเป็นตัวชี้ไปยังp charเมื่อเราพูดว่า "pointer to a char" หมายความว่าอย่างไร หมายความว่าค่าของpคือที่อยู่ของ a char; pบอกเราว่าในหน่วยความจำมีพื้นที่ว่างไว้ให้เก็บไฟล์char.
คำสั่งยังเริ่มต้นpเพื่อชี้ไปที่อักขระตัวแรกในสตริงลิเทอรั"Hello"ล เพื่อประโยชน์ของแบบฝึกหัดนี้สิ่งสำคัญคือต้องเข้าใจpว่าไม่ได้ชี้ไปที่สตริงทั้งหมด แต่เป็นเพียงอักขระตัวแรก'H'เท่านั้น หลังจากที่ทุกคนpเป็นตัวชี้ไปหนึ่งcharไม่สตริงทั้งหมด ค่าของpที่อยู่ของใน'H'"Hello"
จากนั้นคุณตั้งค่าลูป:
while (*p++)
เงื่อนไขการวนซ้ำ*p++หมายถึงอะไร? สามสิ่งกำลังทำงานอยู่ที่นี่ซึ่งทำให้งง (อย่างน้อยก็จนกว่าความคุ้นเคยจะเข้ามา):
- ลำดับความสำคัญของตัวดำเนินการสองตัวคือ postfix
++และ indirection*
- ค่าของนิพจน์ส่วนเพิ่ม postfix
- ผลข้างเคียงของนิพจน์ส่วนเพิ่ม postfix
1. ความเป็นผู้นำ การดูตารางลำดับความสำคัญของตัวดำเนินการอย่างรวดเร็วจะบอกคุณว่าการเพิ่มขึ้นของ postfix มีลำดับความสำคัญสูงกว่า (16) มากกว่าการลดค่าอ้างอิง / ทิศทาง (15) ซึ่งหมายความว่าการแสดงออกที่ซับซ้อนจะถูกจัดกลุ่มเป็น:*p++ *(p++)กล่าวคือ*ส่วนหนึ่งจะถูกนำไปใช้กับค่าของp++ส่วน งั้นมาแบ่งp++ส่วนกันก่อน
2. ค่านิพจน์ ค่าของp++คือค่าของก่อนที่จะเพิ่มขึ้นp ถ้าคุณมี:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
ผลลัพธ์จะเป็น:
7
8
เพราะi++ประเมินiก่อนส่วนเพิ่ม ในทำนองเดียวกันจะไปประเมินมูลค่าปัจจุบันของp++ pที่เรารู้ว่ามูลค่าปัจจุบันของเป็นที่อยู่ของp'H'
ตอนนี้p++ส่วนหนึ่ง*p++ได้รับการประเมินแล้ว pมันเป็นมูลค่าปัจจุบันของ จากนั้น*ส่วนจะเกิดขึ้น *(current value of p)วิธีการ: pการเข้าถึงค่าที่อยู่ที่จัดขึ้นโดย 'H'เรารู้ว่าคุ้มค่าที่อยู่ที่ว่าคือ ดังนั้นการแสดงออกที่ประเมิน*p++'H'
รอสักครู่คุณกำลังพูด หากได้*p++รับการประเมินเป็น'H'เหตุใดจึงไม่'H'พิมพ์รหัสด้านบน นั่นคือที่มาของผลข้างเคียง
3. Postfix ผลข้างเคียงการแสดงออก postfix ++มีค่าของตัวถูกดำเนินการปัจจุบัน แต่มีผลข้างเคียงของการเพิ่มตัวถูกดำเนินการนั้น ฮะ? ดูintรหัสนั้นอีกครั้ง:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
ตามที่ระบุไว้ก่อนหน้าผลลัพธ์จะเป็น:
7
8
เมื่อi++ได้รับการประเมินในครั้งแรกprintf()จะได้รับการประเมินเป็น 7 แต่มาตรฐาน C รับประกันว่าเมื่อถึงจุดหนึ่งก่อนที่วินาทีprintf()จะเริ่มดำเนินการผลข้างเคียงของตัว++ดำเนินการจะเกิดขึ้น กล่าวคือก่อนที่สองprintf()เกิดขึ้นiจะได้รับเพิ่มขึ้นเป็นผลมาจากการดำเนินการในครั้งแรก++ printf()โดยวิธีนี้เป็นหนึ่งในไม่กี่แห่งที่รับประกันมาตรฐานเกี่ยวกับระยะเวลาของผลข้างเคียง
ในรหัสของคุณแล้วเมื่อการแสดงออกได้รับการประเมินก็ประเมิน*p++ 'H'แต่เมื่อถึงเวลานี้:
printf ("%c", *p)
ผลข้างเคียงที่น่ารำคาญเกิดขึ้น pได้รับการเพิ่มขึ้น โว้ว! ไม่ได้ชี้ไปที่อีกต่อไป'H'แต่เป็นอักขระหนึ่งตัวที่ผ่านมา'H': ถึง'e'ในอีกนัยหนึ่ง ที่อธิบายเอาท์พุท cockneyfied ของคุณ:
ello
ดังนั้นการขับร้องของข้อเสนอแนะที่เป็นประโยชน์ (และถูกต้อง) ในคำตอบอื่น ๆ : ในการพิมพ์การออกเสียงที่ได้รับ"Hello"และไม่ใช่คู่หูของมันคุณต้องมีบางอย่างเช่น
while (*p)
printf ("%c", *p++);
มากสำหรับสิ่งนั้น ส่วนที่เหลือล่ะ? คุณถามเกี่ยวกับความหมายของสิ่งเหล่านี้:
*ptr++
*++ptr
++*ptr
*++ptrเราเพียงแค่พูดคุยเกี่ยวกับครั้งแรกเพื่อให้รูปลักษณ์ที่สอง:
เราเห็นในการอธิบายก่อนหน้านี้ของเราที่เพิ่มขึ้น postfix p++มีบางอย่างที่มีความสำคัญเป็นมูลค่าและผลข้างเคียง การเพิ่มขึ้นของคำนำหน้า++pมีเดียวกันผลข้างเคียงที่เป็นของคู่ postfix ของมันเพิ่มขึ้นถูกดำเนินการโดยการ 1. แต่ก็มีความแตกต่างกันมีความสำคัญและที่แตกต่างกันค่า
การเพิ่มคำนำหน้ามีความสำคัญต่ำกว่า postfix มันมีความสำคัญ 15 ในคำอื่น ๆ *ก็มีความสำคัญเช่นเดียวกับผู้ประกอบการ ในนิพจน์เช่น
*++ptr
สิ่งที่สำคัญไม่ใช่ความสำคัญ: ตัวดำเนินการทั้งสองมีความสำคัญเหมือนกัน การเชื่อมโยงจึงเริ่มต้นขึ้นการเพิ่มคำนำหน้าและตัวดำเนินการทิศทางมีการเชื่อมโยงด้านซ้ายขวา เนื่องจากการเชื่อมโยงนั้นตัวถูกดำเนินการptrจะถูกจัดกลุ่มด้วยตัวดำเนินการด้านขวาสุด++ก่อนที่ตัวดำเนินการจะไปทางซ้าย*มากขึ้น ในคำอื่น ๆ *(++ptr)การแสดงออกเป็นไปได้จัดกลุ่ม ดังนั้นเช่นเดียวกับ*ptr++แต่ด้วยเหตุผลอื่นตรง*นี้ก็จะนำส่วนนี้ไปใช้กับค่าของชิ้น++ptrส่วนด้วย
แล้วค่านั้นคืออะไร? มูลค่าของการแสดงออกคำนำหน้าเพิ่มขึ้นคือค่าของตัวถูกดำเนินการหลังจากที่เพิ่มขึ้น สิ่งนี้ทำให้มันเป็นสัตว์ร้ายที่แตกต่างจากตัวดำเนินการส่วนเพิ่มของ postfix สมมติว่าคุณมี:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
ผลลัพธ์จะเป็น:
8
8
... แตกต่างจากที่เราเห็นด้วยตัวดำเนินการ postfix ในทำนองเดียวกันหากคุณมี:
const char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
ผลลัพธ์จะเป็น:
H e e l // good dog
คุณเห็นไหมว่าทำไม?
++*ptrตอนนี้เราได้รับการแสดงออกที่สามที่คุณถามเกี่ยวกับ นั่นเป็นสิ่งที่ยากที่สุดจริงๆ ตัวดำเนินการทั้งสองมีความสำคัญเหมือนกันและการเชื่อมโยงด้านซ้าย - ขวา ++(*ptr)ที่นี้หมายถึงการแสดงออกจะถูกจัดกลุ่ม ++ส่วนหนึ่งจะนำไปใช้กับค่าของ*ptrส่วนหนึ่ง
ดังนั้นหากเรามี:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
ผลลัพธ์ที่แสดงออกอย่างน่าประหลาดใจจะเป็น:
I
อะไร?! เอาล่ะดังนั้นส่วนหนึ่งเป็นไปได้ที่จะประเมิน*p 'H'จากนั้นการ++เข้ามาเล่น ณ จุดนั้นมันจะถูกนำไปใช้กับ'H'ตัวชี้ไม่ใช่เลย! จะเกิดอะไรขึ้นเมื่อคุณเพิ่ม 1 ใน'H'? คุณจะได้รับ 1 บวกค่า ASCII เท่ากับ'H'72; คุณจะได้รับ 73 แสดงว่าเป็นcharและคุณจะได้รับcharกับค่า ASCII 'I'73:
ที่ดูแลสามสำนวนที่คุณถามในคำถามของคุณ นี่คืออีกสิ่งหนึ่งที่กล่าวถึงในความคิดเห็นแรกสำหรับคำถามของคุณ:
(*ptr)++
อันนั้นก็น่าสนใจเหมือนกัน ถ้าคุณมี:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
มันจะให้ผลลัพธ์ที่กระตือรือร้นนี้:
HI
เกิดอะไรขึ้น? อีกครั้งก็เป็นเรื่องของความสำคัญ , ค่าการแสดงออกและผลข้างเคียง เนื่องจากวงเล็บ*pส่วนนั้นจึงถือเป็นนิพจน์หลัก นิพจน์หลักสำคัญกว่าสิ่งอื่นใด พวกเขาได้รับการประเมินก่อน และในขณะที่คุณรู้ว่าประเมิน*p 'H'ส่วนที่เหลือของนิพจน์++ส่วนจะถูกนำไปใช้กับค่านั้น ดังนั้นในกรณีนี้จะกลายเป็น(*p)++'H'++
มูลค่าของ'H'++อะไร? ถ้าคุณบอกว่า'I'คุณลืมไปแล้ว (แล้ว!) โปรดจำไว้ว่า'H'++ประเมินกับค่าปัจจุบันของ 'H'เพื่อให้เป็นครั้งแรกที่เกิดขึ้นในการพิมพ์printf() 'H'จากนั้นเป็นผลข้างเคียงที่เป็นไปได้ที่จะเพิ่มขึ้น'H' 'I'ที่สองพิมพ์ว่าprintf() 'I'และคุณมีคำทักทายที่ร่าเริง
เอาล่ะ แต่ในสองกรณีสุดท้ายทำไมฉันถึงต้องการ
char q[] = "Hello";
char* p = q;
ทำไมฉันไม่สามารถมีบางอย่างเช่น
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
เนื่องจาก"Hello"เป็นสตริงลิเทอรัล ถ้าคุณพยายามที่++*pคุณกำลังพยายามที่จะเปลี่ยน'H'ในสตริงเพื่อทำให้สตริงทั้ง'I' "Iello"ในภาษา C ตัวอักษรสตริงเป็นแบบอ่านอย่างเดียว การพยายามแก้ไขทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด "Iello"ไม่ได้ระบุเป็นภาษาอังกฤษเช่นกัน แต่นั่นเป็นเพียงเรื่องบังเอิญ
ตรงกันข้ามคุณไม่สามารถมี
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
ทำไมจะไม่ล่ะ? เนื่องจากในกรณีนี้pคืออาร์เรย์ อาร์เรย์ไม่ใช่ค่า l ที่ปรับเปลี่ยนได้ คุณไม่สามารถเปลี่ยนตำแหน่งpโดยการเพิ่มหรือลดก่อนหรือหลังได้เนื่องจากชื่อของอาร์เรย์ทำงานราวกับว่าเป็นตัวชี้คงที่ (นั่นไม่ใช่สิ่งที่เป็นจริงนั่นเป็นเพียงวิธีที่สะดวกในการดู)
สรุปได้สามสิ่งที่คุณถามเกี่ยวกับ:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
และนี่คือหนึ่งในสี่ทุก ๆ ความสนุกพอ ๆ กับอีกสาม:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
ตัวแรกและตัวที่สองจะผิดพลาดหากptrเป็นตัวระบุอาร์เรย์จริงๆ อันที่สามและสี่จะผิดพลาดหากptrชี้ไปที่สตริงลิเทอรัล
ที่นั่นคุณมี ฉันหวังว่าตอนนี้จะเป็นคริสตัลทั้งหมด คุณเป็นผู้ชมที่ยอดเยี่ยมและฉันจะอยู่ที่นี่ตลอดทั้งสัปดาห์
(*ptr)++(ต้องใช้วงเล็บเพื่อทำให้ไม่*ptr++