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