C语言中的typedef struct name {...}与typedef struct{...} name;有什么区别?

71

正如标题所述,我有这段代码:

    typedef struct Book{
        int id;
        char title[256];
        char summary[2048];
        int numberOfAuthors;
        struct Author *authors;
    };


    typedef struct Author{
        char firstName[56];
        char lastName[56];
    };


    typedef struct Books{
        struct Book *arr;
        int numberOfBooks;
    };

我从gcc得到以下错误:

bookstore.c:8:2: error: unknown type name ‘Author’
bookstore.c:9:1: warning: useless storage class specifier in empty declaration [enabled by default]
bookstore.c:15:1: warning: useless storage class specifier in empty declaration [enabled by default]
bookstore.c:21:2: error: unknown type name ‘Book’
bookstore.c:23:1: warning: useless storage class specifier in empty declaration [enabled by default]

如果我像这样更改了typedef,则不会出现警告和错误:

    typedef struct{
        char firstName[56];
        char lastName[56];
    } Author;

在搜索了 C语言程序设计(第2版) 并谷歌了几个小时后,我无法弄清楚为什么第一次实现不起作用。


9
将“作者(Author)”移至“书籍(Book)”之前。另外请注意,您的“typedef”是冗余的。 - SomeWittyUsername
仅仅更改 Author 结构体怎么可能就能移除 error:unknown type name ‘Book’ 错误呢?请查看 这里,其中明确说明了使用 typedef 定义结构体和直接定义结构体的区别。 - Dayal rai
6个回答

175
这里有几件事情需要注意。首先,正如其他人所说,编译器对未知类型的抱怨可能是因为在使用它们之前需要先声明类型。但更重要的是要理解以下3个方面的语法:
  1. 结构体类型的定义
  2. 结构体变量的定义和声明
  3. typedef(类型定义)
(请注意,在C编程语言中,定义和声明通常同时发生,因此本质上是相同的。在许多其他语言中,情况并非如此。请参见下面的脚注以获取更多详细信息。)
在定义结构体时,结构体可以被标记(命名),也可以是未加标记的。如果未加标记,则必须立即使用该结构体(将在下文进一步解释其含义)。
struct Name {
   ...
};

这定义了一个名为“struct Name”的类型,然后可以用它来定义一个结构体变量/实例:

struct Name myNameStruct;

这里定义了一个名叫 myNameStruct 的变量,它是一个类型为 struct Name 的结构体。

你也可以同时定义一个结构体并声明/定义一个结构体变量:

struct Name {
   ...
} myNameStruct;

与之前相同,这定义了一个名为myNameStruct的变量,它是struct Name类型的实例... 但它同时定义了类型struct Name。然后可以再次使用该类型来声明和定义另一个变量:
struct Name myOtherNameStruct;

现在,typedef 只是一种用特定名称别名类型的方式:
typedef OldTypeName NewTypeName;

在上述typedef中,任何时候使用NewTypeName都等同于使用OldTypeName在C编程语言中,这对于结构体特别有用,因为它使您能够在声明和定义该类型的变量时省略单词"struct",并将结构体名称简单地视为自己的类型(就像我们在C++中做的那样)。以下是首先定义结构体,然后typedef结构体的示例:

struct Name {
   ...
};

typedef struct Name Name_t;

在上面的代码中,OldTypeName是struct Name,而NewTypeName是Name_t。因此,现在定义一个类型为struct Name的变量,不再需要写:
struct Name myNameStruct;

我可以简单写出:

Name_t myNameStruct;

注意,typedef可以与结构体定义组合使用,在您的代码中就是这样做的。
typedef struct {
   ...
} Name_t;

在命名结构体时也可以进行此操作。这对于自引用结构体(例如链接列表节点)非常有用,但是在其他情况下是多余的。尽管如此,许多人都遵循始终标记结构体的做法,就像这个例子一样:

typedef struct Name {
   ...
} Name_t;
注意:在上面的语法中,因为您使用了“typedef”,所以整个语句都是一个typedef语句,其中OldTypeName恰好是一个结构体定义。因此编译器将右大括号}后面的名称解释为NewTypeName......它不是变量名称(如果没有typedef语法,则会在同时定义结构体和声明/定义结构体变量的情况下定义结构体)。 此外,如果您声明了typedef,但是在末尾留下了Name_t,则实际上创建了一个不完整的typedef语句,因为编译器认为"struct Name { ... }"中的所有内容都是OldTypeName,并且您没有为typedef提供NewTypeName。这就是为什么编译器不喜欢您编写的代码的原因(尽管编译器的消息有点难以理解,因为它不太确定您做错了什么)。
现在,正如我上面所指出的,如果您在定义结构类型时没有标记(命名)它,则必须立即使用它,以定义变量:
struct {
   ...
} myNameStruct;  // defines myNameStruct as a variable with this struct
                 // definition, but the struct definition cannot be re-used.

或者您可以在typedef中使用未标记的结构类型:

typedef struct {
   ...
} Name_t;

这个最终的语法就是你写下以下代码时实际做的事情:
typedef struct{
   char firstName[56];
   char lastName[56];
} Author;

编译器很高兴。祝好。

关于_t后缀的注释/问题:

_t后缀是一种约定,用于向阅读代码的人表明带有_t的符号名称是类型名称(而不是变量名称)。编译器不解析也不知道_t。

C89和特别是C99标准库定义了许多类型并选择使用_t作为这些类型的名称。例如,C89标准定义了wchar_t、off_t、ptrdiff_t等类型。C99标准定义了许多额外的类型,例如uintptr_t、intmax_t、int8_t、uint_least16_t、uint_fast32_t等等。但是_t没有被保留,也没有被特别解析或被编译器注意到,它只是一个在C中定义新类型时遵循的约定。在C++中,许多人使用以大写字母开头的约定来开始类型名称,例如MyNewType(与C约定的my_new_type_t相对应)。祝好。


关于声明和定义的脚注:首先特别感谢@CJM提出澄清编辑,尤其是关于这些术语的使用方面。

通常声明和定义以下项:类型、变量函数

  • 声明仅为编译器提供符号名称和“类型”信息。
    • 例如,声明变量告诉编译器该变量的名称和类型。
  • 定义为编译器提供项目的全部细节:
    • 在类型的情况下,定义为编译器提供名称和该类型的详细结构。
    • 在变量的情况下,定义告诉编译器分配内存(在哪里以及多少)以创建该变量的实例。

一般来说,在由多个文件组成的程序中,变量、类型和函数可以在许多文件中进行声明,但每个文件可能只有一个定义。

在许多编程语言(例如C++)中,声明和定义很容易分开。这使得类型、变量和函数可以进行“前向声明”,从而可以在稍后定义这些项之前编译文件。然而,在C编程语言中,变量的声明和定义是同一件事情。(据我所知,C编程语言中唯一的例外是使用关键字extern允许变量进行声明而无需定义。)正因为如此,在此答案的先前编辑中,我提到了结构体的“定义”和“结构体[变量]的声明”,其中“结构体[变量]的声明”的意义被理解为创建该结构体的实例(变量)。

2
我们可以在任何类型后面加上 _t 后缀吗?我认为这个后缀是 C99 保留的。我错了吗? - nowox
8
需要注意的是,自引用结构体需要为结构体命名或进行前向声明定义。例如,如果我想要一个具有数据和下一个节点指针的链表节点,我更喜欢这样写:typedef struct node_t {void * data; struct node_t next; } Node; 这样可以使声明完整而简洁。 - aaroncarsonart
6
真的,这解决了我对C结构体的所有困惑。谢谢! - slow
2
“因为结构体类型是无名的,所以你不能声明另一个这样的变量。所以这种类型只能有一个实例,对吗?” 这是正确的。这就是为什么很少使用匿名结构体的原因。我唯一使用这种方法的情况是,如果我只需要在函数内部临时使用本地结构体。当然,如果您需要将数据传递到其他地方,您会给结构体命名,或者更常见的是typedef它。如果多个文件需要访问结构体定义,则应将其放在头文件中。希望这可以帮助到您。 - Daniel Goldfarb
1
struct Name {}; 中的 Name 被称为结构体的标签。编译器使用此标签来查找定义。这很重要,因为如果您的结构体引用自身(例如在链表中),则必须使用带标签的结构体,否则 typedef 声明尚未完成,类型名称对编译器是未知的。据我所知,这是定义结构体的这两种方式之间唯一的区别。 - artronics
显示剩余4条评论

7

typedef 的语法如下:

typedef old_type new_type

在你的第一次尝试中,你定义了struct Book类型而不是Book。换句话说,你的数据类型被称为struct Book而不是Book
在第二种形式中,你使用了正确的typedef语法,因此编译器识别到了名为Book的类型。

那只是简单情况下的语法,一个反例是 typedef int x[5]; 或者 typedef int (*p)(); - M.M

2
想要补充说明的是,当你实际声明一个变量时,请注意。
struct foo {
   int a;
} my_foo;

定义foo并立即声明一个struct foo类型的变量my_foo,这意味着您可以像这样使用它:my_foo.a = 5;

然而,由于typedef语法遵循typedef <oldname> <newname>的格式。

typedef struct bar {
   int b;
} my_bar;

如果没有声明一个类型为struct bar的变量my_bar,那么my_bar.b = 5;是不合法的。相反,它是在给struct bar类型新命名,命名为my_bar。现在,您可以像这样使用my_bar来声明struct bar类型:

my_bar some_bar;

1
其他答案都是正确和有用的,但可能比必要的更长。按照以下步骤操作:
typedef struct Book Book;
typedef struct Books Books;
typedef struct Author Author;

struct Book {
    ... as you wish ...
};

struct Author {
    ... as you wish ...
};

struct Books {
    ... as you wish ...
};

你可以按照任意顺序定义你的结构体,只要它们只包含指向其他结构体的指针。

0

我认为这会帮助你理解。 http://www.tutorialspoint.com/cprogramming/c_typedef.htm

bookstore.c:8:2: error: unknown type name ‘Author’
bookstore.c:21:2: error: unknown type name ‘Book’

这是因为在使用之前必须先定义它们。将结构体“Author”和“Books”移到结构体“Book”上面即可解决问题。

此外,您收到的警告说明存在问题,编译器认为“typedef struct Author”不必要,因为您没有正确地对结构体进行typedef,所以编译器无法“读取”任何有用信息。

既然您已经知道答案应该是这种形式。

typedef struct {
 ...
 ... 
 ...
} struct-name;

坚持下去。


0

在定义书之前,您只需要先定义作者。

由于您在书中使用了作者,因此需要先定义它。


谢谢你们的快速回复。在 Kernighan 和 Ritchie 的书中检查过之后,定义 Book 在 Author 之前并没有错误。 - Alek Sobczyk
我错了,显然如果我改变位置,它会消除错误。我需要再学习一下。(很抱歉双重评论,我在stackoverflow是第一次使用:P) - Alek Sobczyk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接