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

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

答案

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

C 语言标准( C89§3.1.2.3C99§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 的答案

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 ++ 中的类。确实,您可以纠正该错误,但是它需要额外的编程纪律和不必要的工作。

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

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

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

7.1.3 typedef 说明符

1 [...]

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

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

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

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

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

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

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

这样的前向声明不起作用:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

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

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; };

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

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

struct Foo bar;