C中的结构继承


Answers:


86

您可以得到的最接近的是相当普遍的习惯用法:

作为Derived与副本开始Base,你可以这样做:

d的实例在哪里Derived?因此它们是多态的。但是拥有虚拟方法是另一个挑战-为此,您需要具有vtable指针的等效项Base,其中包含指向接受Base作为其第一个参数的函数的函数指针(您可以命名为this)的。

到那时,您还可以使用C ++!


7
好吧,这是假设您的平台上有C ++编译器可用!

6
如果有C编译器可用,那么C ++编译器也可用-只需使用产生C的编译器作为其输出即可。
丹尼尔·艾威克

1
ew ..你救了我的命。我通常使用Java编写代码,当遇到与您发布的代码类似的代码时,我认为这是一种组合,当他们进行转换时感到很困惑。
Surya Wijaya Madjid 2012年

2
您会看到它在复杂的实际项目(例如git)中的使用。参见log-tree.c。来自结构对象的结构标记“继承”
albfan

我曾经做过完全相同的事情。问题是,如果实现“方法”的间接访问,您将获得非常差的可读性,例如personDAO->getPersonById(personDAO, personDTO->getId(personDTO));。不幸的是,我没有其他办法。
Powerslave'Apr

45

与C ++不同,C没有明确的继承概念。但是,您可以在另一个结构中重用一个结构:


1
据我所知,您也可以在C ++中的另一个成员中包含一个struct / class成员。
泰勒·米利肯

59
C表示没有填充出现在结构的第一个成员之前。因此,您实际上可以(并且被允许)将LiteraryCharacter *强制转换为Person *,并将其视为一个人。+1
Johannes Schaub-litb

4
@ JohannesSchaub-litb您的评论比答案本身更好的解释:)
Greg

1
重要的是要注意,您必须仅将这些类型作为参考。您无法将它们复制到Person对象中,否则它将进行拼接。
凯文·考克斯

37

我喜欢并使用C中Typesafe继承的想法。

例如:

用法:


我从来没有想过。而且,如果您将工会设为匿名,它会很整洁。但是,缺点是您需要列出所有父项,以避免嵌套变量。
凯文·考克斯

2
有趣的方法。但是,只要键入leopard.base,就将消除继承/多态性的整个观点。
Powerslave'Apr

您在这里遇到钻石继承问题吗?leopard.base.felidae.base.animal.weightleopard.base.animal.weight
亚历山大·托斯特林'16

4
@Alexander Torstling不,你不会。leopard.base.felidae.base.animal.weight只是它的另一个名字leopard.base.animal.weight-它在内存中的同一位置是同一件事。
马丁,

这看起来很棒。谢谢。
PSkocik

9

如果您想使用某些gcc魔术(我认为可以与Microsoft的C编译器一起使用),则可以执行以下操作:

使用gcc,可以使用-fms-extensions进行编译(允许像Microsoft的编译器一样使用未命名的结构成员)。这类似于Daniel Earwicker给出的解决方案,除了它允许您访问结构B实例上的memeber1。即B.member1而不是BAmember1。

这可能不是最可移植的方法,并且在使用C ++编译器时将不起作用(不同的语言语义意味着它正在重新声明/定义结构A而不是实例化它)。

但是,如果您住在gcc / C土地中,那么它将正常工作并完全按照您的意愿进行。


这不是构图吗?
Sumit Gera

1
不,这是适当的继承。假设您有一个名为b的struct B类型的struct,b.member1将按照您的期望进行编译和工作。组成类似于b.base.member1。GCC为您执行此魔术。实际上,在这种情况下,结构B的定义为两个整数。
马特2013年

这只能在C中实现,而在C ++中不可行吗?如果没有,那么请访问
Sumit Gera 2013年

只有C。语法在C ++中是非法的。尽管C ++像类一样具有适当的结构继承。
马特2013年

现在我感觉好多了!
Sumit Gera 2013年

8

如果您的编译器支持匿名结构,则可以执行以下操作:

这样,可以直接访问基本结构成员,这样更好。


6
后缀_t在POSIX中保留。做任何您想做的事情,只是建议您如果为POSIX系统(例如Linux)编写代码,或者最终有人希望将您的代码移植到POSIX系统,您可能会遇到冲突。
L0j1k 2014年

8
这实际上不适用于标准C(甚至C11)。
大通

4

你可以做上面提到的

但是,如果你想避免指针铸造,你可以使用指针到unionBaseDerived


3

这可以与-fms-extensions一起编译

图表图像

main.c

抽象产品

抽象产品

书本

书本

产品.c

产品h

TravelGuide.c

TravelGuide.h

生成文件


非常适合用于概念验证,但是如果必须采取各种措施才能在C中实现OOP,那么使用C ++会更好。IMHO C不是为OOP设计的,如果确实需要纯OOP(为此付出了很多努力),那么您使用的是错误的语言。我不想保留这样的代码。
亚历克斯

另外,通过这种方法,我认为您正在浪费一些内存。与C ++不同,将为每个实例提供额外的sizeof(函数指针)*方法数量。在C ++中,sizeof(class)不包括方法指针,并且在有虚方法的情况下-包含1个指向vtable的附加指针。
Alex

3

匿名答案的答案略有不同(和其他答案相似)。对于一级深度继承,可以执行以下操作:

这样,与其他答案相同,接受Person指针的函数将接受Employee或Manager的指针(带有强制转换),但在其他情况下,初始化也是自然的:

我相信这是一些受欢迎的项目的做法(例如libuv)

更新:在使用结构和联合的libsdl事件实现中,也有一些类似(但不相同)概念的好例子。


1
这会产生棘手的混叠后果。据我了解,只要结构位于动态内存中,您就只能跨继承链进行重铸。(请参阅gustedt.wordpress.com/2016/08/17/effective-types-and-aliasing)如果没有动态内存,则联合方法可能更好。
PSkocik

@PSkocik显然,在玩这种游戏时要多加注意,因为很多事情都可能出错。我确实相信该示例同样适用于堆栈变量的指针。话虽如此,我确实同意,在这种情况下进行铸造时,联盟永远是一个更好的解决方案。
亚历克斯

对象的分配方式(自动,静态,动态)无关紧要。潜在问题的一个示例是,不同的对象在namesex字段之间可能具有不同的填充量,但是还有其他可能性。主要问题是不能保证它能正常工作,从而使该代码不可靠,因此不可用。
user694733 '16

1
@ user694733我不同意填充语句。我们只关心BASEFIELDS,对于那些填充在每个给定平台上都是确定性的,前提是它们总是放在结构的开头。显然,在现实生活中,BASEFIELDS将需要包含类型标识符以确保对字段的正确处理,并且事情很快就会变得混乱。我同意这种骇客行为不是很好,如果有人需要C ++样式继承-他们应该使用C ++。
亚历克斯

CPython还使用类似的语法来构造python类型,请参阅docs.python.org/2/extending/newtypes.html,此处PyObject_HEAD定义了python类型所需的公共字段。
Glen Fletcher


-2

您可以模拟它,但不能真正继承。


3
什么是现实?C ++只是用于分发的非常简单的运行时库,并且在需要时有很多编译器语法可以调用它。毕竟,原始的C ++编译器生成C代码。(实际上是非常易读的C)
哈维尔(Javier


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.