关于C:typedef结构与结构定义

typedef struct vs struct definitions

本问题已经有最佳答案,请猛点这里访问。

我是C编程的初学者,但我想知道在定义结构时使用typedef与不使用typedef有什么区别。在我看来,似乎没有什么区别,他们实现了相同的目标。

1
2
3
4
struct myStruct{
    int one;
    int two;
};

VS

1
2
3
4
typedef struct{
    int one;
    int two;
}myStruct;


常用的成语是同时使用:

1
2
3
typedef struct X {
    int x;
} X;

它们是不同的定义。为了使讨论更清楚,我将把句子分开:

1
2
3
4
5
struct S {
    int x;
};

typedef struct S S;

在第一行中,您定义了结构名称空间内的标识符EDCOX1 OR 13(不在C++意义上)。通过将参数类型定义为struct S可以使用它并定义新定义类型的变量或函数参数:

1
void f( struct S argument ); // struct is required here

第二行在全局名称空间中添加了一个类型别名S,因此您只需编写:

1
void f( S argument ); // struct keyword no longer needed

注意,由于两个标识符名称空间都不同,因此在结构和全局空间中定义S不是一个错误,因为它不是重新定义同一个标识符,而是在不同的位置创建不同的标识符。

为了使区别更清楚:

1
2
3
4
5
6
7
typedef struct S {
    int x;
} T;

void S() { } // correct

//void T() {} // error: symbol T already defined as an alias to 'struct S'

您可以定义结构名称相同的函数,因为标识符保留在不同的空间中,但不能定义与typedef同名的函数,因为这些标识符发生冲突。

在C++中,稍微不同,因为定位符号的规则已经发生了微妙的变化。C++仍然保留两个不同的标识符空间,但是与C不同,当您只在类标识符空间中定义符号时,不需要提供Stutt/Class关键字:

1
2
3
4
5
6
 // C++
struct S {
    int x;
}; // S defined as a class

void f( S a ); // correct: struct is optional

搜索规则的变化是什么,而不是标识符的定义。编译器将搜索全局标识符表,在找不到S之后,将在类标识符中搜索S

前面介绍的代码的行为方式相同:

1
2
3
4
5
6
7
typedef struct S {
    int x;
} T;

void S() {} // correct [*]

//void T() {} // error: symbol T already defined as an alias to 'struct S'

在第二行中定义S函数后,编译器无法自动解析结构,要创建对象或定义该类型的参数,必须返回到包含struct关键字:

1
2
3
4
5
// previous code here...
int main() {
    S();
    struct S s;
}


structtypedef是两个非常不同的东西。

struct关键字用于定义或引用结构类型。例如,这:

1
2
3
struct foo {
    int n;
};

创建名为struct foo的新类型。名称foo是一个标记;只有当它前面紧跟着struct关键字时,它才有意义,因为标记和其他标识符在不同的名称空间中。(这类似于,但比namespace的C++概念更受限制。

typedef不定义新的类型,它只是为现有的类型创建一个新的名称。例如,给定:

1
typedef int my_int;

my_intint的新名称;my_intint是完全相同的类型。同样,考虑到上面的struct定义,您可以编写:

1
typedef struct foo foo;

类型已经有一个名称,struct footypedef声明为同一类型提供了一个新名称,foo

语法允许您将structtypedef组合成一个声明:

1
2
3
typedef struct bar {
    int n;
} bar;

这是一个常见的成语。现在您可以将这种结构类型称为struct bar,也可以称为bar

注意,typedef名称直到声明结束后才可见。如果结构包含指向自身的指针,您可以使用struct版本来引用它:

1
2
3
4
typedef struct node {
    int data;
    struct node *next; /* can't use just"node *next" here */
} node;

一些程序员将对struct标记和typedef名称使用不同的标识符。在我看来,这样做没有什么好的理由;使用同一个名字是完全合法的,而且更清楚地表明它们是同一类型的。如果必须使用不同的标识符,请至少使用一致的约定:

1
2
3
typedef struct node_s {
    /* ... */
} node;

(就我个人而言,我更喜欢省略typedef,将该类型称为struct bartypedef保存了一些类型,但它隐藏了一个事实,即它是一种结构类型。如果您希望类型是不透明的,这可能是一件好事。如果客户机代码将按名称引用成员n,那么它不是不透明的;它显然是一个结构,在我看来,将其称为一个结构是有意义的。但是很多聪明的程序员在这一点上不同意我的观点。准备好阅读和理解以任何方式编写的代码。)

(C++有不同的规则。给定struct blah的声明,您可以将该类型称为blah,即使没有typedef。使用TyPulf可以使C代码更像C++——如果你认为这是一件好事。


另一个未指出的区别是,为结构命名(即structmystruct)也可以使您提供结构的前向声明。所以在其他文件中,你可以写:

1
2
struct myStruct;
void doit(struct myStruct *ptr);

无需访问定义。我建议你结合两个例子:

1
2
3
4
typedef struct myStruct{
    int one;
    int two;
} myStruct;

这为您提供了更简洁的typedef名称的便利性,但仍然允许您根据需要使用完整的结构名称。


在C(非C++)中,必须声明结构变量,例如:

1
struct myStruct myVariable;

为了能够使用myStruct myVariable;,您可以使用typedef结构:

1
2
typedef struct myStruct someStruct;
someStruct myVariable;

您可以将struct定义和typedef的定义组合在一个声明中,声明匿名structtypedef的定义。

1
typedef struct { ... } myStruct;


如果你用struct而不使用typedef的话,你总是要写

1
struct mystruct myvar;

写是违法的

1
mystruct myvar;

如果使用typedef,则不再需要struct前缀。


在C语言中,结构、联合和枚举的类型说明符关键字是强制的,即在引用类型时,必须在类型名称(其标记)前面加上structunionenum

通过使用typedef可以去掉关键字,这是一种信息隐藏形式,因为在声明对象时,对象的实际类型将不再可见。

因此,建议(如Linux内核编码风格指南,第5章)仅在您实际上想要隐藏这些信息,而不仅仅是保存一些按键。

一个应该何时使用typedef的示例是一个不透明的类型,它只与相应的访问函数/宏一起使用过。


不能对typedef struct使用forward声明。

struct本身是一个匿名类型,因此您没有要转发声明的实际名称。

1
2
3
4
typedef struct{
    int one;
    int two;
} myStruct;

这样的远期申报是行不通的:

1
2
3
4
5
struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

下面的代码创建一个匿名结构,其别名为myStruct

1
2
3
4
typedef struct{
    int one;
    int two;
} myStruct;

没有别名不能引用它,因为没有为结构指定标识符。


当你使用struct时,差异就出现了。

第一种方法是:

1
struct myStruct aName;

第二种方法允许您删除关键字struct

1
myStruct aName;

与其他构造一样,typedef用于为数据类型提供新名称。在这种情况下,主要是为了使代码更干净:

1
struct myStruct blah;

VS

1
myStruct blah;

我看到一些澄清是正确的。C和C++不以不同的方式定义类型。C++最初只不过是C上的一个附加集合。

现在几乎所有的C/C++开发者都有一个问题:A)大学不再教授基础知识,B)人们不理解定义和声明之间的区别。

这种声明和定义存在的唯一原因是链接器可以计算到结构中字段的地址偏移量。这就是为什么大多数人都会忽略那些实际上写得不正确的代码——因为编译器能够确定地址。当有人试图提前做一些事情时,比如队列、链表,或者清管支持O/S结构时,问题就会出现。

声明以"struct"开头,定义以"typedef"开头。

此外,结构有一个正向声明标签和一个定义的标签。大多数人不知道这一点,而是将前向声明标签用作定义标签。

错误:

1
2
3
4
5
struct myStruct
   {
   int field_1;
   ...
   };

他们刚刚使用了forward声明来标记结构——所以现在编译器意识到了这一点——但它不是一个实际定义的类型。编译器可以计算地址——但这并不是它的预期用途,因为我将马上展示的原因。

使用这种声明形式的人,必须在实践中始终使用"struct",因为它不是正式的新类型。

相反,任何不引用自身的结构都应该以这种方式声明和定义:

1
2
3
4
5
typedef struct
   {
   field_1;
   ...
   }myStruct;

现在它是一个实际类型,使用时,您可以将at用作"mystruct",而不必在其前面加上"struct"一词。

如果需要指向该结构的指针变量,请包含辅助标签:

1
2
3
4
5
typedef struct
   {
   field_1;
   ...
   }myStruct,*myStructP;

现在您有了一个指向该结构的指针变量,它是自定义的。

远期申报--

现在,这里有一些花哨的东西,转发声明是如何工作的。如果要创建引用自身的类型,如链表或队列元素,则必须使用forward声明。编译器不会考虑定义的结构,直到它到达末尾的分号,所以它只是在这一点之前声明的。

1
2
3
4
5
6
typedef struct myStructElement
   {
   myStructElement*  nextSE;
   field_1;
   ...
   }myStruct;

现在,编译器知道尽管它还不知道整个类型是什么,但它仍然可以使用前向引用引用引用它。

请正确声明和typedef您的结构。其实是有原因的。


在后一个示例中,使用结构时省略struct关键字。因此,在代码的任何地方,您都可以编写:

1
myStruct a;

而不是

1
struct myStruct a;

这节省了一些输入,而且可能更易读,但这是一个品味问题。