C++语言学习记录-27:虚函数和抽象类

虚函数

在函数定义之前加上关键字virtual的函数被称为虚函数。
虚函数是实现多态性的手段之一,也就是虽然它有一样的声明,但是具体下到不同的类当中,实现的方法又有所差别
下面通过一个实例来理解虚函数

#include<iostream>
using namespace std;
class base
{
    
    
public:
	virtual void vfunc()
	{
    
    
		cout << "This is base's vfunc" << endl;
	}
};
class derived1 :public base {
    
    
public:
	void vfunc() {
    
    
		cout << "This is derived1's vfunc" << endl;
	}
};
class derived2 :public base {
    
    
public:
	void vfunc()
	{
    
    
		cout << "This is derived2's vfunc()" << endl;
	}
};
int main()
{
    
    
	base* p, b;
	derived1 d1;
	derived2 d2;
	p = &b;
	p->vfunc();
	p = &d1;
	p->vfunc();
	p = &d2;
	p->vfunc();
	return 0;
}

程序里面还定义了基类指针,展示了这种用法,通过指针来调用函数。p所指的对象的类型决定了执行vfunc的哪一个形式。此外,在运行时也可以得出这个结论,形成了运行时多态性的基础。当然使用一般的类运算符"."也是可以进行调用的

动态绑定和静态绑定

C++为了支持多态性,引入了动态绑定和静态绑定。
静态绑定是指绑定的是对象的静态类型,某一个特性(比如函数)依赖于对象的静态类型,发生在编译时期;动态绑定指的是绑定了对象的动态类型,特性依赖于对象的动态类型会发生在运行期。
只有才用“指针->函数”或“引用对象.函数”的方法调用C++类中的虚函数才会执行动态绑定。

#include<iostream>
using namespace std;
class CBase {
    
    
public:
	virtual int func() const
	{
    
    
		cout << "CBase function" << endl;
		return 100;
	}
};
class CDerived :public CBase
{
    
    
public:
	int func() const {
    
    
		cout << "CDerived function" << endl;
		return 200;
	}
};

void main()
{
    
    
	CDerived obj1;
	CBase* p1 = &obj1;
	CBase& p2 = obj1;
	CBase obj2;
	obj1.func();//静态绑定,调用了对象本身的func函数
	p1->func();//动态绑定,调用了被引用对象所属类的func函数
	p2.func();//动态绑定,调用了被引用对象所属类的func函数
	obj2.func();//静态绑定,调用对象本身的函数
}

运行结果:

CDerived function
CDerived function
CDerived function
CBase function

当引用或者指针在运行过程中被创建调用对象的过程,由于是在编译运行的时候进行的,所以是一种动态绑定,因此,一般情况下也只有通过地址,即指针和引用才能进行动态绑定

抽象类与纯虚函数

有一类虚函数,在基类当中并不能给出很好的实现,还不如放到子类当中进行功能实现的,可以使用纯虚函数,带有纯虚函数的类称为抽象类。
纯虚函数的定义格式为

virtual 函数类型 函数名(参数) = 0;

纯虚函数只需要声明,如果写了具体的定义也会被编译器忽略

抽象类是一种没有具体实现方法的类,只是作为一个基类来实现对事物的抽象,一个抽象类也是不能定义对象的,只能作为基类被继承,由它作为一个公共的接口,实现多态性。
当一个类继承了基类的时候,派生类就实现了基类中定义的虚函数。如果一个派生类没有将基类的纯虚函数全部实现,那么这个派生类仍然是一个抽象类,不能用来定义对象。如果一个派生类将抽象类全部实现了,那么这个派生类就不是一个抽象类了

虚析构函数

虚函数不能作为构造函数,因为如果作为的话初始化对象时就不能确定正确的成员数据类型,但是析构函数可以声明为虚函数

#include<stdlib.h>
#include<iostream>
using namespace std;
class A
{
    
    
public:
	virtual ~A()
	{
    
    
		cout << "A::~A()" << endl;
	}
};
class B :public A
{
    
    
public:
	B(int i)
	{
    
    
		buf = new char[i];
	}
	virtual ~B()
	{
    
    
		delete[]buf;
		cout << "B::~B()" << endl;
	}
private:
	char* buf;
};
void fun(A* a)
{
    
    
	delete a;
}
void main()
{
    
    
	A* a = new B(15);
	fun(a);
}
B::~B()
A::~A()

本例中,定义了一个类A,在基类中定义了一个虚析构函数。接下来,定义了该类的一个子类,定义了虚析构函数调用A的析构函数,同时定义了一个fun函数来对A的申请的控件进行删除。在主函数中定义了一个A的指针,用子类B的对象来初始化,然后调用fun函数,运行结果证明先调用了B的析构函数然后调用A的析构函数,也就是说在调用B的析构函数之后,系统运行时发现析构函数继承自A,就再调用一次A中的虚析构函数

抽象类的多重继承

实际生活中,一个事物往往拥有多个属性,在面向对象程序设计方法中,引入了多重继承的概念,即一个派生类可以有多个基类。
比如交通工具类可以派生出车和船两个子类,但是水陆两用船就必须继承车和船的属性
抽象类的多重继承与类的多重继承使用方法是一样的

虚函数表

C++中式通过虚函数表实现虚函数的调用。虚函数表(V-Table)主要存储的就是某个类的虚函数的地址,还保存了这个虚函数由哪个类继承实现,这个表能够真实地反映函数的继承情况。
其实,虚函数表只起到了地图的作用,当有一个派生类通过父类的指针来进行操作时,就可以查找虚函数表中的地址以寻找虚函数所占到的内存地址了。
使用虚函数表的过程是这样的:通过一个对象地址以寻找该表的地址,遍历该表中保存的虚函数的地址,通过地址调用相应的函数

虚函数在编程过程中的使用技巧

1.为了提高程序的清晰性,最好在类的每一个层次中都显式声明这些函数
2.没有定义虚函数的派生类简单的继承了其直接基类的虚函数
3.如果一个函数被定义为虚函数,那么重新定义类时即便没有声明这个虚函数,在之后的继承类层结构中都是虚函数

虚函数的注意内容

如果一个类中含有纯虚函数,那么任何试图对这个类进行实例化的操作都是错误的,因为抽象基类是不能直接被调用的,只能由子类进行继承并调用子类的方法
虚函数和纯虚函数的定义中不能有static标识符,因为被static修饰的函数在编译的时候要求前期绑定,然而虚函数是动态绑定的

猜你喜欢

转载自blog.csdn.net/leanneTN/article/details/112385567