54

我是 C 编程的初学者,我知道struct类型声明和typedef结构声明之间的区别。我遇到了一个答案,说如果我们定义一个structlike:

typedef struct { 
    some members;
} struct_name;

然后它就像为匿名结构提供别名(因为它没有标签名称)。所以它不能用于前向声明。我不知道前向声明是什么意思。

另外,我想知道以下代码:

typedef struct NAME { 
    some members;
} struct_alias;

NAME和有什么区别struct_alias吗?或者两者都相等,就像 struct_aliasstruct NAME 的别名一样?

此外,我们是否可以声明如下类型的变量struct NAME

struct_alias variable1;

和/或喜欢:

struct NAME variable2;

或喜欢:

NAME variable3; 
4

5 回答 5

60

struct当您需要循环结构声明时,前向声明可能很有用。例子:

struct a {
    struct b * b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

何时struct a声明它还不知道规格struct b,但您可以转发引用它。

当您 typedef 一个匿名结构时,编译器将不允许您在 typedef 之前使用它的名称。

这是非法的:

struct a {
    b * b_pointer;
    int c;
};

typedef struct {
    struct a * a_pointer;
    void * d;
} b;

// struct b was never declared or defined

这虽然是合法的:

struct a {
    struct b * b_pointer;
    int c;
};

typedef struct b {
    struct a * a_pointer;
    void * d;
} b;

// struct b is defined and has an alias type called b

这是这样的:

typedef struct b b;
// the type b referes to a yet undefined type struct b

struct a {
    b * struct_b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

这(仅在 C 中,在 C++ 中是非法的):

typedef int b;

struct a {
    struct b * struct_b_pointer;
    b b_integer_type;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

// struct b and b are two different types all together. Note: this is not allowed in C++
于 2013-09-06T13:05:54.100 回答
25

前向声明是一个承诺,在无法进行定义的地方定义您对编译器所做的事情。编译器可以使用您的单词来解释其他无法解释的声明。

一个常见的例子是 astruct设计为链表中的一个节点:您需要将一个指向节点的指针放入 中struct,但编译器不会让您在没有前向声明或标记的情况下这样做:

// Forward declaration
struct element;
typedef struct {
    int value;
    // Use of the forward declaration
    struct element *next;
} element; // Complete definition

所以它不能用于前向声明

我认为作者的观点是给你struct一个标签相当于一个前向声明:

typedef struct element {
    int value;
    // No need for a forward declaration here
    struct element *next;
} element;
于 2013-09-06T13:09:44.507 回答
15

前向声明是在实际定义之前的声明,通常是为了在定义不可用时能够引用声明的类型。当然,并非所有事情都可以使用声明未定义结构完成,但在某些上下文中可以使用它。这种类型称为不完整的,它的使用有许多限制。例如:

struct X; // forward declaration

void f(struct X*) { }  // usage of the declared, undefined structure

// void f(struct X) { }         // ILLEGAL
// struct X x;                  // ILLEGAL
// int n =sizeof(struct X);     // ILLEGAL

// later, or somewhere else altogether
struct X { /* ... */ };

这对于打破循环依赖关系或减少编译时间很有用,因为定义通常要大得多,因此需要更多资源来解析它。

在你的例子中,struct NAME确实struct_alias是等价的。

struct_alias variable1;
struct NAME variable2;

是正确的;

NAME variable3;

不是,因为在 C 中struct关键字是必需的。

于 2013-09-06T13:06:00.143 回答
8

struct_alias并且struct NAME是相同的,struct_alias是一个别名struct NAME

这些都是相同的并且是允许的

struct_alias variable1;  

struct NAME variable1; 

这是非法的

NAME variable3;   

请参阅这篇关于前向声明的文章

于 2013-09-06T13:03:05.357 回答
2

如前所述,C/C++ 中的前向声明是实际定义不可用的东西的声明。它是一个声明,告诉编译器“有一个数据类型 ABC”。

让我们假设这是一些键/值存储的标题my_dict.h

...
struct my_dict_t;
struct my_dict_t* create();

char* get_value(const struct my_dict_t* dict, const char* name);
char* insert(struct my_dict_t* dict, const char* name, char* value);
void destroy(struct my_dict_t* dict);
...

您对此一无所知my_dict_t,但实际上,使用商店您不需要知道:

#include "my_dict.h"
...
struct my_dict_t* dict = create();
if(0 != insert(dict, "AnEntry", strdup("AValue"))) {
    ...
}
...

这样做的原因是:您只对数据结构使用 POINTERS。

指针只是数字,为了处理它们,你不需要知道它们指向什么。

仅当您尝试实际访问它们时,这才有意义,例如

struct my_dict_t* dict = create();
printf("%s\n", dict->value);  /* Impossible if only a forward decl is available */

因此,为了实现这些功能,您需要实际定义my_struct_t. 您可以my_dict.c像这样在源文件中执行此操作:

#include "my_dict.h"

struct my_dict_t {
    char* value;
    const char* name;
    struct my_dict_t* next;
}

struct my_dict_t* create() {
    return calloc(1, sizeof(struct my_dict_t));
}

这在几种情况下很方便,例如

  • 为了解决循环类型依赖关系,像 Sergei L. 解释的那样。
  • 对于封装,就像上面的例子一样。

所以剩下的问题是:为什么我们不能在使用上面的函数时完全省略前向声明?最后,编译器知道所有dict都是指针就足够了。

但是,编译器确实会执行类型检查:它需要验证您没有执行类似的操作

...
int i = 12;
char* value = get_value(&i, "MyName");
...

它不需要知道my_dict_t长什么样子,但它需要知道这&i不是指针所get_value()期望的类型。

于 2017-12-06T10:23:14.020 回答