今天我做了一个简单的测试:
struct C{virtual void f()=0;};
void C::f(){printf("weird\n");}
字符串
程序还可以,但对我来说很奇怪,当我们使用=0
时,这意味着函数体应该在继承的类中定义,但似乎我仍然可以给予它实现函数。
我尝试了GCC和VC,都很好。所以在我看来,这应该是C标准的一部分。
但为什么这不是语法错误呢?
我能想到的一个原因是,就像C#同时有'interface'和'abstract'关键字一样,interface不能有实现,而abstract可以有一些实现。
这是我的困惑,C应该支持这样一种奇怪的语法?
8条答案
按热度按时间1l5u6lss1#
C++支持带有实现的纯虚函数,因此类设计者可以强制派生类重写该函数以添加特定的细节,但仍然提供了一个有用的默认实现,它们可以用作公共基础。
经典例子:
字符串
**技术编辑:**C++标准要求纯虚函数的函数体定义在类定义体之外。换句话说,类成员函数不能既是纯虚函数又是内联函数。前面的代码示例在MSVC(Visual Studio)上编译,但不在GCC或CLANG上编译。
C03的第10.4节第2段告诉我们什么是抽象类,作为旁注,下面是:[注意:函数声明不能同时提供纯说明符和定义结束注解]
按照C标准,带有主体的纯虚函数看起来像这样:
型
krugob8w2#
其他人提到了与析构函数的语言一致性,所以我将从软件工程的Angular 出发:
这是因为你定义的类可能有一个有效的默认实现,但是调用它是有风险的/扩展的/不管怎样。如果你不把它定义为纯虚的,派生类将隐式地继承这个实现。并且可能直到运行时才知道。
如果将其定义为纯虚函数,则派生类必须实现该函数。如果风险/成本/其他都没问题,它可以静态调用默认实现
Base::f();
重要的是,这是一个有意识的决定,而且这个决定是明确的。
wwodge7n3#
基本上,两个世界中最好的(或最坏的...)。
派生类是实现纯虚方法所必需的,而基类的设计器出于某种原因需要这样做。基类还提供了此方法的默认实现,如果派生类希望或需要它,则可以使用该实现。
所以一些示例代码看起来像这样;
字符串
这种技术的一个常见用例与析构函数有关-基本上基类的设计者希望它是一个抽象类,但没有一个方法作为纯虚函数有多大意义。析构函数是一个可行的候选对象。
型
uxhixvfz4#
纯虚函数必须在子类中被重写。但是,您可以提供一个默认实现,它将适用于子类,但可能不是最佳的。
构造的用例用于抽象形状,例如
字符串
面积法适用于任何形状,但效率非常低。我们鼓励子类提供合适的实现,但是如果没有可用的实现,它们仍然可以显式地调用基类的方法
pkbketx95#
纯虚拟意味着“子必须覆盖”。
于是:
字符串
A有一个虚拟的foo。
B是抽象的。在生成示例之前,派生类型必须立即实现该示例。
C实现了它。
D使其抽象化,并增加了一个实现。
E通过调用D的实现来实现它。
A、C和E可以创建示例。B和D不能。
抽象与实现的技术可用于提供部分或低效的实现,当派生类型想要使用它时,它们可以显式地调用,但不要“默认”,因为这是不明智的。
另一个有趣的用例是父接口处于变化中,并且代码库很大。它有一个完整的功能实现。使用默认值的子级必须重复签名并显式转发到它。那些想要覆盖的只是覆盖。
当基类sigrnature更改时,除非每个子类显式调用默认值或正确重写,否则代码将无法编译。在
override
关键字之前,这是确保您不会意外创建新虚函数而不是重写父类的唯一方法,并且它仍然是在父类中强制执行策略的唯一方法。ujv3wf0j6#
请注意,你不能用纯虚方法示例化一个对象。
尝试示例化:
字符串
在VC2015中,出现了预期的错误:
型
回答您的问题:这些机制只声明函数是纯虚的,但仍然有虚函数表和基类。它将避免示例化Baseclass(C),但不会避免使用它:
型
ldxq2e6h7#
必须定义析构函数,即使它是纯虚的。如果你没有定义析构函数,编译器将生成一个析构函数。
编辑:你不能在没有define的情况下声明析构函数,这会导致链接错误。
of1yzvn48#
你可以从派生类调用函数体。您可以实作纯虚函式的函式体以提供预设行为,同时希望衍生类别的设计工具明确使用该函式。