为什么我们要在C语言中如此频繁地定义一个结构体?

我见过许多由如下结构组成的程序

typedef struct
{
int i;
char k;
} elem;


elem user;

为什么如此频繁地需要它?有什么具体原因或适用领域吗?

629715 次浏览

使用typedef可以避免每次声明该类型的变量时都必须写struct:

struct elem
{
int i;
char k;
};
elem user; // compile error!
struct elem user; // this is correct

正如Greg Hewgill所说,类型定义意味着你不再需要到处写struct。这不仅节省了击键,还可以使代码更简洁,因为它提供了更多的抽象。

之类的

typedef struct {
int x, y;
} Point;


Point point_new(int x, int y)
{
Point a;
a.x = x;
a.y = y;
return a;
}

当你不需要到处看到“struct”关键字时,它看起来更像是在你的语言中真的有一个名为“Point”的类型。在typedef之后,我猜是这种情况。

还要注意,虽然你的例子(和我的例子)省略了命名struct本身,但当你想提供一个不透明类型时,实际上命名它也是有用的。然后你会在头文件中有这样的代码,例如:

typedef struct Point Point;


Point * point_new(int x, int y);

然后在实现文件中提供struct定义:

struct Point
{
int x, y;
};


Point * point_new(int x, int y)
{
Point *p;
if((p = malloc(sizeof *p)) != NULL)
{
p->x = x;
p->y = y;
}
return p;
}

在后一种情况下,您不能按值返回Point,因为它的定义对头文件的用户隐藏了。例如,这是在GTK +中广泛使用的技术。

更新注意,也有高度重视的C项目,使用typedef隐藏struct被认为是一个坏主意,Linux内核可能是最著名的这样的项目。Linus生气的话见Linux内核编码风格文档的第5章。:)我的观点是,这个问题中的“应该”也许并不是一成不变的。

你(可选地)给这个结构的名字叫做标签名,正如前面提到的,它本身并不是一个类型。要获得该类型,需要使用结构体前缀。

除了GTK+,我不确定tagname是否像struct类型的typedef一样常用,所以在c++中,这是可以识别的,你可以省略struct关键字,并使用tagname作为类型名:

struct MyStruct
{
int i;
};


// The following is legal in C++:
MyStruct obj;
obj.i = 7;

来自Dan Saks (http://www.ddj.com/cpp/184403396?pgno=3)的一篇旧文章:


C语言命名规则 结构有点古怪,但是 它们是无害的。然而,当 扩展到c++中的类 规则为漏洞打开了一条小裂缝 爬行通过。< / p >

在C中,名字出现在

struct s
{
...
};

是一个标签。标记名不是类型 的名字。根据上面的定义, 像

s x;    /* error in C */
s *p;   /* error in C */

是c中的错误,你必须写它们 < / p >

struct s x;     /* OK */
struct s *p;    /* OK */

联合和枚举的名称 也是标签而不是类型。

在C语言中,标签与其他标签是不同的 名称(用于函数、类型、 变量和枚举常量)。 C编译器维护符号中的标记 如果不是概念上的 与桌子分开 它包含所有其他名称。因此,它 是一个C程序可能拥有的 的标记和另一个名称 相同范围内的相同拼写。 例如,< / p >
struct s s;

是一个有效的声明 类型为struct s的变量s 不是很好的实践,而是C编译器 必须接受。我从没见过 C语言设计成这样的基本原理 道路我一直以为那是

.

. 很多程序员(包括你的 确实)更喜欢考虑结构名 作为类型名,所以它们定义了别名 对于使用类型定义的标记。为 例如,定义< / p >
struct s
{
...
};
typedef struct s S;

让你用S代替struct S, 在< / p >

S x;
S *p;

程序不能使用S作为的名称 类型和变量(或 函数或枚举常量):

S S;    // error

这很好。

在结构、联合或 Enum定义可选。许多 程序员折叠结构定义 类型定义,而不用 标记,如:

typedef struct
{
...
} S;

链接的文章还讨论了不需要typedef的c++行为如何导致微妙的名称隐藏问题。为了防止这些问题,在c++中typedef你的类和结构也是一个好主意,尽管乍一看这似乎是不必要的。在c++中,typedef的名称隐藏成为编译器告诉你的错误,而不是潜在问题的隐藏来源。

另一个总是对枚举和结构进行类型定义的原因是:

enum EnumDef
{
FIRST_ITEM,
SECOND_ITEM
};


struct StructDef
{
enum EnuumDef MyEnum;
unsigned int MyVar;
} MyStruct;

注意到struct (EnuumDef)中的EnumDef中的拼写错误了吗?这个编译没有错误(或警告),并且(取决于C标准的字面解释)是正确的。问题是我刚刚在我的结构中创建了一个新的(空的)枚举定义。我没有(如预期)使用前面的定义EnumDef。

对于typdef,类似类型的打字错误将导致使用未知类型的编译器错误:

typedef
{
FIRST_ITEM,
SECOND_ITEM
} EnumDef;


typedef struct
{
EnuumDef MyEnum; /* compiler error (unknown type) */
unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

我主张ALWAYS类型定义结构和枚举。

不仅是为了节省一些输入(没有双关语;)),而且因为它更安全。

在C语言中,struct/union/enum是由C语言预处理器处理的宏指令(不要与处理“#include”和other的预处理器混淆)

所以:

struct a
{
int i;
};


struct b
{
struct a;
int i;
int j;
};

结构b是这样展开的:

struct b
{
struct a
{
int i;
};
int i;
int j;
}

,因此,在编译时,它在堆栈上的演变是这样的: b: int人工智能 int我 int j < / p >

这也是为什么在不能终止的déclaration循环中很难有自不同的结构,C预处理器循环。

typedef是类型说明符,这意味着只有C编译器处理它,它可以像他想要的那样优化汇编代码实现。它也不会像préprocessor那样愚蠢地使用par类型的成员,而是使用更复杂的引用构造算法,因此构造如下:

typedef struct a A; //anticipated declaration for member declaration


typedef struct a //Implemented declaration
{
A* b; // member declaration
}A;

是允许的,功能齐全。当执行线程离开初始化函数的应用程序字段时,这个实现还允许访问编译器类型转换,并删除一些bug影响。

这意味着在C中,typedef比单独的结构体更接近c++类。

我认为typedef甚至不可能实现前向声明。使用struct, enum和union允许在依赖关系(知道)是双向的情况下转发声明。

< p >风格: 在c++中使用typedef是很有意义的。在处理需要多个和/或变量参数的模板时,它几乎是必要的。类型定义有助于保持命名的直直性

在C编程语言中并非如此。typedef的使用通常没有任何目的,只会混淆数据结构的使用。由于只有{struct (6), enum (4), union(5)}个按键被用于声明一个数据类型,因此结构的别名几乎没有任何用处。数据类型是联合还是结构?使用简单的非类型定义声明可以让您立即知道它是什么类型。

请注意Linux是如何在编写时严格避免typedef带来的这种无意义的别名的。结果是一个极简主义和干净的风格。

令人惊讶的是,很多人都错了。请不要在C中定义结构类型,它不必要地污染全局命名空间,在大型C程序中,全局命名空间通常已经被严重污染。

此外,没有标记名的类型定义结构是头文件之间不必要的排序关系的主要原因。

考虑:

#ifndef FOO_H
#define FOO_H 1


#define FOO_DEF (0xDEADBABE)


struct bar; /* forward declaration, defined in bar.h*/


struct foo {
struct bar *bar;
};


#endif

使用这样的定义,而不使用typedefs,编译单元可以包含foo.h来获得FOO_DEF定义。如果它不试图解引用foo结构体的'bar'成员,则不需要包含"bar.h"文件。

此外,由于标签名和成员名之间的名称空间是不同的,因此可以编写非常可读的代码,例如:

struct foo *foo;


printf("foo->bar = %p", foo->bar);

由于名称空间是分开的,因此在命名变量时与其struct标记名一致时不存在冲突。

如果我必须维护你的代码,我会删除你的类型定义结构。

第5章给出了使用typedef的优点和缺点(主要是缺点)。

请不要使用“;vps_t"”之类的词。

为结构和指针使用typedef是错误。当你看到

vps_t a;

在原文中,它是什么意思?

相反,如果它说

struct virtual_container *a;

你实际上可以分辨出“;a;”是多少。

很多人认为类型“有助于可读性”。不是这样的。它们只在以下情况下有用:

(a)完全不透明的对象(其中typedef主动用于隐藏对象是什么)。

例如:“pte_t"等等,只能使用适当的访问器函数访问的不透明对象。

注意!不透明性和“访问函数”;他们自己都不好。我们为pte_t等使用它们的原因是那里确实有可移植访问的信息。

(b)明确整数类型,其中抽象帮助避免混淆它是否是"int"或“;long"。

U8 /u16/u32是非常好的类型defs,尽管它们比这里更适合(d)类。

注意!同样,这需要一个原因。如果某件事是“unsigned long”,那么就没有理由去做

typedef unsigned long myflags_t;

但如果有明确的理由说明为什么在某些情况下它可能是“unsigned int”;而在其他配置下可能是“unsigned long”,那么无论如何都要使用类型定义。

(c)当你使用sparse逐字创建类型用于类型检查时。

(d)在某些特殊情况下与标准C99型号相同的新型号。

虽然它只会花很短的时间让眼睛和大脑习惯像'uint32_t'这样的标准类型,但有些人反对他们的使用。

因此,linux特定的'u8/u16/u32/u64'类型及其与标准类型相同的有符号等量物是允许的——尽管它们在您自己的新代码中不是强制性的。

当编辑已经使用一组或另一组类型的现有代码时,应该遵循该代码中的现有选项。

(e)在用户空间中安全使用的类型。

在用户空间可见的某些结构中,我们不能要求C99类型,也不能使用上面的'u32'形式。因此,我们在所有与用户空间共享的结构中使用__u32和类似的类型。

也许还有其他情况,但规则基本上应该是永远不要使用类型定义,除非你能清楚地匹配这些规则之一。

一般来说,具有可以合理地直接访问的元素的指针或结构应该从来没有是类型定义。

事实证明,这是有利有弊的。一个有用的信息来源是开创性的书“Expert C programming”。(第三章)。简单地说,在C语言中有多个命名空间:标签、类型、成员名和标识符typedef为类型引入别名,并将其定位在标记命名空间中。也就是说,

typedef struct Tag{
...members...
}Type;

定义了两个东西。1)在标签命名空间中标记,2)在类型命名空间中键入。所以你可以同时做Type myTypestruct Tag myTagType。像struct Type myTypeTag myTagType这样的声明是非法的。此外,在这样的声明中:

typedef Type *Type_ptr;

定义一个指向类型的指针。如果我们声明:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

那么var1var2myTagType1是指向类型的指针,而myTagType2不是。

在上面提到的书中,它提到对结构进行类型定义并不是很有用,因为它只是让程序员不用编写struct这个词。然而,像许多其他C程序员一样,我有一个反对意见。虽然它有时会混淆一些名称(这就是为什么在像内核这样的大型代码库中不可取),但当你想在C中实现多态性时,它会有很大帮助。例子:

typedef struct MyWriter_t{
MyPipe super;
MyQueue relative;
uint32_t flags;
...
}MyWriter;

你可以:

void my_writer_func(MyPipe *s)
{
MyWriter *self = (MyWriter *) s;
uint32_t myFlags = self->flags;
...
}

因此,您可以通过强制转换通过内部结构(MyPipe)访问外部成员(flags)。对我来说,每次你想执行这样的功能时,强制转换整个类型比执行(struct MyWriter_ *) s;更容易混淆。在这种情况下,简要引用是很重要的,特别是当你在代码中大量使用这种技术时。

最后,与宏相反,typedefed类型的最后一个方面是无法扩展它们。例如,你有:

#define X char[10] or
typedef char Y[10]

然后你可以声明

unsigned X x; but not
unsigned Y y;

对于结构体,我们并不关心这一点,因为它不适用于存储说明符(volatileconst)。

Typedef不会提供一组相互依赖的数据结构。你不能用typdef做到这一点:

struct bar;
struct foo;


struct foo {
struct bar *b;
};


struct bar {
struct foo *f;
};

当然你还可以加上:

typedef struct foo foo_t;
typedef struct bar bar_t;

这到底有什么意义?

在'C'编程语言中,关键字'typedef'用于为一些对象(struct, array, function..enum类型)声明一个新名称。例如,我将使用struct-s。 在C语言中,我们经常在main函数之外声明一个struct。例如:< / p >
struct complex{ int real_part, img_part }COMPLEX;


main(){


struct KOMPLEKS number; // number type is now a struct type
number.real_part = 3;
number.img_part = -1;
printf("Number: %d.%d i \n",number.real_part, number.img_part);


}

每次我决定使用结构体类型时,我将需要这个关键字'struct 'something' name'。'typedef'将简单地重命名该类型,我可以在我的程序中随时使用这个新名称。所以我们的代码是:

typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.


main(){


COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);


}

如果你有一些局部对象(结构,数组,有价值的),将在你的整个程序中使用,你可以简单地给它一个名字使用'typedef'。

在C99中,typedef是必需的。它已经过时了,但是很多工具(比如HackRank)使用c99作为它的纯C实现。这里需要typedef。

我不是说他们应该改变(也许有两个C选项),如果要求改变,我们这些在网站上学习面试的人将是SOL。

< p > > typdef通过允许为数据类型创建更有意义的同义词来帮助定义程序的含义和文档。此外,它们还有助于参数化程序以解决可移植性问题(K&R, pg147, C prog lang) B < p > > 结构体定义了类型。Structs允许方便地对变量集合进行分组,以便将(K&R, pg127, C prog lang.)作为单个单元处理

< p > C >

. type - pedef' struct在上面的a中解释

对我来说,struct是自定义类型、容器、集合、命名空间或复杂类型,而typdef只是一种创建更多昵称的方法。

让我们从最基本的开始,慢慢来。

下面是一个结构定义的例子:

struct point
{
int x, y;
};

这里的名字point是可选的。

结构可以在定义期间声明,也可以在定义之后声明。

在定义期间声明

struct point
{
int x, y;
} first_point, second_point;

在定义之后声明

struct point
{
int x, y;
};
struct point first_point, second_point;

现在,仔细注意上面的最后一种情况;你需要写struct point来声明该类型的结构,如果你决定在你的代码中稍后创建该类型。

输入typedef。如果你打算在以后使用相同的蓝图在程序中创建新的结构(结构是自定义数据类型),在定义过程中使用typedef可能是一个好主意,因为你可以节省一些输入。

typedef struct point
{
int x, y;
} Points;


Points first_point, second_point;

在命名自定义类型时要注意一点

没有什么可以阻止您在自定义类型名称的末尾使用_t后缀,但POSIX标准保留使用后缀_t来表示标准库类型名称。