我在应用程序的各个地方都使用了typedef。然后我开始重构几个 头文件开始变得笨重时。我注意到我需要转发声明Object和Klass。 嗯,令我惊讶的是,我无法转发声明Object或Klass。这是因为,正如你在中看到的那样 对象和Klass结构,我使用的是Object和Klass的typedef。
//Klass.h
typedef struct Klass Klass_t;
struct Klass
{
void (*_initialize)(Object_t* object, Klass_t* klass);
};
//Object.h
typedef struct Object Object_t;
struct Object
{
Klass_t* _klass;
};
首先,使用typedef很棒。但是试图转发声明对象:
struct Object_t;
不起作用,因为我需要通过函数声明重写为:
void (*_initialize)(struct Object_t* object, Klass_t* klass);
所以我决定在Klass.h头文件中输入typedef:
typedef struct Object Object_t;
当所有头文件都包含在我的Main.c文件中时,它会编译:
Object.h:5: error: redefinition of typedef 'Object_t'
所以,然后我决定放弃所有struct typedef并明确声明我的结构。
有没有办法在不明确使用struct Object的情况下在另一个文件中输入结构和转发声明?
我想在头文件里面保留结构typedef 结构被宣布。如果我必须将所有typedef分组到一个头文件中,那么我宁愿不使用typedef。无论如何,谢谢你的时间。
答案 0 :(得分:4)
您错过了struct
关键字。它应该是
typedef struct Object Object_t;
应始终以这种方式进行前瞻声明(但见下文)。此转发同时声明typedef
标识符和struct
标记。
在真实声明之前,只需将 all 的前向声明放在struct
。只要您在声明中只使用指向这些struct
的指针,那么一切都应该没问题。
nitpick:POSIX保留名为_t
的名称。这意味着您应该避免使用它,因为某些平台上某天可能会预定义Object_t
并且会与您的类型冲突。
我个人更喜欢以下惯例
typedef struct Object Object;
所以带有或不带struct
的单词Object总是指相同的。
答案 1 :(得分:3)
请记住,typedef
只是一种类型的替代名称。有一个原因,Linux内核不会将typedef
用于结构类型,并且您正在演示它。
您可以通过多种方式解决问题。我注意到C11确实允许多次出现相同的typedef
,但我假设你遇到了一个不支持它的旧编译器。
即使Linux内核不使用typedef
,我通常做使用typedef
s,但我主要避免相互引用结构类型(我不能想想我使用过这种类型的代码。并且,就像他Jens Gustedt中的answer注释一样,我几乎总是使用这些符号:
typedef struct SomeTag SomeTag;
以便类型名称和结构标记相同(它们位于不同的名称空间中)。在C ++中不需要此操作;当您定义struct SomeTag
或class SomeTag
时,名称SomeTag
将成为类型名称,而不需要明确的typedef
(尽管typedef
除了_t
之外没有其他任何损害。揭示作者在C中比C ++更有经验,或者代码起源于C代码。
我还观察到以下划线开头的名称最好被视为“为实现保留”。这些规则比这复杂得多,但是当你使用以下划线开头的名字时,你冒着篡改你的名字 - 以及篡夺你的名字的权利 - 的风险,所以不要这样做。同样,如果您在非严格标准C模式下编译时包含任何POSIX标头(例如<stdio.h>
),POSIX会为实现保留结束typedef struct Object Object_t;
typedef struct Klass Klass_t;
的类型名称。避免创建这样的名字;他们迟早会伤到你的。 (我没有修复下面的代码来处理这些问题中的任何一个: caveat emptor !)。
在下面的代码片段中,我大多忽略了防止多个包含的代码(但是你应该在代码中使用它。)
#include "typedefs.h"
struct Klass
{
void (*_initialize)(Object_t *object, Klass_t *klass);
};
#include "typedefs.h"
struct Object
{
Klass_t *_klass;
};
Klass_t
这是有效的,因为在使用它们之前声明了两个类型名Object_t
和struct Object
。
typedef struct Klass Klass_t;
struct Object;
struct Klass
{
void (*_initialize)(struct Object *object, Klass_t *klass);
};
void (*_initialize)(struct Object *object, struct Klass *klass);
或者,为了保持一致性,它甚至可以使用:
#include "klass.h"
struct Object
{
Klass_t *_klass;
};
struct Object
这是有效的,因为(在宽范围内 - 基本上,如果类型是在文件范围内定义的,而不是在函数内定义的话)-std=c89
总是引用相同的类型,无论是否所有细节都已完全定义。
在所有-std=c99
,-std=c11
和typedef
下,GCC 4.8.2接受已复制的-std=c89 -pedantic
,如下面的代码所示。它需要-std=c99 -pedantic
或typedef
才能获得有关重复-pedantic
的错误。
即使没有-pedantic
选项,GCC 4.5.2也会拒绝此代码;但是,GCC 4.6.0及更高版本在没有#ifndef KLASS_H_INCLUDED
#define KLASS_H_INCLUDED
typedef struct Klass Klass_t;
typedef struct Object Object_t;
struct Klass
{
void (*_initialize)(Object_t *object, Klass_t *klass);
};
#endif /* KLASS_H_INCLUDED */
选项的情况下接受它。
#ifndef OBJECT_H_INCLUDED
#define OBJECT_H_INCLUDED
typedef struct Klass Klass_t;
typedef struct Object Object_t;
struct Object
{
Klass_t *klass;
};
#endif /* OBJECT_H_INCLUDED */
#include "klass.h"
#include "object.h"
Klass_t k;
Object_t o;
{{1}}
您必须决定这是否是您愿意承担代码的风险 - 可移植性的重要性,以及C(以及哪些C编译器)必须可移植的版本。