【C++】多态

多态概念

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向父类的指针,来调用实现子类中的方法。

ege:Person买火车票是全价,Student买火车票是半价

多态的定义及实现

构成多态的条件
1. 被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写
2. 调用虚函数的类型必须是指针或者引用
3.一般使用父类的指针或者引用指向子类对象

虚函数:virtual修饰的类成员函数
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价" << endl;
	}
};
虚函数的重写:子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了父类的虚函数。
class Person
{
public:
	virtual void Buy_Ticket()
	{
		cout << "全价" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Buy_Ticket()
	{
		cout << "半价" << endl;
	}
};
class Teacher :public Person
{
public :
	virtual void Buy_Ticket()
	{
		cout << "Teacher全价" << endl;
	}
};
void D_T_Test1()
{
	Student s;
	//不是多态:值拷贝
	Person p = s;
	p.Buy_Ticket();
	//多态:引用执行虚函数
	Person& pp = s;
	pp.Buy_Ticket();
	//多态:指针指向虚函数
	Person* ppp = &s;
	ppp->Buy_Ticket();
	//多态:引用执行虚函数
	Teacher t;
	Person& pt = t;
	pt.Buy_Ticket();
	t.Buy_Ticket();//不是多态:这是t对象的具体行为
}
输出:

全价
半价
半价
Teacher全价
Teacher全价
注意:
1.多态看指针/引用实际指向的对象是什么,实际指向的就是对应对象的方法
2.非多态看类型,看指针/引用,是什么类型就执行对应类型的方法
3。只要继承的父类有虚函数对象,则此子类与父类才可能产生多态行为

若父类没有虚函数声明,子类有虚函数声明,子类和父类对应函数构成的是隐藏,而不是重写,所以不是多态行为。

子类有对应的子类(孙子类),子类的函数和子子类(孙子类)的函数就构成了函数重写(因为假如父类有虚函数的声明,子类对应的函数不管有没有写virtual关键字,都已经默认为虚函数了),就会产生多态行为。

虚函数重写的特例

1.协变:父类虚函数返回父类对象的指针/引用,子类虚函数返回子类对象的指针/引用,即返回值类型构成父子关系的指针/引用

class X_B_A
{
public:
	virtual X_B_A& Out()
	{
		cout << "A" << endl;
		return X_B_A();
	}
};
class X_B_B : public X_B_A
{
public :
	virtual X_B_B& Out()
	{
		cout << "B" << endl;
		return X_B_B();
	}
};
void D_T_Test2()
{
	X_B_A a;
	X_B_B b;
	a.Out();
	b.Out();
}
输出
A
B
2.析构函数的重写(父类与子类的析构函数名肯定不同)

父类析构函数与子类析构函数会构成同名隐藏,底层编译器会修改析构函数的名字,导致父类与子类的析构函数同名。所以如果父类析构函数是虚函数,则此时子类析构函数只要定义,都会与父类析构函数构成重写,然后产生多态行为

class X_B_A
{
public:
	virtual ~X_B_A()
	{
		cout << "~X_B_A()" << endl;
	}
};
class X_B_B : public X_B_A
{
public :
	~X_B_B()
	{
		cout << "~X_B_B()" << endl;
	}
};
void D_T_Test2()
{
	X_B_A* pa = new X_B_B;
	delete pa;
}
输出
~X_B_B()
~X_B_A()
当父类X_B_A的析构函数~X_B_A()不是虚函数时,输出
~X_B_A()

假如子类X_B_B中有大量的空间,那么当父类析构函数不是虚函数时,在释放空间时,就不会释放这些空间,这就造成了内存泄露。
所以,在写大型项目的时候,最好将父类的析构函数设为虚函数
C++11 overridefinal
1. final:修饰虚函数,表示该虚函数不能再被继承
class Dad
{
public :
	virtual void say() final
	{
		cout << "Dad" << endl;
	}
};
class Son :public Dad
{
public:
	virtual void say()
	{
		cout << "Son" << endl;
	}
};

在这里插入图片描述

2. override:强制子类重写父类的函数,强制实现多态,如果父类中无此函数,或父类中的函数并不是虚函数,编译器会给出相关错误信息
class Dad
{
public :
	virtual void say() 
	{
		cout << "Dad" << endl;
	}
	void act()
	{
		cout << "act()" << endl;
	}
};
class Son :public Dad
{
public:
	virtual void act() override//父类不是虚函数,编译器会报错
	{
		cout << "Son" << endl;
	}
	virtual void Say() override//父类中无此函数,编译器会报错
	{
		cout << "Son" << endl;
	}
	virtual void say() override
	{
		cout << "Son" << endl;
	}
};

在这里插入图片描述

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

函数重载:函数名相同,参数返回值无所谓,两个函数在相同作用域

函数隐藏(函数重定义):只要函数名相同,参数返回值无所谓,继承场景,不同作用域(一个是父类的函数,一个是子类的函数)

函数重写(函数覆盖):函数名相同,参数相同,返回值相同(协变除外),继承场景的虚函数,不同作用域(一个是父类的虚函数,一个是子类的虚函数)

class bike
{
public:
	virtual void color()
	{
		cout << "bike_color()" << endl;
	}
	virtual void color(string s)
	{
		cout << "bike_color()_string" << endl;
	}
};
/*
	函数重载: bike::color()
			  bike::color(string)
	函数重写:bike::color()
			 ofo::color()
	函数隐藏:bike::color(string)
			 ofo::color()
*/
class ofo :public bike
{
public:
	virtual void color()
	{
		cout << "ofo_color()" << endl;
	}
};

在这里插入图片描述

抽象类

概念:包含纯虚函数的类,该类不能实例化出对象,子类继承后也不能实例化出对象,只有通过重写纯虚函数,子类才可以实例化对象。如果该类所有的虚函数都是纯虚函数,则该类就是一个接口
纯虚函数:虚函数的后面写上 = 0
class animal
{
public:
	virtual void eat() = 0;
};
class cat :public animal
{
public:
	virtual void eat()
	{
		cout << "猫吃鱼" << endl;
	}
};
void D_T_Test3()
{
	animal a;//不能实例化抽象类	
	cat c;
	c.eat();
}
输出
猫吃鱼
虚函数继承与普通继承

普通继承:子类继承父类的函数,可以直接使用该函数。

虚函数继承:子类继承父类虚函数的接口,目的是为了实现函数重写,从而构成多态。

虚表指针:
1.只要类中有虚函数,对应的对象中就会有虚表指针,一般存放在对象的前4个字节中(32位)/前8个字节(64位),存放位置与平台有关,
2.虚表指针指向虚表
虚表:
1.虚函数的指针数组
2.虚表在VS中存放在代码段
class Car
{
public:
	 virtual void brand()
	{
		cout << "Car_brand" << endl;
	}
	 virtual void color()
	 {
		 cout << "Car_color" << endl;
	 }
	 void run()
	 {
		 cout << "run" << endl;
	 }
	
};
class Ao_Di :public Car
{
public:
	virtual void brand()
	{
		cout << "Ao_Di" << endl;
	}
};
//全局变量:数据段
int Qval = 20;
void D_T_Test5()
{
	Car c;
	Ao_Di ad;
	cout << "sizeof(c)=" << sizeof(c) << endl;
	cout << "sizeof(ad)=" << sizeof(ad) << endl;
	//局部变量:栈
	int val = 10;
	//动态开辟:堆
	int* p = new int(2);
	
	cout << "栈:" << &val << endl;
	cout << "堆:" << p << endl;
	cout << "数据段:" << &Qval << endl;
	cout << "代码段:" << D_T_Test4 << endl;//函数名就是一个地址
	
	cout << "虚表:" << (int*)(*(int*)(&ad)) << endl;
	//&ad 虚表指针
	//(int*)(&ad) 取前4个字节的内容
	//(int*)(*(int*)(&ad)) 强转成int*
}
输出
sizeof(c)=4
sizeof(ad)=4
栈:005FF6B0
堆:009E50A0
数据段:004B0014
代码段:00440094
虚表:004A64BC
由上可知,VS中虚表存在代码段

## 图4

多态理论执行过程:
1.引用/指针调用虚函数,
2.通过引用/指针获取对应对象的虚表指针
3.通过虚表指针访问对应的虚表
4.在虚表中获取对应虚函数的地址
5.通过该地址找到该函数,执行该函数,完成多态
void f(Car& c)
{
	c.brand();
}
void D_T_Test6()
{
	Car c;
	Ao_Di ad;
	f(c);
	f(ad);
}

## 图5

多态实际执行过程:
void D_T_Test6()
{
	Car* adc = new Ao_Di;
	Ao_Di ad;
	//多态
	adc->brand();
	//非多态
	ad.brand();
}

## 图6

动态绑定与静态绑定
1.静态绑定也叫静态多态,编译时多态,前期绑定。比如:函数重载,模板

静态多态:在程序编译期间就已经确定了程序的行为的方式,函数的重载也是一种多态,但是函数重载的多态是利用了命名规则,我们调用不同参数的函数等于调用了不同函数的函数,与调用普通函数没有差别,其调用规则在程序编译期间就已经决定,这种绑定方式也称为早绑定

2.动态绑定也叫动态多态,运行时多态,后期绑定。

动态多态:在程序运行期间根据具体类型动态决定程序执行行为的方式称为动态绑定,例如利用虚函数重写的方式达成的多态,就是在程序执行期间根据类型去不同的虚表中调用不同的函数,以此达成多态功能,这种绑定方式也成为晚绑定

为什么调用虚函数的类型必须是指针或者引用

待补充

单继承中的虚函数表

子类继承父类的虚表,重写父类虚函数的指针,新定义的虚函数,它的指针安装声明顺序依次向后增添

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};
typedef void(*vfptr) ();
void OutVfptr(vfptr* vftable)
{
	cout << "虚表地址:" << vftable << endl;
	while (*vftable != nullptr)
	{
		vfptr f = *vftable;
		f();
		++vftable;
	}
}
void D_T_Test7()
{
	Base b;
	Derive d;
	cout << "Base vftable" << endl;
	OutVfptr((vfptr*)(*((int*)&b)));
	cout << "Derive vftable" << endl;
	OutVfptr((vfptr*)(*((int*)&d)));
	//访问虚表的内容
}
输出
Base vftable
虚表地址:00F90E20
Base::func1
Base::func2
Derive vftable
虚表地址:00F90F20
Derive::func1
Base::func2
Derive::func3
Derive::func4

在这里插入图片描述

多继承中的虚函数表

虚表个数与直接父类的个数相同

子类继承父类的虚表,重写父类虚函数的指针,新定义的虚函数,它的指针安装声明顺序依次在第一个虚表中增添

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};
class Derive1 : public Base1, public Base2{
public:
  virtual void func1() { cout << "Derive::func1" << endl; }
  virtual void func3() { cout << "Derive::func3" << endl; }
private:
  int d1;
};
typedef void(*vfptr) ();
void OutVfptr(vfptr* vftable)
{
	cout << "虚表地址:" << vftable << endl;
	while (*vftable != nullptr)
	{
		vfptr f = *vftable;
		f();
		++vftable;
	}
}
void D_T_Test8()
{
	Base1 b1;
	Base2 b2;
	Derive1 d1;
	cout << "Base1 vftable:" << endl;
	OutVfptr((vfptr*)(*((int*)&b1)));
	cout << "Base2 vftable:" << endl;
	OutVfptr((vfptr*)(*((int*)&b2)));
	cout << "Derive1:Base1 vftable:" << endl;
	OutVfptr((vfptr*)(*((int*)&d1)));
	cout << "Derive1:Base2 vftable:" << endl;
	//指针偏移:指向Base2部分
	Base2* pb2 = &d1;
	OutVfptr((vfptr*)(*((int*)pb2)));
}
输出
Base1 vftable:
虚表地址:00C375D0
Base1::func1
Base1::func2
Base2 vftable:
虚表地址:00C37600
Base2::func1
Base2::func2
Derive1:Base1 vftable:
虚表地址:00C37630
Derive::func1
Base1::func2
Derive::func3
Derive1:Base2 vftable:
虚表地址:00C37644
Derive::func1
Base2::func2

## 图8

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的多态(Polymorphism)是指在父类和子类之间的相互转换,以及在不同对象之间的相互转换。 C++中的多态性有两种:静态多态和动态多态。 1. 静态多态 静态多态是指在编译时就已经确定了函数的调用,也称为编译时多态C++中实现静态多态的方式主要有函数重载和运算符重载。 函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同。编译器根据传递给函数的参数类型和数量来确定调用哪个函数。例如: ```c++ void print(int num) { std::cout << "This is an integer: " << num << std::endl; } void print(double num) { std::cout << "This is a double: " << num << std::endl; } int main() { int a = 10; double b = 3.14; print(a); // 调用第一个print函数 print(b); // 调用第二个print函数 } ``` 运算符重载是指对C++中的运算符进行重新定义,使其能够用于自定义的数据类型。例如: ```c++ class Complex { public: Complex(double real, double imag) : m_real(real), m_imag(imag) {} Complex operator+(const Complex& other) const { return Complex(m_real + other.m_real, m_imag + other.m_imag); } private: double m_real; double m_imag; }; int main() { Complex a(1.0, 2.0); Complex b(3.0, 4.0); Complex c = a + b; // 调用Complex类中重载的+运算符 } ``` 2. 动态多态 动态多态是指在运行时根据对象的实际类型来确定调用哪个函数,也称为运行时多态C++中实现动态多态的方式主要有虚函数和纯虚函数虚函数是在父类中定义的可以被子类重写的函数,使用virtual关键字声明。当一个对象的指针或引用指向一个子类对象时,调用虚函数时会根据实际的对象类型来确定调用哪个函数。例如: ```c++ class Shape { public: virtual void draw() { std::cout << "Drawing a shape." << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ``` 纯虚函数是在父类中定义的没有实现的虚函数,使用纯虚函数声明(如virtual void func() = 0;)。父类中包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能作为基类来派生子类。子类必须实现父类的纯虚函数才能实例化。例如: ```c++ class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值