关于C#:什么是’前向声明’以及’typedef struct X’和’struct X’之间的区别?

What is 'forward declaration' and the difference between 'typedef struct X' and 'struct X'?

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

1
2
3
typedef struct {
    some members;
} struct_name;

然后它将类似于为匿名结构提供别名(因为它没有标记名)。因此不能用于转发声明。我不知道远期申报是什么意思。

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

1
2
3
typedef struct NAME {
    some members;
} struct_alias;

NAMEstruct_alias有什么区别吗?或者两者都等于struct_alias是结构名的别名吗?

此外,我们可以像这样声明一个struct NAME类型的变量吗?

1
struct_alias variable1;

和/或类似:

1
struct NAME variable2;

或类似:

1
NAME variable3;

当需要使用循环结构声明时,struct转发声明非常有用。例子:

1
2
3
4
5
6
7
8
9
struct a {
    struct b * b_pointer;
    int c;
};

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

struct a被宣布时,它还不知道struct b的规格,但是你可以向前参照它。

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

这是非法的:

1
2
3
4
5
6
7
8
9
10
11
struct a {
    b * b_pointer;
    int c;
};

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

// struct b was never declared or defined

但这是合法的:

1
2
3
4
5
6
7
8
9
10
11
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

这就是:

1
2
3
4
5
6
7
8
9
10
11
12
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++中是非法的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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++


forward声明是一种承诺,它将定义一些您在无法定义时对编译器所做的事情。编译器可以使用您的单词来解释其他声明,否则它将无法解释这些声明。

一个常见的例子是设计为链接列表中的节点的struct:您需要将指向节点的指针放入struct中,但编译器不允许您在没有正向声明或标记的情况下执行此操作:

1
2
3
4
5
6
7
// Forward declaration
struct element;
typedef struct {
    int value;
    // Use of the forward declaration
    struct element *next;
} element; // Complete definition

and so it cant be used for forward declaration

我认为作者的观点是,给你的struct一个标签就相当于一个转发声明:

1
2
3
4
5
typedef struct element {
    int value;
    // No need for a forward declaration here
    struct element *next;
} element;


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

1
2
3
4
5
6
7
8
9
10
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 NAMEstruct_alias确实是等效的。

1
2
struct_alias variable1;
struct NAME variable2;

是正确的;

1
NAME variable3;

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


struct_aliasstruct NAME相同,struct_aliasstruct NAME的别名。

这两个都是一样的

1
2
3
struct_alias variable1;  

struct NAME variable1;

这是违法的

1
NAME variable3;

见这篇关于远期申报的文章。


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

假设这是某个键/值存储区my_dict.h的标题:

1
2
3
4
5
6
7
8
...
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一点也不了解,但实际上,是因为使用了商店你不需要知道:

1
2
3
4
5
6
7
#include"my_dict.h"
...
struct my_dict_t* dict = create();
if(0 != insert(dict,"AnEntry", strdup("AValue"))) {
    ...
}
...

原因是:您只使用指向数据结构的指针。

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

只有当你尝试真正地访问它们,比如

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

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

1
2
3
4
5
6
7
8
9
10
11
#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都是指针。

但是,编译器会执行类型检查:它需要确认你没有做类似的事情

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

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