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

tom*_*ser 2 c struct

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

struct triangle_s {
    int a,b,c;
};
Run Code Online (Sandbox Code Playgroud)

typedef struct triangle_s {
    int a,b,c;
} triangle;
Run Code Online (Sandbox Code Playgroud)

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

nic*_*pro 7

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

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


ric*_*ici 5

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

第一部分:structure 类型

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

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

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

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

struct Triangle {
    int a,b,c;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

struct Triangle { int a, b, c; } aTriangle, anotherTriangle;
Run Code Online (Sandbox Code Playgroud)

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

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

第二部分:类型别名

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

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

int someNumber;
Run Code Online (Sandbox Code Playgroud)

因而你可以声明的别名类型int

typedef int someType;
Run Code Online (Sandbox Code Playgroud)

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

const Triangle* eightSlices[8];
Run Code Online (Sandbox Code Playgroud)

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

typedef const Triangle* EightSlices[8];
Run Code Online (Sandbox Code Playgroud)

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

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

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

一个不完整的结构定义:

typedef struct Triangle Triangle;
Run Code Online (Sandbox Code Playgroud)

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

typedef struct Triangle {
   int a, b, c;
} Triangle;
Run Code Online (Sandbox Code Playgroud)

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

typedef struct Triangle Triangle;
struct Triangle {
    int a, b, c;
};
Run Code Online (Sandbox Code Playgroud)

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

所以,回到我的开场白:

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

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

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

typedef struct {
    int a, b, c;
} Triangle;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

typedef struct Triangle Triangle; /* type alias */
struct Triangle {
  int a, b, c;
};                                /* type definition */
Run Code Online (Sandbox Code Playgroud)

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

typedef struct TriangleList TriangleList;
struct TriangleList {
    Triangle      slice;
    TriangleList* next;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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