ความแตกต่างระหว่าง 'struct' และ 'typedef struct' ใน C ++?


834

ในC ++มีความแตกต่างระหว่าง:

struct Foo { ... };

และ:

typedef struct { ... } Foo;

คำถามและคำตอบที่เกี่ยวข้อง: ความแตกต่างระหว่างการใช้และ typedef ใน C ++ 11
Joost

คำตอบ:


1202

ใน C ++ มีความแตกต่างเพียงเล็กน้อยเท่านั้น มันเป็นสิ่งที่หลงเหลือจาก C ซึ่งมันสร้างความแตกต่าง

มาตรฐานภาษา C ( C89 .13.1.2.3 , C99 §6.2.3และC11 §6.2.3 ) กำหนดหน้าที่แยกชื่อสำหรับประเภทตัวระบุต่าง ๆ รวมถึงตัวระบุแท็ก (สำหรับstruct/ union/ enum) และตัวระบุสามัญ (สำหรับtypedefและตัวระบุอื่น ๆ ) .

หากคุณเพิ่งพูดว่า:

struct Foo { ... };
Foo x;

คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์เนื่องจากFooกำหนดไว้ในแท็กเนมสเปซเท่านั้น

คุณต้องประกาศว่า:

struct Foo x;

เมื่อใดก็ตามที่คุณต้องการที่จะหมายถึงคุณจะต้องเรียกมันว่าFoo struct Fooสิ่งนี้จะน่ารำคาญอย่างรวดเร็วดังนั้นคุณสามารถเพิ่มtypedef:

struct Foo { ... };
typedef struct Foo Foo;

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


โครงสร้าง:

typedef struct Foo { ... } Foo;

typedefเป็นเพียงย่อสำหรับการประกาศและ


สุดท้าย

typedef struct { ... } Foo;

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


ใน C ++ การประกาศทั้งหมดstruct/ union/ enum/ classทำหน้าที่เหมือนเป็นการบอกนัยโดยปริยายtypedefตราบใดที่ชื่อไม่ถูกซ่อนโดยการประกาศอื่นที่มีชื่อเหมือนกัน ดูคำตอบของ Michael Burrสำหรับรายละเอียดทั้งหมด


53
ในขณะที่สิ่งที่คุณพูดนั้นเป็นความจริง AFAIK ข้อความ 'typedef struct {... } Foo;' สร้างนามแฝงสำหรับโครงสร้างที่ไม่มีชื่อ
dirkgently

25
จับดีมีความแตกต่างเล็กน้อยระหว่าง "typedef struct Foo {... } Foo;" และ "typedef struct {... } Foo;"
Adam Rosenfield

8
ใน C แท็ก struct แท็กรวมและแท็กการแจงนับแบ่งปันเนมสเปซหนึ่งแทนที่จะใช้ เนมสเปซที่อ้างอิงสำหรับชื่อ typedef นั้นแยกออกจากกันจริงๆ นั่นหมายความว่าคุณไม่สามารถมีทั้ง 'union x {... };' และ 'struct x {... };' ในขอบเขตเดียว
Jonathan Leffler

10
นอกเหนือจากสิ่งที่ไม่ค่อยพิมพ์ความแตกต่างระหว่างรหัสสองชิ้นในคำถามคือฟูสามารถกำหนดตัวสร้างในตัวอย่างแรก แต่ไม่ใช่ในสอง (เนื่องจากคลาสนิรนามไม่สามารถกำหนดตัวสร้างหรือ destructors) .
Steve Jessop

1
@Lazer: มีมีความแตกต่าง แต่หมายถึงอดัม (ในขณะที่เขากล่าวต่อไป) ที่คุณสามารถใช้ 'ประเภท var' ประกาศตัวแปรโดยไม่ต้อง typedef
Fred Nurk

226

ในบทความ DDJ นี้ Dan Saks อธิบายพื้นที่เล็ก ๆ แห่งหนึ่งที่ซึ่งแมลงสามารถคืบคลานผ่านไปได้หากคุณไม่พิมพ์ structs ของคุณ (และคลาส!):

หากคุณต้องการคุณสามารถจินตนาการได้ว่า C ++ สร้าง typedef สำหรับทุกชื่อแท็กเช่น

typedef class string string;

น่าเสียดายที่นี่ไม่ถูกต้องทั้งหมด ฉันหวังว่ามันจะง่าย แต่ก็ไม่ใช่ C ++ ไม่สามารถสร้าง typedefs ดังกล่าวสำหรับ struct, unions หรือ enums โดยไม่ต้องแนะนำความไม่ลงรอยกันกับ C

ตัวอย่างเช่นสมมติว่าโปรแกรม C ประกาศทั้งฟังก์ชั่นและโครงสร้างชื่อสถานะ:

int status(); struct status;

อีกครั้งนี่อาจเป็นการฝึกฝนที่ไม่ดี แต่มันคือ C ในโปรแกรมนี้สถานะ (โดยตัวมันเอง) หมายถึงฟังก์ชัน สถานะ struct หมายถึงประเภท

หาก C ++ สร้าง typedefs โดยอัตโนมัติสำหรับแท็กดังนั้นเมื่อคุณคอมไพล์โปรแกรมนี้เป็น C ++ คอมไพเลอร์จะสร้าง:

typedef struct status status;

น่าเสียดายที่ชื่อประเภทนี้ขัดแย้งกับชื่อฟังก์ชันและโปรแกรมจะไม่คอมไพล์ นั่นเป็นเหตุผลที่ C ++ ไม่สามารถสร้าง typedef สำหรับแต่ละแท็กได้

ใน C ++ แท็กทำหน้าที่เหมือนกับชื่อ typedef ยกเว้นว่าโปรแกรมสามารถประกาศวัตถุฟังก์ชันหรือตัวแจงนับที่มีชื่อเดียวกันและขอบเขตเดียวกับแท็ก ในกรณีนั้นวัตถุฟังก์ชันหรือชื่อตัวระบุจะซ่อนชื่อแท็ก โปรแกรมสามารถอ้างถึงชื่อแท็กโดยใช้คลาสคีย์เวิร์ด, struct, union หรือ enum (ตามความเหมาะสม) หน้าชื่อแท็ก ชื่อประเภทประกอบด้วยหนึ่งในคำหลักเหล่านี้ตามด้วยแท็กเป็นตัวระบุชนิดที่ซับซ้อน ตัวอย่างเช่นสถานะโครงสร้างและเดือน enum เป็นตัวระบุชนิดที่ซับซ้อน

ดังนั้นโปรแกรม C ที่มีทั้ง:

int status(); struct status;

ทำงานเหมือนกันเมื่อคอมไพล์เป็น C ++ สถานะชื่อคนเดียวหมายถึงฟังก์ชั่น โปรแกรมสามารถอ้างถึงชนิดเท่านั้นโดยใช้สถานะโครงสร้าง elaborated-type-specifier

ดังนั้นวิธีนี้จะช่วยให้ข้อบกพร่องเพื่อคืบเข้าไปในโปรแกรม? พิจารณาโปรแกรมใน รายชื่อ 1 โปรแกรมนี้กำหนด class foo ด้วยตัวสร้างค่าเริ่มต้นและตัวดำเนินการแปลงที่แปลงวัตถุ foo เป็น char const * การแสดงออก

p = foo();

ในหลักควรสร้างวัตถุ foo และใช้ตัวดำเนินการแปลง คำสั่งเอาต์พุตที่ตามมา

cout << p << '\n';

ควรแสดงคลาส foo แต่ไม่แสดง มันแสดงฟังก์ชั่น foo

นี้ผลที่น่าแปลกใจเกิดขึ้นเนื่องจากโปรแกรมรวมถึงส่วนหัว lib.h แสดงในรายชื่อ 2 ส่วนหัวนี้กำหนดฟังก์ชั่นชื่อฟู ชื่อฟังก์ชัน foo ซ่อนชื่อคลาส foo ดังนั้นการอ้างอิงถึง foo ใน main หมายถึงฟังก์ชันไม่ใช่คลาส main สามารถอ้างถึงคลาสเท่านั้นโดยใช้ตัวระบุชนิดที่ซับซ้อนเช่นเดียวกับใน

p = class foo();

วิธีที่จะหลีกเลี่ยงความสับสนดังกล่าวตลอดทั้งโปรแกรมคือการเพิ่ม typedef ต่อไปนี้สำหรับชื่อคลาส foo:

typedef class foo foo;

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

ฉันรู้ว่าไม่มีใครที่จริง ๆ แล้วพิมพ์ typedefs แน่นอน มันต้องมีวินัยมาก เนื่องจากอุบัติการณ์ของข้อผิดพลาดเช่นรายการ 1อาจมีขนาดค่อนข้างเล็กคุณหลายคนไม่เคยประสบปัญหานี้ แต่หากมีข้อผิดพลาดในซอฟต์แวร์ของคุณอาจทำให้เกิดการบาดเจ็บทางร่างกายคุณควรเขียน typedefs ไม่ว่าข้อผิดพลาดจะเกิดขึ้นได้อย่างไร

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


11
ในกรณีที่คุณลอง "class foo ()" และล้มเหลว: ใน ISO C ++ "class foo ()" เป็นโครงสร้างที่ผิดกฎหมาย (บทความเขียนขึ้นในปี 97 ก่อนที่จะมีมาตรฐานดูเหมือนว่า) คุณสามารถใส่ "typedef class foo foo;" เป็น main แล้วคุณสามารถพูดว่า "foo ();" (เพราะตอนนั้นชื่อ typedef นั้นจะอยู่ใกล้กับชื่อของฟังก์ชันมากกว่า) วากยสัมพันธ์ใน T (), T ต้องเป็นตัวระบุแบบง่าย ไม่อนุญาตตัวระบุประเภทที่ซับซ้อน ยังคงเป็นคำตอบที่ดีแน่นอน
Johannes Schaub - litb

Listing 1และListing 2ลิงก์ต่าง ๆ ก็พัง ได้ดู
Prasoon Saurav

3
หากคุณใช้หลักการตั้งชื่อที่แตกต่างกันสำหรับคลาสและฟังก์ชั่นคุณยังหลีกเลี่ยงข้อขัดแย้งในการตั้งชื่อโดยไม่จำเป็นต้องเพิ่ม typedefs พิเศษ
zstewart

64

ความแตกต่างที่สำคัญอีกประการหนึ่ง: typedefไม่สามารถประกาศล่วงหน้าได้ ดังนั้นสำหรับtypedefตัวเลือกที่คุณต้อง#includeมีไฟล์ซึ่งtypedefหมายถึงทุกอย่างที่#includeคุณ.hมีรวมถึงไฟล์นั้นไม่ว่ามันจะต้องการโดยตรงหรือไม่และอื่น ๆ มันสามารถส่งผลกระทบต่อเวลาสร้างของคุณในโครงการขนาดใหญ่ได้อย่างแน่นอน

หากไม่มีtypedefในบางกรณีคุณสามารถเพิ่มการประกาศไปข้างหน้าของstruct Foo;ที่ด้านบนของ.hไฟล์ของคุณและเพียง#includeนิยาม struct ใน.cppไฟล์ของคุณ


เหตุใดการเปิดเผยคำจำกัดความของ struct จึงส่งผลต่อเวลาสร้าง คอมไพเลอร์ทำการตรวจสอบพิเศษหรือไม่แม้ว่าจะไม่ต้องการ (ให้ตัวเลือก typedef เพื่อให้คอมไพเลอร์รู้ถึงคำจำกัดความ) เมื่อเห็นสิ่งที่คล้ายกับFoo * nextFoo; ?
Rich

3
มันไม่ได้ตรวจสอบพิเศษจริงๆมันเป็นโค้ดที่คอมไพเลอร์ต้องจัดการด้วย สำหรับไฟล์ cpp ทุกไฟล์ที่พบที่ typedef ที่ใดที่หนึ่งในเชนของมันการคอมไพล์ของ typedef ในโครงการขนาดใหญ่ไฟล์. h ที่มี typedef นั้นสามารถทำการคอมไพล์ได้หลายร้อยครั้งแม้ว่าส่วนหัวที่คอมไพล์แล้วจะช่วยได้มาก หากคุณสามารถหลีกเลี่ยงการใช้การประกาศไปข้างหน้าได้ง่ายกว่าที่จะ จำกัด การรวมของ. h ที่มีข้อมูลจำเพาะของโครงสร้างเต็มรูปแบบเพียงรหัสที่ใส่ใจจริงๆเท่านั้นและไฟล์รวมที่สอดคล้องกันจะถูกรวบรวมน้อยกว่า
Joe

โปรด @ ผู้แสดงความคิดเห็นก่อนหน้า (นอกเหนือจากเจ้าของโพสต์) ฉันเกือบพลาดคำตอบ แต่ขอบคุณสำหรับข้อมูล
Rich

สิ่งนี้ไม่เป็นความจริงอีกต่อไปใน C11 ซึ่งคุณสามารถพิมพ์ struct แบบเดียวกันด้วยชื่อเดียวกันหลาย ๆ ครั้งได้
michaelmeyer

32

มีคือความแตกต่าง แต่ลึกซึ้ง ดูด้วยวิธีนี้: struct Fooแนะนำประเภทใหม่ อันที่สองสร้างนามแฝงที่เรียกว่า Foo (ไม่ใช่ประเภทใหม่) สำหรับstructประเภทที่ไม่มีชื่อ

7.1.3 ตัวระบุ typedef

1 [... ]

ชื่อที่ประกาศพร้อมตัวระบุ typedef จะกลายเป็นชื่อ typedef ภายในขอบเขตของการประกาศ typedef-name จะเทียบเท่ากับคำหลักและชื่อประเภทที่เกี่ยวข้องกับตัวระบุในวิธีที่อธิบายไว้ในข้อ 8 typedef ชื่อจึงเป็นคำพ้องสำหรับประเภทอื่น ชื่อ typedef ไม่แนะนำชนิดใหม่ในลักษณะที่การประกาศคลาส (9.1) หรือการประกาศ enum

8 หากการประกาศ typedef กำหนดคลาสที่ไม่มีชื่อ (หรือ enum) ชื่อ typedef- แรกที่ประกาศโดยการประกาศว่าเป็นประเภทคลาส (หรือประเภท enum) จะใช้เพื่อแสดงประเภทคลาส (หรือประเภท enum) เพื่อจุดประสงค์ในการเชื่อมโยงเท่านั้น ( 3.5) [ตัวอย่าง:

typedef struct { } *ps, S; // S is the class name for linkage purposes

ดังนั้น typedef จะถูกใช้เป็นตัวยึด / คำพ้องสำหรับประเภทอื่นเสมอ


10

คุณไม่สามารถใช้การประกาศไปข้างหน้ากับโครงสร้าง typedef

struct เองเป็นชนิดที่ไม่ระบุชื่อดังนั้นคุณไม่มีชื่อจริงที่จะส่งต่อประกาศ

typedef struct{
    int one;
    int two;
}myStruct;

ประกาศไปข้างหน้าเช่นนี้จะไม่ทำงาน:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

ฉันไม่ได้รับข้อผิดพลาดที่สองสำหรับต้นแบบฟังก์ชัน ทำไมมันถึงพูดว่า "นิยามใหม่; ประเภทพื้นฐานที่แตกต่างกัน"? คอมไพเลอร์ไม่จำเป็นต้องรู้ว่านิยามของ myStruct เป็นอย่างไร ไม่ว่าจะนำมาจากด้วยส่วนของรหัส (หนึ่ง typedef หรือประกาศไปข้างหน้าหนึ่ง), myStruct หมายถึงประเภทของโครงสร้างใช่มั้ย?
Rich

@ ที่อุดมไปด้วยบ่นว่ามีความขัดแย้งของชื่อ มีการประกาศไปข้างหน้าว่า "มองหา struct ที่เรียกว่า myStruct" จากนั้นก็มี typedef ซึ่งเปลี่ยนชื่อโครงสร้างแบบไม่มีชื่อเป็น "myStruct"
Yochai Timmer

คุณหมายถึงการใส่ทั้ง typedef และการประกาศไปข้างหน้าในไฟล์เดียวกัน? ฉันทำแล้วและ gcc ก็คอมไพล์มันดี myStruct ถูกตีความอย่างถูกต้องเป็นโครงสร้างนิรนาม แท็กmyStructอาศัยอยู่ในแท็กเนมสเปซและ typedef_ed myStructอาศัยอยู่ในเนมสเปซปกติที่ตัวระบุอื่น ๆ เช่นชื่อฟังก์ชั่นชื่อตัวแปรท้องถิ่นอาศัยอยู่ดังนั้นจึงไม่ควรมีข้อขัดแย้งใด ๆ .. ฉันสามารถแสดงรหัสของฉันถ้าคุณสงสัยว่ามีข้อผิดพลาด
Rich

@Rich GCC ให้ข้อผิดพลาดเหมือนกันข้อความจะแตกต่างกันเล็กน้อย: gcc.godbolt.org/…
Yochai Timmer

ฉันคิดว่าฉันเข้าใจว่าเมื่อคุณมีเพียงการtypedefประกาศไปข้างหน้าด้วยtypedefชื่อ ed ไม่ได้อ้างถึงโครงสร้างที่ไม่มีชื่อ myStructแทนการประกาศไปข้างหน้าประกาศโครงสร้างที่ไม่สมบูรณ์ที่มีแท็ก นอกจากนี้หากไม่เห็นคำจำกัดความของtypedefต้นแบบฟังก์ชั่นที่ใช้typedefชื่อ ed ไม่ถูกกฎหมาย ดังนั้นเราต้องรวม typedef ทั้งหมดเมื่อใดก็ตามที่เราจำเป็นต้องใช้myStructเพื่อแสดงประเภท ถูกต้องฉันถ้าฉันเข้าใจผิดคุณ ขอบคุณ
Rich

0

ความแตกต่างที่สำคัญระหว่าง 'typedef struct' และ 'struct' ใน C ++ คือการเริ่มต้นสมาชิกแบบอินไลน์ใน 'typedef structs' จะไม่ทำงาน

// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;

// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };

3
ไม่จริง. ในทั้งสองกรณีxมีการเริ่มต้น ดูการทดสอบใน Coliru ออนไลน์ IDE (ฉันเริ่มต้นได้ที่ 42 ดังนั้นจึงเห็นได้ชัดกว่าศูนย์ที่มีการมอบหมายจริง ๆ )
โคลินดีเบนเน็ตต์

ที่จริงแล้วฉันทดสอบใน Visual Studio 2013 และไม่ได้เริ่มต้น นี่เป็นปัญหาที่เราเจอในรหัสการผลิต คอมไพเลอร์ทั้งหมดนั้นแตกต่างกันและจะต้องผ่านเกณฑ์บางอย่างเท่านั้น
user2796283

-3

ไม่มีความแตกต่างใน C ++ แต่ฉันเชื่อใน C มันจะช่วยให้คุณสามารถประกาศอินสแตนซ์ของ struct Foo โดยไม่ต้องทำอย่างชัดเจน:

struct Foo bar;

3
@ ดูที่คำตอบของ dirkgently --- มีคือความแตกต่าง แต่มันลึกซึ้ง
Keith Pinson

-3

โครงสร้างคือการสร้างชนิดข้อมูล typedef คือการตั้งชื่อเล่นสำหรับประเภทข้อมูล


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