ChatGPT解决这个技术问题 Extra ChatGPT

C++中'struct'和'typedef struct'的区别?

在 C++ 中,两者之间有什么区别:

struct Foo { ... };

和:

typedef struct { ... } Foo;

Y
YePhIcK

在 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(在普通标识符命名空间中)都指的是同一个东西,您可以在没有 struct 关键字的情况下自由声明 Foo 类型的对象。

构造:

typedef struct Foo { ... } Foo;

只是声明和typedef的缩写。

最后,

typedef struct { ... } Foo;

声明一个匿名结构并为其创建一个 typedef。因此,使用此构造,它在标记命名空间中没有名称,只有 typedef 命名空间中的名称。这意味着它也不能被前向声明。 如果要进行前向声明,则必须在标签命名空间中为其命名

在 C++ 中,所有 struct/union/enum/class 声明的行为就像它们是隐式 typedef'ed,只要名称没有被另一个同名声明隐藏。有关完整详细信息,请参阅 Michael Burr's answer


虽然你说的是真的,AFAIK,声明,'typedef struct { ... } Foo;'为未命名的结构创建别名。
很好,“typedef struct Foo { ... } Foo;”之间存在细微差别和“typedef struct { ... } Foo;”。
在 C 中,struct 标签、union 标签和枚举标签共享一个命名空间,而不是像上面声称的那样(struct 和 union)使用两个; typedef 名称引用的命名空间确实是独立的。这意味着你不能同时拥有'union x { ... };'和'结构 x { ... };'在单个范围内。
除了 not-quite-typedef 事情之外,问题中两段代码之间的另一个区别是 Foo 可以在第一个示例中定义构造函数,但不能在第二个示例中定义(因为匿名类不能定义构造函数或析构函数) .
@Lazer:存在细微差别,但亚当的意思是(正如他继续说的那样)您可以使用“Type var”来声明没有 typedef 的变量。
M
Michael Burr

this DDJ article 中,Dan Saks 解释了一个小区域,如果您不对结构(和类!)进行类型定义,错误可能会蔓延:

如果你愿意,你可以想象C++为每个标签名生成一个typedef,比如typedef class string string;不幸的是,这并不完全准确。我希望它是那么简单,但事实并非如此。如果不引入与 C 的不兼容性,C++ 无法为结构、联合或枚举生成此类类型定义。例如,假设 C 程序同时声明了一个函数和一个名为 status 的结构: int status();结构状态;同样,这可能是不好的做法,但它是 C。在这个程序中,状态(本身)指的是函数;结构状态是指类型。如果 C++ 确实为标签自动生成 typedef,那么当您将此程序编译为 C++ 时,编译器会生成: typedef struct status status;不幸的是,这个类型名会与函数名冲突,程序无法编译。这就是为什么 C++ 不能简单地为每个标签生成一个 typedef。在 C++ 中,标签的作用与 typedef 名称类似,只是程序可以声明与标签具有相同名称和相同范围的对象、函数或枚举数。在这种情况下,对象、函数或枚举器名称会隐藏标签名称。程序只能通过在标签名称前使用关键字 class、struct、union 或 enum(视情况而定)来引用标签名称。由这些关键字之一和标签组成的类型名称是详细类型说明符。例如,struct status 和 enum month 是详细的类型说明符。因此,一个包含以下两者的 C 程序: int status();结构状态;编译为 C++ 时的行为相同。名称状态仅指功能。程序只能通过使用详细类型说明符结构状态来引用类型。那么这如何让错误潜入程序中呢?考虑清单 1 中的程序。该程序定义了一个具有默认构造函数的类 foo,以及一个将 foo 对象转换为 char const * 的转换运算符。表达式 p = foo(); in main 应该构造一个 foo 对象并应用转换运算符。随后的输出语句 cout << p << '\n';应该显示类 foo,但它没有。它显示函数 foo。之所以会出现这个令人惊讶的结果,是因为该程序包含清单 2 中所示的头文件 lib.h。该头文件定义了一个也名为 foo 的函数。函数名 foo 隐藏了类名 foo,所以 main 中对 foo 的引用指的是函数,而不是类。 main 只能通过使用详细类型说明符来引用该类,如 p = class foo();在整个程序中避免这种混淆的方法是为类名 foo 添加以下 typedef: typedef class foo foo;紧接在类定义之前或之后。此 typedef 导致类型名称 foo 和函数名称 foo (来自库)之间的冲突,这将触发编译时错误。我知道没有人会理所当然地实际编写这些 typedef。它需要大量的纪律。由于诸如清单 1 中的错误的发生率可能非常小,因此很多人都不会遇到这个问题。但是,如果您的软件中的错误可能会导致人身伤害,那么无论错误多么不可能,您都应该编写 typedef。我无法想象为什么有人会想要在与类相同的范围内隐藏具有函数或对象名称的类名称。 C 中的隐藏规则是一个错误,它们不应该扩展到 C++ 中的类。确实,您可以纠正错误,但它需要额外的编程纪律和不必要的努力。


如果您尝试“class foo()”但它失败了:在 ISO C++ 中,“class foo()”是一个非法构造(这篇文章是 97 年写的,似乎在标准化之前)。你可以把“typedef class foo foo;”进入 main,然后你可以说“foo();” (因为那时,typedef-name 在词法上比函数名更接近)。在语法上,在 T() 中,T 必须是简单类型说明符。不允许使用详细的类型说明符。当然,这仍然是一个很好的答案。
Listing 1Listing 2 链接已损坏。看一看。
如果对类和函数使用不同的命名约定,也可以避免命名冲突,而无需添加额外的 typedef。
M
Michael Burr

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

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


为什么暴露 struct 的定义会影响构建时间?当编译器看到类似 Foo* nextFoo; 之类的内容时,编译器是否会进行额外检查,即使它不需要(给定 typedef 选项,以便编译器知道定义)?
它并不是真正的额外检查,它只是编译器需要处理的更多代码。对于在其包含链中某处遇到该 typedef 的每个 cpp 文件,它都会编译 typedef。在较大的项目中,包含 typedef 的 .h 文件很容易最终被编译数百次,尽管预编译的头文件有很大帮助。如果您可以使用前向声明,那么将包含完整结构规范的 .h 限制为仅包含真正关心的代码会更容易,因此相应的包含文件的编译频率会降低。
请@之前的评论者(帖子的所有者除外)。我几乎错过了你的回复。但感谢您的信息。
这在 C11 中不再适用,您可以在其中多次键入具有相同名称的相同结构。
@Joe:历史上的另一个问题是,当使用 struct tagName 语法时,可以有两个头文件,每个头文件都包含一个函数,该函数接受指向另一个定义的结构的指针,同时在它们之间对每个结构都有一个定义。
d
dirkgently

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

7.1.3 typedef 说明符 1 [...] 使用 typedef 说明符声明的名称成为 typedef-name。在其声明的范围内,typedef-name 在语法上等价于关键字,并以第 8 节中描述的方式命名与标识符关联的类型。因此 typedef-name 是另一种类型的同义词。 typedef-name 不会像类声明 (9.1) 或枚举声明那样引入新类型。 8 如果 typedef 声明定义了一个未命名的类(或枚举),则声明为该类类型(或枚举类型)的第一个 typedef 名称仅用于表示类类型(或枚举类型),仅用于链接目的3.5)。 [ 例子:

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

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


Y
Yochai Timmer

您不能将前向声明与 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”。
你的意思是把 typedef 和 forward 声明放在同一个文件中?我做到了,gcc 编译得很好。 myStruct 被正确解释为无名结构。标记 myStruct 位于标记命名空间中,而 typedef_ed myStruct 位于普通命名空间中,其他标识符(如函数名称、局部变量名称)位于其中。所以不应该有任何冲突。如果您对此有疑问,我可以向您展示我的代码是否有任何错误。
@Rich GCC 给出了同样的错误,文本略有不同:gcc.godbolt.org/…
我想我理解当您只有 typedef 时,带有 typedefed 名称的前向声明并不引用未命名的结构。相反,前向声明声明了一个带有标签 myStruct 的不完整结构。此外,如果没有看到 typedef 的定义,使用 typedefed 名称的函数原型是不合法的。因此,当我们需要使用 myStruct 来表示一个类型时,我们必须包含整个 typedef。如果我误解了你,请纠正我。谢谢。
u
user2796283

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

不正确。在这两种情况下,x 都已初始化。 请参阅 test in the Coliru online IDE(我将其初始化为 42,因此它比使用 0 更明显地表明分配确实发生了)。
实际上,我在 Visual Studio 2013 中测试过它并没有初始化它。这是我们在生产代码中遇到的问题。所有编译器都是不同的,只需要满足某些标准。
x
xian

C++ 没有区别,但我相信在 C 中,它允许您声明 struct Foo 的实例而无需显式执行:

struct Foo bar;

看看@dirkgently 的回答——有区别,但很微妙。
D
David Tu

struct 是创建一个数据类型。 typedef 是为一个数据类型设置一个昵称。


你没看懂这个问题。