【C++进阶】多态

目录

多态的条件 

析构函数的重写 

C++11 override 和 final

1.final:修饰虚函数,表示该虚函数不能再被重写

2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

重载、覆盖(重写)、隐藏(重定义)的对比

抽象类

多态的原理

虚函数表

多态的原理

单继承和多继承关系的虚函数表 

单继承中的虚函数表


多态的条件 

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "正常排队—全价买票" << endl;
	}
protected:
	int _age;
	string _name;
};
class Student:public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "正常排队—半价买票" << endl;
	}
protected:
	int _age;
	string _name;
};
class Solider:public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "优先排队—全价买票" << endl;
	}
protected:
	int _age;
	string _name;
};
//多态两个条件
//1,子类重写父类的虚函数
//2,必须是父类的指针或者引用去调用虚函数
void Func(Person* ptr)
{
	//多态—ptr指向父类对象调用父类的虚函数,指向子类对象调子类虚函数
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Solider sd;

	Func(&ps);
	Func(&st);
	Func(&sd);
}

多态的两个条件

1.子类重写父类的虚函数

2.必须是父类的指针或者引用去调用虚函数 

析构函数的重写 

class Person
{
public:
	virtual ~Person()
	{
		cout << "~Person()" << endl;
	}
};
class Student :public Person
{
public:
	virtual ~Student()
	{
		cout << "~Student()" << endl;
	}
};
//如果不是虚函数,他们之间是隐藏关系
//是虚函数,他们是重写关系
int main()
{
	普通场景下面,虚函数是否重写都是OK的
	//Person p;
	//Student s;
	//new对象特殊场景
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;//p1->destructor()
	delete p2;//p2->destructor()
	return 0;
}

注意!析构函数要定义成虚函数

有些书籍会把多态进行更多的划分:

1,静态的多态——函数的重载,调用同一个函数,传不同的参数,就有不同的行为/形态

2,动态的多态——调用一个虚函数,不同对象去调用,就有不同的行为/形态

多态的条件:

a,子类中重写父类的虚函数

b,必须是父类的指针或者引用去调用重写的虚函数

虚函数重写条件:

a,子类和父类都必须是虚函数

b,函数名,参数,返回值都必须相同

例外:

a,协变

b,析构函数被特殊处理

c,子类中的重写虚函数可以不加virtual

C++11 override 和 final

1.final:修饰虚函数,表示该虚函数不能再被重写

【C++进阶】多态_第1张图片

2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

class Car{
public:
	virtual void Drive(){}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

重载、覆盖(重写)、隐藏(重定义)的对比

【C++进阶】多态_第2张图片

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

多态的原理

虚函数表

虚函数表:

如下代码的输出结果是?

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
int main()
{
	Base b;
	cout << sizeof(Base) << endl;
	return 0;
}

【C++进阶】多态_第3张图片

 在看一段代码:

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

【C++进阶】多态_第4张图片

父子类无论是否完成虚函数的重载,都有各自独立的虚表

一个类的所有对象,共享一张虚表

多态的原理

class Person 
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

【C++进阶】多态_第5张图片

 分析===》

【C++进阶】多态_第6张图片

这里存在问题:多态的这种调用也是付出代价的,多态的调用要到虚表里面去找,会有一定程度的性能损失

复习:普通函数是如何确认地址的?编译或链接时确定它的地址

编译:编译的时候往上去找有无这个函数的定义,编译完,找到地址,编程call

链接:只有他的声明,他的定义在其他的目标文件里面,这个时候先过,call(?),链接的时候拿这个函数名去找

多态的调用?

先检查是不是满足多态,多态的调用是运行时去确定地址,如何确定?==》去指向对象的虚函数表中知道虚函数的地址

单继承和多继承关系的虚函数表 

单继承中的虚函数表

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	private:
		int _b = 1;
	};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
	virtual void Func4()
	{
		cout << "Derive::Func4()" << endl;
	}
private:
	int _d = 2;
};
typedef void(*VFPTR)();
//打印虚表
void PrintVFT(void* vft[])
{
	printf("%p\n",vft);
	for (size_t i = 0; vft[i] != nullptr; i++)
	{
		printf("vft[%d]:%p->",i,vft[i]);
		VFPTR f = (VFPTR)vft[i];
		f();
	}
	printf("\n");
}
int main()
{
	Base b;
	Derive d;
	PrintVFT((void**)*(int*)&b);
	PrintVFT((void**)*(int*)&d);

	return 0;
}

【C++进阶】多态_第7张图片

【C++进阶】多态_第8张图片

虚函数存在哪的?虚表存在哪的?

答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。

那么虚表存在哪的呢?===>常量区(代码段)

虚函数编译出来函数指令跟普通函数一样,存在代码段,虚函数地址又被放到虚函数表中

int main()
{
	Base bb;
	int a = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	auto pf = PrintVFT;
	static int b = 1;
	printf("栈祯变量:%p\n",&a);
	printf("堆变量:%p\n", p1);
	printf("常量区变量:%p\n", p2);
	printf("函数地址变量:%p\n", pf);
	printf("静态区变量:%p\n", &b);
	printf("虚函数表地址:%p\n", *(int*)&bb);
	return 0;
}

【C++进阶】多态_第9张图片

你可能感兴趣的:(c++,开发语言)