C++多态

C++多态

一.多态的概念

通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态),这里我们重点讲运行时多态,编译时多态(静态多态)和运行时多态(动态多态)。编译时多态(静态多态)主要就是我们前面讲的函数重载和函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运行时归为动态,这里我们讲解运行时的多态

二.多态的定义以及它的构成

  • 定义:
    多态是⼀个继承关系的下的类对象,去调用同⼀函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象优惠买票。
  • 两个必要的条件
    1 . 必须是基类的指针或者引用调用虚函数
    2 .被调用的函数必须是虚函数,并且完成了虚函数重写/覆盖。

说明:要实现多态效果,第⼀必须是基类的指针或引用,因为只有基类的指针或引用才能既指向基类对象又指向派生类对象;第⼆派生类必须对基类的虚函数完成重写/覆盖,重写或者覆盖了,基和派生类之间才能有不同的函数,多态的不同形态效果才能达到

三.虚函数

3.1什么是虚函数

  • 类成员函数前面加virtual修饰,那么这个成员函数被称为虚函数,注意非成员函数不能加virtual修饰
cpp 复制代码
class Person 
{
public:
   virtual void BuyTicket() { cout << "买票-全价"<<endl;}
};

3.2虚函数的重写/覆盖

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

注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来在派生类依旧保持虚函数属性)但是该种写法不是很规范,不建议这样使用,不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态

场景一

cpp 复制代码
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
void Func(Person* ptr)
{
	// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket 
	// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。 
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}

场景二

cpp 复制代码
class Animal
{
public:
	virtual void talk() const
	{
	}
};
class Dog : public Animal
{
public:
	virtual void talk() const
	{
		std::cout << "汪汪" << std::endl;
	}
};
class Cat : public Animal
{
public:
	virtual void talk() const
	{
		std::cout << "(>^ω^<)喵" << std::endl;
	}
};
void letsHear(const Animal& animal)
{
	animal.talk();
}
int main()
{
	Cat cat;
	Dog dog;
	letsHear(cat);
	letsHear(dog);
	return 0;
}

3.3 派生类的虚函数在private被调用

派生类的虚函数在private是可以被调用的,我们可以简单理解我们这个函数是被重写的 用的是基类的壳

3.4 析构函数的重写

先提前说一下基类和派生类析构函数即使函数名不一样也可以构成虚函数

先看下面的代码 有没有什么问题

cpp 复制代码
class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
};
class B : public A {
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

打印结果:我们发现B的析构没有被调用到 因为我们的指针全部指向A 所以就出现了内存泄漏的问题 ,所以我们应该把它们写成虚函数从而达成析构B的目的

加上virtual的代码和打印结果

cpp 复制代码
class A
{
public:
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};
class B : public A {
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
//构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

3.5override和 final关键字

从上面可以看出,C++对虚函数重写的要求比较严格,但是有些情况下由于疏忽,比如函数名写错参数写错等导致无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此C++11提供了override,可以帮助用户检测是否重写。如果我们不想让派生类重写这个虚函数,那么可以用final去修饰

cpp 复制代码
// error C3668: "Benz::Drive": 包含重写说明符"override"的⽅法没有重写任何基类⽅法 
class Car {
public:
	 virtual void Dirve()
	 {
	 }
};
class Benz :public Car {
public:
	 virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{
	 return 0;
}
cpp 复制代码
// error C3248: "Car::Drive": 声明为"final"的函数⽆法被"Benz::Drive"重写 
class Car
{
public:
	 virtual void Drive() final {}
};
class Benz :public Car
{
public:
	 virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
	 return 0;
}

3.6重载/重写/隐藏的对比

我们可以看下面的思维导图对比一下

3.7纯虚函数和抽象类

在虚函数的后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派生类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象

cpp 复制代码
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;
	}
};
int main()
{
	// 编译报错:error C2259: "Car": ⽆法实例化抽象类 
	//Car car;
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
	return 0;
}
相关推荐
小菜要扣篮22 分钟前
Ubuntu 下使用 vscode 打开 C++ 项目后,无法通过 F12 定位到定义问题解决
c++·vscode·ubuntu
wuminyu23 分钟前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
旖旎夜光1 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
C++ 老炮儿的技术栈1 小时前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio
dtq042410 小时前
C++小案例 - 日志实战
开发语言·c++
码匠许师傅11 小时前
【C++三方组件】Qt 概览:桌面 C++ 的事实标准与许可证三轨
开发语言·c++·qt
码匠许师傅12 小时前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++
WBluuue14 小时前
数据结构与算法:分块(一):块状数组、块状链表
数据结构·c++·算法·链表
必须得开心呀16 小时前
字节序大小端科普
c++·程序人生
会编程的小孩16 小时前
C++ 子集
c++