【C++】多态

发布于:2025-06-03 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

1. 多态的概念

2. 多态的定义和实现

2.1 构成多态的条件

2.2 虚函数

2.3 虚函数的重写(覆盖)

2.4 小试牛刀

3. 重载/重写/隐藏的对比

4. 纯虚函数和抽象类

5.多态的原理

5.1 虚表

5.2 虚表指针

5.3 对比虚函数、虚表、虚表指针

1. 多态的概念

多态(Polymorphism)是面向对象编程的三大基本特征(继承,多态,封装)之一,指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

多态的字面意思是"多种形态",在编程中表现为:

同一个接口,使用不同的实例而执行不同操作

同一消息可以根据发送对象的不同而采用多种不同的行为方式

2. 多态的定义和实现

2.1 构成多态的条件

●必须是基类指针或者引用调用虚函数

●被调用的函数必须是虚函数,并完成了虚函数的重写/覆盖

2.2 虚函数

基类中加virtual修饰的类成员函数就是虚函数。

如图中的shout函数:

2.3 虚函数的重写(覆盖)

派生类中有有一个与基类完全相同(即返回类型、函数名、参数列表完全相同)的虚函数,则称派生类的虚函数重写了基类的虚函数。

注意:重写虚函数时,派生类中的虚函数可以不加virtual关键字,也可以构成重写,因为这样也可以构成重写,因为基类的虚函数属性被派生类虚函数继承了,但是这种写法并不规范。

2.4 小试牛刀

下面我们来试着做一道题,加深我们对虚函数的理解

下面的程序输出结果是什么:

 class A
 {
 public:
 virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
 virtual void test(){ func();}
 };
 
 class B : public A
 {
 public:
 void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; }
 };
 
 int main(int argc ,char* argv[])
 {
 B*p = new B;
 p->test();
 return 0;
 }

解析:

p->test() 调用 A::test()(test() 是继承自 A 的虚函数,但未被 B 重写)。

A::test() 内部调用 func(),由于 func() 是虚函数且 p 指向 B 对象,实际调用 B::func()。

在 C++ 中,默认参数是静态绑定(编译时确定),而虚函数是动态绑定(运行时确定)。因此,当通过基类指针或引用调用虚函数时:调用的是派生类(实际对象类型)的重写版本。使用的是基类函数声明的默认值,而非派生类的默认值。

所以默认参数 val 的值来自 A::func() 的定义(val = 1),而非 B::func() 的 val = 0。

因此,执行 B::func(1),输出 B->1

3. 重载/重写/隐藏的对比

重载:两个函数在同一作用域;函数名相同,参数相同,参数类型或个数不同;返回值可同可不同

重写(覆盖):两个函数分别在父类和子类不同作用域;函数名,参数,返回值必须相同,协变例外;两个函数都必须是虚函数

隐藏:两个函数分别在父类和子类不同作用域;函数名相同,只要不构成重写,就是隐藏;父子类的成员变量相同也是隐藏

4. 纯虚函数和抽象类

在虚函数后面加上=0,这类函数就是纯虚函数。纯虚函数语法上是可以定义实现的,但是也没必要,因为要被派生类重写,所以一般只需声明即可。

包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。如果派生类继承抽象类后不重写纯虚函数,那么派生类也是抽象类。

//抽象类
class Car
{
public:
	//纯虚函数
	virtual void Drive() = 0;
};

//库里南
class Cullinan:public Car
{
public:
	virtual void Drive()
	{
		cout << "Cullinan" << endl;
	}
};

//宾利
class Bentley :public Car
{
public:
	virtual void Drive()
	{
		cout << "Bentley" << endl;
	}
};

int main()
{
	//Car car;//抽象类不能实例化

	Car* pC = new Cullinan;
	pC->Drive();

	Car* pB = new Bentley;
	pB->Drive();

	return 0;
}

5.多态的原理

5.1 虚表

虚表(vTable):存储虚函数地址的表,每个类一份。

虚表是编译器为每个包含虚函数的类创建的一个隐藏数据结构,它是一个函数指针数组,存储了该类所有虚函数的实际地址。

虚表的特点包括:

每个具有虚函数的类都有自己的虚表

虚表在编译阶段生成,并存储在程序的只读数据段

派生类的虚表会继承基类的虚表内容,并替换掉被重写的虚函数地址

5.2 虚表指针

虚表指针(vptr):每个对象内部隐藏的指针,指向所属类的虚表。

虚表指针特点:

在对象构造时被初始化,指向该对象所属类的虚表

通常位于对象内存布局的最前面

大小通常为一个指针的大小(32位系统4字节,64位系统8字节)

在VS2022中调试下列代码

class animal
{
public:
	virtual void shout()
	{
		cout << "喊叫" << endl;
	}
};

class cat : public animal
{
public:
	virtual void shout()
	{
		cout << "喵喵" << endl;
	}
};

void say(animal& an)
{
	an.shout();
}

int main()
{
	animal a;
	cat c;
	say(a);
	say(c);
	return 0;
}

5.3 对比虚函数、虚表、虚表指针


网站公告

今日签到

点亮在社区的每一天
去签到