496

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

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

elem user;

为什么经常需要它?有什么具体原因或适用范围吗?

4

15 回答 15

532

正如 Greg Hewgill 所说,typedef 意味着您不再需要到处写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 内核可能是最知名的此类项目。请参阅Linux Kernel CodingStyle 文档的第 5 章了解 Linus 的愤怒言论。:) 我的观点是,问题中的“应该”毕竟可能不是一成不变的。

于 2008-10-31T07:37:05.737 回答
232

令人惊讶的是有多少人犯了这个错误。请不要在 C 中 typedef 结构,它会不必要地污染全局命名空间,这通常在大型 C 程序中已经被严重污染。

此外,没有标签名称的 typedef 结构是不必要地在头文件之间强加排序关系的主要原因。

考虑:

#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,编译单元可以包含 foo.h 来获得FOO_DEF定义。如果它不尝试取消引用foo结构的“bar”成员,则不需要包含“bar.h”文件。

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

struct foo *foo;

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

由于命名空间是独立的,因此在命名变量与其结构标记名称一致时不会发生冲突。

如果我必须维护您的代码,我将删除您的 typedef 结构。

于 2010-12-30T21:20:44.927 回答
151

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


命名结构的 C 语言规则有点古怪,但它们是无害的。然而,当扩展到 C++ 中的类时,这些相同的规则打开了一些小裂缝,让 bug 爬过。

在 C 中,名字 s 出现在

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,如

S x;
S *p;

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

S S;    // error

这很好。

结构、联合或枚举定义中的标记名称是可选的。许多程序员将 struct 定义折叠到 typedef 中并完全省去标记,如下所示:

typedef struct
    {
    ...
    } S;

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

于 2008-10-31T17:12:55.430 回答
69

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

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
于 2008-10-31T07:16:58.380 回答
46

总是 typedef 枚举和结构的另一个很好的理由是由这个问题引起的:

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

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

注意到 struct (Enu u mDef) 中 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 typedef'ing 结构和枚举。

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

于 2009-03-31T00:00:32.773 回答
33

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 是非常好的类型定义,尽管它们比这里更适合 (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。

于 2013-08-07T14:19:02.913 回答
17

事实证明,有利有弊。一个有用的信息来源是开创性的书“Expert C Programming”(第 3 章)。简而言之,在 C 中,您有多个名称空间:标签、类型、成员名称和标识符typedef为类型引入别名并将其定位在标签命名空间中。即,

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

定义了两件事。标签命名空间中的一个 Tag 和类型命名空间中的一个 Type。所以你可以同时做Type myTypestruct Tag myTagType。声明喜欢struct Type myTypeorTag myTagType是非法的。此外,在这样的声明中:

typedef Type *Type_ptr;

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

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

thenvar1和是指向 Type 但不是var2的指针。myTagType1myTagType2

在上面提到的书中,它提到 typedefing structs 不是很有用,因为它只是使程序员免于编写单词 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因此,您可以通过强制转换通过内部结构 ( )访问外部成员 ( ) MyPipe。对我来说,转换整个类型比(struct MyWriter_ *) s;每次想要执行此类功能时都更容易混淆。在这些情况下,简短的引用很重要,尤其是当您在代码中大量使用该技术时。

最后,与宏相比,ed 类型的最后一个方面typedef是无法扩展它们。例如,如果您有:

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

然后你可以声明

unsigned X x; but not
unsigned Y y;

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

于 2013-08-11T15:48:27.143 回答
13

我认为 typedef 甚至不可能进行前向声明。当依赖项(知道)是双向的时,使用 struct、enum 和 union 允许转发声明。

风格:在 C++ 中使用 typedef 很有意义。在处理需要多个和/或可变参数的模板时,它几乎是必要的。typedef 有助于保持命名的正确性。

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

请注意,Linux 是如何在严格避免 typedef 带来的这种别名胡说八道的情况下编写的。结果是简约和干净的风格。

于 2010-05-29T07:12:11.727 回答
8

让我们从基础开始,逐步向上。

这是结构定义的示例:

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 来表示标准库类型名称。

于 2018-03-09T18:16:39.807 回答
3

您(可选地)为结构命名的名称称为标记名称,并且如前所述,它本身不是类型。要获取类型需要结构前缀。

除了 GTK+,我不确定标记名是否像结构类型的 typedef 一样普遍使用,因此在 C++ 中可以识别,您可以省略 struct 关键字并将标记名也用作类型名称:


    struct MyStruct
    {
      int i;
    };

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

于 2008-10-31T08:24:40.007 回答
1

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;

那到底有什么意义呢?

于 2016-01-30T15:47:18.497 回答
1

A> typdef 通过允许为数据类型创建更有意义的同义词来帮助解释程序的含义和文档。此外,它们有助于针对可移植性问题(K&R、pg147、C prog lang)对程序进行参数化。

B> 一个结构定义了一个类型。Structs 允许对一组 var 进行方便的分组,以便于将(K&R、pg127、C prog lang.)作为一个单元进行处理

C> typedef'ing a struct 在上面的 A 中解释。

D> 对我来说,结构是自定义类型或容器或集合或命名空间或复杂类型,而 typdef 只是创建更多昵称的一种手段。

于 2017-07-05T00:19:12.497 回答
0

在“C”编程语言中,关键字“typedef”用于为某些对象(结构、数组、函数..枚举类型)声明一个新名称。例如,我将使用“struct-s”。在“C”中,我们经常在“main”函数之外声明一个“struct”。例如:

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”给它一个名称。

于 2016-06-11T00:50:08.283 回答
0

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

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

于 2016-11-02T09:34:21.423 回答
-3

根本上,在C语言中,struct/union/enum都是C语言预处理器处理的宏指令(不要误认为预处理器处理“#include”等)

所以 :

struct a
{
   int i;
};

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

struct b 被用作这样的东西:

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

因此,在编译时它在堆栈上演变为: b: int ai int i int j

这也是为什么很难拥有自引用结构,C 预处理器在无法终止的声明循环中循环。

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

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

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

被允许并且功能齐全。当执行线程离开初始化函数的应用程序字段时,此实现还可以访问编译器类型转换并消除一些错误影响。

这意味着在 C 中 typedef 更接近于 C++ 类而不是单独的结构。

于 2010-03-06T14:54:22.570 回答