C++中的"struct"和"typedef struct"有什么区别?

959
在 C++ 中,下面两者是否有区别: ()
struct Foo { ... };

并且:

typedef struct { ... } Foo;

相关问题和答案:C++11中使用using和typedef的区别 - Joost
2
请参阅关于C语言的相关问题:https://dev59.com/sXVC5IYBdhLWcg3wjx_u - Raedwald
8个回答

1373
在C++中,只有微小的区别。这是从C语言遗留下来的,在其中它会有所区别。
C语言标准(C89 §3.1.2.3, C99 §6.2.3C11 §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命名空间中有名称。这意味着它也不能被前向声明。如果您想进行前向声明,必须在标签命名空间中给它一个名称。
在C++中,所有的struct/union/enum/class声明都像隐式地使用typedef一样,只要名称没有被另一个具有相同名称的声明隐藏。请参见Michael Burr的答案以获取完整的详细信息。

64
虽然你所说的是正确的,但据我所知,语句“typedef struct { ... } Foo;”创建了一个未命名结构体的别名。 - dirkgently
31
很好,"typedef struct Foo { ... } Foo;"和"typedef struct { ... } Foo;"之间有微妙的区别。 - Adam Rosenfield
10
在 C 语言中,结构体标签、联合体标签和枚举标签共享一个命名空间,而不是像上面所声称的那样(在结构体和联合体中)使用两个命名空间;用于 typedef 名称的命名空间确实是单独的。这意味着你不能在同一作用域内同时存在 'union x { ... };' 和 'struct x { ... };'。 - Jonathan Leffler
11
除了不完全是typedef的问题外,问题中两段代码之间的另一个区别是在第一个示例中Foo可以定义构造函数,但在第二个示例中不能(因为匿名类无法定义构造函数或析构函数)。 - Steve Jessop
1
阅读所有关于这个问题的帖子后,我有点困惑。您能在这里解答我的一些问题吗?我相信Ben是正确的,但我只是想在这里获得更多的输入。谢谢! - Rich
显示剩余12条评论

252

这篇DDJ文章中,Dan Saks解释了如果你不对你的结构体(和类!)进行typedef的话,bug可能会从哪些小地方悄悄蔓延:

如果你愿意,你可以想象C++为每个标签名称生成一个typedef,比如

typedef class string string;

不幸的是,这并不完全准确。我希望它能如此简单,但事实并非如此。C++不能为结构体、联合体或枚举生成这样的typedef,否则会引入与C不兼容的问题。

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

int status(); struct status;

再次说明,这可能是不好的编程实践,但它是C语言的。在该程序中,status(本身)指的是函数;结构体status则指的是类型。

如果C ++自动为标签生成typedefs,则在将此程序编译为C ++时,编译器将会生成:

typedef struct status status;

遗憾的是,这个类型名称会与函数名称冲突,程序将无法编译。这就是为什么C++不能仅仅为每个标签生成一个typedef。

在C++中,标签(tag)的作用与typedef名称相同,只是程序可以声明具有相同名称和范围的对象、函数或枚举器作为一个标签。在这种情况下,对象、函数或枚举器名称将隐藏标签名称。程序只能通过在标签名称前使用关键字class、struct、union或enum(视情况而定)来引用标签名称。由一个关键字加上一个标签组成的类型名称称为 elaborated-type-specifier。例如,struct status 和 enum month 都是 elaborated-type-specifiers。

因此,一个包含以下内容的C程序:

int status(); struct status;

使用C++编译后具有相同的行为。名称“status”仅指函数,程序只能使用限定符“struct status”来引用类型。

那么这如何允许程序中出现错误呢?考虑清单1中的程序。此程序定义了一个具有默认构造函数和转换运算符(将foo对象转换为char const *)的类foo。表达式

p = foo();

在主函数中应构造一个foo对象并使用转换运算符。随后输出语句。

cout << p << '\n';

应该显示类foo,但实际上它却显示了函数foo。

这个令人惊讶的结果是因为程序包含了头文件lib.h,该文件在清单2中展示。该头文件定义了一个也叫做foo的函数。函数名foo遮蔽了类名foo,因此main中对foo的引用指向函数而非类。要使用类名foo,main只能通过 elaborated-type-specifier 进行引用,如下:

p = class foo();

为了避免整个程序中出现这样的混淆, 可以为类名foo添加以下typedef:

typedef class foo foo;

在类的定义之前或之后立即使用typedef会导致类型名foo和库中的函数名foo发生冲突,从而触发编译时错误。

我不知道有谁会经常写这些typedef。这需要很多纪律性。由于清单1中出现此类错误的概率可能非常小,您可能永远不会遇到这个问题。但是,如果您的软件中出现的错误可能会导致身体伤害,那么您应该始终编写typedefs,无论错误的可能性有多小。

我无法想象为什么会有人想要将类名与同一范围内的函数或对象名称混淆。在C中隐藏规则是一个错误,它们不应该被扩展到C++中的类。实际上,您可以纠正这个错误,但这需要额外的编程纪律和努力,这是不必要的。


18
如果你尝试“class foo()”并失败了:在ISO C++中,“class foo()”是一种非法构造(该文章似乎是在标准化之前的'97年写的)。你可以在main函数中放置“typedef class foo foo;”,然后就可以使用“foo();”(因为此时typedef名称比函数名称更接近语法解析器)。在T()中,T必须是一个简单类型说明符。不允许使用完整类型说明符。当然,这仍然是一个很好的答案。 - Johannes Schaub - litb
Listing 1Listing 2的链接已经失效,请查看一下。 - Prasoon Saurav
7
如果您对类和函数使用不同的命名约定,则可以避免命名冲突,而无需添加额外的typedef。 - zstewart

71

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

如果没有typedef,在某些情况下,您只需在.h文件的顶部添加struct Foo;的前向声明,并且仅在.cpp文件中#include结构定义即可。


为什么暴露结构的定义会影响构建时间?即使使用typedef选项(以便编译器知道定义),当它看到__Foo* nextFoo;__这样的东西时,编译器是否会进行额外的检查,即使不需要? - Rich
4
这并不是额外的检查,而只是更多需要编译器处理的代码。每个遇到该typedef的cpp文件都会编译它。在较大的项目中,包含typedef的.h文件可能会被编译数百次,尽管预编译头文件有很大帮助。如果您可以使用前向声明,那么更容易限制只在真正需要的代码中包含完整结构体规范的.h文件,因此相应的包含文件会被编译得更少。 - Joe
请@之前的评论者(不包括帖子所有者):我差点错过了你的回复。但是感谢你提供的信息。 - Rich
3
在C11中,你可以使用相同的名称多次typedef同一个结构体,这与以前不同。 - michaelmeyer
@Joe:历史上的另一个问题是,当使用struct tagName语法时,可以有两个头文件,每个文件都包含一个接受在另一个文件中定义的结构体指针的函数,同时它们之间只有一个结构体定义。 - supercat

35

两者之间有一点微妙的区别。可以这样看待:struct Foo引入了一个新类型。而第二个则为未命名的struct类型创建了一个别名Foo(并不是一个新类型)。

7.1.3 typedef关键字

1[...]

使用typedef关键字声明的名称成为typedef名称。在其声明的作用域内,typedef名称在语法上等同于关键字,并以第8条所述的方式命名与标识符相关联的类型。因此,typedef名称是另一种类型的同义词。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
@Rich 它在抱怨名称冲突。有一个前向声明说“寻找名为myStruct的结构体”,然后有一个typedef,将一个无名结构体重命名为“myStruct”。 - Yochai Timmer
你的意思是在同一个文件中放置typedef和前向声明吗?我这样做了,gcc编译通过了。myStruct被正确解释为无名结构体。标签myStruct位于标签命名空间中,而typedef_ed myStruct位于普通命名空间中,其他标识符如函数名、局部变量名也位于此处。因此不应该有任何冲突...如果你怀疑其中存在错误,我可以向你展示我的代码。 - Rich
@Rich GCC 给出了相同的错误,文本略有不同:http://gcc.godbolt.org/#%7B%22version%22%3A3%2C%22filterAsm%22%3A%7B%22labels%22%3Atrue%2C%22directives%22%3Atrue%2C%22commentOnly%22%3Atrue%7D%2C%22compilers%22%3A%5B%7B%22sourcez%22%3A%22FAFwngDgpgJlBmACAziATgVwMYkQb2ESMQEsA7XAezKgG5DjzcQB3S%2BgXwFswBldbCHrBUmHIh78xQ4MCYSAhuQAUASmAFiEvgPEL6xBojRQQGNGUQAGTkAA%22%2C%22compiler%22%3A%22%2Fusr%2Fbin%2Fclang%2B%2B%22%2C%22options%22%3A%22-O2%22%7D%5D%7D - Yochai Timmer
我认为我明白了,当你只有一个typedef时,使用typedef名称的前向声明并不是指未命名结构。相反,前向声明声明了一个具有标签myStruct的不完整结构。此外,在没有看到typedef定义的情况下,使用typedef名称的函数原型是不合法的。因此,每当我们需要使用myStruct表示类型时,我们必须包含整个typedef。如果我理解有误,请纠正我。谢谢。 - Rich
我认为你的代码是使用g++编译器而不是gcc编译器编译的。在C++中,这绝对是不允许的。因为在C++中,结构体名称会被隐式地typedef。但如果你尝试使用gcc编译器,它可以正常编译。 - Rich

3
在C ++中,“typedef struct”和“struct”的一个重要区别是,在“typedef struct”中的成员初始化不能使用内联方式。
// 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; };

4
不正确。在这两种情况下,都对 x 进行了初始化。请参见 Coliru 网上 IDE 上的测试(我将其初始化为 42,以便更明显地看出赋值确实已经发生)。 - Colin D Bennett
1
实际上,我在Visual Studio 2013中对其进行了测试,但它未被初始化。这是我们在生产代码中遇到的问题。所有编译器都不同,并且只需要满足特定的标准。 - user2796283

-3

在C++中没有区别,但我相信在C中它会允许您声明结构体Foo的实例而不需要显式地执行:

struct Foo bar;

5
看一下@dirkgently的回答——确实有区别,但是很微妙。 - Keith Pinson

-6

结构体是用来创建数据类型的。 typedef 是为一个数据类型设置一个别名。


21
你没有理解这个问题。 - Winter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接