C++ pure virtual,impure virtual,non-virtual函数关于继承的实际意义

本文探讨了C++中纯虚函数、虚函数和非虚函数在继承中的实际意义。纯虚函数作为抽象类接口,强制派生类实现;虚函数允许派生类覆写,提供默认实现或接口;非虚函数则继承接口并强制实现,但派生类也可覆盖。文章通过示例说明了这些概念在面向对象设计中的应用。

区分接口继承和实现继承

本篇主要讨论pure virtual函数(纯虚函数),impure virtual函数(虚函数),以及non-virtual函数(非虚函数)继承中的实际意义.

概念上通过public继承,实际上可以分为函数接口继承(function interfaces)和函数实现继承(funcion implementations).

考虑下面这个例子

class Shape{
   
   
public:
virtual void draw()const = 0;//pure virtual 
virtual void error(const std::string& msg);//virtual
int objectID()const;//non-virtual
...
};
class Rectangle:public Shape{
   
   ...};
class Ellipse:public Shape{
   
   ...};

首先来看pure virtual函数:
Shape定义了一个纯虚函数,所以它是一个抽象类,客户不能创建抽象类的实体,只能创建其派生类(derived classes)的实体.
成员函数的接口总会被继承,Shape的派生类都必须在其内部对这个接口(draw)进行实现.(属于指定接口继承)
这里语法上是可以对Shape的pure virtual函数进行定义的,虽然基类无法创建对象使用,但派生类作用域包含基类,可以通过基类显示利用作用域运算符(::)调用其基类部分使用(看似鸡肋但部分情况下有用,后面详述)。

接下来看impure virtual函数,根据语法,任何派生类继承基类的虚函数,无论是否给与virtual关键字显示指出,其都将拥有虚属性.这里的实际意义在于.你可以自己覆写(override)一个error函数版本,也可以不覆写,默认使用基类提供的缺省版本.(属于指定接口继承&缺省实现继承)
以下面的例子解释一下:

class Airport{
   
   
<think>我们正在讨论的是接口继承和实现继承的关系。根据引用[1]的内容,我们可以将继承分为接口继承和实现继承。在public继承中,派生类总是继承基类的接口,但具体的实现继承方式取决于成员函数的类型: 1. pure virtual函数:只继承接口,不继承实现(派生类必须提供实现) 2. impure virtual函数(普通虚函数):继承接口和缺省实现(派生类可以选择是否重写) 3. non-virtual函数继承接口和强制性实现(派生类不应重写,因为不符合多态) 现在,用户的问题是:“当继承作为接口的一部分时,应该使用哪种类型的继承”。这里需要明确,接口继承和实现继承是两种不同的概念。在接口设计中,我们通常关注的是接口继承,即定义一组方法签名(接口),而不关心具体实现。 在面向对象编程中,接口继承通常通过以下方式实现: -C++中,使用纯虚函数pure virtual function)来定义接口,这样派生类必须实现这些函数,从而实现了接口继承- 在Java和C#等语言中,有专门的interface关键字,用于定义纯接口(只包含方法签名,没有实现),类可以实现(implement)多个接口。 因此,当继承作为接口的一部分时,我们应该使用接口继承(即只继承方法签名,不继承实现)。具体到不同语言: 1. **C++**:使用抽象基类(Abstract Base Class, ABC),其中所有函数都是纯虚函数pure virtual)。例如: ```cpp class Shape { public: virtual double area() const = 0; // 纯虚函数,只继承接口 virtual ~Shape() = default; }; ``` 2. **Java/C#**:使用接口(interface)关键字定义纯接口。例如: ```java public interface Shape { double area(); // 接口方法,无实现 } ``` 3. **Go**:接口是隐式实现的,只要类型实现了接口中定义的所有方法,就视为实现了该接口。接口定义只包含方法签名,不包含实现(引用[2]中提到了Go的接口类型转换,说明Go的接口也是只继承接口,不继承实现)。 另外,引用[4]提到,在Java中,List和Set是Collection的子接口,而Map是独立的接口。这进一步说明,接口继承(子接口)用于扩展接口,而不涉及实现。 **总结**:当继承作为接口的一部分时,应该使用纯接口继承(即只继承方法签名,不继承实现)。具体实现方式: - C++:抽象基类(纯虚函数- Java/C#:interface关键字 - Go:interface类型 这样设计的好处是: - 实现接口与实现的分离,提高灵活性和可扩展性。 - 支持多继承(多个接口),避免实现继承带来的问题(如菱形继承问题)。 **注意事项**: - 避免在接口中定义非虚函数(non-virtual),因为这会强制实现继承,破坏接口的纯洁性。 - 谨慎使用虚函数(提供默认实现),因为这可能导致实现继承,如果派生类忘记重写,可能引发问题。 **相关问题**: 1. 在C++中,如何设计一个纯接口类? 2. 接口继承和实现继承的主要区别是什么? 3. 在Go语言中,接口的隐式实现有什么优缺点? 4. 为什么Map接口在Java中不继承自Collection接口?</think>### 接口继承类型的选择指南 当继承作为接口设计的一部分时,**应优先选择纯接口继承Pure Virtual Inheritance)**,这是面向对象设计的核心原则。不同继承方式在接口设计中的适用性如下: --- ### 一、接口继承的三种类型对比 | **继承类型** | 语法特征 | 适用场景 | 风险 | 语言支持示例 | |---------------------------|------------------------------|-----------------------------------|-----------------------------------|-------------------------------| | **纯接口继承** | 纯虚函数(`=0`) | 定义强制实现的规范 | 无默认实现需完全重写 | C++抽象类, Java接口, Go接口 | | **接口+缺省实现继承** | 虚函数(带实现体) | 提供可选的扩展点 | 可能导致"脆弱基类"问题[^1] | C++函数, Java默认方法 | | **强制实现继承** | 非虚函数 | 固定不变的核心行为 | 破坏多态性,违反Liskov替换原则[^1] | 所有语言的普通成员函数 | --- ### 二、最佳实践场景分析 #### 1. 首选纯接口继承(✅ 推荐) **适用场景**: - 定义跨模块通信契约(如插件系统) - 需要多继承的场景(Java/C#/Go的接口可多继承- 强制实现者提供特定功能 **代码示例**: ```java // Java 纯接口 public interface Drawable { void render(); // 纯接口声明 } // Go 接口 type Writer interface { Write([]byte) (int, error) } ``` **优势**: - 解耦接口与实现(符合DIP原则) - 支持运行时类型检查与转换[^2] - 避免实现继承的耦合风险 #### 2. 谨慎使用接口+缺省实现(⚠️ 有限使用) **适用场景**: - 为接口提供向后兼容的扩展 - 非核心功能的可选实现 - 减少重复代码(模板方法模式) **风险控制**: ```cpp // C++ 带保护的缺省实现 class Loggable { public: virtual ~Loggable() = default; virtual void log() { // 提供基础实现但允许重写 defaultLogImpl(); } private: void defaultLogImpl(); // 隐藏实现细节 }; ``` #### 3. 避免强制实现继承(❌ 不推荐) **问题案例**: ```cpp class Collection { public: void clear() { ... } // 非虚函数 }; // 派生类无法重写clear(),违反集合接口规范[^4] ``` --- ### 三、语言特性差异 | **语言** | 纯接口机制 | 缺省实现机制 | 多继承支持 | |----------|---------------------|--------------------|-------------------| | C++ | `class IInterface { virtual void f() = 0; }` | 虚函数实现体 | 支持多接口继承 | | Java | `interface` 关键字 | `default` 方法 | 支持多接口实现 | | Go | `interface` 类型 | 通过嵌入结构体实现 | 隐式实现多接口[^2]| | C# | `interface` | 显式接口实现 | 支持多接口 | > **关键设计原则**: > $$ \text{接口稳定性} \propto \frac{\text{纯抽象方法数量}}{\text{具体方法数量}} $$ > 纯接口比例越高,系统的扩展性和维护性越强[^1][^3] --- ### 四、实际应用决策树 ```mermaid graph TD A[需要定义接口] --> B{是否需要默认实现?} B -->|否| C[使用纯接口继承] B -->|是| D{默认实现是否稳定?} D -->|是| E[使用虚函数+缺省实现] D -->|否| F[拆分为纯接口+独立工具类] ``` --- ### 五、典型错误案例 **错误**:在接口中使用非虚函数 ```cpp // 违反接口隔离原则 class IDataStorage { public: void save() { /* 强制实现 */ } // 应改为virtual virtual void load() = 0; }; ``` **后果**: - 派生类无法重写`save()`方法 - 违反开闭原则,修改基类会影响所有派生类[^1] - 无法通过接口引用调用正确实现(失去多态性) --- ### 相关问题 1. 如何在C++中实现类似Java的`interface`机制? 2. 接口继承与模板元编程如何协同工作? 3. Go语言的接口隐式实现机制有哪些优缺点[^2]? 4. 为什么Java的Map接口不继承自Collection[^4]? 5. 设计模式(如策略模式)中如何应用纯接口继承
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值