C 语言中的虚函数 [重复]

Virtual functions in C [duplicate]

提问人:Luchian Grigore 提问时间:7/27/2011 最后编辑:CommunityLuchian Grigore 更新时间:7/27/2011 访问量:12733

问:

这个问题在这里已经有答案了:
12年前关闭。

可能的重复:
如何在 C 中实现 C++ 虚函数

在 C++ 中,类和结构之间的唯一区别是默认访问级别。因此,您可以在结构中拥有虚拟函数,从结构继承等等。我的问题是,你也可以用 C 语言做到这一点吗?

c

评论

3赞 Péter Török 7/27/2011
请参阅如何在 C 中实现 C++ 虚拟函数
0赞 Luchian Grigore 7/27/2011
我知道它们是如何在 C++ 中实现的,只是想知道它是否在 C 中工作。我不知道 virtual 不是 C 语言中的关键字,所有谷歌搜索都把我带到了 c++ 的东西......
1赞 Clifford 7/27/2011
这不是“唯一的区别”,它们在默认继承方面也有所不同。C 不支持任何类型的成员函数 - 虚拟或其他。
0赞 Jens Gustedt 7/27/2011
简而言之,你的问题没有多大意义,C 没有成员函数。C没有苹果,所以问它有没有青苹果是没有用的。struct
1赞 7/27/2011
@Luchian:欢迎来到SO。如果这个问题已经关闭,请不要感到沮丧,因为它之前已经问过了(至少两次)。请看彼得提供的链接问题。

答:

5赞 Vinicius Kamakura 7/27/2011 #1

不,你不能。“虚拟”不是 C 词汇的一部分,“访问级别”也不是

评论

0赞 André Caron 7/27/2011
事实上,关键字不是 C 语言的一部分。这并不意味着在 C 语言中无法进行面向对象编程。virtual
4赞 Sebastian Mach 7/27/2011
-1:缺少保留字并不意味着不能拥有虚拟成员函数。根据这个定义,smalltalk 也不具有多态性。virtual
2赞 Vinicius Kamakura 7/27/2011
你不能在 C 语言中使用成员函数,无论是否虚。 句点。指向函数的指针是,指向函数的指针不是成员函数。请省去我的迂腐。
3赞 Sebastian Mach 7/27/2011
@hexa:关键是:声明缺少关键字是缺少成员函数等的原因是无效的。语言的语法不需要定义为关键字来定义运行时多态性或成员函数。同样,语法中缺乏这种生产或原子本身并不宣布缺乏这种功能和行为。因此不是答案。因此 -1.时期。virtualvirtual
1赞 Sebastian Mach 7/27/2011
我修剪了一些多余的空白,可以删除我的反对票,我击败了系统:)
12赞 Karoly Horvath 7/27/2011 #2

您可以使用存储在结构中的函数指针执行“虚拟函数”。对于固有结构,您可以将一个结构嵌入到另一个结构中,但语法将再次与您预期的不同。你可以用 C 语言编写面向对象的程序(经典的例子是 unix file/socket/...API),但语法非常笨拙。

相关答案在这里: https://stackoverflow.com/search?q=C+virtual+functions

评论

2赞 Eli Iser 7/27/2011
事实上,Linux 内核经常这样做。包含多个函数指针(即 )的 A 。每个实现都可以选择仅分配部分函数指针;其余均为 NULL。structinterface
0赞 Luchian Grigore 7/27/2011
我知道这一点,只是想知道你是否可以在 C 中拥有虚拟。
1赞 Eli Iser 7/27/2011
@Luchian - 正如其他人所说,关键字和相关的编译器限制在 C 中不存在。但是你可以在 C 语言中实现与 yi_H 和我所描述的类似的行为。virtual
7赞 Jack Edmonds 7/27/2011 #3

C 没有虚拟方法的本机语法。但是,您仍然可以通过模仿 C++ 实现虚拟方法的方式来实现虚拟方法。C++ 为每个虚拟方法在每个类中存储一个指向函数定义的附加指针。因此,您可以简单地将函数指针添加到结构中以模拟虚拟方法。

例如

#include <stdio.h>
#include <stdlib.h>

int f2(int x)
{
    printf("%d\n",x);
}

typedef struct mystruct
{
    int (*f)(int);
} mystruct;


int main()
{
    mystruct *s=malloc(sizeof(mystruct));
    s->f=f2;
    s->f(42);
    free(s);
    return 0;
}

评论

3赞 Nawaz 7/27/2011
+1 很好的尝试。但是你不能在结构定义本身中做。int (*f)(int x)=NULL;
1赞 Eli Iser 7/27/2011
我同意 Nawaz 的观点 - 这必须在实例的定义中(或依靠编译器保留未初始化的字段 0)。此外,在调用它之前,您需要确保不是这样。s.fNULL
0赞 Jack Edmonds 7/27/2011
@Eli和纳瓦兹:谢谢你指出这一点。我更新了代码,现在它使用 gcc 进行编译。
3赞 bliss 7/27/2011 #4

C 结构不能有行为。他们只能拥有数据。

请参阅 http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=tenBestQuestions 了解 C 结构和 C++ 结构之间的差异。 它写在第二个问题中。

C++ 结构与 C++ 类不同于 C 结构。这只是一个类比。

另外,在C语言中没有继承这样的东西。如果没有继承,你会用虚拟函数做什么?

评论

0赞 André Caron 7/27/2011
问题:函数指针是数据还是“行为”?
0赞 bliss 7/27/2011
我对函数指针了解不多,但我认为它是一个数据。毕竟,它是一个指针,不是吗?
0赞 Jack Edmonds 7/27/2011
你可以通过使“基本”结构成为“子”结构的第一个成员来在 C 中进行继承。然后,在实例化“子”结构后,只需将其函数指针设置为重写的方法即可。
5赞 André Caron 7/27/2011 #5

您可以使用函数指针模拟虚拟函数。例如

struct foo
{
    void(*bar)(struct foo*, int, int);
};

void default_bar ( struct foo * f, int a, int b )
{
    printf("bar(%d,%d)\n", a, b);
}

void setup_foo ( struct foo * f )
{
    f->bar = &default_bar;
}

然后,你可以用类似的东西来“子类化”结构:

struct meh
{
   /* inherit from "class foo". MUST be first. */
   struct foo base;
   int more_data;
};

/* override "method bar". */
struct custom_bar ( struct foo * f, int a, int b )
{
    struct meh * m = (struct meh*)f;
    printf("custom_bar(%d,%d)\n", a, b);
}

void setup_meh ( struct meh * m )
{
    setup_foo(&m->base);
    m->bar = &custom_bar;
}

所有这些都是劳动密集型且容易出错的,但这是可以做到的。这种类型的“继承”和“覆盖”实现是一些知名 C 库(包括 和 )中的常见做法。如果您对标准 C I/O 不满意,他们使用此技术允许您覆盖 I/O 过程。jpegliblibpng

编辑:如评论中所述,其中一些代码依赖于(官方)非标准行为,这些行为“恰好”适用于大多数编译器。主要问题是代码假定(例如,成员的偏移量为 0)。如果不是这种情况,则强制转换会导致未定义的行为。若要解决此问题,可以添加一个额外的指针,如下所示:&m.base == &mbasecustom_bar()struct foo

struct foo
{
    /* same as before ...*/
    /* extra pointer. */
    void * hook;
};

然后,修改接触演员表的东西,

void setup_meh ( struct meh * m )
{
    m->base.hook = m;
   /* set up function pointers as usual... */
}

void custom_bar ( struct foo * f, int a, int b )
{
    struct meh * m = (struct meh*)f->hook;
    /* override. */
}

这种技术更可靠,特别是如果您计划用 C++ 编写“派生结构”并使用虚函数。在这种情况下,第一个成员的偏移量通常为非 0,因为编译器将运行时类型信息和类的 v-table 存储在那里。

评论

0赞 Vinicius Kamakura 7/27/2011
尽管这可行,但您仍然依赖编译器扩展进行类型双关语,这可能会导致未定义的行为 blablabla。事实上,这在 GCC 和 MSVC 中有效。也应该是void(*bar)(int, int);void(*bar)(void *, int, int);
0赞 André Caron 7/27/2011
谢谢,确实函数指针被宣布错误。事实上,对齐的东西依赖于编译器扩展。您可以使用指向“派生对象”的额外指针使其可移植。然而,该技术在知名库中非常流行,被认为是“可移植的”。任何进行此类代码破坏的编译器都会收到很多客户的抱怨。void*struct foo
0赞 André Caron 7/27/2011
@hexa:删除了未定义的行为。请参阅编辑。
0赞 Vinicius Kamakura 7/27/2011
干得好,如果可以的话,我会再次投赞成票;)