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

时间:2008-10-31 07:14:03

标签: c struct typedef

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

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

elem user;

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

15 个答案:

答案 0 :(得分:423)

正如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本身,但实际命名它对于您想要提供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+中广泛使用的一种技术。

UPDATE 请注意,还有一些备受推崇的C项目,其中使用typedef来隐藏struct被认为是一个坏主意,Linux内核可能是最多的众所周知的这样的项目。请参阅The Linux Kernel CodingStyle document第5章,了解Linus的愤怒话语。 :)我的观点是,问题中的“应该”可能并非一成不变。

答案 1 :(得分:198)

令人惊讶的是,有多少人犯了这个错误。请不要在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定义。如果它不试图取消引用foo结构的'bar'成员,则不需要包含“bar.h”文件。

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

struct foo *foo;

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

由于名称空间是分开的,因此命名变量与其结构标记名称不一致。

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

答案 2 :(得分:135)

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或中的标记名称   枚举定义是可选的。许多   程序员折叠结构定义   进入typedef并免除   完全标记,如:

typedef struct
    {
    ...
    } S;

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

答案 3 :(得分:58)

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

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

答案 4 :(得分:38)

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

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结构和枚举。

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

答案 5 :(得分:29)

Linux kernel coding style第5章给出了使用typedef的优点和缺点(主要是利弊)。

  

请不要使用" vps_t"等内容。

     

错误将typedef用于结构和指针。当你看到

vps_t a;
     

在来源中,它是什么意思?

     

相反,如果它说

struct virtual_container *a;
     

你实际上可以告诉他们" a"是

     

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

     

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

     

示例:" pte_t"等等只能使用正确的访问器功能访问的不透明对象。

     

请注意!不透明和"存取功能"他们自己并不好。我们将它们用于pte_t之类的原因是因为那里确实存在可移植的信息。

     

(b)清除整数类型,其中抽象帮助避免混淆,无论它是" int"或"长"。

     

u8 / u16 / u32是完美的typedef,尽管它们比(d)更适合这里。

     

请注意!再次 - 这需要一个原因。如果某事是"未签名的长",那么就没有理由

typedef unsigned long myflags_t;
     

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

     

(c)当你使用稀疏字面创建 new 类型进行类型检查时。

     

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

     

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

     

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

     

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

     

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

     

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

     

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

     

通常,指针或具有可以合理直接访问的元素的结构永远不会成为typedef。

答案 6 :(得分:10)

我认为使用typedef甚至无法进行前向声明。当依赖关系(知道)是双向的时,struct,enum和union的使用允许转发声明。

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

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

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

答案 7 :(得分:10)

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

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

定义了两件事。标记命名空间中的一个标记和类型命名空间中的一个类型。因此,您可以同时执行Type myTypestruct Tag myTagTypestruct Type myTypeTag myTagType等声明是非法的。另外,在这样的声明中:

typedef Type *Type_ptr;

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

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

然后var1var2myTagType1是指向类型的指针,但myTagType2不是。

在上面提到的书中,它提到typedefing结构不是很有用,因为它只会使程序员不会编写单词struct。但是,我和许多其他C程序员一样反对。虽然它有时会变得混淆一些名称(这就是为什么在内核这样的大型代码库中不可取),但是当你想在C中实现多态时它会帮助很多look here for details。例如:

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;更容易混淆。在这些情况下,简短的引用是一个大问题,特别是如果你在代码中大量使用这种技术。

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

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

然后你可以声明

unsigned X x; but not
unsigned Y y;

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

答案 8 :(得分:4)

让我们从基础知识开始,继续努力。

以下是结构定义的示例:

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。如果您打算稍后在程序中使用相同的蓝图创建新的Structure(Structure是自定义数据类型),在定义期间使用typedef可能是一个好主意,因为您可以保存一些输入

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

Points first_point, second_point;

命名自定义类型时要小心

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

答案 9 :(得分:3)

您(可选)给出结构的名称称为标记名称,并且如上所述,它本身不是类型。要获取该类型,需要使用struct前缀。

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


    struct MyStruct
    {
      int i;
    };

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

答案 10 :(得分: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;

究竟是什么意思?

答案 11 :(得分:1)

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

B个 结构定义类型。结构允许方便地将一组变量分组,以便于处理(K& R,pg127,C prog lang。)作为单个单元

c取代; 在上面的A中解释了typedef的结构。

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

答案 12 :(得分:0)

在'C'编程语言中,关键字'typedef'用于为某个对象(struct,array,function..enum类型)声明一个新名称。例如,我将使用'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'某些''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);

}

如果你有一些将在整个程序中使用的本地对象(struct,array,有价值),你可以使用'typedef'给它命名。

答案 13 :(得分:0)

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

我不是说他们应该改变(可能有两个C选项)如果要求改变了,我们这些在网站上接受采访的人将是SOL。

答案 14 :(得分:-2)

总之,在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编译器处理它,它可以像他想要的那样优化汇编程序代码实现。它也不会像préprocessor那样愚蠢地使用类型的成员使用结构但是使用更复杂的参考构造算法,所以构造如下:

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

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

被授予并且功能齐全。此实现还允许访问编译器类型转换,并在执行线程离开初始化函数的应用程序字段时删除一些窃听效果。

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