C++虚函数,纯虚函数,抽象类以及虚基类的区别
Part1.C++中的虚函数
- 什么是虚函数:
- 直观表达就是,如果一个函数的声明中有 virtual 关键字,那么这个函数就是虚函数。
- 虚函数的作用:
- 虚函数的最大作用就是实现面向对象程序设计的一大特点,多态性,多态性表达的是一种动态的概念,是在函数调用期间,进行动态绑定,以达到什么样的对象就实现什么样的功能的效果。
- 虚函数的一般声明语法:
- virtual 函数类型 函数名 (形参表)
- virtual 函数类型 函数名 (形参表)
- 注意:
- 虚函数的声明只能出现在类的定义中,不能出现在成员函数实现的时候
- 虚函数一般不声明为内联函数,但是声明为内联函数也不会引起错误
- 在运行过程中要实现多态的三个条件:
- 类之间满足赋值兼容关系(也就是类之间有继承关系)
- 要声明为虚函数
- 调用虚函数时,要由成员函数或者是指针和引用来访问
- 代码举例
#include <iostream> using namespace std; class Base1 { public: public: virtual void play(); }; void Base1::play() { cout << "Base1::play()" << endl; } class Base2: public Base1 { virtual void play(); }; void Base2::play() { cout << "Base2::play()" << endl; } class Derived : public Base2 { virtual void play(); }; void Derived::play() { cout << "Derived:: play()" << endl; } void fun(Base1* ba) { //声明一个基类的指针 ba->play(); } int main() { Base1 ba1; Base2 ba2; Derived de; //分别用不同的对象指针来调用 fun 函数 fun(&ba1); fun(&ba2); fun(&de); return 0; }
这代码含义就充分体现了虚函数作为实现多态条件的原因,由于 Base1 是 Base2 和 Derived 的父类,所以,Base1 是可以兼容 Base2 和 Derived 的,所以在 fun 函数这里是用的 Base1 的指针来作为形参,不同的是当我传入参数不同时,fun 函数执行的是不同的结果,这就体现了多态的效果,我需要那个类型的实例,他就执行那个实例对应的方法。
需要注意的是:
基类的指针可以指向派生类的对象,基类的引用可以作为派生类的别名,但是基类的对象不能表示派生类的对象,所以在 fun 函数中我使用的是 Base1 的指针(换成引用也可以),而不是 Base1 的对象。
Part2.纯虚函数和抽象类
纯虚函数一般是和抽象类一起使用的,因为抽象类的定义就是:拥有纯虚函数的类是抽象类,所以需要先了解一下纯虚函数。
- 什么是纯虚函数
- 在虚函数的声明基础上,在函数声明后面加上 =0,的函数为纯虚函数
- 一般定义语法:
- virtual 函数类型 函数名(参数表)=0;
- 什么时候使用纯虚函数:
- 当一个方法不用实例化
- 方法的实现必须在派生类中
- 注意:
- 纯虚函数不用被实现
- 区分纯虚函数和函数体为空的函数,这是两中不同的函数
抽象类:
- 抽象类的主要作用:
- 为一个类族建立起一个公共的接口,使他们能够完整发挥多态的特性
- 注意:
- 抽象类不能实例化
代码举例
#include <iostream> using namespace std; class Base1 { public: public: virtual void play()=0; }; void Base1::play() { cout << "Base1::play()" << endl; } class Base2: public Base1 { virtual void play(); }; void Base2::play() { cout << "Base2::play()" << endl; } class Derived : public Base2 { virtual void play(); }; void Derived::play() { cout << "Derived:: play()" << endl; } void fun(Base1* ba) { //声明一个基类的指针 ba->play(); } int main() { Base2 ba2; Derived de; //分别用不同的对象指针来调用 fun 函数 fun(&ba2); fun(&de); return 0; }
这段代码和上面的代码区别不大,唯一的区别在于,将 Base1 的 play方法声明为了纯虚函数,所以 base1 成了一个抽象类,就不能在直接声明一个 Base1 类型的对象,如果在声明一个纯虚函数,编译器就会报错,因为抽象类不能被实例化。
Part3.虚基类
之所以把虚基类放到这里为了做一个比较,因为学习了虚函数,抽象类后容易把这几个概念弄混,所以在这里对比记忆。
- 为什么要使用虚基类
#include <iostream> using namespace std; class A { public : int varA; }; class B: public A { public: int varB; }; class C: public A { public: int varC; }; class D : public A, public B { public: int varC; }; int main() { D d; d.varA; return 0; }
解析上面的代码,由于对象 D 是继承与 B 和 C的,然而 B 和 C 又共同继承与 A,所以 B 和 C都具有从 A 中继承下来的属性 varA,所以我可以用 d 对象来调用 varA ,但是正是因为 B 和 C 中都具有 varA,所以我用
d 对象调用 varA 时,编译器会产生疑问,到底是调用的 B 的 varA 还是 C 的 varA,这样就会产生错误,当然我们可以使用作用域分辨符来区分到底是谁的 varA,但是这样就不能很好的体现继承的概念了,所以这里使用虚函数来解决这个问题。
- 虚基类的作用
- 当一个派生类中有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接基类的数据成员的多份同名成员,就会造成二义性,将这个共同基类设置为虚基类时,同名的数据成员就会只存在一个副本,同一个函数名只有一个映射,就解决了二义性。
也就是说,当声明为虚基类时,在程序中只保留了一个共同基类的数据副本,对象 B 和对象 C 的 varA 都指向同一个 varA(本来也应该是这样),这样再调用 varA 时,在程序中只有一个 varA,编译器就不会产生疑问了。
- 基本语法
class A { public : int varA; }; class B: virtual public A { public: int varB; }; class C: virtual public A { public: int varC; }; class D : public A, public B { public: int varC; };
Part4.总结
虚函数,是在类中中的概念,他的重要作用就是实现面向对象的多态的特性,
抽象类,是类的概念,他必须和纯虚函数函数搭配使用
虚基类,是继承中的概念,是继承过程中用来解决多个间接基类同一个副本产生的二义性的方法。