จะอธิบายพอยน์เตอร์ C (การประกาศเทียบกับโอเปอเรเตอร์ unary) ให้กับผู้เริ่มต้นได้อย่างไร


141

ฉันมีความยินดีที่จะอธิบายพอยน์เตอร์ให้กับการเขียนโปรแกรม C และเริ่มสะดุดเมื่อมีปัญหาต่อไปนี้ อาจไม่เหมือนปัญหาเลยถ้าคุณรู้วิธีใช้ตัวชี้ไปแล้ว แต่ลองดูตัวอย่างต่อไปนี้ด้วยใจที่ชัดเจน:

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

สำหรับผู้เริ่มต้นการส่งออกอาจจะน่าแปลกใจ ในบรรทัดที่ 2 เขา / เธอเพิ่งประกาศ * bar ให้เป็น & foo แต่ในบรรทัดที่ 4 ปรากฎว่า * bar นั้นเป็น foo แทนที่จะเป็น & foo!

ความสับสนคุณอาจพูดว่าเกิดจากความกำกวมของสัญลักษณ์ *: ในบรรทัดที่ 2 มันถูกใช้เพื่อประกาศพอยน์เตอร์ ในบรรทัดที่ 4 จะใช้เป็นตัวดำเนินการ unary ซึ่งดึงค่าจุดตัวชี้ที่ สองสิ่งที่แตกต่างใช่มั้ย

อย่างไรก็ตาม "คำอธิบาย" นี้ไม่ได้ช่วยผู้เริ่มต้นเลย มันแนะนำแนวคิดใหม่โดยชี้ให้เห็นความแตกต่างที่ละเอียดอ่อน นี่ไม่ใช่วิธีที่ถูกต้องในการสอน

ดังนั้น Kernighan และ Ritchie อธิบายได้อย่างไร

ตัวดำเนินการ unary * เป็นตัวดำเนินการทางอ้อมหรือการยกเลิกการลงทะเบียน เมื่อนำไปใช้กับตัวชี้มันเข้าถึงวัตถุที่ตัวชี้ชี้ไปที่ [ ... ]

การประกาศของตัวชี้ ip int *ipนั้นมีวัตถุประสงค์เพื่อช่วยในการจำ มันบอกว่าการแสดงออก*ipเป็น int ไวยากรณ์ของการประกาศสำหรับการเลียนแบบตัวแปรไวยากรณ์ของการแสดงออกในการที่ตัวแปรอาจปรากฏ

int *ipควรอ่านเช่น " *ipจะคืนค่าint" หรือไม่ แต่ทำไมการมอบหมายหลังจากประกาศไม่เป็นไปตามรูปแบบนั้น ถ้าผู้เริ่มต้นต้องการเริ่มต้นตัวแปร? int *ip = 1(อ่าน: *ipจะส่งคืนintและintเป็น1) จะไม่ทำงานตามที่คาดไว้ โมเดลเชิงแนวคิดดูเหมือนจะไม่สอดคล้องกัน ฉันทำอะไรบางอย่างหายไปหรือเปล่า


แก้ไข: มันพยายามที่จะสรุปคำตอบที่นี่


15
คำอธิบายที่ดีที่สุดคือโดยการวาดภาพสิ่งที่อยู่บนกระดาษและเชื่อมต่อพวกเขามีลูกศร;)
Maroun

16
เมื่อฉันต้องอธิบายไวยากรณ์ของพอยน์เตอร์ฉันมักจะยืนยันความจริงที่ว่า*ในการประกาศเป็นความหมายโทเค็น "ประกาศตัวชี้" ในการแสดงออกมันเป็นผู้ดำเนินการอ้างอิงและทั้งสองแสดงสิ่งต่าง ๆ ที่มีสัญลักษณ์เดียวกัน (เหมือนกับตัวดำเนินการคูณ - สัญลักษณ์เดียวกันความหมายต่างกัน) มันสับสน แต่สิ่งที่แตกต่างจากสภาพของกิจการที่แท้จริงจะยิ่งแย่ลงไปอีก
Matteo Italia

40
อาจจะเขียนว่าint* barทำให้ชัดเจนยิ่งขึ้นว่าดาวนั้นเป็นส่วนหนึ่งของชนิดไม่ใช่ส่วนของตัวระบุ หลักสูตรนี้จะทำงานให้คุณเข้าสู่ปัญหาที่แตกต่างกับสิ่งที่ unintuitive int* a, bเช่น
Niklas B.

9
ฉันคิดเสมอว่าคำอธิบาย K&R นั้นโง่และไม่จำเป็น ภาษาใช้สัญลักษณ์เดียวกันสำหรับสองสิ่งที่แตกต่างกันและเราต้องจัดการกับมัน *สามารถมีสองความหมายที่แตกต่างกันขึ้นอยู่กับบริบท เช่นเดียวกับตัวอักษรเดียวกันสามารถออกเสียงได้แตกต่างกันขึ้นอยู่กับคำที่มันทำให้ยากที่จะเรียนรู้ที่จะพูดหลายภาษา หากทุกแนวคิด / การดำเนินการมีสัญลักษณ์ของตัวเองเราต้องการแป้นพิมพ์ขนาดใหญ่กว่ามากดังนั้นสัญลักษณ์จะถูกนำกลับมาใช้ใหม่เมื่อเหมาะสม
ศิลปะ

8
ฉันพบปัญหาเดียวกันหลายครั้งเมื่อสอน C ให้ผู้อื่นและจากประสบการณ์ของฉันมันสามารถแก้ไขได้ในแบบที่คนส่วนใหญ่แนะนำไว้ ก่อนอื่นให้อธิบายแนวคิดของตัวชี้โดยไม่มีไวยากรณ์ C จากนั้นสอนไวยากรณ์และเน้นที่เครื่องหมายดอกจันเป็นส่วนหนึ่งของประเภท ( int* p) ในขณะที่เตือนนักเรียนของคุณไม่ให้ใช้การประกาศหลายรายการในบรรทัดเดียวกันเมื่อพอยน์เตอร์เกี่ยวข้อง เมื่อนักเรียนเข้าใจแนวคิดของพอยน์เตอร์ได้อย่างครบถ้วนให้อธิบายให้นักเรียนฟังว่าint *pไวยากรณ์นั้นเทียบเท่ากันแล้วอธิบายปัญหาด้วยการประกาศหลายครั้ง
Theodoros Chatzigiannakis

คำตอบ:


43

เพื่อให้นักเรียนของคุณเข้าใจความหมายของ*สัญลักษณ์ในบริบทต่าง ๆ พวกเขาต้องเข้าใจก่อนว่าบริบทนั้นแตกต่างกันอย่างแท้จริง เมื่อพวกเขาเข้าใจว่าบริบทแตกต่างกัน (เช่นความแตกต่างระหว่างด้านซ้ายมือของการมอบหมายและการแสดงออกทั่วไป) มันไม่ได้เป็นการก้าวกระโดดทางปัญญามากเกินไปที่จะเข้าใจความแตกต่าง

ประการแรกอธิบายว่าการประกาศตัวแปรไม่สามารถมีโอเปอเรเตอร์ได้ (แสดงสิ่งนี้โดยแสดงว่าการใส่ a -หรือ+symbol ในการประกาศตัวแปรนั้นทำให้เกิดข้อผิดพลาด) จากนั้นไปแสดงว่านิพจน์ (เช่นทางด้านขวามือของการมอบหมาย) สามารถมีโอเปอเรเตอร์ได้ ตรวจสอบให้แน่ใจว่านักเรียนเข้าใจว่าการแสดงออกและการประกาศตัวแปรเป็นบริบทที่แตกต่างกันโดยสิ้นเชิง

เมื่อพวกเขาเข้าใจว่าบริบทนั้นแตกต่างกันคุณสามารถอธิบายได้ว่าเมื่อ*สัญลักษณ์นั้นอยู่ในการประกาศตัวแปรที่ด้านหน้าของตัวระบุตัวแปรมันหมายถึง 'ประกาศตัวแปรนี้เป็นตัวชี้' จากนั้นคุณสามารถอธิบายได้ว่าเมื่อใช้ในนิพจน์ (ในฐานะผู้ดำเนินการเอกนารี) *สัญลักษณ์คือ 'ผู้ดำเนินการตามข้อกำหนด' และหมายถึง 'มูลค่าที่ที่อยู่ของ' แทนที่จะเป็นความหมายก่อนหน้านี้

อย่างแท้จริงโน้มน้าวให้นักเรียนอธิบายว่าผู้สร้างของ C จะได้ใช้สัญลักษณ์ใด ๆ ที่จะหมายถึงผู้ประกอบการ dereference (เช่นที่พวกเขาจะได้ใช้@แทน) *แต่ด้วยเหตุผลสิ่งที่พวกเขาได้ตัดสินใจการออกแบบเพื่อการใช้งาน

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


80

เหตุผลที่จดชวเลข:

int *bar = &foo;

ในตัวอย่างของคุณอาจสร้างความสับสนว่ามันง่ายที่จะเข้าใจผิดว่าเทียบเท่ากับ:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

เมื่อมันจริงหมายถึง:

int *bar;
bar = &foo;

เขียนออกมาเช่นนี้เมื่อมีการประกาศตัวแปรและการมอบหมายแยกกันจึงไม่มีความเป็นไปได้ที่จะเกิดความสับสนและการใช้การประกาศ parallel แบบขนานที่อธิบายไว้ในการอ้าง K&R ของคุณนั้นสมบูรณ์แบบ:

  • บรรทัดแรกประกาศตัวแปรbarดังกล่าวว่าเป็น*barint

  • บรรทัดที่สองกำหนดที่อยู่ของfooถึงให้barสร้าง*bar(an int) นามแฝงสำหรับfoo(ยังint)

เมื่อแนะนำไวยากรณ์ตัวชี้ C ให้กับผู้เริ่มต้นมันอาจจะเป็นประโยชน์ในขั้นต้นเมื่อติดกับรูปแบบของการแยกการประกาศตัวชี้จากการมอบหมายและแนะนำเฉพาะชวเลขรวมไวยากรณ์ (พร้อมคำเตือนที่เหมาะสมเกี่ยวกับความสับสน) เมื่อแนวคิดพื้นฐาน C ได้รับการทำให้เพียงพอภายใน


4
typedefฉันถูกล่อลวงไป typedef int *p_int;หมายความว่าตัวแปรของชนิดp_intมีคุณสมบัติที่เป็น*p_int แล้วเรามีint p_int bar = &foo;กระตุ้นให้ทุกคนสร้างข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้นและกำหนดให้ภายหลังเนื่องจากเป็นเรื่องของนิสัยเริ่มต้นดูเหมือนว่า ... เป็นความคิดที่ไม่ดี
Yakk - Adam Nevraumont

6
นี่เป็นเพียงรูปแบบการทำลายของการประกาศ C ไม่เฉพาะเจาะจงสำหรับพอยน์เตอร์ พิจารณาint a[2] = {47,11};ที่ไม่ได้เป็น initialisation ของ (ที่ไม่มีอยู่จริง) องค์ประกอบa[2]eiher
Marc van Leeuwen

5
@MarcvanLeeuwen เห็นด้วยกับสมองเสียหาย โดย*หลักการแล้วควรเป็นส่วนหนึ่งของประเภทไม่ผูกกับตัวแปรแล้วคุณจะสามารถเขียนint* foo_ptr, bar_ptrเพื่อประกาศพอยน์เตอร์สองตัวได้ แต่จริง ๆ แล้วมันประกาศตัวชี้และจำนวนเต็ม
Barmar

1
มันไม่ได้เป็นเพียงแค่การประกาศ / การมอบหมาย "ชวเลข" ปัญหาทั้งหมดเกิดขึ้นอีกครั้งเมื่อคุณต้องการใช้พอยน์เตอร์เป็นอาร์กิวเมนต์ของฟังก์ชัน
Armin

30

สั้น ๆ เกี่ยวกับการประกาศ

มันเป็นการดีที่ได้ทราบความแตกต่างระหว่างการประกาศและการเริ่มต้น เราประกาศตัวแปรเป็นประเภทและเริ่มต้นพวกเขาด้วยค่า หากเราทำทั้งสองอย่างพร้อมกันเรามักจะเรียกมันว่าคำจำกัดความ

1. int a; a = 42;

int a;
a = 42;

เราประกาศintชื่อ จากนั้นเราก็เริ่มต้นได้โดยให้มันคุ้มค่า42

2. int a = 42;

เราประกาศและintชื่อและให้มันคุ้มค่า 42 มันจะเริ่มต้นด้วย คำจำกัดความ42

3. a = 43;

เมื่อเราใช้ตัวแปรที่เราพูดว่าเราดำเนินการกับพวกเขา a = 43เป็นการดำเนินการที่ได้รับมอบหมาย เรากำหนดหมายเลข 43 ให้กับตัวแปร a

โดยบอกว่า

int *bar;

เราประกาศให้barเป็นตัวชี้ไปยัง int โดยบอกว่า

int *bar = &foo;

เราประกาศบาร์และเริ่มต้นกับที่อยู่ของfoo

หลังจากที่เรามีแถบเริ่มต้นเราสามารถใช้ประกอบการเดียวกันดอกจันเพื่อการเข้าถึงและการดำเนินการเกี่ยวกับค่าของfoo หากไม่มีตัวดำเนินการเราจะเข้าถึงและดำเนินการตามที่อยู่ที่ตัวชี้ชี้ไป

นอกจากนั้นฉันปล่อยให้ภาพพูด

อะไร

ASCIIMATION ที่เรียบง่ายเกี่ยวกับสิ่งที่เกิดขึ้น (และนี่คือเวอร์ชั่นผู้เล่นหากคุณต้องการหยุด ฯลฯ )

          ASCIIMATION


22

คำสั่งที่ 2 int *bar = &foo;สามารถดูได้ในหน่วยความจำ pictorially เป็น,

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

ตอนนี้barเป็นตัวชี้ประเภทintที่มีอยู่ของ& fooใช้ประกอบการเอก*เราเคารพเพื่อเรียกคืนค่าที่มีอยู่ใน 'foo' barโดยใช้ตัวชี้

แก้ไข : วิธีการของฉันกับผู้เริ่มต้นคือการอธิบายmemory addressตัวแปรเช่น

Memory Address:ตัวแปรทุกตัวมีที่อยู่ที่เกี่ยวข้องกับระบบปฏิบัติการ ในint a;, เป็นที่อยู่ของตัวแปร&aa

อธิบายตัวแปรพื้นฐานต่อไปอย่างต่อเนื่องCดังนี้

Types of variables: ตัวแปรสามารถเก็บค่าของประเภทที่เกี่ยวข้อง แต่ไม่ใช่ที่อยู่

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: ดังกล่าวข้างต้นตัวแปรตัวอย่างเช่น

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

มันเป็นไปได้มอบหมายb = aแต่ไม่b = &aเนื่องจากตัวแปรbค่าถือ แต่ไม่สามารถอยู่ดังนั้นเราจำเป็นต้องมีตัวชี้

Pointer or Pointer variables :หากตัวแปรมีที่อยู่ก็จะเรียกว่าตัวแปรตัวชี้ ใช้*ในการประกาศเพื่อแจ้งว่าเป็นตัวชี้

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable

3
ปัญหาคือว่าการอ่านint *ipเป็น "IP เป็นตัวชี้ (*) ชนิด int เป็น" x = (int) *ipคุณได้รับเป็นปัญหาเมื่ออ่านสิ่งที่ชอบ
Armin

2
@abw นั่นเป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิงดังนั้นวงเล็บ ฉันไม่คิดว่าผู้คนจะมีปัญหาในการเข้าใจความแตกต่างระหว่างการประกาศและการคัดเลือกนักแสดง
bzeaman

@abw ในx = (int) *ip;, ได้รับค่าโดย dereferencing ชี้ipและโยนค่าไปintจากสิ่งที่ประเภทipคือ
นิล Bojanapally

1
@BennoZeeman คุณพูดถูก: การคัดเลือกนักแสดงและการประกาศเป็นสองสิ่งที่แตกต่างกัน ฉันพยายามบอกใบ้เกี่ยวกับบทบาทที่แตกต่างกันของเครื่องหมายดอกจัน: อันดับที่ 1 นี่ไม่ใช่ int แต่เป็นตัวชี้ไปยัง int "2nd" สิ่งนี้จะให้ค่า int แต่ไม่ใช่ตัวชี้ไปที่ int "
Armin

2
@abw: ซึ่งเป็นเหตุผลที่การเรียนการสอนint* bar = &foo;ที่ทำให้โหลดรู้สึกมากขึ้น ใช่ฉันรู้ว่ามันทำให้เกิดปัญหาเมื่อคุณประกาศพอยน์เตอร์หลายรายการในการประกาศเดียว ไม่ฉันไม่คิดว่าสำคัญ
การแข่งขัน Lightness ใน Orbit

17

ดูคำตอบและความคิดเห็นที่นี่ดูเหมือนจะมีข้อตกลงทั่วไปที่ไวยากรณ์ของคำถามอาจสร้างความสับสนสำหรับผู้เริ่มต้น ส่วนใหญ่เสนออะไรบางอย่างตามบรรทัดเหล่านี้:

  • ก่อนที่จะแสดงรหัสใด ๆ ให้ใช้ไดอะแกรมสเก็ตช์หรือภาพเคลื่อนไหวเพื่อแสดงวิธีการทำงานของพอยน์เตอร์
  • เมื่อนำเสนอไวยากรณ์อธิบายสองบทบาทที่แตกต่างกันของสัญลักษณ์เครื่องหมายดอกจัน บทเรียนมากมายขาดหายไปหรือหลีกเลี่ยงส่วนนั้น Confues ensues ("เมื่อคุณแบ่งการประกาศตัวเริ่มต้นขึ้นเป็นการประกาศและการมอบหมายในภายหลังคุณต้องจำไว้ว่าต้องลบ *" - คำถามที่พบบ่อย comp.lang.c ) ฉันหวังว่าจะหาวิธีอื่น แต่ฉันเดาว่านี่คือ วิธีที่จะไป

คุณอาจเขียนint* barแทนที่จะint *barเน้นความแตกต่าง ซึ่งหมายความว่าคุณจะไม่ปฏิบัติตามวิธีการ "เลียนแบบการประกาศใช้ K&R" แต่วิธีStroustrup C ++ :

เราไม่ได้ประกาศ*barว่าเป็นจำนวนเต็ม เราขอประกาศให้เป็นbar int*ถ้าเราต้องการที่จะเริ่มต้นตัวแปรที่สร้างขึ้นใหม่ในสายเดียวกันก็เป็นที่ชัดเจนว่าเราจะจัดการกับไม่bar*barint* bar = &foo;

ข้อเสีย:

  • คุณต้องเตือนนักเรียนของคุณเกี่ยวกับปัญหาการประกาศตัวชี้หลายครั้ง ( int* foo, barvs int *foo, *bar)
  • คุณจะต้องเตรียมความพร้อมสำหรับโลกของการบาดเจ็บ โปรแกรมเมอร์หลายคนต้องการเห็นเครื่องหมายดอกจันที่อยู่ติดกับชื่อของตัวแปรและพวกเขาจะใช้ความยาวมากเพื่อปรับสไตล์ของพวกเขา และไกด์หลายคนบังคับใช้สัญลักษณ์นี้อย่างชัดเจน (สไตล์การเข้ารหัสเคอร์เนล Linux, NASA C Style Guide ฯลฯ )

แก้ไข: แนวทางที่แตกต่างที่ได้รับการแนะนำคือไปทาง K&R "เลียนแบบ" แต่ไม่มีไวยากรณ์ "ชวเลข" (ดูที่นี่ ) ทันทีที่คุณละเว้นการประกาศและการมอบหมายในบรรทัดเดียวกันทุกอย่างจะดูสอดคล้องกันมากขึ้น

อย่างไรก็ตามไม่ช้าก็เร็วนักเรียนจะต้องจัดการกับพอยน์เตอร์เป็นอาร์กิวเมนต์ของฟังก์ชัน และพอยน์เตอร์เป็นประเภทส่งคืน และตัวชี้ไปยังฟังก์ชั่น คุณจะต้องอธิบายความแตกต่างระหว่างและint *func(); int (*func)();ฉันคิดว่าไม่ช้าก็เร็วสิ่งต่าง ๆ จะแตกสลาย และอาจเร็วกว่าดีกว่าในภายหลัง


16

มีเหตุผลว่าทำไม K & R โปรดปรานสไตล์int *pและ Stroustrup โปรดปรานสไตล์int* p; ทั้งสองถูกต้อง (และหมายถึงสิ่งเดียวกัน) ในแต่ละภาษา แต่เป็น Stroustrup วางไว้:

ตัวเลือกระหว่าง "int * p;" และ "int * p;" ไม่เกี่ยวกับถูกและผิด แต่เกี่ยวกับสไตล์และการเน้น C เน้นการแสดงออก การประกาศมักถูกมองว่าเป็นความชั่วร้ายที่จำเป็น ในทางกลับกัน C ++ นั้นมีความสำคัญอย่างมากต่อประเภท

ตอนนี้เนื่องจากคุณกำลังพยายามสอน C ที่นี่นั่นจะแนะนำให้คุณเน้นการแสดงออกมากกว่าประเภทนั้น แต่บางคนก็สามารถเน้นการเน้นอย่างใดอย่างหนึ่งได้เร็วกว่าอีกแบบและนั่นเกี่ยวกับพวกเขามากกว่าภาษา

ดังนั้นบางคนจะพบว่าเริ่มต้นด้วยความคิดที่ว่าint*สิ่งนั้นแตกต่างจากintและไปจากที่นั่นง่ายกว่า

ถ้ามีใครสักคนรีบดูวิธีการที่int* barเคยbarเป็นสิ่งที่ไม่ใช่ int แต่เป็นตัวชี้ไปintพวกเขาจะเห็นอย่างรวดเร็วว่า*barกำลังทำอะไรอยู่barและคนอื่น ๆ จะตามมา เมื่อคุณทำเสร็จแล้วคุณสามารถอธิบายได้ว่าทำไม C coders ถึงชอบint *barมักจะชอบ

หรือไม่. หากมีวิธีหนึ่งที่ทุกคนเข้าใจแนวคิดเป็นครั้งแรกคุณจะไม่มีปัญหาใด ๆ ในตอนแรกและวิธีที่ดีที่สุดในการอธิบายให้คน ๆ หนึ่งไม่จำเป็นต้องเป็นวิธีที่ดีที่สุดในการอธิบายให้คนอื่นฟัง


1
ฉันชอบข้อโต้แย้งของ Stroustrup แต่ฉันสงสัยว่าทำไมเขาถึงเลือก & สัญลักษณ์เพื่อแสดงถึงการอ้างอิง - ข้อผิดพลาดที่เป็นไปได้อีกประการหนึ่ง
Armin

1
@abw ผมคิดว่าเขาเห็นสมมาตรหากเราสามารถทำได้แล้วเราสามารถทำได้int* p = &a int* r = *pฉันค่อนข้างแน่ใจว่าเขาพูดถึงเรื่องนี้ในการออกแบบและวิวัฒนาการของ C ++แต่มันนานมากแล้วที่ฉันได้อ่านมัน
Jon Hanna

3
int& r = *pผมคิดว่าคุณหมายถึง และฉันพนันได้ว่าผู้ยืมยังคงพยายามแยกแยะหนังสือ
Armin

@ เกี่ยวกับใช่นั่นคือสิ่งที่ฉันหมายถึง พิมพ์ผิดในความคิดเห็นไม่เพิ่มข้อผิดพลาดในการรวบรวม หนังสือเล่มนี้ค่อนข้างอ่านเร็ว
Jon Hanna

4
หนึ่งในเหตุผลที่ผมโปรดปรานไวยากรณ์ของปาสคาล (เป็นที่นิยมขยาย) มากกว่า C คือว่าVar A, B: ^Integer;ทำให้เห็นได้ชัดว่าพิมพ์ "ตัวชี้ไปยังจำนวนเต็ม" นำไปใช้กับทั้งสองและA Bการใช้K&Rสไตล์int *a, *bยังสามารถใช้งานได้ แต่การประกาศเช่นint* a,b;แต่ดูเหมือนว่าaและbมีทั้งที่ถูกประกาศให้เป็นint*แต่ในความเป็นจริงมันบอกaเป็นint*และในฐานะที่เป็นb int
supercat

9

TL; DR:

ถามวิธีอธิบายตัวชี้ C (การประกาศเทียบกับโอเปอเรเตอร์ unary) ให้กับผู้เริ่มต้นได้อย่างไร

ตอบ: อย่า อธิบายพอยน์เตอร์ให้กับผู้เริ่มต้นและแสดงวิธีการแสดงแนวคิดพอยน์เตอร์ของพวกเขาในรูปแบบ C หลังจาก


ฉันมีความยินดีเมื่อเร็ว ๆ นี้ที่จะอธิบายพอยน์เตอร์ให้กับการเขียนโปรแกรม C และเริ่มสะดุดเมื่อมีปัญหาต่อไปนี้

IMO ไวยากรณ์ C ไม่น่ากลัว แต่ก็ไม่ได้ยอดเยี่ยมเช่นกันมันไม่ใช่อุปสรรคที่ดีหากคุณเข้าใจพอยน์เตอร์อยู่แล้วและไม่มีความช่วยเหลือใด ๆ ในการเรียนรู้มัน

ดังนั้น: เริ่มต้นด้วยการอธิบายพอยน์เตอร์และตรวจสอบให้แน่ใจว่าพวกเขาเข้าใจจริง ๆ :

  • อธิบายด้วยไดอะแกรมแบบกล่องและลูกศร คุณสามารถทำได้โดยไม่ต้องที่อยู่ฐานสิบหกหากพวกเขาไม่เกี่ยวข้องเพียงแค่แสดงลูกศรชี้ไปที่กล่องอื่นหรือสัญลักษณ์บางอย่าง

  • อธิบายกับ pseudocode: เขียนเพียงแค่อยู่ของ fooและค่าที่เก็บไว้ที่บาร์

  • จากนั้นเมื่อมือใหม่ของคุณเข้าใจว่าพอยน์เตอร์คืออะไรและทำไมและวิธีการใช้งาน จากนั้นแสดงการแมปลงในไวยากรณ์ C

ฉันสงสัยว่าเหตุผลที่ข้อความ K&R ไม่ได้ให้แบบจำลองเชิงแนวคิดคือพวกเขาเข้าใจพอยน์เตอร์แล้วและอาจคิดว่าโปรแกรมเมอร์ผู้มีความสามารถทุกคนในเวลานั้นก็ทำเช่นกัน ตัวช่วยจำเป็นเพียงเครื่องเตือนความจำในการทำแผนที่จากแนวคิดที่เข้าใจกันดีไปจนถึงไวยากรณ์


แท้จริง; เริ่มด้วยทฤษฎีก่อนไวยากรณ์มาภายหลัง (และไม่สำคัญ) โปรดทราบว่าทฤษฎีการใช้งานหน่วยความจำไม่ได้ขึ้นอยู่กับภาษา โมเดลบ็อกซ์และลูกศรนี้จะช่วยให้คุณทำงานในภาษาโปรแกรมใด ๆ
oɔɯǝɹ

ดูที่นี่สำหรับตัวอย่าง (แม้ว่า google จะช่วยด้วย) eskimo.com/~scs/cclass/notes/sx10a.html
oɔɯǝɹ

7

ปัญหานี้ค่อนข้างสับสนเมื่อเริ่มเรียนรู้ C

นี่คือหลักการพื้นฐานที่อาจช่วยให้คุณเริ่มต้นได้:

  1. มีชนิดพื้นฐานเพียงไม่กี่ชนิดใน C:

    • char: ค่าจำนวนเต็มที่มีขนาด 1 ไบต์

    • short: ค่าจำนวนเต็มที่มีขนาด 2 ไบต์

    • long: ค่าจำนวนเต็มที่มีขนาด 4 ไบต์

    • long long: ค่าจำนวนเต็มที่มีขนาด 8 ไบต์

    • float: ค่าที่ไม่ใช่จำนวนเต็มที่มีขนาด 4 ไบต์

    • double: ค่าที่ไม่ใช่จำนวนเต็มที่มีขนาด 8 ไบต์

    โปรดทราบว่าขนาดของแต่ละประเภทโดยทั่วไปจะถูกกำหนดโดยคอมไพเลอร์และไม่ได้ตามมาตรฐาน

    ประเภทจำนวนเต็มshort, longและมักจะตามมาด้วยlong longint

    intมันไม่ได้เป็นต้องอย่างไรและคุณสามารถใช้พวกเขาโดยไม่ต้อง

    อีกวิธีหนึ่งคุณสามารถระบุintแต่อาจตีความได้แตกต่างกันโดยคอมไพเลอร์ที่แตกต่างกัน

    ดังนั้นเพื่อสรุปสิ่งนี้:

    • shortเป็นเช่นเดียวแต่ไม่จำเป็นต้องเหมือนกับshort intint

    • longเป็นเช่นเดียวแต่ไม่จำเป็นต้องเหมือนกับlong intint

    • long longเป็นเช่นเดียวแต่ไม่จำเป็นต้องเหมือนกับlong long intint

    • ในคอมไพเลอร์ที่ได้รับintเป็นอย่างใดอย่างหนึ่งshort intหรือหรือlong intlong long int

  2. หากคุณประกาศตัวแปรบางประเภทคุณสามารถประกาศตัวแปรอื่นที่ชี้ไปยังตัวแปรนั้นได้

    ตัวอย่างเช่น:

    int a;

    int* b = &a;

    ดังนั้นโดยพื้นฐานแล้วสำหรับแต่ละประเภทพื้นฐานเราก็มีประเภทของตัวชี้ที่สอดคล้องกัน

    ตัวอย่างเช่น: และshortshort*

    มีสองวิธีในการ "ดู" ตัวแปรb (นั่นคือสิ่งที่อาจสับสนสำหรับผู้เริ่มต้นส่วนใหญ่) :

    • คุณสามารถพิจารณาเป็นตัวแปรของชนิดbint*

    • คุณสามารถพิจารณาเป็นตัวแปรของชนิด*bint

    ดังนั้นบางคนจะประกาศint* bในขณะที่คนอื่น ๆ int *bจะประกาศ

    แต่ความจริงของเรื่องนี้ก็คือการประกาศทั้งสองนี้เหมือนกัน (ช่องว่างไม่มีความหมาย)

    คุณสามารถใช้bเป็นตัวชี้ไปยังค่าจำนวนเต็มหรือ*bเป็นค่าจำนวนเต็มจริง

    คุณจะได้รับ (อ่าน) int c = *bมูลค่าแหลม:

    และคุณสามารถตั้ง (เขียน) *b = 5มูลค่าแหลม:

  3. ตัวชี้สามารถชี้ไปยังที่อยู่หน่วยความจำใด ๆ และไม่เพียง แต่ที่อยู่ของตัวแปรบางตัวที่คุณได้ประกาศไว้ก่อนหน้านี้ อย่างไรก็ตามคุณต้องระวังเมื่อใช้พอยน์เตอร์เพื่อรับหรือตั้งค่าที่อยู่หน่วยความจำปลายแหลม

    ตัวอย่างเช่น:

    int* a = (int*)0x8000000;

    ที่นี่เรามีตัวแปรที่aชี้ไปยังที่อยู่หน่วยความจำ 0x8000000

    หากที่อยู่หน่วยความจำนี้ไม่ได้ถูกแมปภายในพื้นที่หน่วยความจำของโปรแกรมของคุณแสดงว่ามีการดำเนินการอ่านหรือเขียนใด ๆ *aจะทำให้โปรแกรมของคุณหยุดทำงานเนื่องจากการละเมิดการเข้าถึงหน่วยความจำ

    คุณสามารถเปลี่ยนค่าของแต่คุณควรจะระมัดระวังการเปลี่ยนค่าของa*a

  4. ประเภทvoid*มีความพิเศษในความจริงที่ว่ามันไม่มี "ประเภทค่า" ที่สอดคล้องกันซึ่งสามารถใช้ได้ (เช่นคุณไม่สามารถประกาศvoid a) ชนิดนี้ใช้เป็นตัวชี้ทั่วไปไปยังที่อยู่หน่วยความจำโดยไม่ระบุประเภทของข้อมูลที่อยู่ในที่อยู่นั้น


7

บางทีการก้าวผ่านมันไปอีกเล็กน้อยทำให้มันง่ายขึ้น:

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

ให้พวกเขาบอกคุณว่าพวกเขาคาดหวังผลลัพธ์ที่จะอยู่ในแต่ละบรรทัดแล้วให้พวกเขาเรียกใช้โปรแกรมและดูว่าเกิดอะไรขึ้น อธิบายคำถามของพวกเขา (รุ่นที่เปลือยเปล่าในนั้นจะแจ้งให้บางอย่าง - แต่คุณสามารถกังวลเกี่ยวกับสไตล์ความเข้มงวดและพกพาได้ในภายหลัง) จากนั้นก่อนที่จิตใจของพวกเขาจะเปลี่ยนจากการคิดมากหรือพวกเขากลายเป็นผีดิบหลังอาหารกลางวันเขียนฟังก์ชั่นที่รับค่าและสิ่งเดียวกับที่ใช้ตัวชี้

จากประสบการณ์ของฉันการได้รับมากกว่า "ทำไมพิมพ์นี้ด้วยวิธีนี้?" โคกแล้วแสดงทันทีว่าทำไมสิ่งนี้จึงมีประโยชน์ในพารามิเตอร์ของฟังก์ชันโดยการลงมือทำ (เพื่อนำเสนอเนื้อหา K&R พื้นฐานเช่นการวิเคราะห์สตริง / การประมวลผลอาร์เรย์) ที่ทำให้บทเรียนไม่เพียง แต่สมเหตุสมผล

ขั้นตอนต่อไปคือให้พวกเขาอธิบายให้คุณฟังว่าi[0]เกี่ยวข้อง&iอย่างไร หากพวกเขาสามารถทำเช่นนั้นพวกเขาจะไม่ลืมมันและคุณสามารถเริ่มพูดคุยเกี่ยวกับ structs แม้ก่อนเวลาเล็กน้อยเพียงเพื่อให้มันจม

คำแนะนำข้างต้นเกี่ยวกับกล่องและลูกศรก็ดีเช่นกัน แต่ก็สามารถปิดท้ายการสนทนาอย่างเต็มรูปแบบเกี่ยวกับวิธีการทำงานของหน่วยความจำ - ซึ่งเป็นการพูดคุยที่ต้องเกิดขึ้นในบางจุด แต่สามารถหันเหความสนใจจากจุดทันที : วิธีตีความสัญลักษณ์ตัวชี้ใน C


นี่คือการออกกำลังกายที่ดี แต่ปัญหาที่ฉันต้องการนำมาใช้เป็นประโยคเฉพาะที่อาจมีผลกระทบต่อแบบจำลองทางจิตที่นักเรียนสร้างขึ้น พิจารณาสิ่งนี้: int foo = 1;. int *bar; *bar = foo;ตอนนี้ตกลง: นี้ไม่ได้ตกลง:int *bar = foo;
Armin

1
@abw สิ่งเดียวที่สมเหตุสมผลคือสิ่งที่นักเรียนเลิกบอกตัวเอง นั่นหมายถึง "เห็นหนึ่งทำหนึ่งสอนหนึ่ง" คุณไม่สามารถป้องกันหรือคาดการณ์ว่าไวยากรณ์หรือรูปแบบใดที่พวกเขาจะเห็นในป่า (แม้แต่ repos เก่าของคุณ!) ดังนั้นคุณต้องแสดงการเรียงสับเปลี่ยนมากพอที่แนวคิดพื้นฐานจะเข้าใจได้อย่างอิสระตามสไตล์ - และ จากนั้นเริ่มสอนพวกเขาว่าทำไมรูปแบบบางอย่างได้รับการตัดสิน เช่นเดียวกับการสอนภาษาอังกฤษ: การแสดงออกขั้นพื้นฐานสำนวนสไตล์สไตล์เฉพาะในบริบทที่กำหนด ไม่ใช่เรื่องง่ายน่าเสียดาย ไม่ว่าในกรณีใดขอให้โชคดี!
zxq9

6

ประเภทของการแสดงออก *barคือint; ดังนั้นประเภทของตัวแปร (และการแสดงออก) คือbar int *เนื่องจากตัวแปรมีชนิดพอยน์เตอร์ initializer จะต้องมีประเภทพอยน์เตอร์ด้วย

มีความไม่สอดคล้องกันระหว่างการกำหนดค่าเริ่มต้นของตัวแปรตัวชี้และการกำหนด; นั่นเป็นเพียงสิ่งที่จะต้องเรียนรู้อย่างหนัก


3
ดูคำตอบที่นี่ฉันรู้สึกว่าโปรแกรมเมอร์ที่มีประสบการณ์หลายคนไม่สามารถมองเห็นปัญหาได้อีกต่อไป ฉันเดาว่าเป็นผลพลอยได้จาก "การเรียนรู้ที่จะอยู่กับความไม่สอดคล้อง"
Armin

3
@abw: กฎสำหรับการเริ่มต้นจะแตกต่างจากกฎสำหรับการมอบหมาย สำหรับประเภทคณิตศาสตร์สเกลาร์ความแตกต่างนั้นเล็กน้อย แต่สำคัญสำหรับตัวชี้และชนิดรวม นั่นคือสิ่งที่คุณจะต้องอธิบายพร้อมกับทุกอย่างอื่น
John Bode

5

ฉันอยากจะอ่านมันเป็นครั้งแรกที่*นำไปใช้มากกว่าintbar

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

2
จากนั้นคุณต้องอธิบายว่าทำไมint* a, bไม่ทำในสิ่งที่พวกเขาคิด
Pharap

4
จริง แต่ฉันไม่คิดว่าint* a,bควรจะใช้เลย เพื่อความเป็นไปได้ที่ดีในการอัพเดท ฯลฯ ควรมีการประกาศตัวแปรหนึ่งรายการต่อหนึ่งบรรทัดเท่านั้น มันเป็นสิ่งที่อธิบายให้ผู้เริ่มต้นด้วยเช่นกันแม้ว่าคอมไพเลอร์สามารถจัดการ
grorel

นั่นเป็นความคิดเห็นของชายคนหนึ่ง มีโปรแกรมเมอร์หลายล้านคนที่ทำงานได้ดีพร้อมประกาศตัวแปรมากกว่าหนึ่งตัวต่อบรรทัดและทำมันทุกวันเป็นส่วนหนึ่งของงานของพวกเขา คุณไม่สามารถซ่อนนักเรียนจากวิธีอื่นในการทำสิ่งต่าง ๆ ได้ดีกว่าที่จะแสดงทางเลือกทั้งหมดและให้พวกเขาตัดสินใจว่าพวกเขาต้องการทำสิ่งใดเพราะถ้าพวกเขากลายเป็นลูกจ้างพวกเขาจะถูกคาดหวังให้ทำตามสไตล์ที่แน่นอน พวกเขาอาจหรืออาจจะไม่พอใจกับ สำหรับโปรแกรมเมอร์ความเก่งกาจเป็นคุณสมบัติที่ดีมาก
Pharap

1
ฉันเห็นด้วยกับ @grorel มันเป็นเรื่องง่ายที่จะคิดว่าเป็นส่วนหนึ่งของชนิดและเพียงแค่ท้อ* int* a, bยกเว้นกรณีที่คุณต้องการบอกว่า*aเป็นประเภทintแทนที่จะaเป็นตัวชี้ไปint...
เควิน Ushey

@grorel ถูกต้อง: int *a, b;ไม่ควรใช้ การประกาศตัวแปรสองตัวที่มีประเภทต่างกันในข้อความเดียวกันคือแนวปฏิบัติที่ไม่ดีนักและผู้สมัครที่แข็งแกร่งสำหรับปัญหาการบำรุงรักษา อาจแตกต่างกันสำหรับพวกเราที่ทำงานในฟิลด์ฝังตัวที่int*และintขนาดมักแตกต่างกันและบางครั้งเก็บไว้ในตำแหน่งหน่วยความจำที่แตกต่างกันโดยสิ้นเชิง เป็นหนึ่งในหลาย ๆ แง่มุมของภาษา C ที่จะสอนได้ดีที่สุดเมื่อ 'อนุญาต แต่ไม่ควรทำ'
Evil Dog Pie

5
int *bar = &foo;

Question 1: อะไรนะbar?

Ans: เป็นตัวแปรตัวชี้ (เพื่อพิมพ์int) ตัวชี้ควรชี้ไปยังตำแหน่งหน่วยความจำที่ถูกต้องบางส่วนและในภายหลังควรถูกยกเลิกการลงทะเบียน (* บาร์) โดยใช้โอเปอเรเตอร์ unary *เพื่ออ่านค่าที่เก็บไว้ในตำแหน่งนั้น

Question 2: อะไรนะ&foo?

Ans: foo เป็นตัวแปรประเภทint.which ถูกเก็บไว้ในหน่วยความจำตำแหน่งที่ถูกต้องและบางสถานที่ที่เราได้รับจากผู้ประกอบการดังนั้นตอนนี้สิ่งที่เรามีอยู่ในบางสถานที่ตั้งของหน่วยความจำที่ถูกต้อง&&foo

ดังนั้นทั้งคู่จึงรวมตัวกันนั่นคือสิ่งที่ตัวชี้ที่ต้องการคือตำแหน่งหน่วยความจำที่ถูกต้องและได้มาด้วย&fooดังนั้นการเริ่มต้นนั้นดี

ตอนนี้ตัวชี้barกำลังชี้ไปยังตำแหน่งหน่วยความจำที่ถูกต้องและค่าที่เก็บไว้ในนั้นสามารถได้รับการยกเลิกการอ้างอิงเช่น*bar


5

คุณควรชี้ให้เห็นว่าผู้เริ่มต้นที่ * มีความหมายแตกต่างกันในการประกาศและการแสดงออก อย่างที่คุณทราบ * ในนิพจน์เป็นตัวดำเนินการเอกและ * ในการประกาศไม่ใช่ตัวดำเนินการและเป็นเพียงชนิดของไวยากรณ์ที่รวมกับชนิดเพื่อให้คอมไพเลอร์ทราบว่าเป็นประเภทตัวชี้ มันเป็นการดีกว่าถ้าจะบอกว่าผู้เริ่มต้น "* มีความหมายต่างกันเพื่อทำความเข้าใจความหมายของ * คุณควรหาที่ * ใช้"


4

ฉันคิดว่าปีศาจอยู่ในอวกาศ

ฉันจะเขียน (ไม่เพียง แต่สำหรับผู้เริ่มต้น แต่สำหรับตัวเองเช่นกัน): int * bar = & foo; แทน int * bar = & foo;

สิ่งนี้จะทำให้เห็นได้ชัดว่าอะไรคือความสัมพันธ์ระหว่างไวยากรณ์และความหมาย


4

มีการบันทึกไว้แล้วว่า * มีหลายบทบาท

มีแนวคิดง่ายๆอีกอย่างหนึ่งที่อาจช่วยผู้เริ่มต้นในการเข้าใจสิ่งต่าง ๆ :

คิดว่า "=" มีหลายบทบาทเช่นกัน

เมื่อมีการใช้งานที่มอบหมายในบรรทัดเดียวกันกับการประกาศให้คิดว่าเป็นการเรียกคอนสตรัคเตอร์ไม่ใช่การมอบหมายโดยพลการ

เมื่อคุณเห็น:

int *bar = &foo;

คิดว่าใกล้เคียงกับ:

int *bar(&foo);

วงเล็บใช้เครื่องหมายดอกจันมากกว่าเครื่องหมายดอกจันดังนั้น "& foo" จึงมีความเกี่ยวข้องกับ "บาร์" มากกว่า "* บาร์" โดยง่าย


4

ผมเห็นคำถามนี้ไม่กี่วันที่ผ่านมาและจากนั้นเกิดขึ้นจะอ่านคำอธิบายของโกประกาศประเภทในบล็อกไป มันเริ่มต้นด้วยการให้บัญชีของการประกาศประเภท C ซึ่งดูเหมือนว่าเป็นทรัพยากรที่มีประโยชน์ในการเพิ่มไปยังหัวข้อนี้แม้ว่าฉันคิดว่ามีคำตอบที่สมบูรณ์กว่าที่ได้รับแล้ว

C ใช้วิธีการที่ผิดปกติและฉลาดในการประกาศไวยากรณ์ แทนที่จะอธิบายชนิดด้วยไวยากรณ์พิเศษหนึ่งรายการจะเขียนนิพจน์ที่เกี่ยวข้องกับรายการที่กำลังประกาศและระบุประเภทของการแสดงออกที่จะมี ดังนั้น

int x;

ประกาศให้ x เป็น int: นิพจน์ 'x' จะมีชนิด int โดยทั่วไปหากต้องการทราบวิธีการเขียนชนิดของตัวแปรใหม่ให้เขียนนิพจน์ที่เกี่ยวข้องกับตัวแปรนั้นที่ประเมินเป็นชนิดพื้นฐานจากนั้นใส่ประเภทพื้นฐานทางด้านซ้ายและนิพจน์ทางด้านขวา

ดังนั้นการประกาศ

int *p;
int a[3];

ระบุว่า p เป็นตัวชี้ไปยัง int เพราะ '* p' มีชนิด int และ a คืออาร์เรย์ของ ints เนื่องจาก [3] (ไม่สนใจค่าดัชนีเฉพาะซึ่งถูกลงโทษให้มีขนาดของอาร์เรย์) มีประเภท int

(อธิบายเพิ่มเติมเกี่ยวกับวิธีการขยายความเข้าใจนี้ไปยังตัวชี้ฟังก์ชัน ฯลฯ )

นี่เป็นวิธีที่ฉันไม่เคยคิดมาก่อน แต่ดูเหมือนว่าวิธีการบัญชีที่ค่อนข้างตรงไปตรงมาสำหรับการใช้งานมากเกินไปของไวยากรณ์


3

หากปัญหาคือไวยากรณ์อาจเป็นประโยชน์ในการแสดงโค้ดที่เทียบเท่ากับเทมเพลต / การใช้

template<typename T>
using ptr = T*;

สิ่งนี้สามารถใช้เป็น

ptr<int> bar = &foo;

หลังจากนั้นเปรียบเทียบไวยากรณ์ / C ปกติกับวิธี C ++ นี้เท่านั้น สิ่งนี้ยังมีประโยชน์สำหรับการอธิบายตัวชี้ const


2
สำหรับผู้เริ่มต้นมันจะสับสนมากขึ้น
Karsten

ฉันคิดว่าคุณจะไม่แสดงคำจำกัดความของ PTR เพียงใช้มันสำหรับการประกาศพอยน์เตอร์
MI3Guy

3

แหล่งที่มาของความสับสนเกิดขึ้นจากความจริงที่ว่า*สัญลักษณ์สามารถมีความหมายแตกต่างกันใน C ขึ้นอยู่กับความจริงที่ใช้ เพื่ออธิบายตัวชี้ไปยังผู้เริ่มต้น*ควรอธิบายความหมายของสัญลักษณ์ในบริบทที่แตกต่างกัน

ในการประกาศ

int *bar = &foo;  

*สัญลักษณ์คือไม่ได้ดำเนินการตรง แต่มันช่วยในการระบุชนิดของการbarแจ้งคอมไพเลอร์ที่barเป็นตัวชี้ไปยัง intในทางกลับกันเมื่อมันปรากฏในคำสั่ง*สัญลักษณ์ (เมื่อใช้เป็นผู้ประกอบการเอก ) ดำเนินการทางอ้อม ดังนั้นคำสั่ง

*bar = &foo;

จะผิดเพราะมันกำหนดที่อยู่ของfooไปยังวัตถุที่barชี้ไปที่ไม่ใช่barตัวมันเอง


3

"อาจจะเขียนเป็น int * bar ทำให้เห็นได้ชัดเจนว่าจริง ๆ แล้วดาวนั้นเป็นส่วนหนึ่งของประเภทไม่ใช่ส่วนหนึ่งของตัวระบุ" ดังนั้นฉันทำ และฉันบอกว่ามันเป็นอะไรบางอย่างเช่นประเภท แต่เพียงชื่อตัวชี้เดียว

"แน่นอนว่าสิ่งนี้จะนำคุณไปสู่ปัญหาที่แตกต่างกับสิ่งที่ไม่ได้ใช้งานง่ายเช่น int * a, b."


2

ที่นี่คุณต้องใช้ทำความเข้าใจและอธิบายตรรกะของคอมไพเลอร์ไม่ใช่ตรรกะของมนุษย์ (ฉันรู้ว่าคุณเป็นมนุษย์ แต่ที่นี่คุณต้องเลียนแบบคอมพิวเตอร์ ... )

เมื่อคุณเขียน

int *bar = &foo;

กลุ่มคอมไพเลอร์ที่เป็น

{ int * } bar = &foo;

นั่นคือที่นี่เป็นตัวแปรใหม่ชื่อของมันคือbarชนิดของมันเป็นตัวชี้ไป int &fooและค่าเริ่มต้นของมันคือ

และคุณต้องเพิ่ม: =ข้างต้นหมายถึงการเริ่มต้นไม่ใช่ความรู้สึกในขณะที่ในการแสดงออกต่อไปนี้*bar = 2;มันเป็นความรู้สึก

แก้ไขต่อความคิดเห็น:

ระวัง: ในกรณีที่มีการประกาศหลายครั้ง*จะเกี่ยวข้องกับตัวแปรต่อไปนี้เท่านั้น:

int *bar = &foo, b = 2;

bar เป็นตัวชี้ไปยังเริ่มต้น int โดยที่อยู่ของ foo, b เป็นเริ่มต้น int ถึง 2 และใน

int *bar=&foo, **p = &bar;

แถบในตัวชี้ไปยัง int และ p เป็นตัวชี้ไปยังตัวชี้ไปยัง int เริ่มต้นไปยังที่อยู่หรือแถบ


2
ที่จริงคอมไพเลอร์ไม่ได้กลุ่มมันเหมือนว่า: int* a, b;ประกาศที่จะชี้ไปยังแต่เพื่อเป็นint สัญลักษณ์เพียงแค่มีสองความหมายที่แตกต่างกันในการประกาศก็แสดงประเภทตัวชี้และในการแสดงออกมันเป็นผู้ประกอบการ dereference เอก int*
tmlen

@ HTML: สิ่งที่ฉันหมายถึงคือในการเริ่มต้น*ใน in rattached กับประเภทเพื่อให้ตัวชี้จะเริ่มต้นได้ในขณะที่ในผลกระทบที่ส่งผลกระทบต่อค่าแหลมได้รับผลกระทบ แต่อย่างน้อยคุณก็ให้หมวกอันดี :-)
Serge Ballesta

0

โดยทั่วไปตัวชี้ไม่ได้เป็นตัวบ่งชี้อาร์เรย์ ผู้เริ่มต้นคิดว่าตัวชี้นั้นดูเหมือนอาร์เรย์อย่างง่ายดาย ตัวอย่างสตริงส่วนใหญ่โดยใช้

"char * pstr" มันดูเหมือนว่า

"ถ่าน str [80]"

แต่สิ่งสำคัญตัวชี้ถือว่าเป็นจำนวนเต็มในคอมไพเลอร์ระดับล่าง

ลองดูตัวอย่าง ::

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

ผลลัพธ์จะเป็นเช่นนี้ 0x2a6b7ed0 คือที่อยู่ของ str []

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

โปรดจำไว้ว่า Pointer คือ Integer บางประเภท นำเสนอที่อยู่


-1

ฉันจะอธิบายว่า ints เป็นวัตถุเช่นเดียวกับลอย ฯลฯ ตัวชี้เป็นประเภทของวัตถุที่มีค่าแสดงถึงที่อยู่ในหน่วยความจำ (ดังนั้นทำไมตัวชี้เริ่มต้นที่ NULL)

เมื่อคุณประกาศตัวชี้ครั้งแรกคุณจะใช้ไวยากรณ์ตัวชี้ชนิดชื่อ มันถูกอ่านเป็น "ชื่อตัวชี้จำนวนเต็มที่สามารถชี้ไปยังที่อยู่ของวัตถุจำนวนเต็มใด ๆ " เราใช้ไวยากรณ์นี้เฉพาะในช่วง decleration ซึ่งคล้ายกับที่เราประกาศ int ว่า 'int num1' แต่เราใช้ 'num1' เมื่อเราต้องการใช้ตัวแปรนั้นเท่านั้นไม่ใช่ 'int num1'

int x = 5; // วัตถุจำนวนเต็มที่มีค่าเท่ากับ 5

int * ptr; // จำนวนเต็มที่มีค่าเป็น NULL โดยค่าเริ่มต้น

ในการทำให้ตัวชี้ชี้ไปยังที่อยู่ของวัตถุเราใช้สัญลักษณ์ '&' ซึ่งสามารถอ่านได้ว่า "ที่อยู่ของ"

ptr = & x; // now value คือที่อยู่ของ 'x'

เนื่องจากตัวชี้เป็นเพียงที่อยู่ของวัตถุการรับค่าจริงที่เก็บที่อยู่นั้นเราต้องใช้สัญลักษณ์ '*' ซึ่งเมื่อใช้ก่อนตัวชี้หมายถึง "ค่าตามที่อยู่ที่ชี้ไป"

std :: ศาล << * ptr; // พิมพ์ค่าตามที่อยู่

คุณสามารถอธิบายสั้น ๆ ว่า ' ' เป็น 'โอเปอเรเตอร์' ที่ให้ผลลัพธ์ที่แตกต่างกันพร้อมกับวัตถุประเภทต่างๆ เมื่อใช้กับตัวชี้ตัวดำเนินการ ' ' จะไม่แปลว่า "คูณด้วย" อีกต่อไป

ช่วยในการวาดไดอะแกรมแสดงว่าตัวแปรมีชื่อและค่าอย่างไรและตัวชี้มีที่อยู่ (ชื่อ) และค่าและแสดงว่าค่าของตัวชี้จะเป็นที่อยู่ของ int


-1

ตัวชี้เป็นเพียงตัวแปรที่ใช้เพื่อจัดเก็บที่อยู่

หน่วยความจำในคอมพิวเตอร์ประกอบด้วยไบต์ (A ไบต์ประกอบด้วย 8 บิต) ที่จัดเรียงตามลำดับ แต่ละไบต์มีตัวเลขที่เกี่ยวข้องเช่นเดียวกับดัชนีหรือตัวห้อยในอาร์เรย์ซึ่งเรียกว่าที่อยู่ของไบต์ ที่อยู่ของไบต์เริ่มต้นจาก 0 ถึงน้อยกว่าหนึ่งขนาดของหน่วยความจำ ตัวอย่างเช่นพูดใน RAM ขนาด 64MB มี 64 * 2 ^ 20 = 67108864 ไบต์ ดังนั้นที่อยู่ของไบต์เหล่านี้จะเริ่มจาก 0 ถึง 67108863

ป้อนคำอธิบายรูปภาพที่นี่

มาดูกันว่าจะเกิดอะไรขึ้นเมื่อคุณประกาศตัวแปร

เครื่องหมาย int;

ดังที่เราทราบว่า int ใช้ข้อมูล 4 ไบต์ (สมมติว่าเราใช้คอมไพเลอร์ 32 บิต) ดังนั้นคอมไพเลอร์จึงสงวน 4 ไบต์ติดต่อกันจากหน่วยความจำเพื่อเก็บค่าจำนวนเต็ม ที่อยู่ของไบต์แรกของ 4 ไบต์ที่จัดสรรนั้นเรียกว่าที่อยู่ของเครื่องหมายตัวแปร สมมติว่าที่อยู่ 4 ไบต์ต่อเนื่องกันคือ 5004, 5005, 5006 และ 5007 ดังนั้นที่อยู่ของเครื่องหมายตัวแปรจะเป็น 5004 ป้อนคำอธิบายรูปภาพที่นี่

ประกาศตัวแปรพอยน์เตอร์

ดังที่ได้กล่าวไปแล้วตัวชี้คือตัวแปรที่เก็บที่อยู่หน่วยความจำ เช่นเดียวกับตัวแปรอื่น ๆ ที่คุณต้องประกาศตัวแปรตัวชี้ก่อนที่คุณจะสามารถใช้มันได้ นี่คือวิธีที่คุณสามารถประกาศตัวแปรตัวชี้

ไวยากรณ์: data_type *pointer_name;

data_type เป็นประเภทของตัวชี้ (หรือเรียกอีกอย่างว่าประเภทฐานของตัวชี้) pointer_name เป็นชื่อของตัวแปรซึ่งสามารถเป็นตัวระบุ C ใด ๆ ที่ถูกต้อง

ลองมาตัวอย่างบางส่วน:

int *ip;

float *fp;

int * ip หมายความว่า ip เป็นตัวแปรตัวชี้ที่สามารถชี้ไปยังตัวแปรประเภท int กล่าวอีกนัยหนึ่ง ip ตัวแปรชี้สามารถเก็บที่อยู่ของตัวแปรประเภท int เท่านั้น ในทำนองเดียวกันตัวแปรตัวชี้ fp สามารถเก็บที่อยู่ของตัวแปรประเภทลอย ชนิดของตัวแปร (หรือเรียกอีกอย่างว่าประเภทฐาน) ip เป็นตัวชี้ไปยัง int และประเภทของ fp เป็นตัวชี้ไปลอย ตัวแปรพอยน์เตอร์ของประเภทพอยน์เตอร์ถึง int สามารถถูกแทนด้วยสัญลักษณ์ (int *) ในทำนองเดียวกันตัวแปรพอยน์เตอร์ของตัวชี้ชนิดที่จะลอยสามารถแสดงเป็น (float *)

หลังจากประกาศตัวแปรตัวชี้ขั้นตอนต่อไปคือการกำหนดที่อยู่หน่วยความจำที่ถูกต้องให้กับมัน คุณไม่ควรใช้ตัวแปรพอยน์เตอร์โดยไม่ได้กำหนดที่อยู่หน่วยความจำที่ถูกต้องให้กับมันเพราะหลังจากประกาศแล้วมันมีค่าขยะและมันอาจจะชี้ไปที่ใดก็ได้ในหน่วยความจำ การใช้ตัวชี้ที่ไม่ได้กำหนดอาจให้ผลลัพธ์ที่คาดเดาไม่ได้ มันอาจทำให้โปรแกรมขัดข้อง

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

ที่มา: thecguruเป็นคำอธิบายที่ง่ายที่สุด แต่มีรายละเอียดที่ฉันเคยพบ

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