我见过许多程序,包括如下所示的结构
typedef struct { int i; char k; } elem; elem user;
为什么经常这么需要?任何具体原因或适用范围?
正如Greg Hewgill所说,typedef意味着你不再需要struct
在整个地方写作.这不仅可以节省击键次数,还可以使代码更清晰,因为它提供了更多抽象的smidgen.
好像的东西
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
本身,但实际命名它对于您想要提供opaque类型时也很有用.然后你在标题中有这样的代码,例如:
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 by值,因为它的定义对头文件的用户是隐藏的.例如,这是GTK +中广泛使用的技术.
更新请注意,还有一些备受推崇的C项目,其中使用typedef
隐藏struct
被认为是一个坏主意,Linux内核可能是最知名的此类项目.有关Linus的愤怒话语,请参阅Linux Kernel CodingStyle文档的第5章.:)我的观点是,问题中的"应该"可能不是一成不变的.
令人惊讶的是有多少人弄错了.请不要在C中输入typedef结构,它会不必要地污染全局命名空间,这通常在大型C程序中已经被污染了.
此外,没有标记名称的typedef'd结构是导致头文件之间不必要的排序关系的主要原因.
考虑:
#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
有了这样的定义,不使用typedef,compiland单元可以包含foo.h来获得FOO_DEF
定义.如果它不试图取消引用结构的"bar"成员,foo
则不需要包含"bar.h"文件.
此外,由于标记名称和成员名称之间的名称空间不同,因此可以编写非常易读的代码,例如:
struct foo *foo; printf("foo->bar = %p", foo->bar);
由于命名空间是独立的,因此命名变量与其struct标记名称不一致.
如果我必须维护你的代码,我将删除你的typedef结构.
来自Dan Saks的旧文章(http://www.ddj.com/cpp/184403396?pgno=3):
命名结构的C语言规则有点古怪,但它们是无害的.但是,当扩展到C++中的类时,这些相同的规则会为要爬行的错误打开一些小问题.
在C中,名称出现在
struct s { ... };是一个标签.标签名称不是类型名称.鉴于上面的定义,声明如
s x; /* error in C */ s *p; /* error in C */是C中的错误.你必须把它们写成
struct s x; /* OK */ struct s *p; /* OK */联合和枚举的名称也是标签而不是类型.
在C中,标记与所有其他名称(对于函数,类型,变量和枚举常量)不同.C编译器在符号表中维护标记,如果没有与包含所有其他名称的表在物理上分离,则在概念上.因此,C程序可以在同一范围内同时具有相同拼写的标签和另一个名称.例如,
struct s s;是一个有效的声明,它声明了struct s类型的变量s.这可能不是一个好习惯,但C编译器必须接受它.我从未见过为什么C是这样设计的理由.我一直认为这是一个错误,但确实如此.
许多程序员(包括你的程序员)更喜欢将结构名称视为类型名称,因此它们使用typedef为标记定义别名.例如,定义
struct s { ... }; typedef struct s S;让你用S代替struct,就像在
S x; S *p;程序不能使用S作为类型和变量(或函数或枚举常量)的名称:
S S; // error这很好.
struct,union或enum定义中的标记名称是可选的.许多程序员将结构定义折叠到typedef中并完全取消标记,如下所示:
typedef struct { ... } S;
链接的文章还讨论了不需要的C++行为如何typedef
导致隐藏的名称隐藏问题.为了防止出现这些问题,typedef
使用C++编写类和结构也是一个好主意,即使乍一看似乎没必要.在C++中,typedef
隐藏名称会成为编译器告诉您的错误,而不是隐藏的潜在问题源.
使用typedef
避免struct
每次声明该类型的变量时都必须写入:
struct elem { int i; char k; }; elem user; // compile error! struct elem user; // this is correct
总是输入定义枚举和结构的另一个好理由是这个问题的结果:
enum EnumDef { FIRST_ITEM, SECOND_ITEM }; struct StructDef { enum EnuumDef MyEnum; unsigned int MyVar; } MyStruct;
注意结构中EnumDef中的拼写错误(Enu u mDef)?这编译没有错误(或警告),并且(取决于C标准的字面解释)是正确的.问题是我刚刚在我的struct中创建了一个新的(空)枚举定义.我不是(按预期)使用之前的定义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 typedef'ing结构和枚举.
不仅要保存一些打字(没有双关语;)),而是因为它更安全.
Linux内核编码风格第5章给出了使用的优点和缺点(主要是利弊)typedef
.
请不要使用"vps_t"之类的东西.
将typedef用于结构和指针是错误的.当你看到一个
vps_t a;在源头,这是什么意思?
相反,如果它说
struct virtual_container *a;你实际上可以告诉"a"是什么.
很多人认为typedef"有助于提高可读性".不是这样.它们仅适用于:
(a)完全不透明的对象(其中typedef主动用于隐藏对象的内容).
示例:"pte_t"等不透明对象,您只能使用正确的访问器函数进行访问.
注意!不透明和"访问者功能"本身并不好.我们有他们喜欢的事情pte_t等,其原因是,真的是绝对的零的可访问信息.
(b)清除整数类型,其中抽象有助于避免混淆,无论它是"int"还是"long".
u8/u16/u32是完美的typedef,虽然它们比(d)更适合这里.
注意!再次 - 需要有一个原因.如果某些东西是"无符号长",那么没有理由这样做
typedef unsigned long myflags_t;但是如果有明确的理由说明为什么它在某些情况下可能是"unsigned int"并且在其他配置下可能是"unsigned long",那么一定要继续使用typedef.
(c)当你使用sparse从字面上创建一个新类型进行类型检查时.
(d)在某些特殊情况下与标准C99类型相同的新类型.
虽然只需要很短的时间让眼睛和大脑习惯于像'uint32_t'这样的标准类型,但有些人反对使用它们.
因此,允许使用特定于Linux的"u8/u16/u32/u64"类型及其与标准类型相同的带符号等效项 - 尽管它们在您自己的新代码中不是必需的.
编辑已使用一个或另一组类型的现有代码时,应该符合该代码中的现有选项.
(e)在用户空间中安全使用的类型.
在用户空间可见的某些结构中,我们不能要求C99类型,也不能使用上面的"u32"形式.因此,我们在与用户空间共享的所有结构中使用__u32和类似的类型.
也许还有其他情况,但规则应该基本上是永远不要使用typedef,除非你能清楚地匹配其中一个规则.
通常,具有可以合理地直接访问的元素的指针或结构永远不应该是typedef.
我不认为使用typedef可以进行前向声明.使用struct,enum和union允许在依赖(知道)是双向时转发声明.
风格:在C++中使用typedef非常有意义.处理需要多个和/或可变参数的模板时几乎是必要的.typedef有助于保持命名直线.
在C编程语言中并非如此.使用typedef最常见的目的不是为了混淆数据结构的使用.由于只有{struct(6),enum(4),union(5)}数量的击键用于声明数据类型,因此几乎没有用于结构的别名.该数据类型是联合或结构吗?使用简单的非typdefed声明可以让您立即知道它是什么类型.
注意如何编写Linux,严格避免使用这种别名的废话类型.结果是简约而干净的风格.
事实证明,有利有弊.一个有用的信息来源是开创性的书"专家C编程"(第3章).简而言之,在C中,您有多个名称空间:标签,类型,成员名称和标识符.typedef
为类型引入别名并将其定位在标记名称空间中.也就是说,
typedef struct Tag{ ...members... }Type;
定义了两件事.标记命名空间中的一个标记和类型命名空间中的一个类型.所以,你都可以做Type myType
和struct Tag myTagType
.声明喜欢struct Type myType
或Tag myTagType
非法.另外,在这样的声明中:
typedef Type *Type_ptr;
我们定义了一个指向Type的指针.所以如果我们宣布:
Type_ptr var1, var2; struct Tag *myTagType1, myTagType2;
然后var1
,var2
并myTagType1
指向Type但myTagType2
不是.
在上面提到的书中,它提到typedefing结构不是很有用,因为它只能使程序员免于编写单词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; ... }
因此,您可以flags
通过内部struct(MyPipe
)通过强制转换访问外部成员().对我来说,抛出整个类型比(struct MyWriter_ *) s;
每次想要执行此类功能时更容易混淆.在这些情况下,简短的引用是一个大问题,特别是如果你在代码中大量使用这种技术.
最后,typedef
与宏相比,ed类型的最后一个方面是无法扩展它们.例如,您有:
#define X char[10] or typedef char Y[10]
然后你可以宣布
unsigned X x; but not unsigned Y y;
我们并不真正关心结构,因为它不适用于存储说明符(volatile
和const
).