【C++多态】

目录

  • 1.多态的概念
  • 2.多态的定义及实现
    • 2.1多态的构成条件
    • 2.2虚函数的一些细节
    • 2.3析构函数可以是虚函数吗?
    • 2.4 重载、覆盖(重写)、隐藏(重定义)的对比
  • 3.抽象类
  • 4.多态的原理
    • 4.1虚函数表
    • 4.2虚函数地址的打印
    • 4.3多继承的函数虚表

1.多态的概念

什么是多态?
这种思想我们在很早的时候就已经接触过。如:水就有三种形态(固态、液态、气态)由于条件的变化三种形态可以互相转换,而每一种形态都有自己的特定的属性。
在比如:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

2.多态的定义及实现

2.1多态的构成条件

构成多态的两个条件:
1.必须使用父类的指针或者引用调用虚函数
2.被调用的必须是虚函数,且子类必须对父类的虚函数进行重写
如:

#include
using namespace std;
class Water
{
public:
	virtual void func()
	{
		cout << "液态的水" << endl;
	}
};
class Ice : public Water
{
public:
	virtual void func()
	{
		cout << "冰" << endl;
	}
};
class Vapor : public Water
{
public:
	virtual void func()
	{
		cout << "水蒸气" << endl;
	}
};
void test(Water& w)
{
	w.func();
}
int main()
{
	Water w;
	Ice i;
	Vapor v;
	test(w);
	test(i);
	test(v);
	return 0;
}

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

2.2虚函数的一些细节

1.子类的虚函数前可以不用加virtual关键字
2.虚函数的重写:即子类虚函数与类虚函数的返回值类型、函数名字、参数列表完全相同,称子类的虚函数重写了父类的虚函数。
3.重写的条件本来是虚函数+三同,但是有些例外。如协变。
协变:返回值可以不同,但必须是父子关系的指针或者引用。如:

#include
using namespace std;
class Water
{
public:
	virtual Water* func()
	{
		cout << "液态的水" << endl;
		return 0;
	}
};
class Ice : public Water
{
public:
	virtual Ice* func()
	{
		cout << "冰" << endl;
		return 0;
	}
};
class Vapor : public Water
{
public:
	virtual Vapor* func()
	{
		cout << "水蒸气" << endl;
		return 0;
	}
};
void test(Water& w)
{
	w.func();
}
int main()
{
	Water w;
	Ice i;
	Vapor v;
	test(w);
	test(i);
	test(v);

	return 0;
}

2.3析构函数可以是虚函数吗?

问题1:析构函数前加virtual 是虚函数吗?
答:是的。在C++中析构函数都被处理成destructor这个统一的名字。所以满足三同。
问题2:为什么要这样处理呢?
答:因为想让他们构成重写。
问题3:为什么要让他们构成重写呢?
答:因为下面的场景如果不构成重写会造成内存泄漏问题。如:

class person
{
public:
	//virtual ~person()
	~person()
	{
		cout << "~person()" << endl;
	}
};
class student : public person
{
public:
	//virtual ~student()
	~student()
	{
		delete[] arr;
		cout << "~student()" << endl;
	}
private:
	int* arr = new int[10];
};
int main()
{
	person* p;
	p = new student();
	delete p;//p->destructor() + operator delete(p)
	return 0;
}

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

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

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

3.抽象类

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

class Car
{
public:
	virtual void func() = 0;
};

class Benz : public Car
{
public:
	virtual void func()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW : public Car
{
public:
	virtual void func()
	{
		cout << "BMW-操控" << endl;
	}
};
int main()
{
	Car* pbenz = new Benz();
	Car* pbmw = new BMW();
	pbenz->func();
	pbmw->func();
	return 0;
}

4.多态的原理

4.1虚函数表

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
 virtual void Func1()
 {
 cout << "Func1()" << endl;
 }
private:
 int _b = 1;
};

上面的代码在x86平台下的答案是8。为什么是8呢?可以通过监视窗口来观察一下它的存储。【C++多态】_第4张图片
【C++多态】_第5张图片
通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,。那么派生类中这个表放了些什么呢?我们接着往下分析。
【C++多态】_第6张图片
通过上面的监视窗口可以观察到:
1.派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
2.基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
3.另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
4.虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
【C++多态】_第7张图片
5… 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
6.虚函数表存放的是虚函数的指针,虚函数与普通函数一样存放在代码段,那么虚函数表存放在哪里?是栈?堆?静态区?常量区?可以验证一下。
1.首先验证所有类型的对象都共用一个虚表。如:
【C++多态】_第8张图片
2.验证虚表的存储位置。如:

int main()
{
	int a = 0;
	printf("栈区:%p\n", &a);

	int* p = new int;
	printf("堆区:%p\n", p);

	static int b = 10;
	printf("静态区:%p\n", &b);

	const char* str = "hello tt";
	printf("常量区:%p\n", str);

	Base be;
	printf("虚表:%p\n", *((int*)&be));
	return 0;
}

【C++多态】_第9张图片
可以看出虚表是在常量区。

4.2虚函数地址的打印

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Func2()" << endl;
	}

private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
	virtual void Func3()
	{
		cout << "Func3()" << endl;
	}
private:
	int _d = 2;
};
typedef void (*FUNC) ();
void PrintVFT(FUNC* table)
{
	for (size_t i = 0; table[i] != nullptr; i++)
	{
		printf("[%d]:%p\n", i, table[i]);
	}
	printf("\n");

}
int main()
{
	/*int a = 0;
	printf("栈区:%p\n", &a);

	int* p = new int;
	printf("堆区:%p\n", p);

	static int b = 10;
	printf("静态区:%p\n", &b);

	const char* str = "hello tt";
	printf("常量区:%p\n", str);

	Base be;
	printf("虚表:%p\n", *((int*)&be));*/

	Base b;
	Derive d;
	int vft1 = *((int*)&b);
	int vft2 = *((int*)&d);
	PrintVFT((FUNC*)vft1);
	PrintVFT((FUNC*)vft2);
	return 0;
}

【C++多态】_第10张图片
【C++多态】_第11张图片
【C++多态】_第12张图片

4.3多继承的函数虚表

#include
using namespace std;
class Base1
{
public:
	virtual void func1()
	{
		cout << "Base1::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base1::func2" << endl;
	}
};
class Base2
{
public:
	virtual void func1()
	{
		cout << "Base2::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base2::func2" << endl;
	}
};
class Derive :public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}
	virtual void func3()
	{
		cout << "Derive::func3" << endl;
	}
};
typedef void (*FUNC_VFT) ();
void PrintVFT(FUNC_VFT* table)
{
	for (size_t i = 0; table[i] != nullptr; i++)
	{
		printf("[%zd]:%p\n", i, table[i]);
		table[i]();
	}
	printf("\n");
}
int main()
{
	Derive d;
	Base1 b1;
	//Base2 b2;
	Base2* b2=&d;
	int vft1 = *((int*)&d);
	int vft2 = *((int*)b2);
	PrintVFT((FUNC_VFT*)vft1);
	PrintVFT((FUNC_VFT*)vft2);
	return 0;
}

【C++多态】_第13张图片

注意点:
1.子类的func3的虚函数地址放在从第一个父类继承过来的虚函数表中(可能跟不同的编译器有关)
2.子类func1看似在两个父类的虚函数表中的存放的地址不一样,实际只有一个地址且调用的同一个函数(原因感兴趣的可以观察汇编)

你可能感兴趣的:(c++)