ตำแหน่งของเครื่องหมายดอกจันในการประกาศตัวชี้


95

ฉันเพิ่งตัดสินใจว่าในที่สุดฉันก็ต้องเรียนรู้ C / C ++ และมีสิ่งหนึ่งที่ฉันไม่เข้าใจจริงๆเกี่ยวกับพอยน์เตอร์หรือคำจำกัดความของมัน

ตัวอย่างเหล่านี้เป็นอย่างไร:

  1. int* test;
  2. int *test;
  3. int * test;
  4. int* test,test2;
  5. int *test,test2;
  6. int * test,test2;

ตอนนี้ตามความเข้าใจของฉันสามกรณีแรกล้วนทำเหมือนกัน: การทดสอบไม่ใช่ int แต่เป็นตัวชี้ไปที่หนึ่ง

ตัวอย่างชุดที่สองค่อนข้างยุ่งยากกว่าเล็กน้อย ในกรณีที่ 4 ทั้ง test และ test2 จะเป็นตัวชี้ไปที่ int ในขณะที่ในกรณีที่ 5 การทดสอบเท่านั้นที่เป็นตัวชี้ในขณะที่ test2 เป็น int "จริง" แล้วกรณีที่ 6 ล่ะ? เหมือนกับกรณีที่ 5 หรือไม่?


10
ในช่องว่างสีขาว C / C ++ จะไม่เปลี่ยนความหมาย
Sulthan

19
7. int*test;?
Jin Kwon

3
+1 เพราะฉันคิดว่าจะถามเกี่ยวกับ 1 - 3 เท่านั้นการอ่านคำถามนี้ทำให้ฉันมีบางอย่างเกี่ยวกับ 4 - 6 ที่ฉันไม่เคยคิดมาก่อน
vastlysuperiorman

@ สุลต่านนั่นคือความจริง 99% ของเวลา แต่ไม่เสมอไป ด้านบนของหัวของฉันมีประเภทของประเภทเทมเพลตในข้อกำหนดพื้นที่ประเภทเทมเพลต (ก่อน C ++ 11) ในจะต้องมีการเขียนเพื่อที่จะไม่ได้รับการรักษาเป็นสิทธิกะ Foo<Bar<char>>>>> >
AnorZaken

3
@AnorZaken คุณพูดถูกนั่นเป็นความคิดเห็นที่ค่อนข้างเก่า มีหลายสถานการณ์ที่ช่องว่างจะเปลี่ยนความหมายตัวอย่างเช่นตัว++ดำเนินการส่วนเพิ่มไม่สามารถแบ่งด้วยช่องว่างตัวระบุไม่สามารถแบ่งตามช่องว่างได้ (และผลลัพธ์ยังคงถูกต้องตามกฎหมายสำหรับคอมไพลเลอร์ แต่มีลักษณะการทำงานรันไทม์ที่ไม่ได้กำหนดไว้) สถานการณ์ที่แน่นอนนั้นยากมากที่จะกำหนดโดยพิจารณาจากความยุ่งเหยิงทางไวยากรณ์ที่ C / C ++ คือ
Sulthan

คำตอบ:


128

4, 5 และ 6 เป็นสิ่งเดียวกันการทดสอบเท่านั้นที่เป็นตัวชี้ หากคุณต้องการคำชี้สองตัวคุณควรใช้:

int *test, *test2;

หรือดีกว่า (เพื่อให้ทุกอย่างชัดเจน):

int* test;
int* test2;

3
กรณีที่ 4 เป็นกับดักแห่งความตายจริงหรือ? มีข้อกำหนดหรือข้ออ่านเพิ่มเติมที่อธิบายว่าเหตุใด int * test, test2 จึงทำให้ตัวแปรแรกเป็นตัวชี้เท่านั้น
Michael Stum

8
@ Michael Stum เป็น C ++ แล้วคุณคิดว่ามีคำอธิบายที่สมเหตุสมผลหรือไม่?
Joe Phillips

6
อ่าน K&R (ภาษาการเขียนโปรแกรม C) มันอธิบายทั้งหมดนี้อย่างชัดเจน
Ferruccio

8
กรณีที่ 4, 5 และ 6 เป็น "กับดักแห่งความตาย" นี่เป็นเหตุผลหนึ่งว่าทำไม gudes สไตล์ C / C ++ จำนวนมากจึงแนะนำการประกาศเพียงครั้งเดียวต่อหนึ่งคำสั่ง
Michael Burr

14
ช่องว่างไม่มีความสำคัญกับคอมไพเลอร์ C (ละเว้นตัวประมวลผลล่วงหน้า) ดังนั้นไม่ว่าเครื่องหมายดอกจันจะมีช่องว่างหรือไม่อยู่ระหว่างเครื่องหมายดอกจันกับรอบ ๆ ก็มีความหมายเหมือนกันทุกประการ
ephemient

45

ช่องว่างสีขาวรอบ ๆ เครื่องหมายดอกจันไม่มีความสำคัญ ทั้งสามหมายถึงสิ่งเดียวกัน:

int* test;
int *test;
int * test;

" int *var1, var2" เป็นไวยากรณ์ที่ชั่วร้ายที่มีไว้เพื่อสร้างความสับสนให้กับผู้คนและควรหลีกเลี่ยง ขยายเป็น:

int *var1;
int var2;

16
อย่าลืมint*test.
Mateen Ulhaq

2
ช่องว่างก่อนหรือหลังเครื่องหมายดอกจันเป็นเพียงเรื่องของความสวยงาม อย่างไรก็ตามมาตรฐานการเข้ารหัสของ Google ใช้กับint *test( google-styleguide.googlecode.com/svn/trunk/… ) แค่เสมอต้นเสมอปลาย

@SebastianRaschka คู่มือสไตล์ C ++ ของ Googleอนุญาตให้ใส่เครื่องหมายดอกจันได้อย่างชัดเจน บางทีมันอาจเปลี่ยนไปตั้งแต่คุณอ่าน
Jared Beck

33

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


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

int* test;   // test is a pointer to an int

สิ่งนี้เริ่มทำงานได้ดีโดยเฉพาะอย่างยิ่งเมื่อคุณเริ่มประกาศตัวชี้ const และมันจะยากที่จะรู้ว่าตัวชี้นั้นเป็น const หรือไม่หรือว่าสิ่งที่ตัวชี้ชี้ไปนั้นคือ const หรือไม่

int* const test; // test is a const pointer to an int

int const * test; // test is a pointer to a const int ... but many people write this as  
const int * test; // test is a pointer to an int that's const

แม้ว่า "หนึ่งตัวแปรต่อบรรทัด" ดูเหมือนจะมีประโยชน์ แต่เรายังไม่สามารถแก้ไขสถานการณ์ที่เครื่องหมายดอกจันอยู่ทางซ้ายหรือทางขวามากกว่าได้อย่างสมบูรณ์ ฉันค่อนข้างแน่ใจว่าในโค้ด out in the wild ตัวแปรเดียวมีชัย เช่นบางประเทศขับรถทางด้านขวาและประเทศอื่น ๆ ขับรถผิดทางเช่นสหราชอาณาจักร ;-)
shevy

น่าเสียดายจากการผจญภัยในป่าฉันเห็นทั้งสองสไตล์มากมาย ในทีมของฉันตอนนี้เราใช้รูปแบบเสียงดังกับรูปแบบที่เราได้ตกลงไว้ อย่างน้อยก็หมายความว่าโค้ดทั้งหมดที่ทีมงานของเราสร้างขึ้นมีรูปแบบเดียวกันกับช่องว่างที่ไป
Scott Langham

33

ใช้"กฎเกลียวตามเข็มนาฬิกา"เพื่อช่วยแยกวิเคราะห์การประกาศ C / C ++

มีสามขั้นตอนง่ายๆดังต่อไปนี้:

  1. เริ่มต้นด้วยองค์ประกอบที่ไม่รู้จักให้เคลื่อนที่ไปในทิศทางเกลียว / ตามเข็มนาฬิกา เมื่อพบองค์ประกอบต่อไปนี้ให้แทนที่ด้วยคำสั่งภาษาอังกฤษที่เกี่ยวข้อง:

    [X]หรือ[]: ขนาดอาร์เรย์ X ของ ... หรือขนาดอาร์เรย์ที่ไม่ได้กำหนดของ ...

    (type1, type2): ฟังก์ชั่นส่งคืน type1 และ type2 ...

    *: ตัวชี้ถึง ...

  2. ทำสิ่งนี้ไปเรื่อย ๆ ในทิศทางเกลียว / ตามเข็มนาฬิกาจนกว่าโทเค็นทั้งหมดจะถูกครอบคลุม
  3. แก้ไขอะไรในวงเล็บก่อนเสมอ!

นอกจากนี้การประกาศควรอยู่ในข้อความแยกต่างหากเมื่อเป็นไปได้ (ซึ่งเป็นความจริงโดยส่วนใหญ่)


4
นั่นดูน่ากลัวและค่อนข้างน่ากลัวขอโทษที่ต้องพูด
Joe Phillips

7
มันเป็นเช่นนั้น แต่ดูเหมือนจะเป็นคำอธิบายที่ดีสำหรับโครงสร้างที่ซับซ้อนกว่านั้น
Michael Stum

@ d03boy: ไม่มีคำถาม - การประกาศ C / C ++ อาจเป็นฝันร้าย
Michael Burr

2
"เกลียว" ไม่สมเหตุสมผลเลยน้อยกว่า "ตามเข็มนาฬิกา" ฉันควรตั้งชื่อนี้ว่า "กฎด้านซ้ายขวา" เนื่องจากไวยากรณ์ไม่ได้ทำให้คุณดูขวาล่างซ้ายบน แต่เป็นขวาซ้ายเท่านั้น
Ruslan

ฉันเรียนรู้สิ่งนี้ว่าเป็นกฎ "ขวาซ้าย - ขวา" คน C ++ มักชอบแกล้งทำเป็นว่าข้อมูลประเภททั้งหมดอยู่ทางซ้ายซึ่งนำไปสู่int* x;สไตล์มากกว่าint *x;รูปแบบดั้งเดิม แน่นอนว่าระยะห่างไม่สำคัญกับคอมไพเลอร์ แต่มันส่งผลกระทบต่อมนุษย์ การปฏิเสธไวยากรณ์จริงนำไปสู่กฎรูปแบบที่อาจรบกวนและทำให้ผู้อ่านสับสน
Adrian McCarthy

12

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

int* x; // "x is a pointer to int"

หรือวิธีนี้:

int *x; // "*x is an int"

FWIW ฉันอยู่ในค่ายแรก แต่เหตุผลที่คนอื่นโต้แย้งในรูปแบบที่สองคือ (ส่วนใหญ่) สามารถแก้ปัญหานี้ได้:

int* x,y; // "x is a pointer to int, y is an int"

ซึ่งอาจทำให้เข้าใจผิด คุณจะเขียนอย่างใดอย่างหนึ่งแทน

int *x,y; // it's a little clearer what is going on here

หรือถ้าคุณต้องการพอยน์เตอร์ 2 ตัว

int *x, *y; // two pointers

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


6
นี่มันปลอมคุณเรียกว่าint *MyFunc(void)อะไร? a *MyFuncเป็นฟังก์ชันที่ส่งคืนint? ไม่. เห็นได้ชัดว่าเราควรเขียนint* MyFunc(void)และบอกว่าMyFuncเป็นฟังก์ชันที่ส่งคืน a int*. สำหรับฉันแล้วนี่เป็นเรื่องที่ชัดเจนกฎการแยกวิเคราะห์ไวยากรณ์ C และ C ++ นั้นไม่ถูกต้องสำหรับการประกาศตัวแปร ควรรวมคุณสมบัติของตัวชี้ไว้เป็นส่วนหนึ่งของประเภทที่ใช้ร่วมกันสำหรับลำดับลูกน้ำทั้งหมด
v.oddou

1
แต่*MyFunc() เป็น intปัญหาเกี่ยวกับไวยากรณ์ C คือการผสมคำนำหน้าและไวยากรณ์หลังการแก้ไข - หากใช้เฉพาะ postfix จะไม่มีความสับสน
Antti Haapala

1
ค่ายแรกต่อสู้ไวยากรณ์ของภาษาที่นำไปสู่ความสับสนโครงสร้างเหมือนที่ผมพบว่าเป็นความเข้าใจผิดเป็นint const* x; a * x+b * y
Adrian McCarthy



3

เหตุผลในภาษา C คือคุณประกาศตัวแปรตามวิธีที่คุณใช้ ตัวอย่างเช่น

char *a[100];

บอกว่า *a[42]จะเป็นchar. และa[42]ตัวชี้ถ่าน ดังนั้นจึงaเป็นอาร์เรย์ของตัวชี้ถ่าน

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


การเขียนchar* a[100]; ยังอนุมานได้ว่า*a[42];จะเป็น a charและa[42];ตัวชี้ถ่าน
yyny

เราทุกคนสรุปข้อสรุปเดียวกันมีเพียงลำดับเท่านั้นที่แตกต่างกัน
Michel Billaud

ข้อความอ้างอิง: "บอกว่า * a [42] จะเป็นตัวอักษรและ [42] ตัวชี้ถ่าน" แน่ใจหรือว่าไม่ใช่ทางอื่น?
deLock

หากคุณต้องการวิธีอื่นให้พูดว่าa[42]เป็นcharตัวชี้และ*a[42]เป็นอักขระ
Michel Billaud

3

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

int *pointer1, *pointer2; // Fully consistent, two pointers
int* pointer1, pointer2;  // Inconsistent -- because only the first one is a pointer, the second one is an int variable
// The second case is unexpected, and thus prone to errors

เหตุใดกรณีที่สองจึงไม่สอดคล้องกัน? เนื่องจากเช่นint x,y;ประกาศตัวแปรสองประเภทที่เป็นประเภทเดียวกัน แต่ประเภทจะถูกกล่าวถึงเพียงครั้งเดียวในการประกาศ สิ่งนี้ก่อให้เกิดพฤติกรรมที่เป็นแบบอย่างและคาดหวัง และint* pointer1, pointer2;ไม่สอดคล้องกับสิ่งนั้นเนื่องจากประกาศpointer1เป็นตัวชี้ แต่pointer2เป็นตัวแปรจำนวนเต็ม มีแนวโน้มที่จะเกิดข้อผิดพลาดอย่างชัดเจนดังนั้นจึงควรหลีกเลี่ยง (โดยใส่เครื่องหมายดอกจันไว้ข้างชื่อตัวชี้แทนที่จะเป็นประเภท)

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

MyClass *volatile MyObjName

void test (const char *const p) // const value pointed to by a const pointer

สุดท้ายในบางกรณีการใส่เครื่องหมายดอกจันไว้ข้างชื่อประเภทอาจชัดเจนกว่าเช่น:

void* ClassName::getItemPtr () {return &item;} // Clear at first sight


2

ฉันจะบอกว่าหลักการเริ่มต้นคือการวางดาวไว้ที่ด้านชื่อตัวชี้ (ด้านขวาของการประกาศ

  • ในภาษาโปรแกรมซีโดย Dennis M. Ritchie ดวงดาวจะอยู่ทางด้านขวาของคำประกาศ

  • โดยดูซอร์สโค้ด linux ที่https://github.com/torvalds/linux/blob/master/init/main.c เราจะเห็นว่าดาวอยู่ทางด้านขวา

คุณสามารถปฏิบัติตามกฎเดียวกันได้ แต่ไม่ใช่เรื่องใหญ่หากคุณติดดาวไว้ที่ด้านประเภท โปรดจำไว้ว่าความสม่ำเสมอเป็นสิ่งสำคัญดังนั้นเสมอ แต่ดาวจะอยู่ข้างเดียวกันไม่ว่าคุณจะเลือกฝ่ายใดก็ตาม


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

1

ตัวชี้เป็นตัวปรับเปลี่ยนประเภท ควรอ่านจากขวาไปซ้ายเพื่อให้เข้าใจว่าเครื่องหมายดอกจันปรับเปลี่ยนประเภทอย่างไร 'int *' สามารถอ่านเป็น "pointer to int 'ได้ในการประกาศหลายรายการคุณต้องระบุว่าตัวแปรแต่ละตัวเป็นตัวชี้หรือจะสร้างเป็นตัวแปรมาตรฐาน

1,2 และ 3) การทดสอบเป็นประเภท (int *) ช่องว่างไม่สำคัญ

4,5 และ 6) การทดสอบเป็นประเภท (int *) Test2 เป็นประเภท int อีกครั้งช่องว่างไม่สำคัญ


1

ปริศนานี้มีสามชิ้น

ส่วนแรกคือช่องว่างใน C และ C ++ โดยปกติไม่มีนัยสำคัญนอกเหนือจากการแยกโทเค็นที่อยู่ติดกันซึ่งแยกไม่ออก

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

inttest;

tokenizer เพียงเห็นสองราชสกุล - ระบุinttestตาม ;punctuator ไม่รู้จักintเป็นคีย์เวิร์ดแยกต่างหากในขั้นตอนนี้ (ซึ่งจะเกิดขึ้นภายหลังในกระบวนการ) ดังนั้นเพื่อให้บรรทัดที่จะอ่านเป็นการประกาศจำนวนเต็มชื่อtestเราต้องใช้ช่องว่างเพื่อแยกโทเค็นตัวระบุ:

int test;

*ตัวละครที่ไม่เป็นส่วนหนึ่งของตัวระบุใด ๆ เป็นโทเค็นแยกต่างหาก (เครื่องหมายวรรคตอน) ในตัวของมันเอง ดังนั้นถ้าคุณเขียน

int*test;

คอมไพเลอร์เห็น 4 ราชสกุลที่แยกต่างหาก - int, *, และtest ;ดังนั้นช่องว่างจึงไม่มีความสำคัญในการประกาศตัวชี้และทั้งหมด

int *test;
int* test;
int*test;
int     *     test;

ตีความไปในทางเดียวกัน


ส่วนที่สองของปริศนาคือการประกาศทำงานจริงใน C และ C ++ 1อย่างไร การประกาศจะถูกแบ่งออกเป็นสองชิ้นหลัก - ลำดับของspecifiers ประกาศ (specifiers อุปกรณ์เก็บข้อมูลชนิด specifiers ประเภทบ่น ฯลฯ ) ตามด้วยรายการคั่นด้วยเครื่องหมายจุลภาค (อาจจะเริ่มต้น) declarators ในการประกาศ

unsigned long int a[10]={0}, *p=NULL, f(void);

specifiers ประกาศที่มีunsigned long intและ declarators มีa[10]={0}, และ*p=NULL f(void)ผู้ประกาศจะแนะนำชื่อของสิ่งที่กำลังประกาศ ( a, pและf) พร้อมกับข้อมูลเกี่ยวกับอาร์เรย์ของสิ่งนั้นตัวชี้และฟังก์ชัน ผู้ประกาศอาจมีตัวเริ่มต้นที่เกี่ยวข้องด้วย

ประเภทของaคือ "อาร์เรย์ 10 องค์ประกอบของunsigned long int" ประเภทที่ระบุไว้อย่างเต็มที่โดยการรวมกันของ specifiers ประกาศและ declarator ={0}และค่าเริ่มต้นที่ระบุไว้กับการเริ่มต้น ในทำนองเดียวกันประเภทของการpเป็น "ตัวชี้ไปunsigned long int" และอีกประเภทที่ระบุไว้โดยการรวมกันของ specifiers ประกาศและ declarator NULLและจะเริ่มต้นที่จะ และประเภทของfคือ "ฟังก์ชันที่ส่งคืนunsigned long int" โดยใช้เหตุผลเดียวกัน

นี่คือกุญแจสำคัญ - ไม่มีตัวระบุประเภท "pointer-to" เหมือนกับที่ไม่มีตัวระบุประเภท "array-of" เช่นเดียวกับที่ไม่มีตัวระบุประเภท "function-return" เราไม่สามารถประกาศอาร์เรย์เป็น

int[10] a;

เพราะถูกดำเนินการของ[]ผู้ประกอบการคือไม่a intในทำนองเดียวกันในการประกาศ

int* p;

ถูกดำเนินการ*คือไม่p intแต่เนื่องจากตัวดำเนินการ indirection เป็นแบบยูนารีและช่องว่างไม่มีนัยสำคัญคอมไพเลอร์จะไม่บ่นถ้าเราเขียนด้วยวิธีนี้ อย่างไรก็ตามมันก็มักจะint (*p);ตีความว่าเป็น

เพราะฉะนั้นถ้าคุณเขียน

int* p, q;

ตัวถูกดำเนินการของ*is pดังนั้นมันจะถูกตีความว่า

int (*p), q;

ดังนั้นทั้งหมด

int *test1, test2;
int* test1, test2;
int * test1, test2;

ทำสิ่งเดียวกัน - ในทุกกรณีที่สามtest1คือตัวถูกดำเนินการของ*และทำให้มีประเภท "ชี้int" ในขณะที่มีประเภทtest2int

ผู้ประกาศสามารถซับซ้อนได้โดยพลการ คุณสามารถมีอาร์เรย์ของพอยน์เตอร์:

T *a[N];

คุณสามารถมีตัวชี้ไปยังอาร์เรย์:

T (*a)[N];

คุณสามารถมีฟังก์ชันที่ส่งคืนพอยน์เตอร์:

T *f(void);

คุณสามารถมีตัวชี้ไปยังฟังก์ชัน:

T (*f)(void);

คุณสามารถมีอาร์เรย์ของตัวชี้ไปยังฟังก์ชัน:

T (*a[N])(void);

คุณสามารถมีฟังก์ชันที่ส่งคืนตัวชี้ไปยังอาร์เรย์:

T (*f(void))[N];

คุณสามารถมีฟังก์ชันที่ส่งคืนตัวชี้ไปยังอาร์เรย์ของตัวชี้ไปยังฟังก์ชันที่ส่งคืนตัวชี้ไปที่T:

T *(*(*f(void))[N])(void); // yes, it's eye-stabby.  Welcome to C and C++.

แล้วคุณมีsignal:

void (*signal(int, void (*)(int)))(int);

ซึ่งอ่านว่า

       signal                             -- signal
       signal(                 )          -- is a function taking
       signal(                 )          --   unnamed parameter
       signal(int              )          --   is an int
       signal(int,             )          --   unnamed parameter
       signal(int,      (*)    )          --   is a pointer to
       signal(int,      (*)(  ))          --     a function taking
       signal(int,      (*)(  ))          --       unnamed parameter
       signal(int,      (*)(int))         --       is an int
       signal(int, void (*)(int))         --     returning void
     (*signal(int, void (*)(int)))        -- returning a pointer to
     (*signal(int, void (*)(int)))(   )   --   a function taking
     (*signal(int, void (*)(int)))(   )   --     unnamed parameter
     (*signal(int, void (*)(int)))(int)   --     is an int
void (*signal(int, void (*)(int)))(int);  --   returning void
    

และนี่เป็นเพียงแค่รอยขีดข่วนบนพื้นผิวของสิ่งที่เป็นไปได้ แต่โปรดสังเกตว่า array-ness, pointer-ness และ function-ness เป็นส่วนหนึ่งของ declarator เสมอไม่ใช่ type specifier

สิ่งหนึ่งที่ต้องระวัง - constสามารถปรับเปลี่ยนทั้งประเภทตัวชี้และประเภทชี้ไปที่:

const int *p;  
int const *p;

ทั้งสองข้อข้างต้นประกาศpเป็นตัวชี้ไปยังconst intวัตถุ คุณสามารถเขียนค่าใหม่เพื่อpตั้งค่าให้ชี้ไปที่วัตถุอื่น:

const int x = 1;
const int y = 2;

const int *p = &x;
p = &y;

แต่คุณไม่สามารถเขียนไปยังวัตถุที่ชี้ไปที่:

*p = 3; // constraint violation, the pointed-to object is const

อย่างไรก็ตาม

int * const p;

ประกาศpเป็นconstตัวชี้ไปยัง non-const int; คุณสามารถเขียนถึงสิ่งที่pชี้ไป

int x = 1;
int y = 2;
int * const p = &x;

*p = 3;

แต่คุณไม่สามารถกำหนดpให้ชี้ไปที่วัตถุอื่นได้:

p = &y; // constraint violation, p is const

ซึ่งนำเราไปสู่ปริศนาชิ้นที่สาม - ทำไมการประกาศจึงมีโครงสร้างในลักษณะนี้

เจตนาคือโครงสร้างของการประกาศควรสะท้อนโครงสร้างของนิพจน์ในรหัสอย่างใกล้ชิด ("การประกาศเลียนแบบการใช้") ตัวอย่างเช่นสมมติว่าเรามีอาร์เรย์ของพอยน์เตอร์ที่จะintตั้งชื่อapและเราต้องการเข้าถึงintค่าที่ชี้โดยiองค์ประกอบ 'th เราจะเข้าถึงค่านั้นดังนี้:

printf( "%d", *ap[i] );

แสดงออก *ap[i]มีประเภทint; ดังนั้นคำประกาศapจึงเขียนเป็น

int *ap[N]; // ap is an array of pointer to int, fully specified by the combination
            // of the type specifier and declarator

declarator มีรูปแบบเดียวกันกับการแสดงออก*ap[N] *ap[i]ตัวดำเนินการ*และ[]ทำงานในลักษณะเดียวกันในการประกาศว่าพวกเขาทำในนิพจน์ - []มีลำดับความสำคัญสูงกว่ายูนารี*ดังนั้นตัวถูกดำเนินการ*จึงเป็นap[N](แยกวิเคราะห์เป็น*(ap[N]))

อีกตัวอย่างหนึ่งสมมติว่าเรามีตัวชี้ไปยังอาร์เรย์ของintชื่อpaและเราต้องการเข้าถึงค่าของiองค์ประกอบ 'th เราจะเขียนว่าเป็น

printf( "%d", (*pa)[i] );

ประเภทของนิพจน์(*pa)[i]คือintดังนั้นการประกาศจึงถูกเขียนเป็น

int (*pa)[N];

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

*, []และ()ผู้ประกอบการเป็นส่วนหนึ่งของการแสดงออกในรหัสเพื่อให้พวกเขาเป็นส่วนหนึ่งของdeclaratorในการประกาศ ตัวประกาศจะบอกวิธีใช้อ็อบเจกต์ในนิพจน์ หากคุณมีคำประกาศเช่นint *p;นี้จะบอกคุณว่านิพจน์*pในโค้ดของคุณจะให้intค่า โดยขยายมันจะบอกคุณว่าการแสดงออกpมีผลเป็นค่าของชนิด "ชี้int" int *หรือ


แล้วสิ่งต่าง ๆ เช่นการแสดงและsizeofการแสดงออกที่เราใช้สิ่งที่เหมือน(int *)หรือsizeof (int [10])หรือสิ่งนั้น? ฉันจะอ่านสิ่งที่ชอบได้อย่างไร

void foo( int *, int (*)[10] );

ไม่มีตัวประกาศไม่ใช่*และ[]ตัวดำเนินการแก้ไขประเภทโดยตรงหรือไม่

ไม่มี - ยังคงมีผู้ประกาศเพียงแค่มีตัวระบุที่ว่างเปล่า (เรียกว่าผู้ประกาศนามธรรม ) ถ้าเราเป็นตัวแทนของตัวระบุว่างเปล่ากับλสัญลักษณ์แล้วเราสามารถอ่านสิ่งเหล่านั้นเป็น(int *λ), sizeof (int λ[10])และ

void foo( int *λ, int (*λ)[10] );

และพวกเขาปฏิบัติเหมือนคำประกาศอื่น ๆ int *[10]แทนอาร์เรย์ 10 พอยน์เตอร์ในขณะที่int (*)[10]แทนตัวชี้ไปยังอาร์เรย์


และตอนนี้ส่วนที่แสดงความคิดเห็นของคำตอบนี้ ฉันไม่ชอบรูปแบบ C ++ ในการประกาศตัวชี้อย่างง่ายเป็น

T* p;

และพิจารณาว่าเป็นการปฏิบัติที่ไม่ดีด้วยเหตุผลต่อไปนี้:

  1. ไม่สอดคล้องกับไวยากรณ์
  2. แนะนำความสับสน (เป็นหลักฐานโดยคำถามนี้ทั้งหมดที่ซ้ำกันคำถามนี้คำถามเกี่ยวกับความหมายของการT* p, q;ซ้ำกันทั้งหมดเพื่อให้ผู้คำถาม ฯลฯ );
  3. ไม่สอดคล้องกันภายใน - การประกาศอาร์เรย์ของพอยน์เตอร์ที่T* a[N]ไม่สมดุลกับการใช้งาน (เว้นแต่คุณจะมีนิสัยชอบเขียน* a[i])
  4. ไม่สามารถนำไปใช้กับประเภทตัวชี้ไปยังอาร์เรย์หรือตัวชี้ต่อฟังก์ชันได้ (เว้นแต่คุณจะสร้าง typedef เพียงเพื่อให้คุณสามารถใช้รูปT* pแบบได้อย่างหมดจดซึ่ง ... ไม่ );
  5. เหตุผลในการทำเช่นนั้น - "เป็นการเน้นย้ำถึงตัวชี้ของวัตถุ" - เป็นการปลอมแปลง ไม่สามารถใช้กับอาร์เรย์หรือประเภทฟังก์ชันได้และฉันคิดว่าคุณสมบัติเหล่านั้นสำคัญพอ ๆ กับการเน้น

ท้ายที่สุดมันบ่งบอกถึงการคิดสับสนเกี่ยวกับการทำงานของระบบประเภทสองภาษา

มีเหตุผลที่ดีในการประกาศรายการแยกกัน การหลีกเลี่ยงการปฏิบัติที่ไม่ดี ( T* p, q;) ไม่ใช่หนึ่งในนั้น หากคุณเขียนผู้ประกาศของคุณอย่างถูกต้อง ( T *p, q;) คุณมีโอกาสน้อยที่จะทำให้เกิดความสับสน

ฉันคิดว่ามันคล้ายกับการจงใจเขียนforลูปง่ายๆทั้งหมดของคุณเป็น

i = 0;
for( ; i < N; ) 
{ 
  ... 
  i++ 
}

ถูกต้องตามหลักไวยากรณ์ แต่สร้างความสับสนและเจตนามีแนวโน้มที่จะตีความผิด อย่างไรก็ตามการT* p;ประชุมนั้นยึดติดอยู่ในชุมชน C ++ และฉันใช้มันในรหัส C ++ ของฉันเองเพราะความสม่ำเสมอในฐานรหัสเป็นสิ่งที่ดี แต่มันทำให้ฉันคันทุกครั้งที่ทำ


  1. ฉันจะใช้คำศัพท์ภาษา C - คำศัพท์ C ++ นั้นแตกต่างกันเล็กน้อย แต่แนวคิดส่วนใหญ่เหมือนกัน

-3

หลักการทั่วไปที่ดีผู้คนจำนวนมากดูเหมือนจะเข้าใจแนวคิดเหล่านี้โดย: ใน C ++ ความหมายเชิงความหมายจำนวนมากได้มาจากการผูกคีย์เวิร์ดหรือตัวระบุด้านซ้าย

ยกตัวอย่าง:

int const bla;

const ใช้กับคำ "int" เช่นเดียวกับเครื่องหมายดอกจันของพอยน์เตอร์ซึ่งใช้กับคีย์เวิร์ดด้านซ้ายของพวกเขา และชื่อตัวแปรจริงหรือ? ใช่นั่นคือประกาศโดยสิ่งที่เหลืออยู่


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