为什么可以声明一个结构体和一个非结构体同名?

7

显然,

出于 C 语言的起源,我们可以在同一作用域内同时声明一个结构体和一个非结构体,它们的名称相同。

例如:

struct Ambig {};

// the struct must be referred to with the prefix struct
void Ambig(struct Ambig* buf) {}

我只是好奇最初的原因是什么?如果不理解,这似乎是一个糟糕的语言设计示例,会导致歧义和混淆。


也许这就是需要在“struct”前加上的原因。您能指出引用的来源吗? - Karthik T
@KarthikT Bjarne Stroustrup - C++程序设计语言 第四版 - Oleksiy
4
有很多方法可以导致语义模糊和混淆,避免这些问题的责任在于程序员而不是语言设计者。由于“struct”前缀暗示我们正在谈论“struct”,因此不存在句法歧义。 - Bernhard Barker
@Dukeling 是的,但我认为它是出于一个好的原因而被设计成那样,这就是为什么我在询问的原因。 - Oleksiy
请查看https://dev59.com/t3I_5IYBdhLWcg3wK_3E。 - user2249683
4个回答

6

正如Stroustrup所说,命名空间的历史原因是C语言中必须始终在结构体名称前加上struct。 结构体的名称(例如联合或枚举的名称)被称为标签,并存在于与其他符号完全不同的名称空间中。 因此,像以下这样的事情:

struct stat
{
    //  ...
};
int stat( char const* filename, struct stat* buf );

完全合法。(以上实际上是 Posix 的一部分)。

在 C++ 中,一个类的名称(使用 classstructunion 声明)或枚举与其他所有内容位于同一命名空间中,并且与 C 不同,您可以编写以下内容:

struct MyClass {};
MyClass variableName;

这不是合法的C语言。在C语言中,第二行必须为:
struct MyClass variableName;

问题在于C++需要能够使用在C中定义的接口(例如上述的Posix接口)。因此,C++定义了一些特殊规则来实现这一点:你可以给一个变量或函数和一个类类型相同的名称。当你这样做时,变量或函数名称优先,并隐藏类名称,除非在“完整类型说明符”(即 class struct union enum 后跟一个符号)中,在查找中忽略非类型名称。

3
这种情况的原因与C++继承自C有关。它不是“添加”到C ++中的,而是因为在C中以这种方式工作。
在C中,您必须使用struct Xunion Y(C中没有class关键字),或者使用typedef struct X A;然后使用名称A代替strcut X(其中X和A可以是相同的名称)。
在C ++中,只要名称唯一,编译器就会理解X是指struct X。您不必在名称前面输入structunionclass,也不必使用typedef创建一个新的独立名称。
由于C++旨在尽可能允许使用C语法,因此仍然允许在引用结构时写struct X。这允许使用一个否则模糊的名称。
强烈建议除非由历史设计决策所要求,否则不要利用这种“可能性”,因为这只会导致更多的混乱......

嗨,这并不是我的问题 - 我知道在名称前使用结构体是可选的。但是感谢你的努力,我相信有些人会觉得它很有帮助! - Oleksiy
关于您最后一句话,有时候您并没有选择。Posix 定义了一个 struct stat 和一个函数 stat()(它的第二个参数是一个 struct stat*)。如果您想使用这个函数,就必须要有一个 struct stat - James Kanze
我已经编辑了第一段来解释为什么是这样的。 - Mats Petersson
@JamesKanze:我已经添加了一个“除非历史设计决策所必需”的条件。 - Mats Petersson
@MatsPetersson 嗯,我不确定“设计决策”是否是正确的词语;我怀疑这里没有涉及到“设计”,而更多的是偶然性。但是没错... - James Kanze
@JamesKanze:即使从未在源代码之外的任何地方写下来,这仍然是一个设计决策,并且该决策是由某个深夜因咖啡因过度兴奋的人在大约0.5秒内做出的... ;) - Mats Petersson

3
我很好奇最初的原因是什么?如果不了解,似乎这是一种糟糕的语言设计示例,会导致歧义和困惑。
在C语言中,它是名称空间的第一个实现。标识符存在于不同的名称空间中,其思想是如果它们在不同的名称空间中声明,则可以具有相同的名称。 结构标记和普通标识符的名称空间并不是C语言中唯一的两个名称空间。在C语言中有四个名称空间:
(C99, 6.2.3标识符的名称空间p1) “因此,有各种类别的标识符的单独名称空间,如下所示: - 标签名称(通过标签声明和使用的语法进行消歧); - 结构、联合体和枚举类型的标记(通过跟随任何24个关键字struct、union或enum来消除歧义); - 结构体或联合体的成员;每个结构体或联合体都有一个用于其成员的单独名称空间(通过使用 . 或 -> 运算符访问成员的表达式的类型来消除歧义); - 所有其他标识符,称为普通标识符(在普通声明符或作为枚举常量中声明)。”

至少在 C 的早期版本中(我认为即使今天也是如此),所有 struct 成员都有一个确切的命名空间,不同 struct 的成员名称不能冲突。这就是为什么大多数 struct 的成员传统上都以前缀开头的原因;例如,在 struct tm 中,你有 tm_year,而不仅仅是 year - James Kanze
@JamesKanze 至少在标准C中不会出现这种情况,因为每个结构体或联合体都有其成员的独立命名空间 - ouah
1
这是有道理的。但在K&R I中并非如此。我还记得因为C* cp; /*...*/ cp->memberOfD;而追踪错误。不仅没有错误,甚至没有警告(除非使用了lint)。 - James Kanze

1
在结构体之前添加“struct”是合法的C语言。因此,一些C代码也适用于C++。你预期的是什么?C++基于C。
在C++中,我们避免在每个结构体之前使用“struct”。我从未在生产代码中看到过它。
关于歧义,我不认为有人知道为什么他们允许这样做。但我相信这是因为当你添加“struct”时,歧义就被解决了。你本质上告诉编译器这不是一个函数,因此“可能的含义”被消除了。

谢谢你的回答!抱歉,你说的“我们避免在每个结构体前使用struct”是什么意思? - Oleksiy
1
@Oleksiy 他的意思是 C++ 编译器足够聪明,知道你在引用一个结构体,所以在它前面加上关键字"struct"是不必要的。 - greatwolf
1
这个答案有几个问题。首先,当涉及到类型名称时系统地使用struct关键字不仅在C语言中是合法的,而且是必需的。此外,C接口(如Posix的接口)确实有名称相同的函数和结构体,因此在C++中必须这样做。这也是为什么它被允许的真正原因。 - James Kanze

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