C++ - 多态

多态

多态的概念

多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一个消息作出不同的响应。具体来说,多态性是指在基类中定义的虚函数,在派生类中可以被重写,并且在运行时根据对象的实际类型来调用相应的函数。

多态性的实现依赖于继承和虚函数的机制。通过将函数声明为虚函数,可以使得在派生类中重写该函数,从而实现不同的行为。当通过基类指针或引用调用虚函数时,实际上会根据对象的实际类型来调用相应的函数,而不是根据指针或引用的类型。

多态性的优势在于它提高了代码的灵活性和可扩展性。通过使用多态性,可以编写通用的代码,能够处理不同类型的对象,而无需为每个具体类型编写特定的代码。这样可以简化代码的维护和扩展,并且使得代码更加可复用。

总结起来,多态性是面向对象编程中的一个重要概念,它允许不同类型的对象对同一个消息作出不同的响应。通过继承和虚函数的机制,可以实现多态性,提高代码的灵活性和可扩展性。

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

多态案例

#include <iostream>
using namespace std;
#include<string>
//木材
class Wood {
protected:
	string name;
	int weight;
public:
	Wood(string n ,int w):name(n),weight(w){}
	virtual void Show() {//虚函数
		cout << "这是一块重" << weight << "kg的" << name << endl;
	}
};
//凳子
class Stool :public Wood{
public:
	Stool(string n , int w):Wood(n,w){}
	void Show() {//虚函数
		cout << "这是用一块重" << weight << "kg的" << name << "做的凳子" << endl;
	}
};
//桌子
class Dest :public Wood {
public:
	Dest(string n, int w) :Wood(n, w) {}
	void Show() {//虚函数
		cout << "这是用一块重" << weight << "kg的" << name << "做的桌子" << endl;
	}
};

void test(Wood* p) {
	p->Show();
}

int main() {
	Wood w1("橡木", 5), w2("楠木", 6);
	Stool s1("橡木", 5), s2("楠木", 6);
	Dest d1("橡木", 5), d2("楠木", 6);

	w1.Show();
	w2.Show();
	cout << "------------------------------" << endl;
	s1.Show();
	s2.Show();
	cout << "------------------------------" << endl;
	d1.Show();
	d2.Show();

	/*
	这是一块重5kg的橡木
	这是一块重6kg的楠木
	------------------------------
	这是用一块重5kg的橡木做的凳子
	这是用一块重6kg的楠木做的凳子
	------------------------------
	这是用一块重5kg的橡木做的桌子
	这是用一块重6kg的楠木做的桌子
	*/

	cout << "------------------------------" << endl;
	Wood* p;//多肽的体现 同样的指针有不同的结果
	p = &w1;
	test(p);
	cout << "------------------------------" << endl;
	p = &s1;
	test(p);
	cout << "------------------------------" << endl;
	p = &d1;
	test(p);

	/*这是一块重5kg的橡木
	------------------------------
	这是用一块重5kg的橡木做的凳子
	------------------------------
	这是用一块重5kg的橡木做的桌子*/
    
    /*如果没加virtual 则全为“这是一块重5kg的橡木”*/
    
    //引用也可以
    Dest d1("楠木", 10);
	Wood* W1;
	W1 = &d1;
	W1->Show();
    
	return 0;
}

多态的实现靠的是虚函数表

虚函数表是链表在这里插入图片描述

虚函数

虚函数是一种单界面多实现版本的实现方法,即函数名、返回类型、函数类型和个数顺序完全相同,但函数体内容可以完全不同

基类中采用virtual说明一个虚函数后,派生类中定义相同原型函数时可不必加virtual说明

虚函数不允许说明成静态的成员函数

虚函数例子

下面是一个使用C++虚函数的例子:

#include <iostream>

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;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

    shape1->draw(); // 输出 "Drawing a circle"
    shape2->draw(); // 输出 "Drawing a rectangle"

    delete shape1;
    delete shape2;

    return 0;
}

在上面的例子中,Shape是一个基类,它有一个虚函数draw()。Circle和Rectangle是Shape的派生类,它们分别重写了draw()函数。

在main函数中,我们创建了两个Shape类型的指针shape1和shape2,并分别指向Circle和Rectangle的对象。当我们调用shape1和shape2的draw()函数时,实际上会根据对象的实际类型来调用相应的函数。这就是虚函数的多态性。

输出结果为:

Drawing a circle
Drawing a rectangle

可以看到,shape1调用draw()时输出了"Drawing a circle",而shape2调用draw()时输出了"Drawing a rectangle",这正是因为虚函数的多态性

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "func调用" << endl;
	};
};

void test01()
{
	Base * base = NULL;
	//base = new Base; // 错误,抽象类无法实例化对象
	base = new Son;
	base->func();
	delete base;//记得销毁
}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;
类名::~类名(){}
class Animal {
public:
	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;

	//析构函数加上virtual关键字,变成虚析构函数
	//virtual ~Animal()
	//{
	//	cout << "Animal虚析构函数调用!" << endl;
	//}
	virtual ~Animal() = 0;
};
Animal::~Animal()
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name <<  "小猫在说话!" << endl;
	}
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}
public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	//怎么解决?给基类增加一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}
int main() {
	test01();
	system("pause");
	return 0;
}

总结:

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类

重载、重写、重定义

函数重载(overload)
函数重载是指在一个类中声明多个名称相同但参数列表不同的函数,这些的参数可能个数或顺序,类型不同,但是不能靠返回类型来判断。特征是:
(1)相同的范围(在同一个作用域中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无(注:函数重载与有无virtual修饰无关);
(5)返回值可以不同;

函数重写(也称为覆盖 override)
函数重写是指子类重新定义基类的虚函数。特征是:
(1)不在同一个作用域(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字,不能有 static 。
(5)返回值相同,否则报错;
(6)重写函数的访问修饰符可以不同;

重定义(也称隐藏)
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)返回值可以不同;
(4)参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆);
(5)参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆);

上一章:C++ - 继承

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java橙旭源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值