นี่คือคำอธิบายโดยละเอียดซึ่งฉันหวังว่าจะเป็นประโยชน์ เริ่มต้นด้วยโปรแกรมของคุณเพราะมันง่ายที่สุดที่จะอธิบาย
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++