繁体   English   中英

C 中的 struct 和 typedef struct 之间的真正区别是什么?

[英]What is the real difference between struct and typedef struct in C?

定义结构体有两种主要方式:

struct triangle_s {
    int a,b,c;
};

typedef struct triangle_s {
    int a,b,c;
} triangle;

已经被问过很多次了,但每个答案都是关于在使用typedef变体时不必多次编写struct 除了可以避免重复struct关键字之外,还有什么真正的区别吗? 我听说你永远不应该在 C 中使用typedef变体,但没有人说为什么。

没有区别, typedef只是删除了使用struct前缀变量声明的要求。

默认情况下是否使用typedef结构是一场宗教战争,主要是由手头有太多时间的人进行的。 在现有代码库中工作时,您应该执行编码标准或周围代码所做的任何操作。 对于您自己的个人代码,请随心所欲。

没有“typedef 结构”这样的东西。

第一部分: struct类型

struct引入了一种结构,它是由一组命名成员组成的聚合数据类型。 (数组也是聚合,但它们由许多被索引的相同成员组成。联合有一组成员名称,但一次只能包含一个成员,因此它们不是聚合。您可能不需要我知道。)

结构类型通常有标签,因此实际的类型名称将类似于struct Triangle 标签 ( Triangle ) 与标识符不在同一个命名空间中,因此使用也用于其他目的的标签没有问题。 有些人喜欢用_s_u附加标签,分别表示它们是结构或联合标签,但这不是我的风格; 我更喜欢在 CamelCase 中命名类型,对我来说,结构或联合标记代表类型名。 但是当然您可以自由使用自己的约定。

如果您在程序中使用struct SomeTag ,您实际上是在声明存在一个其标记为SomeTag的结构。 您不需要通过命名或描述结构的成员来填写声明,除非您需要在代码中引用它们。 其成员尚未(尚未)声明的结构称为不完整的,但它仍然可以用作指针类型的一部分,因为 C 标准保证所有结构指针都具有相同的格式,而不管结构的内容如何。 (这并不使它们可以互换,但它确实意味着编译器知道指针有多大。)从未定义其成员并且仅用作指针类型的目标的结构称为opaque

您可以通过添加成员声明块来完成结构的声明。 所以

struct Triangle {
    int a,b,c;
};

首先声明存在一个名称为struct Triangle ,然后通过声明三个都是int的命名成员来填充该结构的定义。

顺便说一下,联合声明和定义都非常相似。

结构定义可以在声明中使用,就好像它是一个类型名称一样。 或者换句话说,您可以声明结构类型的标记,立即填写字段,然后声明该类型的一个或多个变量:

struct Triangle { int a, b, c; } aTriangle, anotherTriangle;

这不是一种非常常见的风格,但重要的是要知道语法是可能的。

最后,定义一个结构而不给它一个标签是合法的。 无标签结构类型有一个怪癖:通常没有两种结构类型可以具有相同的标签,但所有无标签结构都是不同的。 这意味着您可以声明一个结构类型,它实际上没有名称,并且不同于任何其他结构类型,甚至是具有完全相同成员的结构类型。 如果您有一个只有一个实例(“单例”)的聚合,这可能会稍微有用,尽管我自己不会真正使用这种样式。 但经过一小段弯路后,我们将看到此功能的另一种用途。

第二部分:类型别名

C 类型名称可能非常复杂,因为它们可以由多个部分组成。 例如, const struct Triangle*[8]是一个包含八个成员的数组,每个成员都是一个指向不可修改的struct Triangle的指针。 double (*)(const struct Triangle*[8])是一个函数,它接受一个这样的数组作为参数(或者,更准确地说,它接受一个指向这样一个数组的第一个元素的指针,因为数组到指针衰变。但这在这里无关紧要。)

为了使复杂类型更易于使用,C 允许您为类型声明别名。 别名使用typedef声明,否则看起来与变量声明完全一样。 因此,例如,您可以声明一个int类型的变量

int someNumber;

因此你可以为int类型声明一个别名

typedef int someType;

类似地,您可以声明一个包含八个指向const Triangle元素的指针的数组

const Triangle* eightSlices[8];

以完全相同的方式,您可以为此类数组的类型声明一个名称:

typedef const Triangle* EightSlices[8];

请注意,类型的名称恰好位于对象名称所在的位置,它可以位于声明中间的某个位置。

第三部分:以上两者合二为一

作为声明类型别名的简单示例,以下是为结构类型声明别名的方法:

一个不完整的结构定义:

typedef struct Triangle Triangle;

或者一个完整的结构定义:

typedef struct Triangle {
   int a, b, c;
} Triangle;

或者两者都分开(这些可以按任何顺序进行):

typedef struct Triangle Triangle;
struct Triangle {
    int a, b, c;
};

请记住,结构标记和其他标识符(例如类型别名)在不同的命名空间中,因此上面Triangle的两种用法没有冲突。 一些程序员觉得有必要区分它们,即使其中一个只能紧跟在struct之后使用,另一个不能在struct 其他人——我想你可以猜到我属于这群人——发现故意为两者使用相同的名称很方便,依靠单词struct的存在或不存在来让我们知道它是类型别名还是一个标签。 (而且,更常见的是,表明我们无意再次使用该标签。)

所以,回到我的开场白:

没有“typedef 结构”这样的东西。

而且没有。 我们这里有一个非常普通的struct ,声明和定义。 我们有一个非常普通的类型别名,它为该struct提供了一个替代名称。 就是这样。

请注意,您可以为匿名类型(例如无标记结构类型)提供别名,之后该类型就不再是匿名的。 所以有些人会在上面的定义中省略标签:

typedef struct {
    int a, b, c;
} Triangle;

这看起来很像上面提到的单例结构类型,但由于它是一种可以用于声明多个实例的类型。 但我实际上并不推荐这种风格。

各自为政:一个可忽略的附录

每个人都有自己的风格偏好,而这些偏好大多是有效的。 我们大多数人都参与过不止一个项目,而且由于每个项目都有自己的风格指南,我们需要学习如何在不同的项目中接受和使用不同的风格。 但是我认为我们中的大多数人都有一些我们觉得最舒服的风格,当我们为自己编写代码时(或者当我们开始一个项目以吸引准备符合我们的同事的意图时,我们会恢复这种风格)风格)。 我上面使用的是我的风格。

事实上,我尽量避免使用压缩声明+定义+别名的语法,更喜欢上面显示的两个声明版本:

typedef struct Triangle Triangle; /* type alias */
struct Triangle {
  int a, b, c;
};                                /* type definition */

我更喜欢这样做的原因是它允许我使用自引用成员定义类型,例如链表和树:

typedef struct TriangleList TriangleList;
struct TriangleList {
    Triangle      slice;
    TriangleList* next;
};

(如果我没有对类型进行前向别名化,我将不得不将成员声明为struct TriangleList* next;这会导致更难看的对齐。)

有时我最终会得到相互引用的类型,在这种情况下,我需要在任何结构定义之前将别名收集在一起。 这也可能是有利的,因为别名定义允许不透明地使用指向类型的指针,因此可以放置到根本不包含类型定义的公共标头中。

但这只是我。 随意忽略它。

定义结构体有两种主要方式

不,只有一个:

struct triangle_s {
    int a,b,c;
}

. 这样的定义可以出现在多种上下文中,包括但不限于您提供的两种情况,但重要的是要了解您提供的两种语句都不仅仅是结构定义。

不可否认,这个“更多”有点空洞:

 struct triangle_s { int a,b,c; };

它是结构类型该类型的零对象的声明。 在右大括号和分号之间有一个标识符列表,在这种特殊情况下它恰好是空的。 但也可能是这样,例如:

struct triangle_s {
    int a,b,c;
} triangle;

,它定义了类型struct triangle_s并将triangle声明为该类型的对象。 当然,这将我们引向第二个例子

typedef struct triangle_s { int a,b,c; } triangle;

,这与前面的完全相同,除了typedef关键字指示声明的标识符是类型别名而不是对象标识符。 同样,结构定义只是从struct关键字到关联的右大括号的部分,请注意typedef可用于声明任何类型的别名,而不仅仅是结构。

除了可以避免重复 struct 关键字之外,还有什么真正的区别吗?

无论哪种方式,您都有struct triangle_s的定义,并且您可以以任何一种方式使用其类型名称的该形式。 在后一种情况下,你也可以用triangle (无论它是在范围内)作为一个别名struct triangle_s ,对于任何和一切可以由形式送达目的struct triangle_s ,在意义上没有区别。 这就是typedef

我听说你永远不应该在 C 中使用 typedef 变体,但没有人说为什么。

有些人从风格上反对第二种形式,因为它在同一个声明中混合了两种不同的东西——结构类型的定义和它的别名声明。 这条推理线继续说,如果你想要typedef ,那么你应该单独声明它:

struct triangle_s {
    int a,b,c;
};

typedef struct triangle_s triangle;

然而,这又是一个代码风格问题,而不是功能问题。

关于typedef的使用,还有其他一些风格点,我个人对此有更强烈的感受。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM