在C语言和C++中typedef struct的区别

21

这在C++中会报错,但在C中不会:

typedef struct nodes
{
    int data;
    struct node *next;
}node;

在C++中,它会出现以下错误。

/home/DS cpp/linkedlist.cpp|10|error: conflicting declaration ‘typedef struct nodes node’|
/home/DS cpp/linkedlist.cpp|9|error: ‘struct node’ has a previous declaration as ‘struct node’|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

为了在C++中使其正常工作,我必须将其更改为:

typedef struct node
{
    int data;
    struct node *next;
}node;

我不明白为什么会发生这种情况,我想知道在C和C++中执行的顺序,以便我能够理解它。


13
你知道在C++中不需要使用typedef吗?“让它起作用”的正确方法是struct node { int data; node* next;};。请注意,翻译并未更改原意,而只是使其更加简洁易懂。 - juanchopanza
7
“执行顺序”与任何事情有什么关系? - Lightness Races in Orbit
3
@alk,在C语言中是有效的,struct node*声明了一个新类型,与struct nodes不同。 - Jonathan Wakely
@MikeHousky:指针已知,只是不知道它所指的内容,除此之外它是一个“struct”对象。该指针可读可写。 - alk
1
@MikeHousky:允许声明不完整类型的指针有点必要。 - Lightness Races in Orbit
显示剩余3条评论
2个回答

27

让我们分析一下你的代码:

typedef struct nodes
{
    int data;
    struct node *next;
}node;

这段代码声明并定义了一个名为nodes的结构体类型,它有两个成员,并声明了一个类型别名node以便我们只需使用该别名即可引用它。
在C++中,成员声明struct node *next自动前向声明了一个名为node的类型。这将与你的typedef目标node产生冲突:就好像你试图给两个类型赋予相同的名称一样。
在C中,没有冲突,因为名为node的类型实际上只能被称为struct node
第二个代码片段有效是因为,在解析成员声明struct node时已经存在了,所以不会有新的类型前向声明……而且由于你只是在同一个typedef语句中重命名它,C++并不真正关心,知道它们都是同一种类型(struct T T;区别在于语法,而不是名称)。

[C++11: 7.1.3/3]: In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers. [ Example:

typedef struct s { / ... / } s;
typedef int I;
typedef int I;
typedef I I;

—end example ]

[C++11: 7.1.3/6]: In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. [ Example:

class complex { / ... / };
typedef int complex; // error: redefinition

—end example ]

当然,在C++中,这一切都是无关紧要的,你只需要写下以下代码:
struct node
{
   int data;
   node* next;
};

你不需要使用 typedef 来隐藏 elaborated-type-specifier 中的 struct

13
当你不得不深入阅读一份1,338页的文件(其中库从第424页开始,因此语言部分只占整个文件的三分之一)才能回答关于名称和作用域的基本问题时,你就可以看出为什么有些人认为C++太复杂了。 :^) - Mike Housky
2
@MikeHousky:这太荒谬了。 - Lightness Races in Orbit
2
@LightnessRacesinOrbit:听起来确实是这样。我还不得不提到Java 8语言规范是一份有788页的PDF文档,而这仅仅是该语言的规范,没有平台API或VM规范。然而奇怪的是,似乎没有人认为这使Java变得太复杂了。我认为,严肃的编程语言无法同时以完整和简单的方式进行规范。 - Christian Hackl
1
@LightnessRacesinOrbit:实现它肯定是非常复杂的,但使用它至少99%的时间并不那么复杂。我认为C++被认为是一个复杂的野兽,主要是因为(1)它经常被错误地用作带类和手动内存管理的C语言,(2)它比其他语言更接近系统,自然会让你面临更多与系统相关的问题。结构体typedef的技术细节从语言角度来看是有趣的,但肯定不会阻止程序员完成工作(例如:OP已经解决了这个问题)。 - Christian Hackl
@AchyutRastogi:在C++中使用typedef并不是无用的。这个例子是人为制造的:它没有任何“用途”,只是在展示语言的特定规则。 - Lightness Races in Orbit
显示剩余10条评论

5
你提供的C语言示例应该是错误的。你使用了一个未被struct node定义的标签名(node)。
在这两个选择中,第二个是需要使用的。我倾向于简洁一些:
typedef struct node_t
{
    int data;
    struct node_t *next;
} node_t;

在C或C++中,标签名称拥有自己的命名空间,因此在使用标记和typedef名称时不会出现问题。在C中,这允许您使用node_tstruct node_t来引用此结构类型。如果已声明类型名称不存在,则C++将搜索标签名称以查找类型名称,因此上述双重定义是不必要的,但也无害。
在两种语言中,在类型完全定义之前的任何时候都需要显式的struct node_t版本,因此任何自我引用和任何前向引用都将使用struct版本。我更喜欢在头文件中使用它,主要是因为它减少了#include指令的顺序问题。
PS:这在两种语言中都可以工作(请参见LRIO的答案,了解C ++ 11标准的指针),并且已经在足够多的双语甚至纯C ++头文件中使用,因此很可能不会很快消失),因此这是一种非常简单而有效的方法。

一个未命名的结构体typedef会更好(在两种语言中)typedef struct {} my_struct_t;,不是吗?我不知道...你对此有什么看法?有什么缺点吗? - nonsensation
据我所知,在C语言中,除非你有一个标记名称,否则无法使用前向引用来引用my_struct_t类型。 struct my_struct_t *ptr不起作用,如果没有看到my_struct_t的完整声明,则my_struct_t *ptr也不起作用。 如果使用和定义在单独的头文件中,则存在包含顺序问题。 如果引用是循环的并且在单独的头文件中,则存在一个无法解决的包含顺序问题。 - Mike Housky
2
开头的句子是不正确的;代码完全有效,但非常不正统,是 C 语言中不可避免的错误。typedef 语句引入了三个类型名称:struct nodes(在语句结束时已经完成),nodestruct nodes 的同义词)和一个不完整的类型 struct node。这个不完整的类型与 struct nodes 是分开的;struct nodes 类型仅包含指向此其他类型的指针。您无法使用 next 指针创建 nodes 列表而不进行滥用转换;struct nodestruct nodes 是不同的。 - Jonathan Leffler
5
请注意,POSIX 保留以“_t”结尾的单词作为类型名称;在您自己的代码中使用此约定并不一定是一个好主意。 - Jonathan Leffler
@JonathanLeffler 赞同 POSIX 信息。我之前不知道这个...但是即使他们是最后一个大规模使用者,POSIX 也不是标准 C。至于第一句话,我对 C 和 C++ 都感到失望,因为似乎允许实例化包含指向不完整类型的指针的结构体。 - Mike Housky

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