首页 > 基础资料 博客日记

【C++】多态

2024-04-01 22:00:05基础资料围观225

本篇文章分享【C++】多态,对你有帮助的话记得收藏一下,看Java资料网收获更多编程知识

 

1. 多态的定义

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为

2. 多态的构成条件

继承中要构成多态还有两个条件:

1. 必须通过基类的指针或者引用调用虚函数

2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 (父类必须要有虚函数,子类可以有可以没有)

3. 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数

代码举例

class Peo
{
public:
	 virtual void Print()
	{
		cout << "Peo" << endl;
	}
};

4. 虚函数的重写

虚函数的重写(覆盖):

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

代码举例

class Peo
{
public:
	 virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
	virtual void Print()
	{
		cout << "Stu->Print()" << endl;
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}

运行结果:

a. 虚函数重写的两个例外

  1. 基类一定要有虚函数,派生类可以不需要

代码举例

	class Peo
	{
	public:
		 virtual void Print()
		 {
			cout << "Peo->Print()" << endl;
		 }
	};
	class Stu :public Peo
	{
		void Print()
		{
			cout << "Stu->Print()" << endl;
		}
	};
	void test()
	{
		Peo t;
		Stu d;
		Peo* t1 = &t;
		Peo* t2 = &d;
		t1->Print();
		t2->Print();
	}

运行结果:

 2. 重写的虚函数不一定要要返回类型相同,如果父类和子类都分别是父类和子类的指针或者引用类型(这种叫协变)

代码举例1

class Peo
{
public:
	 virtual Peo* Print()
	 {
		cout << "Peo->Print()" << endl;
		return this;
	 }
};
class Stu :public Peo
{
	Stu* Print()
	{
		cout << "Stu->Print()" << endl;
		return this;
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}

运行结果:

 代码举例2

class A
{};
class B :public A
{};
class Peo
{
public:
	 virtual const A& Print()
	 {
		cout << "Peo->Print()" << endl;
		return A();
	 }
};
class Stu :public Peo
{
	const const B& Print()
	{
		cout << "Stu->Print()" << endl;
		return B();
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}

运行结果:

b. 析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor (这样才能实现重写)

5. C++11 override 和 final 关键字

  1. override :判断能否构成重写

代码举例

 class Peo
{
public:
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
    virtual void Print() override
	{
		cout << "Stu->Print()" << endl;
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}
class Peo
{
public:
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
   void Print() override
	{
		cout << "Stu->Print()" << endl;
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}

错误提示:

2. final :让虚函数不能被重写 

代码举例

class Peo
{
public:
	virtual void Print() final
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
void test()
{
	Peo t;
	Stu d;
	Peo* t1 = &t;
	Peo* t2 = &d;
	t1->Print();
	t2->Print();
}

错误提示:

6. 重载,重写,隐藏的区别

7. 抽象类

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

代码举例1

class Peo
{
public:
	virtual void Print() = 0
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
};
void test()
{
	Peo t;
	Stu d;
}

错误提示:

 代码举例2

class Peo
{
public:
	virtual void Print() = 0
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
void test()
{
	Stu d;
}

接口继承和实现继承 :

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

8. 多态的原理

a. 虚函数表

虚函数表是一个函数指针数组,存放虚函数的地址

一个含有虚函数的类中都至少都有一个虚函数表指针,(虚函数的地址要被放到虚函数表中),虚函数表也简称虚表, 而虚函数指针存虚表的地址

代码举例

class Peo
{
public:
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
};
class Stu :public Peo
{
	virtual void Print()
	{
		cout << "Peo->Print()" << endl;
	}
	int _b;
};
void test()
{
	cout << sizeof(Stu) << endl;
}

运行结果:

 

  1. 派生类对象中也有一个虚表指针,对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类对象和派生类对象虚表是不一样的,派生类的虚函数完成重写,实际上是虚函数表不同
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr(VS编译器是这么处理的)
  5. 总结一下派生类的虚表生成:

a. 先将基类中的虚表内容拷贝一份到派生类虚表中

b. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数

c. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

  1. 虚函数和普通函数一样的,都是存在代码段的
  2. 满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的
  3. 虚函数表是编译时确定生成的
  4. 多继承中的虚函数表 : 多继承派生类的未重写的虚函数放在第一个继承基类(根据声明去判断)部分的虚函数表中

文章来源:https://blog.csdn.net/2301_79789645/article/details/136873243
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云