我们为什么要在C中经常输入一个结构?

Man*_*bts 379 c struct typedef

我见过许多程序,包括如下所示的结构

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

elem user;
Run Code Online (Sandbox Code Playgroud)

为什么经常这么需要?任何具体原因或适用范围?

unw*_*ind 437

正如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;
}
Run Code Online (Sandbox Code Playgroud)

当您不需要在整个地方看到"struct"关键字时,它变得更干净,它看起来更像是您的语言中确实存在一个名为"Point"的类型.在typedef我猜之后的情况是这样的.

另请注意,虽然您的示例(和我的)省略了命名struct 本身,但实际命名它对于您想要提供opaque类型时也很有用.然后你在标题中有这样的代码,例如:

typedef struct Point Point;

Point * point_new(int x, int y);
Run Code Online (Sandbox Code Playgroud)

然后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;
}
Run Code Online (Sandbox Code Playgroud)

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

更新请注意,还有一些备受推崇的C项目,其中使用typedef隐藏struct被认为是一个坏主意,Linux内核可能是最知名的此类项目.有关Linus的愤怒话语,请参阅Linux Kernel CodingStyle文档的第5章.:)我的观点是,问题中的"应该"可能不是一成不变的.

  • 您不应该使用带有下划线后跟大写字母的标识符,它们是保留的(请参阅第7.1.3节第1段).虽然不太可能是一个问题,但在使用它们时,技术上是不确定的行为(7.1.3第2段). (56认同)
  • @dreamlax:如果其他人不清楚,那只是_starting_带有下划线和大写的标识符,你不应该这样做; 你可以在标识符的中间自由使用它. (15认同)
  • 有趣的是,这里给出的例子(其中typedef防止使用"struct""遍布整个地方")实际上比没有typedef的相同代码长,因为它只保存了一个单词"struct"的使用.获得的抽象的smidgen很少与额外的混淆相比有利. (11认同)
  • 有趣的是,Linux内核编码指南说我们应该对使用typedef更加保守(第5节):https://www.kernel.org/doc/Documentation/CodingStyle (9认同)
  • @Rerito fyi,[C99草案]第166页(http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf),*所有以下划线开头的识别符大写字母或另一个下划线总是保留用于任何用途.*和*所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符.* (7认同)
  • @Milhous在我看来,这只是原因.一个`Point`不是几兆字节.返回值对于语义和可读性来说是非常棒的,并且超级超级防守并且思考"哦,不,有些程序操纵的数据太大而无法返回,让**永远不会**返回值"对我来说毫无意义.使用正确的工具等. (6认同)
  • @alternative:在某些实现中,像`FILE`这样的类型会更好地作为结构; 在其他人中,作为一个工会会更好.如果在一个特定的实现中,它会更好地作为一个联合,让客户端代码说`struct FILE*`到处都会强制实现将联合包装在一个无用的单成员结构中. (5认同)
  • 任何使用类似这样的typedef的人都是傻瓜,我讨厌这样的人,你甚至不能删除typedef ....它没有提供抽象,是最无用的东西.请永远不要这样做,如果必须,请将神dang typedef放在一个单独的标题中,不要强迫我使用它.因为我不想要他们. (3认同)
  • @alternative:为什么只有"标准"库?我认为通过将`union`作为外部成员,并且在其他实现中使用`struct`,可以在某些实现中最好地服务于许多类型的库.我个人认为如果不是使用联合类型会更好,C提供了更详细的结构布局控制(例如`struct quad {unsigned long l; unsigned char b [4] @ l;}`)在这种情况下这个问题没有实际意义[实际上,C仍然可以从中受益],但这显然不是发生的事情. (3认同)
  • @Milhous完全没有,我正在返回一个值,而不是一个指针. (3认同)
  • @unwind +1 值返回函数还有一个好处,即它们可以标记为 __attribute__((const)) 或 pure,这两种方法都不能很好地与通过指针参数返回的函数配合使用。但不幸的是,普通 C 编译器在这方面并没有很好地实现 RVO(根本没有)。我玩过一次,由于避免了 RVO 复制,g++ 按值返回速度快了几个百分点。 (2认同)

小智 199

令人惊讶的是有多少人弄错了.请不要在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
Run Code Online (Sandbox Code Playgroud)

有了这样的定义,不使用typedef,compiland单元可以包含foo.h来获得FOO_DEF定义.如果它不试图取消引用结构的"bar"成员,foo则不需要包含"bar.h"文件.

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

struct foo *foo;

printf("foo->bar = %p", foo->bar);
Run Code Online (Sandbox Code Playgroud)

由于命名空间是独立的,因此命名变量与其struct标记名称不一致.

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

  • 确切地说,一次又一次地输入"struct"会带来什么好处?说到污染,你为什么要在全局命名空间中有一个结构和一个具有相同名称的函数/变量/ typedef(除非它是同一函数的typedef)?安全模式是使用`typedef struct X {...} X`.这样你就可以使用简短形式`X`来定义可用定义的结构,但仍然可以在需要时转发声明并使用`struct X`. (57认同)
  • 更令人惊讶的是,在给出这个答案后的13个月,我是第一个赞成它的人!typedef'ing structs是C中最大的滥用之一,并且在编写良好的代码中没有位置.typedef对于去混淆复杂函数指针类型很有用,并且实际上没有其他用处. (35认同)
  • Linux内核编码样式明确禁止typedefing结构.第5章:Typedef:"对于结构和指针使用typedef是一个_mistake_." http://www.kernel.org/doc/Documentation/CodingStyle (31认同)
  • 彼得·范德林登(Peter van der Linden)在他的启发性着作"专家C编程 - 深度C秘密"(Expert C Programming - Deep C Secrets)中也提出了反对类型定义结构的案例.要点是:你想知道某些东西是结构或联合,而不是隐藏它. (28认同)
  • 我个人很少,如果使用typedef,我不会说其他人不应该使用它,它只是不是我的风格.我喜欢在变量类型之前看到一个结构,所以我立即知道它的结构.更容易键入的参数有点蹩脚,有一个单个字母的变量也更容易键入,也有自动完成现在在任何地方键入struct有多难. (6认同)
  • 抱歉,添加了 -1 :( 因为 typedef-ing 不会阻止您提到的任何好事,例如在 header opaque.h 中:`typedef struct Opaque Opaque; extern Opaque *OpConstructor (void); extern void OpDestructor (Opaque * );` 然后在 opaque.c 中:`struct Opaque { 实际定义 };` (6认同)
  • @ user3629249应该不会太难.前者总是以`struct`关键字开头. (5认同)
  • 啊,是的,非常可读的“struct foo *foo;”。有时我想知道 C 程序员的情况。 (3认同)
  • @JanusTroelsen 是的,我现在明白了:它应该与`struct foo { Bar *bar; 形成对比。};` 需要 `foo.h` 包含 `bar.h`。 (2认同)
  • 这一行:';typedef struct Point Point;' 显然,我的“观点”是关于不要对结构进行 typedef 的。第一个“点”是结构标记名称。第二个“点”是“新”类型。现在,您、编译器和其他任何阅读代码的人都需要跟踪“点”的含义。 (2认同)
  • “一遍又一遍地键入“struct”到底有什么好处?” --- 通过大量运动保持手指健康。 (2认同)

Mic*_*urr 137

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


命名结构的C语言规则有点古怪,但它们是无害的.但是,当扩展到C++中的类时,这些相同的规则会为要爬行的错误打开一些小问题.

在C中,名称出现在

struct s
    {
    ...
    };
Run Code Online (Sandbox Code Playgroud)

是一个标签.标签名称不是类型名称.鉴于上面的定义,声明如

s x;    /* error in C */
s *p;   /* error in C */
Run Code Online (Sandbox Code Playgroud)

是C中的错误.你必须把它们写成

struct s x;     /* OK */
struct s *p;    /* OK */
Run Code Online (Sandbox Code Playgroud)

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

在C中,标记与所有其他名称(对于函数,类型,变量和枚举常量)不同.C编译器在符号表中维护标记,如果没有与包含所有其他名称的表在物理上分离,则在概念上.因此,C程序可以在同一范围内同时具有相同拼写的标签和另一个名称.例如,

struct s s;
Run Code Online (Sandbox Code Playgroud)

是一个有效的声明,它声明了struct s类型的变量s.这可能不是一个好习惯,但C编译器必须接受它.我从未见过为什么C是这样设计的理由.我一直认为这是一个错误,但确实如此.

许多程序员(包括你的程序员)更喜欢将结构名称视为类型名称,因此它们使用typedef为标记定义别名.例如,定义

struct s
    {
    ...
    };
typedef struct s S;
Run Code Online (Sandbox Code Playgroud)

让你用S代替struct,就像在

S x;
S *p;
Run Code Online (Sandbox Code Playgroud)

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

S S;    // error
Run Code Online (Sandbox Code Playgroud)

这很好.

struct,union或enum定义中的标记名称是可选的.许多程序员将结构定义折叠到typedef中并完全取消标记,如下所示:

typedef struct
    {
    ...
    } S;
Run Code Online (Sandbox Code Playgroud)

链接的文章还讨论了不需要的C++行为如何typedef导致隐藏的名称隐藏问题.为了防止出现这些问题,typedef使用C++编写类和结构也是一个好主意,即使乍一看似乎没必要.在C++中,typedef隐藏名称会成为编译器告诉您的错误,而不是隐藏的潜在问题源.

  • 标签名称与非标签名称相同的一个示例是(POSIX或Unix)程序,其中包含[`int stat(const char*restrict path,struct stat*restrict buf)`](http:// pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html)功能.在普通名称空间中有一个函数`stat`,在标签名称空间中有`struct stat`. (2认同)
  • 您的声明,SS;//错误....错误效果很好。我的意思是您的声明“我们不能为typedef标签和var使用相同的名称”是错误的...请检查 (2认同)

Gre*_*ill 61

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

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
Run Code Online (Sandbox Code Playgroud)

  • 这不是C编译器中的一个小故障,它是设计的一部分.他们为C++改变了这一点,我认为这会使事情变得更容易,但这并不意味着C的行为是错误的. (38认同)
  • Manoj,标记名称("struct foo")在需要定义引用自身的结构时是必需的.例如链表中的"下一个"指针.更重要的是,编译器实现了标准,这就是标准所要做的. (4认同)
  • 不幸的是,许多"程序员"定义了一个结构然后用一些"不相关"的名称来键入它(比如struct myStruct ...; typedef struct myStruct susan*;在几乎所有的实例中,typedef只会导致代码混乱,隐藏实际的定义一个变量/参数,并错误地引导每个人,包括代码的原始作者. (4认同)
  • 好的,我们在 C++ 中没有这个问题。那么为什么没有人从 C 的编译器中删除那个小故障并使其与 C++ 中的相同。好吧,C++ 有一些不同的应用领域,因此它具有高级功能。但是我们能否在不改变 C 语言的情况下继承其中一些原来的 C? (2认同)

csc*_*hol 38

总是输入定义枚举和结构的另一个好理由是这个问题的结果:

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

struct StructDef
{
  enum EnuumDef MyEnum;
  unsigned int MyVar;
} MyStruct;
Run Code Online (Sandbox Code Playgroud)

注意结构中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) */
Run Code Online (Sandbox Code Playgroud)

我会提倡ALWAYS typedef'ing结构和枚举.

不仅要保存一些打字(没有双关语;)),而是因为它更安全.

  • 更糟糕的是,您的错字可能与不同的标签相吻合。在结构体的情况下,这可能导致整个程序正确编译并具有运行时未定义的行为。 (3认同)
  • 这个定义:'typedef {FIRST_ITEM,SECOND_ITEM} EnumDef;' 没有定义枚举.我已经写了数百个巨大的程序,并且不幸地对其他人编写的程序进行维护.从经验的硬手中,在结构上使用typedef只会导致问题.希望程序员不会因为在声明结构实例时输入完整定义而遇到问题.C不是Basic,因此输入更多字符对程序的操作没有害处. (3认同)
  • 那个例子不编译,我也不期望它.编译Debug/test.o test.c:10:17:错误:字段有不完整类型'enum EnuumDef'枚举EnuumDef MyEnum; ^ test.c:10:8:注意:向前声明'enum EnuumDef'枚举EnuumDef MyEnum; 生成^ 1错误.gnuc,std = c99. (3认同)
  • 对于那些对输入超过绝对最小字符数而感到有些厌恶的人,我是否可以建议加入一些试图用最少的击键次数编写应用程序的组.只是不要在工作环境中使用他们的新"技能",尤其是严格执行同行评审的工作环境 (2认同)
  • 在 clang ang gcc 和 c99 中,该示例无法编译。但 Visual Studio 似乎没有抱怨任何事情。http://rextester.com/WDQ5821 (2认同)
  • @RobertSsupportsMonicaCellio 我认为 typedef 与无 typedef 争论的最大问题之一是它完全发生在 PC 计算的背景下。在嵌入式设备领域,C 占据主导地位,我们使用一堆既不是 GCC 也不是 clang 的编译器,其中一些可能很旧,这可能会也可能不会编译得很好 - 这就是问题所在。我在嵌入式开发中使用 typedef 结构的典型流程是,每当我要使用 typedef 声明时,都要查看 typedef 声明,以确保我了解需要什么。我不太清楚你们这些PC玩家是做什么的。 (2认同)

Yu *_*Hao 29

Linux内核编码风格第5章给出了使用的优点和缺点(主要是利弊)typedef.

请不要使用"vps_t"之类的东西.

将typedef用于结构和指针是错误的.当你看到一个

vps_t a;
Run Code Online (Sandbox Code Playgroud)

在源头,这是什么意思?

相反,如果它说

struct virtual_container *a;
Run Code Online (Sandbox Code Playgroud)

你实际上可以告诉"a"是什么.

很多人认为typedef"有助于提高可读性".不是这样.它们仅适用于:

(a)完全不透明的对象(其中typedef主动用于隐藏对象的内容).

示例:"pte_t"等不透明对象,您只能使用正确的访问器函数进行访问.

注意!不透明和"访问者功能"本身并不好.我们有他们喜欢的事情pte_t等,其原因是,真的是绝对的零的可访问信息.

(b)清除整数类型,其中抽象有助于避免混淆,无论它是"int"还是"long".

u8/u16/u32是完美的typedef,虽然它们比(d)更适合这里.

注意!再次 - 需要有一个原因.如果某些东西是"无符号长",那么没有理由这样做

typedef unsigned long myflags_t;
Run Code Online (Sandbox Code Playgroud)

但是如果有明确的理由说明为什么它在某些情况下可能是"unsigned int"并且在其他配置下可能是"unsigned long",那么一定要继续使用typedef.

(c)当你使用sparse从字面上创建一个类型进行类型检查时.

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

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

因此,允许使用特定于Linux的"u8/u16/u32/u64"类型及其与标准类型相同的带符号等效项 - 尽管它们在您自己的新代码中不是必需的.

编辑已使用一个或另一组类型的现有代码时,应该符合该代码中的现有选项.

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

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

也许还有其他情况,但规则应该基本上是永远不要使用typedef,除非你能清楚地匹配其中一个规则.

通常,具有可以合理地直接访问的元素的指针或结构永远不应该是typedef.

  • @Yawar我刚读了这篇文章并且有着完全相同的想法.当然,C不是面向对象的,但抽象仍然是一件事. (5认同)
  • '不透明和"访问者功能"本身并不好'.有人可以解释原因吗?我认为信息隐藏和封装将是一个非常好的主意. (4认同)

nat*_*soz 11

我不认为使用typedef可以进行前向声明.使用struct,enum和union允许在依赖(知道)是双向时转发声明.

风格:在C++中使用typedef非常有意义.处理需要多个和/或可变参数的模板时几乎是必要的.typedef有助于保持命名直线.

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

注意如何编写Linux,严格避免使用这种别名的废话类型.结果是简约而干净的风格.

  • 不,我们*关心它是一个结构或联合,而不是一个枚举或一些原子类型.你不能将结构强制转换为整数或指针(或者指向任何其他类型),这有时你需要存储一些上下文.使用'struct'或'union'关键字可以改善推理的局部性.没有人说你需要知道*里面*结构. (11认同)
  • Clean不会重复`struct`everywhere ... Typedef创建新类型.你用什么?类型.我们不关心*如果它是结构,联合或枚举,那就是我们输入它的原因. (10认同)
  • @supercat FILE是一个很好用的typedef.我认为typedef被过度使用*,而不是它是语言的错误.恕我直言,使用typedef的一切都是"猜测过度一般"的代码气味.请注意,您将变量声明为FILE*foo,而不是FILE foo.对我来说,这很重要. (4认同)
  • @supercat:“如果文件标识变量的类型是 FILE 而不是 FILE* ...”但这正是 typedefs 启用的歧义!我们只是习惯于 fopen 获取一个 FILE * 所以我们不担心它,但是每次添加 typedef 时,您都会引入另一点认知开销:这个 API 需要 foo_t args 还是 foo_t *?如果以每个函数定义多几个字符为代价,显式携带“结构”可以提高推理的局部性。 (4认同)
  • @BerndJendrissek:结构体和联合体与其他类型不同,但是客户端代码是否应该关心这两个事物(结构体或联合体)中的哪一个(结构体或联合体)像“FILE”这样的东西? (3认同)

use*_*288 10

事实证明,有利有弊.一个有用的信息来源是开创性的书"专家C编程"(第3章).简而言之,在C中,您有多个名称空间:标签,类型,成员名称和标识符.typedef为类型引入别名并将其定位在标记名称空间中.也就是说,

typedef struct Tag{
...members...
}Type;
Run Code Online (Sandbox Code Playgroud)

定义了两件事.标记命名空间中的一个标记和类型命名空间中的一个类型.所以,你都可以做Type myTypestruct Tag myTagType.声明喜欢struct Type myTypeTag myTagType非法.另外,在这样的声明中:

typedef Type *Type_ptr;
Run Code Online (Sandbox Code Playgroud)

我们定义了一个指向Type的指针.所以如果我们宣布:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;
Run Code Online (Sandbox Code Playgroud)

然后var1,var2myTagType1指向Type但myTagType2不是.

在上面提到的书中,它提到typedefing结构不是很有用,因为它只能使程序员免于编写单词struct.但是,我和许多其他C程序员一样反对.虽然它有时会变得混淆一些名称(这就是为什么它不适合像内核这样的大型代码库)当你想在C中实现多态时,它有助于在这里查看详细信息.例:

typedef struct MyWriter_t{
    MyPipe super;
    MyQueue relative;
    uint32_t flags;
...
}MyWriter;
Run Code Online (Sandbox Code Playgroud)

你可以做:

void my_writer_func(MyPipe *s)
{
    MyWriter *self = (MyWriter *) s;
    uint32_t myFlags = self->flags;
...
}
Run Code Online (Sandbox Code Playgroud)

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

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

#define X char[10] or
typedef char Y[10]
Run Code Online (Sandbox Code Playgroud)

然后你可以宣布

unsigned X x; but not
unsigned Y y;
Run Code Online (Sandbox Code Playgroud)

我们并不真正关心结构,因为它不适用于存储说明符(volatileconst).

  • `MyPipe *s; MyWriter *self = (MyWriter *) s;` 并且您刚刚打破了严格的别名。 (2认同)

Asi*_*sif 8

让我们从基础开始,然后逐步提高。

这是结构定义的示例:

struct point
  {
    int x, y;
  };
Run Code Online (Sandbox Code Playgroud)

这里的名称point是可选的。

结构可以在其定义期间或之后声明。

在定义期间声明

struct point
  {
    int x, y;
  } first_point, second_point;
Run Code Online (Sandbox Code Playgroud)

定义后声明

struct point
  {
    int x, y;
  };
struct point first_point, second_point;
Run Code Online (Sandbox Code Playgroud)

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

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

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

Points first_point, second_point;
Run Code Online (Sandbox Code Playgroud)

命名自定义类型时的注意事项

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


phi*_*red 6

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

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

struct MyStruct
{
  int i;
};

// The following is legal in C++:
MyStruct obj;
obj.i = 7;
Run Code Online (Sandbox Code Playgroud)