c++ 虚函数和多态、虚函数表

参考:
1、c++ primer plus 第六版
2、中国大学慕课:程序设计与算法(三)

基本概念:

虚函数:

在类的定义中,前面有virtual关键字的成员函数就是虚函数

class A
{
public:
	//类中函数声明
	virtual void Function();
}
//类外函数定义
void A :: Function()
{
}

说明:
1、virtual关键字只需要在类定义里的函数声明时加上,函数定义时不用加
2、构造函数和静态成员函数不能是虚函数
3、析构函数应当是虚函数
4、友元函数不能是虚函数
5、基类中的成员函数定义为虚函数,派生类中不加virtual关键字的同名同参函数也是虚函数

多态:

1、同一个方法在基类或者不同的派生类中的行为是不同的。
即具有多种形态,基类指针调用同名虚函数有不同的结果。
2、虚函数参与多态,普通成员函数不行

多态的表现形式有两种:
1、派生类的指针可以赋给基类的指针(反过来不行)
  • 通过基类指针调用基类和派生类的同名虚函数时:
    (1)指针指向的是基类对象,调用基类的虚函数
    (2)指针指向的是派生类对象,调用派生类的虚函数
#include <iostream>
using namespace std;
class BaseClass
{
public:
	void A()
	{
		cout << "基类的A" << endl;
	}
	virtual void B()
	{
		cout << "基类的B" << endl;
	}
};
class DerivedClass : public BaseClass
{
public:
	void A()
	{
		cout << "派生类的A" << endl;
	}
	virtual void B()
	{
		cout << "派生类的B" << endl;
	}
};

int main()
{
	//创建基类对象baseObject
	BaseClass baseObject;

	//创建派生类的对象derived
	DerivedClass derived;

	//创建基类的指针base指向基类对象baseObject
	BaseClass* baseP = &baseObject;
	cout << "基类的指针base指向基类对象baseObject" << endl;
	cout << "baseP->A() : ";
	baseP->A();
	cout << endl << "baseP->B() : ";
	baseP->B();
	cout << endl;

	//基类的指针base指向派生类对象derived
	baseP = &derived;
	cout << "基类的指针base指向派生类对象derived" << endl;
	cout << "baseP->A() : ";
	baseP->A();
	cout << endl << "baseP->B() : ";
	baseP->B();
	cout << endl;

	return 0;
}

在这里插入图片描述
可以看到成员函数B()是虚函数,执行相同的语句却因为指向的对象不同而产生不同的结果,这就是多态。

2、派生类的对象可以赋给基类的引用(反过来不行)
  • 通过基类引用调用基类和派生类的同名虚函数时:
    (1)引用引用的是基类对象,调用基类的虚函数
    (2)引用引用的是派生类对象,调用派生类的虚函数

(同理将上面的代码baseP改为引用)

这种调用同一个函数却有不同结果的机制就叫多态
说明:

1、在非构造函数非析构函数的成员函数中调用虚函数就是多态,会根据不同对象产生不同结果
2、在构造函数和析构函数中调用虚函数不是多态,会调用自己类的成员函数

多态的作用:

1、增强程序的可扩充性:程序修改或增加功能的时候,需要改动和增加的代码较少
2、用基类指针数组存放各种派生类对象的指针,让后遍历该数组,就能对个个派生类的对象做各种操作

多态的实现原理:

1、将源代码种的函数调用解释为执行特定函数代码块被称为函数名联编。

  • 静态联编:在编译过程中完成联编(编译时确定调用的所有函数)
  • 动态联编:多态的关键在于通过基类的指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定:称为动态联编(多态)

2、看一下下边的代码

class Base
{public:
int i;//4字节
}
class Derived : public Base
{public:
int j; //4字节
}


执行下面的语句:

sizeof(Base) //值为 8,
sizeof(Derived) //值为12,

可以看到多出来了4字节

虚函数表:

(不必纠结Print()函数,关键理解虚函数表是存储在数据元素的前面)
在这里插入图片描述
多态的函数调用被变编译一系列根据基类指针(或引用)指向的对象中存放的虚函数表的地址,在虚函数表中查找虚函数的地址并调用虚函数的指令

虚析构函数

问题:

通过基类的指针删除派生类对象时应先调用派生类的析构函数在调用基类的析构函数,但是通常情况下(虚构函数不是虚函数)只调用基类的构造函数。

解决办法:

把基类的析构函数声明为virtual: 这样就可以在基类的指针删除派生类对象时先调用派生类的析构函数再调用基类的析构函数。

// 继承.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
using namespace std;
class BaseClass
{
public:
	BaseClass()
	{
		cout << "基类构造函数被调用"<<endl;
	}
	virtual ~BaseClass() 
	{
		cout << "基类虚析构函数被调用" << endl;
	}
};
class DerivedClass : public BaseClass
{
public:
	DerivedClass()
	{
		cout << "派生类构造函数被调用" << endl;
	}
	virtual ~DerivedClass()
	{
		cout << "派生类析构函数被调用" << endl;
	}
};

int main()
{
	//创建基类对象baseObject
	BaseClass baseObject;

	//创建派生类的对象derived
	DerivedClass derived;
	
	return 0;
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200522214836622.pn

  • 白色部分为派生类对象的构造析构函数调用
  • 最上和最下为基类对象的构造析构函数调用

说明:

1、一般来说应把基类的析构函数声明为虚函数。
2、类定义了虚函数也应将析构函数定义为虚函数。
3、派生类的析构函数可以不加virtual关键字。

纯虚函数:

纯虚函数:没有函数体的虚函数

声明格式:

class A
{public:
	virtual void Function() = 0;
}

抽象类:

包含纯虚函数的类为抽象类。

  • 抽象类只能作为基类,不能创建抽象类的对象
  • 抽象类的指针和引用可以指向它派生出来的类的对象
class A
{public:
	A();
	virtual A();
	virtual void Function() = 0;
}

int main()
{
	A a;//错误:抽象类不能创建对象
	A *pa; //正确:声明抽象类的指针
	pa = new A; //错误:抽象类不能创建对象
}

说明:

1、抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数
2、如果一个类是抽象类的派生类,那么它必须实现所有的纯虚函数才不是抽象类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值