C++虚函数与纯虚函数

2023年7月13日08:08:30

C++的虚函数与纯虚函数

C++项目看起来真的是太恼火了,源码阅读起来有点不便,幸亏有source insight 保命。C++有个很重要的特性,那就是多态。多态的实现主要是两种方式:一个就是函数的重载,另一个就是继承中对虚函数的重写。虚函数的引入对C++大型项目接口设计统一标准起到了非常重要的作用,与虚函数相对应的就是纯虚函数。

虚函数:用 virtual 标识标记的函数就是虚函数,只含有虚函数(且不含纯虚函数)的类就叫做虚类或者虚基类虚函数必须要实现,可以是空函数体。被子类继承,子类不重写的话那就是直接基类的方法。

纯虚函数:同样含有 virtual 标识符,但是定义时函数则是在函数名后直接加 =0 定义;含有纯虚函数的类叫纯虚类纯虚函数不用实现。子类只继承函数名,具体功能需要子类自己实现。

栗子奉上:

virtual void printTest();         //虚函数,需要函数体 
virtual void printTest() = 0;     //纯虚函数,只用定义

Java抽象类和接口

写过Java的人会发现,这个的功能特性与 java 的抽象类和接口的函数是类似的

抽象类:用 abstract 标识符标记的类就叫抽象类。抽象类的函数需要函数体,类似C++虚函数一样。

接口:与类(class)相对,用 Interface 标识符标记。接口只定义函数名,不具体实现。这个类似C++纯虚函数

直接栗子:

// 以下为多个文件
//-------------------- Base.java-----------------
package test;

abstract public class Base {        // 基类,抽象类
    public void printClass() {      // 抽象类函数必须实现,不然会报错
        System.out.println("print base class");
    }
}


//------------------ Child1.java----------------
package test;

public class Child1 extends Base{    // 子类继承,可直接继承基类函数,不用实现

}


//------------------Child2.java----------------
package test;

public class Child2 extends Base {
    @Override
    public void printClass() {        // 子类继承,可对基类函数进行重写
        System.out.println("print child2 class");
    }
}

//****************************************************************************
//------------------------------ IBase.java-------------------------
package test;

public interface IBase {    //接口,只定义接口函数形式,不具体实现
    void printClass();
}


//----------------------------- Child1_IBase.java----------------------
package test;

public class Child1_IBase implements IBase {
    @Override
    public void printClass() {        // 实现接口类,函数必须重写
        System.out.println("print Interface Child1");
    }
}


//***************************************************************************
//------------------TestMain.java--------------
package test;

public class TestMain {
    public static void main(String[] args) {
        Child1 child1 = new Child1();    //未进行函数重写
        Child2 child2 = new Child2();    //已进行函数重写
        child1.printClass();                // 基类函数打印
        child2.printClass();                // Child2 子类函数打印

        Child1_IBase child1Interface = new Child1_IBase();
        child1Interface.printClass();
    }
}

虚类和纯虚类 虚函数和纯虚函数

话说回C++,C++的虚函数和纯虚函数的实现也就跟这 java 抽象类和接口差不多了。栗子二连

#include <iostream>
using namespace std;

class base {    //虚基类
public:
    virtual void printClass() {    //虚函数实现
        cout <<"print base class" <<endl;
    }
};

class baseVirtual {    //纯虚类,只要含有一个纯虚函数即是纯虚类
public:
    virtual void printClass() {        //虚函数,需要实现
        cout <<"print baseVirtual class" <<endl;
    }
    virtual void printClassVirtual() = 0;    //纯虚函数,不需实现
};

//---------------------------------------------------------------
class sameChild1: public base {    //子类继承,不自己重写虚函数

};

class overrideChild2: public base{
public:
    void printClass() override {        // 虚函数重写,override 可不加上
        cout <<"print child2 class" <<endl;
    }
};

class virtualChild3 : public baseVirtual {
public:
    void printClassVirtual() override {    // 纯虚函数实现,override 可不加上
        cout <<"print virtualChild3 class" <<endl;
    }
};

//---------------------------------------------------------------
int main()
{
    base baseIns;        //虚基类,可以进行实例化
    baseIns.printClass();

    sameChild1 child1;    // 子类,函数直接继承父类
    child1.printClass();

    overrideChild2 child2;    // 子类,进行了函数重写
    child2.printClass();

    // baseVirtual baseVirIns;    // 纯虚类,不可实例化
    // baseVirIns.printClassVirtual();

    virtualChild3 child3;        // 纯虚类的子类,进行了纯虚函数的重写
    child3.printClassVirtual();
    return 0;
}

结果输出:

 与java不同之处:

1. java 的抽象类和接口都是不可以单独实例化的,只能由子类实例化

2. C++只有纯虚类不可以实例化,普通的虚基类可以进行实例化;因为方法有实现(但其实Java抽象类也有实现,哈哈哈)。

同时既然说到了 override,就说一下我的理解:

C++ override的使用

首先需要明确一个问题,那就是子类到底从基类中继承到了什么,这里只讨论函数方法

1. 基类中的纯虚函数,子类仅继承接口形式,具体函数体需要自己实现。

2. 基类中的虚函数,子类继承到了接口形式+缺省实现。具体函数一般还是要子类自己实现,当然也可直接使用基类的。

3. 基类中的非虚函数,子类继承到接口形式+具体实现。具体函数一般不需要子类自己实现,当然你强行想再实现也是可以的。

那就好像有没有 override 都可以正常实现虚函数的重写,那为什么要加个 override 呢?其实 override 是C++ 11之后加入的保留字,用于对虚函数重写的强制检查。举个栗子,还是上面那个示例,

class overrideChild2: public base{
public:
    /**
    * 函数名写错!!!
    * 假如没有 override ,程序员以为是对基类函数的重写,实际函数写错了。编译也不会有任何问题,到        
    * 函数调用的时候,仍然使用基类的接口形式调用;那么就会出现于预想中不一样的结果
    **/
    void printClassSSSP() /*override*/ {        
        cout <<"print child2 class" <<endl;
    }
};

对基类虚函数重写的错误,有可能因为函数名,参数类型,返回类型等等这些产生错误;但是这个是书写错误,这个是不容易查找出来的。派生类只会把这个当成是一个全新的函数,这是会带来很大的困扰,于是就有了 override 。被override 标识的函数,就明确表示是对基类函数的重写,那么必然需要有存在这样一个基类函数,在编译过程就会进行一个强制检查。如果发现基类中并没有这个函数,那说明是我们写错了函数,这样会保证虚函数的有效重写。所以 override 针对对象是基类的虚函数非虚函数表示基类一般希望子类继承自己的方法实现,加上一个 override 同样也会报错提醒

override 使用前提

1)子类想改写基类函数;

2)基类函数为虚函数; 

3)函数形式与基类保持一致(包括函数名,参数,返回类型) 

说白了 override 就是一种辅助的措施,防止一些不必要的错误

ps: 还是写这些杂的轻松快乐。磕盐要加糖,我们都一样。

  • 作者:wx_14678
  • 原文链接:https://blog.csdn.net/wx_14678/article/details/103844354
    更新时间:2023年7月13日08:08:30 ,共 3981 字。