C ++中“ struct”和“ typedef struct”之间的区别?


Answers:


1200

在C ++中,只有细微的差别。这是C的遗留物,在其中有所作为。

C语言标准(C89§3.1.2.3C99§6.2.3C11§6.2.3)针对不同类别的标识符(包括标签标识符(用于struct/ union/ enum)和普通标识符(for typedef和其他标识符))规定了单独的命名空间。。

如果您只是说:

struct Foo { ... };
Foo x;

您将得到一个编译器错误,因为Foo它仅在标记名称空间中定义。

您必须将其声明为:

struct Foo x;

每当您要引用a时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名称空间中只有名称。这意味着它也不能被预先声明。 如果要进行前向声明,则必须在标签命名空间中为其命名


在C ++中,所有struct/ union/ enum/ class声明像他们是隐式typedef“版,只要名称不与另一个同名的声明所隐藏。有关完整的详细信息,请参见Michael Burr的答案


53
尽管您说的是正确的,但AFAIK语句“ typedef struct {...} Foo;” 为未命名的结构创建别名。
2009年

25
不错,“ typedef struct Foo {...} Foo;”之间有细微的区别。和“ typedef struct {...} Foo;”。
亚当·罗森菲尔德2009年

8
在C语言中,结构标签,联合标签和枚举标签共享一个名称空间,而不是使用上述两个名称空间(结构和联合);为typedef名称引用的名称空间确实是独立的。这意味着您不能同时拥有“ union x {...};” 和“ struct x {...};” 在一个范围内。
乔纳森·勒夫勒

10
除了not-quite-typedef之外,问题中的两段代码的另一个区别是Foo可以在第一个示例中定义构造函数,但不能在第二个示例中定义(因为匿名类无法定义构造函数或析构函数) 。
史蒂夫·杰索普

1
@Lazer:有一些细微的差异,但是Adam(继续说)意味着您可以使用'Type var'来声明没有typedef的变量。
弗雷德·纽克

226

DDJ的这篇文章中,Dan Saks解释了一个小区域,如果您不对结构(和类!)进行typedef定义,则漏洞可能会蔓延到下面:

如果需要,您可以想象C ++为每个标记名称生成一个typedef,例如

typedef class string string;

不幸的是,这并不完全准确。我希望它是如此简单,但事实并非如此。如果不引入与C的不兼容性,C ++就无法为结构,联合或枚举生成此类typedef。

例如,假设C程序同时声明了一个函数和一个名为status的结构:

int status(); struct status;

再次,这可能是不好的做法,但是它是C。在此程序中,状态(本身)是指功能;结构状态是指类型。

如果C ++确实为标签自动生成了typedef,那么当您将该程序编译为C ++时,编译器将生成:

typedef struct status status;

不幸的是,此类型名称将与函数名称冲突,并且程序将无法编译。这就是C ++不能简单地为每个标签生成typedef的原因。

在C ++中,标记的作用与typedef名称相同,不同之处在于程序可以使用与标记相同的名称和作用域声明一个对象,函数或枚举器。在这种情况下,对象,函数或枚举器名称将隐藏标签名称。该程序只能通过在标签名称前面使用关键字class,struct,union或enum(视情况而定)来引用标签名称。由这些关键字之一和标签组成的类型名称是精心设计的类型说明符。例如,结构状态和枚举月是详尽的类型说明符。

因此,一个包含以下两个方面的C程序:

int status(); struct status;

与C ++编译时的行为相同。名称状态仅指功能。该程序只能使用Elaborated-type-specifier结构状态来引用类型。

那么,这如何使错误爬到程序中呢?考虑清单1中的程序 。该程序使用默认构造函数定义foo类,并使用一个转换运算符将foo对象转换为char const *。表达方式

p = foo();

在main中应该构造一个foo对象并应用转换运算符。后续输出语句

cout << p << '\n';

应该显示foo类,但不显示。它显示函数foo。

由于程序包含清单2中所示的头文件lib.h,所以出现了令人惊讶的结果。此标头定义了一个也称为foo的函数。函数名称foo隐藏了类名称foo,因此main中对foo的引用是函数而不是类。main只能使用elaborated-type-specifier来引用该类,如

p = class foo();

在整个程序中避免这种混乱的方法是为类名foo添加以下typedef:

typedef class foo foo;

在类定义之前或之后。此typedef导致类型名称foo和函数名称foo(来自库)之间发生冲突,这将触发编译时错误。

当然,我不知道实际上有谁写过这些typedef。这需要很多纪律。由于清单1中的错误的发生率可能很小,因此许多人从未遇到过此问题。但是,如果软件中的错误可能导致人身伤害,则无论错误发生的可能性如何,都应编写typedef。

我无法想象为什么有人会在与类相同的作用域中隐藏带有函数或对象名称的类名称。C中的隐藏规则是一个错误,不应将其扩展到C ++中的类。确实,您可以纠正该错误,但是它需要不必要的额外编程纪律和精力。


11
万一您尝试使用“ class foo()”而失败:在ISO C ++中,“ class foo()”是非法构造(该文章写于'97,在标准化之前看来)。您可以输入“ typedef class foo foo;”。进入main,则可以说“ foo();” (因为那样,typedef-name在词法上比函数的名称更近)。语法上,在T()中,T必须是简单类型说明符。不允许使用详细的类型说明符。当然,这仍然是一个很好的答案。
Johannes Schaub-litb

Listing 1Listing 2链接断开。看一看。
Prasoon Saurav

3
如果对类和函数使用不同的命名约定,则还可以避免命名冲突,而无需添加额外的typedef。
zstewart

64

另一个重要的区别是:typedefs不能向前声明。因此,对于该typedef选项,您必须#include包含的文件typedef,这意味着#includes的所有内容.h还包括该文件,无论它是否直接需要它,等等。它肯定会影响大型项目的构建时间。

如果没有使用typedef,在某些情况下,您可以仅struct Foo;.h文件顶部添加前向声明,而在文件中仅添加#include结构定义.cpp


为什么公开结构的定义会影响构建时间?编译器在看到类似Foo * nextFoo之类的东西时是否进行了额外的检查,即使不需要(给出typedef选项,以便编译器知道其定义)
Rich

3
它并不是真正的额外检查,只是编译器需要处理的更多代码。对于在其包含链中某处遇到该typedef的每个cpp文件,都将编译typedef。在较大的项目中,包含typedef的.h文件很容易最终被编译数百次,尽管预编译的头文件有很大帮助。如果您可以使用前向声明,那么将包含完整结构规范的.h仅包含在真正需要的代码中会比较容易,因此相应的include文件的编译频率将降低。
2014年

请@以前的评论者(帖子的所有者除外)。我差点错过了您的回复。但感谢您的信息。
丰富

在C11中,情况不再如此,您可以在其中多次键入具有相同名称的相同结构。
michaelmeyer

32

这里一个区别,但微妙。这样看:struct Foo引入了一种新类型。第二个为未命名struct类型创建一个称为Foo的别名(而不是新类型)。

7.1.3 typedef说明符

1 [...]

使用typedef说明符声明的名称将变为typedef名称。在其声明范围内,typedef-name在语法上等同于关键字,并按照第8章中所述的方式命名与标识符关联的类型。typedef-name因此是另一种类型的同义词。typedef名称不会引入新类型类声明(9.1)或枚举声明那样。

8如果typedef声明定义了一个未命名的类(或枚举类型),则声明中声明为该类类型(或枚举类型)的第一个typedef名称仅用于表示链接目的的类类型(或枚举类型)( 3.5)。[示例:

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

因此,typedef 始终用作另一种类型的占位符/同义词。


10

您不能对typedef结构使用正向声明。

该结构本身是一个匿名类型,因此您没有实际名称要转发声明。

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它抱怨名称冲突。有一个向前的声明说:“寻找一个名为myStruct的结构”,然后有一个typedef将无名结构重命名为“ myStruct”。
Yochai Timmer

您的意思是将typedef和forward声明都放在同一个文件中?我做到了,而gcc编译得很好。myStruct被正确解释为无名结构。标签myStruct位于标签名称空间中,而typedef_ed myStruct位于普通名称空间中,其他标识符(如函数名称,局部变量名称)位于其中。因此,应该没有任何冲突。如果您怀疑其中是否有错误,可以向我展示我的代码。
Rich

@Rich GCC给出了相同的错误,文本略有
Yochai Timmer

我想我知道,当您只有一个typedef带有typedefed名称的正向声明时,不会引用未命名的结构。相反,前向声明使用tag声明了不完整的结构myStruct。同样,在没有看到的定义的情况下typedef,使用typedefed名称的函数原型也不合法。因此,每当需要myStruct用来表示类型时,就必须包括整个typedef 。如果我误解了你,请纠正我。谢谢。
Rich

0

C ++中的“ typedef struct”和“ struct”之间的重要区别是,“ 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,所以赋值确实发生了,比起零更明显)。
Colin D Bennett

实际上,我在Visual Studio 2013中对其进行了测试,并且尚未初始化。这是我们在生产代码中遇到的一个问题。所有编译器都不同,仅需满足某些条件。
user2796283 '16

-3

C ++没有什么区别,但是我相信C可以使您无需显式地声明struct Foo的实例:

struct Foo bar;

3
看@ dirkgently的答案---有有差别,但它是微妙的。
基思·品森

-3

结构是创建数据类型。typedef用于为数据类型设置昵称。


12
您不明白这个问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.