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


187

ทำไมตัวแปรชื่อโปรแกรมเมอร์ซีส่วนใหญ่เช่นนี้:

int *myVariable;

มากกว่าเช่นนี้

int* myVariable;

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


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

คำตอบ:


251

พวกมันเทียบเท่ากันหมด อย่างไรก็ตามใน

int *myVariable, myVariable2;

ดูเหมือนว่าเห็นได้ชัดว่า myVariable มีประเภทint *ในขณะที่ myVariable2 มีประเภทint ใน

int* myVariable, myVariable2;

มันอาจจะดูเหมือนเห็นได้ชัดว่าทั้งสองเป็นประเภทint *แต่ที่ไม่ถูกต้องตามที่myVariable2มีประเภทint

ดังนั้นสไตล์การเขียนโปรแกรมแรกจึงใช้งานง่ายกว่า


135
อาจ แต่ฉันจะไม่ผสมและจับคู่ประเภทในการประกาศเดียว
BobbyShaftoe

15
@ BobShaftoe เห็นด้วย แม้หลังจากอ่านข้อโต้แย้งทั้งหมดที่นี่ฉันยังคงยึดติดกับint* someVarโครงการส่วนบุคคล มันสมเหตุสมผลมากขึ้น
Alyssa Haroldsen

30
@Kupiakos มันเหมาะสมกว่าจนกว่าคุณจะเรียนรู้ไวยากรณ์การประกาศของ C ตาม "การประกาศตามการใช้งาน" การประกาศใช้ไวยากรณ์เดียวกันที่แน่นอนซึ่งใช้ตัวแปรที่พิมพ์เหมือนกัน เมื่อคุณประกาศจำนวนของ ints มันจะไม่เหมือน: int[10] x. นี่ไม่ใช่ไวยากรณ์ของ C ไวยากรณ์แยกวิเคราะห์อย่างชัดเจนว่า: int (*x)และไม่เป็น(int *) xดังนั้นการวางเครื่องหมายดอกจันทางซ้ายเป็นเพียงการทำให้เข้าใจผิดและยึดตามความเข้าใจผิดของไวยากรณ์การประกาศ C
Peaker

8
การแก้ไข: ดังนั้นคุณไม่ควรประกาศตัวแปรมากกว่าหนึ่งตัวในบรรทัดเดียว โดยทั่วไปแล้วคุณไม่ควรกระตุ้นรูปแบบการเข้ารหัสบางอย่างตามสไตล์การเข้ารหัสอื่น ๆ ที่ไม่เกี่ยวข้องไม่ดีและอันตราย
Lundin

6
นี่คือเหตุผลที่ฉันติดหนึ่งตัวแปรต่อการประกาศตัวชี้ ไม่มีความสับสนอย่างแน่นอนถ้าคุณทำint* myVariable; int myVariable2;แทน
Patrick Roberts

122

หากคุณดูอีกวิธีหนึ่ง*myVariableเป็นประเภทintที่เหมาะสม


6
นี่เป็นคำอธิบายที่ฉันโปรดปรานและทำงานได้ดีเพราะมันอธิบายถึงนิสัยใจคอประกาศของ C โดยทั่วไป - แม้แต่ไวยากรณ์ตัวชี้ฟังก์ชันที่น่ารังเกียจและ gnarly
เบนจามินพอลแล็ค

12
มันค่อนข้างเรียบร้อยเนื่องจากคุณสามารถจินตนาการได้ว่าไม่มีประเภทพอยเตอร์ที่แท้จริง มีตัวแปรเท่านั้นที่เมื่ออ้างอิงอย่างถูกต้องหรือถูกปฏิเสธให้หนึ่งในประเภทดั้งเดิม
biozinc

1
ที่จริงแล้ว '* myVariable' อาจเป็นประเภท NULL เพื่อให้เรื่องแย่ลงมันอาจเป็นตำแหน่งหน่วยความจำแบบสุ่ม
qonf

4
qonf: NULL ไม่ใช่ประเภท myVariableสามารถเป็น NULL ซึ่งในกรณีนี้*myVariableทำให้เกิดความผิดพลาดในการแบ่งกลุ่ม แต่ไม่มีประเภท NULL
Daniel Roethlisberger

28
ประเด็นนี้อาจทำให้เข้าใจผิดในบริบทดังกล่าว: int x = 5; int *pointer = &x;เพราะมันชี้ให้เห็นว่าเรากำหนดค่าเป็น*pointerบางค่าไม่ใช่pointerตัวของมันเอง
rafalcieslak

40

เนื่องจาก * ในบรรทัดนั้นผูกกับตัวแปรมากกว่าประเภท:

int* varA, varB; // This is misleading

@Lundin ชี้ให้เห็นด้านล่าง const เพิ่มรายละเอียดปลีกย่อยที่น่าสนใจมากขึ้น คุณสามารถเลี่ยงขั้นตอนนี้ได้โดยการประกาศหนึ่งตัวแปรต่อบรรทัดซึ่งไม่ชัดเจน:

int* varA;
int varB;

ความสมดุลระหว่างโค้ดที่ชัดเจนและโค้ดที่กระชับนั้นยากที่จะตี - บรรทัดที่ซ้ำซ้อนจำนวนโหลที่int a;ไม่ดีเช่นกัน ยังฉันเริ่มต้นที่หนึ่งประกาศต่อบรรทัดและกังวลเกี่ยวกับการรวมรหัสในภายหลัง


2
ตัวอย่างแรกที่ทำให้เข้าใจผิดอยู่ในสายตาของฉันมีข้อผิดพลาดในการออกแบบ ถ้าทำได้ฉันจะลบวิธีการประกาศออกจาก C ทั้งหมดและทำให้ทั้งคู่เป็นประเภท int *
Adam Bajger

1
"the * ผูกกับตัวแปรอย่างใกล้ชิดยิ่งกว่าชนิด" นี่คืออาร์กิวเมนต์ที่ไร้เดียงสา int *const a, b;พิจารณา * "ผูก" อยู่ที่ไหน ประเภทของaคือint* constดังนั้นคุณจะพูดได้อย่างไรว่า * เป็นของตัวแปรเมื่อมันเป็นส่วนหนึ่งของประเภทตัวเอง?
Lundin

1
เฉพาะคำถามหรือครอบคลุมทุกกรณี: เลือกหนึ่งข้อ ฉันจะจดบันทึก แต่นี่เป็นอีกข้อโต้แย้งที่ดีสำหรับคำแนะนำสุดท้ายของฉัน: การประกาศหนึ่งรายการต่อบรรทัดช่วยลดโอกาสที่จะทำสิ่งนี้เสีย
ojrac

36

บางสิ่งบางอย่างที่ไม่มีใครพูดถึงที่นี่จนถึงขณะนี้คือเครื่องหมายดอกจันเป็น " ผู้ควบคุมการปฏิเสธ " ในซี

*a = 10;

บรรทัดข้างต้นไม่ได้หมายความว่าผมต้องการที่จะกำหนด10ไปaก็หมายความว่าผมต้องการที่จะกำหนด10สิ่งที่หน่วยความจำตำแหน่งaจุดที่จะต้อง และฉันไม่เคยเห็นใครเขียน

* a = 10;

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

x = a * b * c * d
  * e * f * g;

นี่*eจะทำให้เข้าใจผิดใช่มั้ย

โอเคตอนนี้บรรทัดต่อไปนี้หมายถึงอะไรจริง:

int *a;

คนส่วนใหญ่จะพูดว่า:

หมายความว่าaเป็นตัวชี้ไปยังintค่า

นี่เป็นเทคนิคที่ถูกต้องคนส่วนใหญ่ชอบที่จะเห็น / อ่านอย่างนั้นและนั่นคือวิธีที่มาตรฐาน C สมัยใหม่จะกำหนดไว้ (โปรดทราบว่าภาษา C เองมีมาก่อนมาตรฐาน ANSI และ ISO ทั้งหมด) แต่มันไม่ใช่วิธีเดียวที่จะมองมัน คุณยังสามารถอ่านบรรทัดนี้ได้ดังนี้:

มูลค่าของ dereferenced เป็นประเภทaint

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

มาตรฐาน C กำหนดเพียงสองความหมายต่อ*ผู้ปฏิบัติงาน:

  • ผู้ประกอบการทางอ้อม
  • ผู้ประกอบการคูณ

และการอ้อมเป็นเพียงความหมายเดียวไม่มีความหมายพิเศษสำหรับการประกาศตัวชี้มีเพียงการอ้อมซึ่งเป็นสิ่งที่การดำเนินการตามกฎหมายทำมันทำการเข้าถึงทางอ้อมดังนั้นภายในคำสั่งเช่นint *a;นี้เป็นการเข้าถึงทางอ้อม( *หมายถึง การเข้าถึงทางอ้อม) และทำให้ข้อความที่สองข้างต้นมีความใกล้เคียงกับมาตรฐานมากกว่าข้อความแรกคือ


6
ขอขอบคุณที่ช่วยฉันจากการเขียน แต่ยังมีคำตอบอื่นที่นี่ BTW ฉันมักจะอ่านint a, *b, (*c)();สิ่งที่ชอบ "ประกาศวัตถุต่อไปนี้เป็นint: วัตถุaวัตถุที่ชี้ไปตามbและวัตถุกลับมาจากฟังก์ชั่นชี้ไปตามc"
Antti Haapala

1
*ในint *a;ไม่ได้เป็นผู้ดำเนินการและจะไม่ dereferencing a(ซึ่งไม่ได้ถูกกำหนดแม้ยัง)
เอ็มเอ็ม

1
@MM โปรดตั้งชื่อหน้าและหมายเลขบรรทัดของมาตรฐาน ISO C ใด ๆ ที่มาตรฐานนี้บอกว่าเครื่องหมายดอกจันอาจเป็นอย่างอื่นได้มากกว่าการคูณหรือทางอ้อม มันแสดงเฉพาะ "การประกาศของตัวชี้" โดยเป็นตัวอย่างมันไม่ได้กำหนดความหมายที่สามสำหรับเครื่องหมายดอกจัน (และจะไม่มีการกำหนดความหมายที่จะไม่เป็นตัวดำเนินการ) โอ้ฉันไม่ได้อ้างว่ามีอะไร "กำหนด" คุณสร้างมันขึ้นมา หรืออย่างที่หมวกของ Jonathan Leffler ใส่ไว้ในมาตรฐาน C * เป็น "ไวยากรณ์" เสมอมันไม่ได้เป็นส่วนหนึ่งของ specifiers-listing ที่ระบุ (ดังนั้นจึงไม่ได้เป็นส่วนหนึ่งของการประกาศจึงต้องเป็นตัวดำเนินการ)
Mecki

1
@MM 6.7.6.1 แสดงเฉพาะประกาศโดยตัวอย่างที่เหมือนผมบอกว่ามันจะช่วยให้ไม่มีความหมายไป*(มันให้เพียงความหมายเพื่อการแสดงออกทั้งหมดไม่ให้*ภายในแสดงออก!) มันบอกว่า "int a;" ประกาศตัวชี้ซึ่งทำไม่เคยอ้างสิทธิ์เป็นอย่างอื่น แต่ไม่มีความหมายที่กำหนดให้*อ่านเป็น * ค่าที่ไม่ลงรอยกันของaintนั้นยังคงใช้ได้โดยสมบูรณ์เนื่องจากมีความหมายตามจริงเหมือนกัน ไม่มีอะไรจริง ๆ ไม่มีอะไรเขียนใน 6.7.6.1 จะขัดแย้งกับคำสั่งนั้น
Mecki

2
ไม่มี "การแสดงออกทั้งหมด" int *a;เป็นการประกาศไม่ใช่นิพจน์ aไม่ได้ dereferenced int *a;โดย aยังไม่ปรากฏว่า*มีการดำเนินการ คุณคิดว่าint *a = NULL;เป็นข้อผิดพลาดหรือไม่เพราะมันเป็นตัวชี้ null?
MM

17

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

int my_function(int arg); สำหรับฟังก์ชั่น;

float my_array[3] สำหรับอาร์เรย์

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

int a_return_value = my_function(729);

float an_element = my_array[2];

และ: int copy_of_value = *myVariable;.

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


12

นั่นเป็นเพียงเรื่องของการตั้งค่า

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

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

int* pMyPointer;

3
คำถามเกี่ยวกับ C ที่ไม่มีการอ้างอิง
Antti Haapala

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

8

กูรูที่ยิ่งใหญ่เคยกล่าวว่า "อ่านวิธีการแปลคุณต้อง"

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

ได้รับสิ่งนี้อยู่ในหัวข้อของตำแหน่ง const แต่กฎเดียวกันใช้ที่นี่

คอมไพเลอร์อ่านว่า:

int (*a);

ไม่เป็น:

(int*) a;

ถ้าคุณติดดาวที่ติดกับตัวแปรมันจะทำให้การประกาศของคุณง่ายต่อการอ่าน นอกจากนี้ยังหลีกเลี่ยงการปวดตาเช่น:

int* a[10];

- แก้ไข -

เพื่ออธิบายว่าสิ่งที่ฉันหมายถึงเมื่อบอกว่ามันแยกวิเคราะห์เป็นint (*a)ที่หมายความว่า*ผูกให้กระชับยิ่งขึ้นไปaกว่าจะintในมากลักษณะที่ว่าในการแสดงออก4 + 3 * 7 3ผูกให้กระชับยิ่งขึ้นไป7มากกว่าที่จะเกิดจากความสำคัญที่สูงขึ้นของ4*

ด้วยการขอโทษสำหรับศิลปะ ASCII สรุปของ AST สำหรับการแยกวิเคราะห์int *aมีลักษณะเช่นนี้:

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

เป็นที่แสดงให้เห็นอย่างชัดเจน*ผูกให้กระชับยิ่งขึ้นไปaตั้งแต่บรรพบุรุษร่วมกันของพวกเขาคือDeclaratorในขณะที่คุณจะต้องไปตลอดทางขึ้นต้นไม้เพื่อที่จะหาบรรพบุรุษร่วมกันที่เกี่ยวข้องกับการDeclarationint


(int*) aไม่มีคอมไพเลอร์แน่นอนที่สุดอ่านประเภทเป็น
Lundin

@Lundin นี่เป็นความเข้าใจผิดที่ยอดเยี่ยมที่โปรแกรมเมอร์ C ++ สมัยใหม่ส่วนใหญ่มี การแยกการประกาศตัวแปรไปบางอย่างเช่นนี้ ขั้นตอนที่ 1 อ่านโทเค็นแรกที่กลายเป็น "ประเภทฐาน" ของการประกาศ intในกรณีนี้. ขั้นตอนที่ 2 อ่านประกาศรวมถึงการตกแต่งประเภทใด ๆ *aในกรณีนี้. ขั้นตอนที่ 3 อ่านตัวละครถัดไป ถ้าเครื่องหมายจุลภาคกินมันและกลับไปที่ขั้นตอนที่ 2 ถ้าเครื่องหมายอัฒภาคหยุด หากสิ่งอื่นโยนข้อผิดพลาดทางไวยากรณ์ ...
dgnuff

@Lundin ... หาก parser อ่านวิธีที่คุณแนะนำเราจะสามารถเขียนint* a, b;และรับพอยน์เตอร์ได้ จุดที่ฉันทำคือการ*ผูกกับตัวแปรและแยกวิเคราะห์กับมันไม่ได้มีประเภทในการสร้าง "ประเภทฐาน" ของการประกาศ นั่นเป็นส่วนหนึ่งของเหตุผลที่มีการพิมพ์ typedefs เพื่ออนุญาตให้typedef int *iptr; iptr a, b;สร้างตัวชี้สองสามตัว โดยใช้ typedef ที่คุณสามารถผูกกับ* int
dgnuff

1
... แน่นอนว่าคอมไพเลอร์ "รวม" ประเภทฐานจากDeclaration-Specifierกับ "การตกแต่ง" ในการที่Declaratorจะมาถึงประเภทสุดท้ายสำหรับแต่ละตัวแปร แต่มันไม่ได้ "ย้าย" ตกแต่งเพื่อระบุการประกาศเป็นอย่างอื่นint a[10], b;จะให้ผลลัพธ์ที่ไร้สาระอย่างสมบูรณ์มันจะแยกวิเคราะห์เป็นint *a, b[10]; int *a , b[10] ;ไม่มีวิธีอื่นที่จะอธิบายว่าเหมาะสม
dgnuff

1
ใช่ส่วนที่สำคัญที่นี่ไม่ใช่ไวยากรณ์หรือคำสั่งในการแยกวิเคราะห์คอมไพเลอร์ แต่ประเภทของint*aการอ่านนั้นอยู่ในท้ายที่สุดโดยคอมไพเลอร์ว่า: " ahas type int*" นั่นคือสิ่งที่ฉันหมายถึงด้วยความคิดเห็นเดิมของฉัน
Lundin

3

เนื่องจากเหมาะสมกว่าเมื่อคุณมีคำประกาศเช่น:

int *a, *b;

5
นี่คือตัวอย่างของการขอร้องคำถาม ไม่มันไม่เข้าท่าเลย "int * a, b" ก็สามารถทำให้ทั้งคู่เป็นตัวชี้ได้เช่นกัน
MichaelGG

1
@MichaelGG คุณมีหางกระดิกสุนัขอยู่ที่นั่น แน่นอนว่า K & R สามารถได้ระบุว่าint* a, b;ทำให้ตัวชี้ไปยังb intแต่พวกเขาไม่ได้ และด้วยเหตุผลที่ดี ภายใต้ระบบที่เสนอของคุณประเภทของbการประกาศต่อไปนี้int* a[10], b;คืออะไร?
dgnuff

2

สำหรับการประกาศพอยน์เตอร์หลาย ๆ ตัวในหนึ่งบรรทัดฉันชอบint* a, * b;ที่จะประกาศว่า "a" เป็นตัวชี้ไปยังจำนวนเต็มโดยสังหรณ์ใจมากขึ้นและไม่ผสมผสานรูปแบบเมื่อประกาศ "b" เช่นเดียวกัน เหมือนมีคนพูดว่าฉันจะไม่ประกาศสองประเภทที่แตกต่างกันในแถลงการณ์เดียวกัน


2

ผู้ที่ต้องการint* x;พยายามบังคับให้รหัสของพวกเขาเข้าสู่โลกแห่งนิยายโดยที่ประเภทนั้นอยู่ทางซ้ายและตัวระบุ (ชื่อ) อยู่ทางขวา

ฉันพูดว่า "ตัวละคร" เพราะ:

ใน C และ C ++ ในกรณีทั่วไปตัวระบุที่ประกาศจะถูกล้อมรอบด้วยข้อมูลประเภท

นั่นอาจฟังดูบ้า แต่คุณรู้ว่ามันเป็นจริง นี่คือตัวอย่างบางส่วน:

  • int main(int argc, char *argv[])หมายถึง " mainเป็นฟังก์ชั่นที่ใช้intและอาร์เรย์ของพอยน์เตอร์ไปcharและส่งคืนint" กล่าวอีกนัยหนึ่งคือข้อมูลประเภทส่วนใหญ่อยู่ทางด้านขวา บางคนคิดว่าการประกาศฟังก์ชั่นไม่นับเพราะพวกเขา "พิเศษ" อย่างใด ตกลงมาลองตัวแปรกัน

  • void (*fn)(int)หมายความว่าfnเป็นตัวชี้ไปยังฟังก์ชั่นที่ใช้intและส่งคืนอะไร

  • int a[10]ประกาศ 'a' เป็นอาร์เรย์ 10 ints

  • pixel bitmap[height][width].

  • เห็นได้ชัดว่าฉันเลือกตัวอย่างเชอร์รี่ที่มีข้อมูลจำนวนมากทางด้านขวาเพื่อให้ประเด็นของฉัน มีจำนวนมากของการประกาศเป็นที่มากที่สุด - ถ้าไม่ทั้งหมด - struct { int x; int y; } centerประเภทที่อยู่ทางด้านซ้ายเช่น

ไวยากรณ์การประกาศนี้ขยายตัวออกมาจากความปรารถนาของ K & R ที่จะให้การประกาศสะท้อนถึงการใช้งาน การอ่านการประกาศอย่างง่ายนั้นง่ายและการอ่านสิ่งที่ซับซ้อนมากขึ้นสามารถเรียนรู้ได้โดยการเรียนรู้กฎทางซ้าย - ขวา - ขวา (บางครั้งเรียกกฎเกลียวหรือเพียงแค่กฎซ้าย - ขวา)

C ก็เพียงพอที่เรียบง่ายที่โปรแกรมเมอร์ C int *pหลายโอบกอดรูปแบบนี้และเขียนประกาศง่ายๆเป็น

ใน C ++ ไวยากรณ์มีความซับซ้อนเพิ่มขึ้นเล็กน้อย (ด้วยคลาสการอ้างอิงเท็มเพลตคลาส enum) และเพื่อตอบสนองต่อความซับซ้อนนั้นคุณจะเห็นความพยายามมากขึ้นในการแยกประเภทจากตัวระบุในการประกาศหลาย ๆ กล่าวอีกนัยหนึ่งคุณอาจเห็นการint* pประกาศสไตล์มากกว่าถ้าคุณลองดูรหัส C ++ ขนาดใหญ่

ในภาษาใดภาษาหนึ่งคุณสามารถมีประเภทที่ด้านซ้ายของการประกาศตัวแปรโดย (1) ไม่เคยประกาศตัวแปรหลายตัวในคำสั่งเดียวกันและ (2) การใช้typedefs (หรือนามแฝงการประกาศซึ่งน่าขันทำให้ชื่อแทน ตัวระบุทางด้านซ้ายของประเภท) ตัวอย่างเช่น:

typedef int array_of_10_ints[10];
array_of_10_ints a;

คำถามเล็ก ๆ ทำไมไม่สามารถโมฆะ (* fn) (int) หมายถึง "fn เป็นฟังก์ชั่นที่รับ int และส่งคืน (void *)?
Soham Dongargaonkar

1
@ a3y3: เนื่องจากวงเล็บอยู่ใน(*fn)ตำแหน่งของพอยน์เตอร์ที่สัมพันธ์กับfnไม่ใช่ประเภทที่ส่งคืน
Adrian McCarthy

เข้าใจแล้ว ฉันคิดว่ามันสำคัญพอที่จะเพิ่มคำตอบของคุณฉันจะแนะนำการแก้ไขในไม่ช้า
Soham Dongargaonkar

1
ฉันคิดว่ากลุ่มคำตอบไม่เพิ่มมูลค่ามากนัก นี่เป็นเพียงรูปแบบของภาษา คนส่วนใหญ่ถามว่าทำไมไวยากรณ์เป็นแบบนี้อาจรู้กฎอยู่แล้ว ใครก็ตามที่สับสนในประเด็นนี้สามารถเห็นความกระจ่างได้ในความคิดเห็นเหล่านี้
Adrian McCarthy

1

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


1

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


ตัวระบุประเภทตัวชี้ที่ถูกลืม

อย่างเป็นทางการ "ดาว" ไม่เป็นชนิดไม่ให้ชื่อตัวแปรมันเป็นส่วนหนึ่งของตัวเองรายการไวยากรณ์ชื่อของตัวชี้ ไวยากรณ์ C ทางการ (ISO 9899: 2018) คือ:

(6.7) การประกาศ:
declaration-specifiers init-declarator-list เลือก ;

โดยที่declaration-specifiersมีประเภท (และที่เก็บข้อมูล) และinit-declarator-listมีตัวชี้และชื่อตัวแปร ซึ่งเราจะเห็นว่าเราแยกไวยากรณ์ลิสต์ของตัวประกาศนี้หรือไม่:

(6.7.6) ผู้ประกาศ:
ตัวเลือก opt direct-declarator
... ตัวชี้
(6.7.6) :
* ประเภทตัวระบุ qualifier รายการopt
* -qualifier ประเภทรายการตัวเลือก opt

โดยที่ declarator คือการประกาศทั้งหมด direct-declarator คือตัวระบุ (ชื่อตัวแปร) และตัวชี้คือดาวตามด้วยรายการตัวระบุประเภทชนิดทางเลือกที่เป็นของตัวชี้

สิ่งที่ทำให้ข้อโต้แย้งรูปแบบต่างๆเกี่ยวกับ "ดาวเป็นของตัวแปร" ไม่สอดคล้องกันคือพวกเขาลืมเกี่ยวกับตัวระบุประเภทตัวชี้เหล่านี้ int* const x, int *const xหรือint*const x?

พิจารณาint *const a, b;, ประเภทของอะไรaและb? ไม่ชัดเจนว่า "ดาวเป็นของตัวแปร" อีกต่อไป ค่อนข้างจะเริ่มไตร่ตรองที่constเป็นของ

คุณสามารถสร้างข้อโต้แย้งเสียงที่แน่นอนว่าดาวนั้นเป็นของตัวระบุประเภทตัวชี้

รายการตัวระบุชนิดของตัวชี้อาจทำให้เกิดปัญหาสำหรับผู้ที่ใช้int *aสไตล์ ผู้ที่ใช้พอยน์เตอร์ภายในtypedef(ซึ่งเราไม่ควรเป็นแบบฝึกหัดที่แย่มาก!) และคิดว่า "ดาวนี้เป็นชื่อของตัวแปร" มักจะเขียนบั๊กที่บอบบางมากนี้:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

คอมไพล์นี้หมดจด ตอนนี้คุณอาจคิดว่ารหัสถูกต้องแล้ว ไม่เช่นนั้น! รหัสนี้เป็นความถูกต้องโดยไม่ตั้งใจของปลอม

ชนิดของfooเป็นจริงint*const*- ตัวชี้ภายนอกส่วนใหญ่สร้างขึ้นแบบอ่านอย่างเดียวไม่ใช่ชี้ไปที่ข้อมูล ดังนั้นในฟังก์ชั่นนี้เราสามารถทำได้**foo = n;และมันจะเปลี่ยนค่าตัวแปรในตัวเรียก

นี่เป็นเพราะในการแสดงออกconst bad_idea_t *fooที่*ไม่ได้อยู่ในชื่อตัวแปรที่นี่! ในรหัสหลอกประกาศพารามิเตอร์นี้คือการอ่านเป็นconst (bad_idea_t *) fooและไม่ได้(const bad_idea_t) *fooเป็น ดาวเป็นของประเภทตัวชี้ที่ซ่อนอยู่ในกรณีนี้ - ชนิดเป็นตัวชี้และตัวชี้ const *constที่มีคุณสมบัติเขียนเป็น

แต่แล้วรากเหง้าของปัญหาในตัวอย่างข้างต้นคือการฝึกฝนการซ่อนพอยน์เตอร์ไว้ด้านหลังtypedefและไม่ใช่*สไตล์


เกี่ยวกับการประกาศตัวแปรหลายตัวในบรรทัดเดียว

ประกาศตัวแปรหลายบนบรรทัดเดียวได้รับการยอมรับอย่างกว้างขวางว่าเป็นปฏิบัติไม่ดี1) CERT-C สรุปผลรวมเป็นอย่างดีเช่น:

DCL04-C อย่าประกาศมากกว่าหนึ่งตัวแปรต่อการประกาศ

เพียงแค่อ่านภาษาอังกฤษจากนั้นสามัญสำนึกยอมรับว่า การประกาศควรเป็นการประกาศอย่างใดอย่างหนึ่ง

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

ข้อโต้แย้งเกี่ยวกับโปรแกรมเมอร์ที่สับสนint* a, bคือไม่ดี รากของปัญหาคือการใช้ declarators *หลายที่ไม่ได้ตำแหน่งของ คุณควรเขียนสิ่งนี้แทน:

    int* a; // or int *a
    int b;

อีกเสียงหนึ่ง แต่เป็นการโต้แย้งแบบอัตนัย int* aประเภทของaนั้นไม่มีคำถามint*และดังนั้นดาวจึงอยู่ในประเภท qualifier

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


1) CERT-C DCL04-C


หากคุณอ่านบทความที่ฉันเชื่อมโยงมีส่วนสั้น ๆ ในหัวข้อของtypedef int *bad_idea_t; void func(const bad_idea_t bar); ตามที่นักพยากรณ์ผู้ยิ่งใหญ่ Dan Saks สอน "ถ้าคุณวางสิ่งconstที่ถูกต้องให้มากที่สุดเท่าที่จะทำได้โดยไม่ต้องเปลี่ยนความหมายเชิงความหมาย" จะเป็นปัญหา นอกจากนี้ยังทำให้constการประกาศของคุณสอดคล้องกับการอ่านมากขึ้น "ทุกอย่างทางด้านขวาของคำว่า const คือตัวที่ทุกตัวทางซ้ายคือประเภทของมัน" สิ่งนี้จะใช้กับ const ทั้งหมดในการประกาศ ลองใช้กับint const * * const x;
dgnuff

-1

พิจารณา

int *x = new int();

สิ่งนี้ไม่แปล

int *x;
*x = new int();

จริง ๆ แล้วแปลเป็น

int *x;
x = new int();

ทำให้int *xสัญกรณ์ค่อนข้างไม่สอดคล้องกัน


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