C++虚函数

C++三大特性

1.继承
一个对象直接使用另一个对象的属性和方法,减少重复的代码,增加了类的耦合性。但父类通常定义了子类的部分行为,父类的改变可能影响子类的行为。
2. 多态
C++中有静多态(编译时多态)和动多态(运行时多态)两种。静多态主要通过模板来实现,宏也是实现静多态的一种途径,所有调用在编译期就可确定,因此它是静态的;动多态在C++中是通过虚函数实现的,即在基类中存在一些接口(一般为纯虚函数),子类必须重载这些接口,通过使用基类的指针或者引用指向子类的对象,通过基类指针实现调用子类对应的函数的功能,函数调用在执行期才能进行确定,所以它是动态的。

/*****静多态*****/
#include <iostream> 
using namespace std;

class line 
{ 
public: 
    void draw() 
    { 
        cout << "line is drawing!" << endl; 
    } 
};

class circle 
{ 
public: 
    void draw() 
    { 
        cout << "circle is drawing!" << endl; 
    } 
};

template<typename T> 
void drawShape(T & shape) 
{ 
    shape.draw(); 
}

int main() 
{ 
    line lining; 
    circle circling; 
    drawShape( lining ); 
    drawShape( circling );
    return 0; 
}

/*****动多态*****/
#include <iostream> 
using namespace std;

class shape 
{ 
public: 
    virtual void draw() = 0; 
};

class line : public shape 
{ 
public: 
    void draw() 
    { 
        cout << "line is drawing!" << endl; 
    } 
};

class circle : public shape 
{ 
public: 
    void draw() 
    { 
        cout << "circle is drawing!" << endl; 
    } 
};

int main() 
{ 
	/*基类的指针指向子类的对象*/
    shape* pLine = new line; 
    shape* pCircle = new circle;
    pLine->draw(); 
    pCircle->draw(); 
    if( pCircle ) delete pCircle ; 
    if( pLine ) delete pLine ; 
    
    /*基类的引用指向子类的对象*/
	//line line; 
	//circle circl; 
	//shape& referenceLine = line;    // 
	//shape& referenceCircl = circl; 
	//referenceLine.draw(); 
	//referenceCircl.draw();
	
    return 0; 
}

动多态代码运行结果:
在这里插入图片描述
3. 封装
隐藏对象的属性和实现细节,仅仅对外提供接口和方法。
重载:写一个与已有函数同名但是参数表不同的函数
覆盖:派生类中改写基类中的虚函数

虚函数与纯虚函数

虚函数使用的其核心目的是通过基类访问派生类定义的函数(virtual),实现了多态的机制。在使用虚函数的过程中存在两个常见错误:无意的重写(具有相同的签名的成员函数)、虚函数签名不匹配(函数名、参数列表 或 const 属性不一样)。针对上述情况,C++ 11 增加了两个继承控制关键字:override 和 final。override:防止虚函数的覆盖,保证基类的虚函数在派生类重载;final:防止基类的虚函数在派生类重载,保证虚函数的覆盖。

/*override:防止虚函数覆盖*/
class Base {
public:
    virtual void display(int x); // 虚函数
};

class Derived : public Base {
public:
    virtual void display(int x) const override; // const 属性不一样,新的虚函数 
}

/*final:防止虚函数重载*/
class Base {
public:
    virtual void display(int x) final; // 虚函数
};
 
class Derived : public Base {
public:
    virtual void display(int x) override; // 重写提示错误  
};

当想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数(virtual 返回类型 函数名() = 0;)。

虚继承

在这里插入图片描述
在上图菱形继承中,类 A 中的成员变量和成员函数继承到类 D 中变成了两份,在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承(继承方式前面加上 virtual 关键字),使得在派生类中只保留一份间接基类的成员。虚继承机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

虚指针与虚表

C++实现虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员保存了一个指针,这个指针叫虚表指针(vptr),它指向一个虚函数表(vtbl),位于该类的首地址(系统为32位时地址为前4个字节,系统为64位时地址为前8个字节)。
在这里插入图片描述
基类对象包含一个虚表指针,指向基类的虚函数表,派生类对象也将包含一个虚表指针,指向派生类虚函数表,如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址,如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址,但如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
例子:

#include <iostream>
int main()
{
	class A
	{
	public:
		virtual void vfunc() { std::cout << "A::vfunc" << std::endl; }
		virtual void vfuncA() { std::cout << "A::vfuncA" << std::endl; }
	public:
		double m_data = 1.57;
	};

	class B:public A
	{
	public:
		virtual void vfunc() { std::cout << "B::vfunc" << std::endl; }
		virtual void vfuncB() { std::cout << "B::vfuncB" << std::endl; }
	public:
		double m_data = 3.14;
	};

	typedef void(*Fun)(void);//声明一个函数指针

	A aObj;
	B bObj;
	A *abObj = new B;
	Fun pFun = NULL;//指向void* pf(void)类的函数的指针pFun

	/*_WIN64--只有64位程序才有,_WIN32--32位和64位程序都有*/
	/*_WIN64 用来判断编译环境是 x86 还是 x64,_WIN32 可以用来判断是否 Windows 系统*/
#if _WIN64	
	std::cout << "aObj实例对象的数据地址:" << &(aObj.m_data) << std::endl;
	std::cout << "A虚函数表的地址:" << (long long int*) * (long long int*)(&aObj) << std::endl;
	std::cout << "A虚函数表的第一个函数地址:" << (long long int*) * (long long int*) * (long long int*)(&aObj) << std::endl;
	std::cout << "A虚函数表的第二个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&aObj) + 1) << std::endl;
	std::cout << std::endl;
	std::cout << "bObj实例对象的数据地址:" << &(bObj.m_data) << std::endl;
	std::cout << "B虚函数表的地址:" << (long long int*) * (long long int*)(&bObj) << std::endl;
	std::cout << "B虚函数表的第一个函数地址:" << (long long int*) * (long long int*) * (long long int*)(&bObj) << std::endl;
	std::cout << "B虚函数表的第二个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 1) << std::endl;
	std::cout << "B虚函数表的第三个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 2) << std::endl;
	std::cout << std::endl;
	//再次取址得到第一个虚函数的地址

	//A第一个虚函数
	pFun = (Fun) * (long long int*) * (long long int*)(&aObj);
	pFun();
	//A第二个虚函数
	pFun = (Fun) * ((long long int*) * (long long int*)(&aObj) + 1);
	pFun();
	std::cout << std::endl;
	//B第一个虚函数
	pFun = (Fun) * (long long int*) * (long long int*)(&bObj);
	pFun();
	//B第二个虚函数
	pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 1);
	pFun();
	//B第三个虚函数
	pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 2);
	pFun();
	std::cout << std::endl;
	//基类指针指向派生类,调用派生类函数vfunc()
	aObj.vfunc();
	abObj->vfunc();
	
#else
	std::cout << "aObj实例对象的数据地址:" << &(aObj.m_data) << std::endl;
	std::cout << "A虚函数表的地址:" << (int*)*(int*)(&aObj) << std::endl;
	std::cout << "A虚函数表的第一个函数地址:" << (int*)*(int*)*(int*)(&aObj) << std::endl;
	std::cout << "A虚函数表的第二个函数地址:" << (int*)*((int*)*(int*)(&aObj) + 1) << std::endl;
	std::cout << std::endl;
	std::cout << "bObj实例对象的数据地址:" << &(bObj.m_data) << std::endl;
	std::cout << "B虚函数表的地址:" << (int*)*(int*)(&bObj) << std::endl;
	std::cout << "B虚函数表的第一个函数地址:" << (int*)*(int*)*(int*)(&bObj) << std::endl;
	std::cout << "B虚函数表的第二个函数地址:" << (int*)*((int*)*(int*)(&bObj) + 1) << std::endl;
	std::cout << "B虚函数表的第三个函数地址:" << (int*)*((int*)*(int*)(&bObj) + 2) << std::endl;
	std::cout << std::endl;
	//再次取址得到第一个虚函数的地址

	//A第一个虚函数
	pFun = (Fun) * (int*)*(int*)(&aObj);
	pFun();
	//A第二个虚函数
	pFun = (Fun) * ((int*)*(int*)(&aObj) + 1);
	pFun();
	std::cout << std::endl;
	//B第一个虚函数
	pFun = (Fun) * (int*)*(int*)(&bObj);
	pFun();
	//B第二个虚函数
	pFun = (Fun) * ((int*)*(int*)(&bObj) + 1);
	pFun();
	//B第三个虚函数
	pFun = (Fun) * ((int*)*(int*)(&bObj) + 2);
	pFun();
	std::cout << std::endl;
	//基类指针指向派生类,调用派生类函数vfunc()
	aObj.vfunc();
	abObj->vfunc();
#endif
	delete abObj;
	return 0;
}

运行结果:
在这里插入图片描述
上例虚函数存储方式如图所示:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值