首页 文章

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

提问于
浏览
337

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

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

elem user;

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

15 回答

  • 54

    总之,在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类比孤独的结构更接近 .

  • 128

    正如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类型时也很有用 . 然后你在 Headers 中有这样的代码,例如:

    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值,因为它的定义对头文件的用户是隐藏的 . 例如,这是一种在_790778中广泛使用的技术 .

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

  • 1

    令人惊讶的是,有多少人犯了这个错误 . 请不要在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 结构的 foo 成员,则不需要包含"bar.h"文件 .

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

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

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

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

  • 36

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


    命名结构的C语言规则有点古怪,但它们是无害的 . 但是,当扩展到C中的类时,这些相同的规则会为要爬过的错误打开一些小问题 . 在C中,名称出现在struct中
    ;
    是一个标签 . 标签名称不是类型名称 . 鉴于上面的定义,声明如s x; / * C * /中的错误
    s * p; / * C * /中的错误
    是C中的错误 . 你必须把它们写成struct s x; /* 好 /
    struct s * p; /
    好 */
    联合和枚举的名称也是标签而不是类型 . 在C中,标记与所有其他名称(对于函数,类型,变量和枚举常量)不同 . C编译器在符号表中维护标记,如果没有与包含所有其他名称的表在物理上分离,则在概念上 . 因此,C程序可以在同一范围内同时具有相同拼写的标签和另一个名称 . 例如,struct s s;
    是一个有效的声明,它声明了struct s类型的变量s . 这可能不是一个好习惯,但C编译器必须接受它 . 我从未见过为什么C是这样设计的理由 . 我一直认为这是一个错误,但它确实存在 . 许多程序员(包括你的程序员)更喜欢将结构名称视为类型名称,因此它们使用typedef为标记定义别名 . 例如,定义struct
    ;
    typedef struct s S;
    让你用S代替struct,就像在S x中一样;
    S * p;
    程序不能使用S作为类型和变量(或函数或枚举常量)的名称:S S; //错误
    这很好 . struct,union或enum定义中的标记名称是可选的 . 许多程序员将结构定义折叠到typedef中并完全省略了标签,如:typedef struct
    {
    ...
    S;


    链接的文章还讨论了不需要 typedef 的C行为如何导致细微的名称隐藏问题 . 为了防止出现这些问题,尽管乍一看似乎没必要,但是你也可以在C语言中使用你的类和结构 . 在C中,使用 typedef 名称隐藏成为编译器告诉您的错误,而不是隐藏的潜在问题源 .

  • -1

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

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

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

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

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

  • 9

    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”还是“long” . u8 / u16 / u32是完美的typedef,尽管它们比(d)更适合这里 . 注意!再次 - 需要有一个理由 . 如果某些东西是“unsigned long”,那么没有理由做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 .

  • 4

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

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

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

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

  • 0

    事实证明,有利有弊 . 一个有用的信息来源是开创性的书"Expert C Programming"(Chapter 3) . 简而言之,在C中,您有多个名称空间: tags, types, member names and identifiers . typedef 为类型引入了别名,并将其定位在标记名称空间中 . 也就是说,

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

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

    typedef Type *Type_ptr;
    

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

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

    那么 var1var2myTagType1 是Type的指针,但 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;
    ...
    }
    

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

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

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

    然后你可以申报

    unsigned X x; but not
    unsigned Y y;
    

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

  • 3

    让我们从基础知识入手,继续努力 .

    以下是结构定义的示例:

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

  • 29

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

    抛开GTK,我不确定标记名是否与结构类型的typedef一样常用,所以在C中可以识别并且你可以省略struct关键字并使用标记名作为类型名称:

    struct MyStruct
        {
          int i;
        };
    
        // The following is legal in C++:
        MyStruct obj;
        obj.i = 7;
    
  • 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;
    

    究竟是什么意思呢?

  • 189

    A> a typdef通过允许 creation of more meaningful synonyms for data types 来帮助程序的含义和文档 . 此外,它们还有助于针对可移植性问题参数化程序(K&R,pg147,C prog lang) .

    B> a structure defines a type . Structs允许方便地对一组变量进行分组,以便于处理(K&R,pg127,C prog lang . )作为一个单元

    C> typedef结构在上面的A中解释 .

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

  • 0

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

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

  • 400

    在'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);
    
    }
    

    如果您有一些将在整个程序中使用的本地对象(结构,数组,有 Value ),您只需使用'typedef'为其命名即可 .

相关问题