C++(12)——多态

多态

多态的概念:统一接口,不同的响应方式。
多态分为静多态(函数重载)和动多态(动态绑定)
所谓静态绑定是指在程序编译过程中,把函数(方法或者过程)调用与响应调用所需的代码结合的过程称之为静态绑定。
动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

动多态是用虚函数实现的(动态绑定)

class Base
{
public:
	Base(){}
	virtual void show(){cout<<"Base::show"<<endl;}
};
class Derive
{
public:
	Derive(){}
	void show(){cout<<"Derive::show"<<endl;
};
int main()
{
	Derive D;
	Base* p = &D;
	p->show();
	return 0;
}

在上述代码,用基类指针指向派生类对象,调用show函数的时候嗲用的是派生类的show函数(动态绑定)
在运行时期,编译器在执行到show函数的调用的时候,会看基类里面的函数是否是虚函数,如果是虚函数,那么他就识别的是运行时期的类型(即Derive),就发生了动态绑定。
那为什么要定义成虚函数呢?
因为我们要在运行阶段确定函数的调用,但是运行阶段内存中之后指令和数据,不存在符号表,不能访问函数的入口地址,所以我们要把函数定义成虚函数,在编译阶段将函数的入口地址做一个备份放在.rodata段的虚函数表上,最终被加载到内存。

虚函数表在这里插入图片描述

这是两个类的虚函数表
1、RTTI,存放的是类型信息,也就是(Base或者Derive)
2、偏移地址:虚函数指针相对于对象内存空间的偏移,一般vfptr都在0偏移位置
3、下面的函数时虚函数入口地址
在运行时期
那么我们再看下段代码

class Base
{
public:
	Base(int data = 10) :ma(data) {}
	//虚函数
	virtual void show()
	{
		cout << "Base::show()" << endl;
	}
	//虚函数
	virtual void show(int) { cout << "Base::show(int)" << endl; }
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int data = 20) :Base(data), mb(data) {}
	void show() { cout << "Derive::show" << endl; }
private:
	int mb;
};
int main()
{
	Derive d(50);
	Base* p = &d;
	/*
	如果发现show是普通函数就进行静态绑定
	如果发现show是虚函数,就进行动态绑定
	(虚函数的地址)动态(运行时期)的绑定(函数调用)
	*/
	p->show();
	//Base::Show()是一个虚函数,此时就得动态绑定
	p->show(10);
	cout << sizeof(Base) << endl;
	cout << sizeof(Derive) << endl;
	/*
	如果Base没有虚函数,*p识别的就是编译时期的类型 *pb《==》Base类型
	如果Base有虚函数,*p识别的就是运行时期的类型,*p识别的是RTTI类型
	*/
	cout << typeid(p).name() << endl;
	cout << typeid(*p).name() << endl;
	return 0;

*总结一:
如果一个类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的virtual虚函数表,虚函数表中主要存储的内容就是RTTI指针(运行时的类型信息,类型字符串,例如:Base)和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区
总结二:
一个类定义了虚函数,那么这个类定义的对象,其运行时,内存中开始部分多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个类型定义的N个对象指向的都是同一张虚函数表。
vfptr永远在对象的前四个字节====》虚函数指针(指向虚函数表的虚函数地址的起始部分)
总结三:
一个类里面虚函数的个数,不影响对象内存大小(vfptr),影响的是虚函数表的大小。
总结四:
如果派生类的方法和基类继承来的某个方法,返回值、函数名、参数列表都相同,而且基类的方法是virtual虚函数,那么派生类这个方法,自动处理成虚函数
*

虚函数依赖:
1、虚函数能产生地址,存储在vftable中
2、对象必须存在(vfptr-》vftable),所以构造函数和静态成员方法是不可成为虚函数的。
下面我来看一个例子:

class Base
{
public:
	Base(int data = 10) :ma(data)
	{ cout << "Base" << endl; }
	//虚析构函数
	~Base()
	{
		cout << "~Base" << endl;
	}
	void show() { cout << "Base::show()" << endl; }
	void show(int) { cout << "Base::show(int)" << endl; }
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int data = 20) :Base(data), mb(data) 
	{
		cout << "Derive" << endl;
	}
	//基类的析构函数是虚函数,那么派生类的析构函数自动成为虚函数
	~Derive()
	{
		cout << "Derive" << endl;
	}
	void show() { cout << "Derive::show" << endl; }
private:
	int mb;
};
int main()
{
	Base *p = new Derive(10);
	delete p;
	p->show();
	return 0;
}

在这里插入图片描述
运行的结果和我们预期的有点差别,我们会发现派生类的析构函数没有调用,因为析构的时候,如果Base里面析构函数不是虚函数,那么识别的就是Base的析构函数,如果是虚函数,那么发生动态绑定,识别的是Derive类型,调用也是Derive类型的虚函数表
构造函数和静态方法都不可以定义成虚函数
构造函数中(调用任何函数,都是静态绑定的)调用虚函数,也不会发生动态绑定
基类的指针(引用)指向堆上new出来的派生类对象的时候,delete 怕(基类的指针),他调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用
(重点)动态绑定必须由指针/引用变量调用虚函数

纯虚函数和抽象类

我们先来问几个问题:
如何解释多态?
静态(编译时期)的多态:函数重载、模板(函数模板和类模板)
动态(运行时期)的多态
在继承结构中,基类指针(引用)指向派生类对象,通过该指(引用)调用同名覆盖方法(虚函数)基类指针指向哪个派生类对象,就会调用哪个派生类对象的覆盖方法,称为多态。
多态底层是通过动态绑定实现的

继承的好处:
1、可以做代码复用
2、可以在基类中给所有派生类提供统一的虚函数接口,让派生类进行重写,然后可以使用多态
纯虚函数、抽象类
含有纯虚成员方法的类叫做抽象类
我们做个练习

/*
纯虚函数、抽象类
含有纯虚成员方法的类叫做抽象类

定义Animal的初衷并不是让animal可以初始化某个实体类型
1、string _name 让所有的动物实体类可以通过继承Animal直接复用该属性
2、给所有派生类保留统一的覆盖/重写接口

抽象类不能实例化对象,但是可以定义指针和引用变量
*/
class Animal
{
public:
	Animal(string name) :_name(name) {}
	virtual void bark() = 0;
protected:
	string _name;
};
class Cat :public Animal
{
public:
	Cat(string name) :Animal(name) {}
	void bark()
	{
		cout << _name << "i am a Cat" << endl;
	}
};
class Dog :public Animal
{
public:
	Dog(string name) :Animal(name) {}
	void bark()
	{
		cout << _name << "i am a Dog" << endl;
	}
};
class Pig :public Animal
{
public:
	Pig(string name) :Animal(name) {}
	void bark()
	{
		cout << _name << "i am a Pig" << endl;
	}
};
void bark(Animal &p)
{
	p.bark();
}

class Car
{
public:
	Car(string name, double oil) :_name(name) ,_oil(oil){}
	//获取汽车剩余流量还可以跑的公里数
	double  GetMiles()
	{
		// 1L 10kile * oil
		return _oil * this -> GetMilesGallon();
	}
protected:
	string _name;
	double _oil;
	virtual double GetMilesGallon() = 0;
};
class Bnze : public Car
{
public:
	Bnze(string name, double oil) :Car(name,oil) {}
	double GetMilesGallon() { return 20; }
};
class Audi : public Car
{
public:
	Audi(string name, double oil) :Car(name, oil) {}
	double GetMilesGallon() { return 15; }
};
class BMW : public Car
{
public:
	BMW(string name, double oil) :Car(name, oil) {}
	double GetMilesGallon() { return 10; }
};
double ShowMiles(Car &car)
{
	return car.GetMiles();
}
int main()
{
	Bnze c1("奔驰",20);
	Audi c2("奥迪",20);
	BMW c3("宝马",20);
	int a = ShowMiles(c1);
	int b = ShowMiles(c2);
	int c = ShowMiles(c3);
	cout << a << b << c;
	Cat cat("猫");
	Dog dog("阿拉");
	Pig pig("猪");

	bark(cat);
	bark(dog);
	bark(pig);
	return 0;
}

关于带默认参数的虚函数

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
		clear();
	}
	void clear()
	{
		memset(this, 0, sizeof(*this));
	}
	virtual void show(int i = 10)
	{
		cout << "Base::show i:" << i << endl;
	}
};
class Derive : public Base
{
public:
	Derive()
	{
		cout << "Derive::Derive" << endl;
	}
	void show(int i = 20)
	{
		cout << "Derive::show i:" << i << endl;
	}
};
int main()
{
	Derive *q = new Derive();

	Base *p = new Derive();
	/*
	输出结果为 Derive::show i: 10
	因为在函数编译阶段只能看到Base里面的show 的默认值,压入栈的是Base的默认实参,所以压入栈的是Base::show的默认值
	*/
	p->show();//函数调用参数压栈是在编译阶段就确定好的
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值