什么是多态

提示:本文主要编写了C++中多态相关的知识,什么是多态,虚函数等等


一、多态

1.1 什么是多态,以及个人多对多态的理解

多态,就是同样的操作,在面向不同的对象时,会有不同的解决办法,多态要满足于基类的指针或者引用指向子类的对象。

class person
{
public:
	virtual void func()
	{
		std::cout << "person func()\n";
	}
	std::string _name;
};
class student : public person
{
public:
	virtual void func()
	{
		std::cout << "student func()\n";
	}
protected:
	int _num;
};
class teacher : public person
{
public:
	virtual void func()
	{
		std::cout << "teacher func()\n";
	}
protected:
	int _id;
};

int main()
{
	student s;
	teacher t;
	person p = s;
	person *pp = &s;
	p.func();
	s.func();
	pp->func();
	return 0;
}
// 结果会是什么样子? 

在这里插入图片描述
可以看到父类的指针对象 pp在调用func()成员函数时,这种由于子类重写父类方法,然后用父类指针、引用指向子类对象,调用方法时,会进行动态绑定,这就构成了多态。(必须在子类与派生类之间)
静态多态与动态多态
动态多态: 动态多态由重写实现,我们可以称之为动态多态,他是在函数运行时,根据基类指针或者引用对象指向不同类的虚函数而构成。
静态多态: 静态多态由函数重载与模板来实现,他是在函数编译阶段,编译器根据传递实参类型、个数来确定具体调用哪一个同名函数,从而实现静态多态。(必须在同一个作用域中)

1.2 切片

  1. 派生类对象可以赋值给基类的对象、指针、引用。寓意把派生类中基类部分切片赋值出去。
  2. 基类对象不可以赋值给派生类对象。
    在这里插入图片描述
    类操作得益于它切片功能的实现。
这里是一个菱形继承的切片演示 ---
class person
{
public:
	std::string _name;
};
class student : public person
{
protected:
	int _num;
};
class teacher : public person
{
protected:
	int _id;
};
class start : public student, public teacher
{
	std::string _course;
};

int main()
{
	start a;
	a.student::_name = "xxx";
	a.teacher::_name = "yyy";
	student b = a;
	teacher c = a;
	// b完美切走了原有a中b部分的名字 ”xxx“
	std::cout << b._name << std::endl;
	// c完美切走了原有a中c部分的名字 ”yyy“
	std::cout << c._name << std::endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
清晰的看出b,c完成了对各自部分的切片。

1.3 重写?(虚函数)

多态的实现依靠的就是重写功能
而满足重写的条件都有:

1.重写的双方必须满足基类与派生类的关系。
2.基类需要重写的一定要是虚函数(使用virtual修饰),且派生类中一定重写了这个虚函数。
3.派生类虚函数必须要与基类虚函数原型完全一致,即:函数名相同。 (参数列表相同)
4.基类与派生类的访问权限可以不同,父类私有成员在类外是完全不能访问的,父类保护成员可以在子类中通过父类来使用。

前三个条件必须同时满足,才可以构成重写。
虚函数的两个例外:
1.协变
协变是指满足虚函数的其他特征的前提前他的返回值可以不同。
协变可以理解成 子类型<=基类型。
协变也可以理解成子类型可以默认转换为父类型。

#include <iostream>

class Animal {
public:
    virtual Animal* clone() const {
        return new Animal(*this);
    }
    
    virtual void makeSound() const {
        std::cout << "Animal makes a sound.\n";
    }
};

class Dog : public Animal {
public:
    virtual Dog* clone() const override {
        return new Dog(*this);
    }
    
    virtual void makeSound() const override {
        std::cout << "Dog barks.\n";
    }
};

int main() {
    Animal* animal = new Dog();
    Animal* clonedAnimal = animal->clone();
    
    animal->makeSound();          // Output: Dog barks.
    clonedAnimal->makeSound();    // Output: Dog barks.

    delete animal;
    delete clonedAnimal;
    
    return 0;
}

在上面的示例中,我们有一个基类 Animal,以及一个派生类 Dog。基类 Animal 中有一个虚函数 clone() 和 makeSound()。在派生类 Dog 中,我们重写了这两个虚函数。
注意在派生类 Dog 中,clone() 函数的返回类型从基类的 Animal* 改为了派生类的 Dog*。这就是协变的体现,我们在派生类中返回了比基类更具体的类型。
在主函数中,我们创建了一个指向 Dog 对象的 Animal 指针 animal,然后使用 clone() 函数克隆了这个对象并将结果赋值给 clonedAnimal。接着,我们分别调用了 makeSound() 函数,发现虽然 animal 和 clonedAnimal 都是指向 Animal 类型的指针,但由于 makeSound() 函数是虚函数且被重写,因此正确地调用了 Dog 类中的方法。
通过协变,我们可以在派生类中返回更具体的类型,但仍然能够通过基类指针来调用这些方法,实现了多态性和代码的灵活性。

2.析构函数
基类与派生类的析构函数构成虚函数时,函数名可以不同。析构顺序为由派生类到基类依次析构。
补充:override和final

虚函数中override和final的作用。
override用于在派生类中标记覆盖基类的虚函数。
final用于在派生类中标记进制被继续派生或覆盖的函数或类。
这两个关键字都是为了增强类的语义与安全性而出现。

1.4 抽象类

抽象类的实现是在类函数中定义有纯虚函数的类。
纯虚函数是指,在虚函数的后面加上 =0,这样的虚函数被称为纯虚函数。
而一般我们将这样的类也称之为接口类,因为抽象类不能实例化对象,一般使用做继承中的基类,它只需要描述类具体的行为与功能,而在抽象类的派生类中我们可以在基类功能的基础上进行额外的添加从而实现多态性。
基类作为抽象类,通过基类的指针或引用派生类对象,就可以实现对派生类的访问,形成一种设计体系。
注: 派生类继承的基类若是抽象类,那么派生类一定要重写基类中的纯虚函数接口!

1.5 多态的实现原理(虚表)

当类中含有虚函数时,对象中将会多4个字节,在对象前4个字节中保存虚表的地址。
虚表是在程序编译时建立的,基类中按照虚函数在类中声明的先后次序依次加载到虚表中。
而派生类虚表的构建是先将基类虚表拷贝一份放到派生类虚表中,然后若派生类中重写了基类哪个虚函数,则使用派生类中虚函数的地址替换虚表中原有的基类虚函数地址(覆盖式判断)。
若派生类增加了新的虚函数实在__vfptr中不可显的,因为__vfptr是基类指针或引用指向派生类对象时,所对应的虚表的指针,他是在编译阶段生成的,所以即使派生类新增了虚函数并重写了基类的虚函数,__vfptr所指向的虚表并不包含新的虚函数指针如图:

// 基类包含了三个虚函数,基类的派生类包含了三个虚函数(两个是重写基类中虚函数的),派生类的派生类包含了一个重写派生类中的虚函数
class person
{
public:
	virtual void func()
	{
		std::cout << "person func()\n";
	}
	virtual void funcc()
	{
		std::cout << "hjkl\n";
	}
	virtual void funccc()
	{
		std::cout << "hdjkal;\n";
	}
protected:
	std::string _name = "xiaozhang";
private:
	int _s = 6;
};
class student : public person
{
public:
	virtual void func() override
	{
		//std::cout << person::_name << std::endl;
		std::cout << "student func()\n";
	}
	virtual void funcc() override
	{
		std::cout << "ss\n";
	}
	virtual void ss()
	{
		std::cout << "hjkl\n";
	}
	virtual void tmp()
	{
		std::cout << "tmp\n";
	}
public:
	int _num;
};

class tt : public student
{
public:
	virtual void func()
	{
		std::cout << "tt \n";
	}
	virtual void tmp() override
	{
		std::cout << "ghjk\n";
	}
	int _num;
};
int main()
{
	person p;
	student s;
	tt t;
	return 0;
}

在这里插入图片描述

可以看到无论哪个层级的类,其中都只包含基类虚函数个数的指针地址,且若是派生类的派生类重写了派生类中新的虚函数,此时派生类的派生类会与派生类构成新的多态关系,但是因为__vfptr中可见的地址指针只有基类虚函数个,所以若重写很多会在基类虚函数个之后增加,而显示在__vfptr中的永远就那几个,它也是按照在类中声明的先后次序依次增加到派生类虚表的最后!
注:__vfptr虚表指针实在编译时生成的!

多态的调用原理:
多态构建的条件满足 1.双方满足基类派生类关系 2.双方达成虚函数的重写关系
然后先从对象前四个字节中获取虚表的地址
再从虚表中找到具体的虚函数 然后传参 然后就可以调用虚函数

注意! 基类指针可以指向或者引用不同子类的对象,将来从对象前4个字节中拿到的就是指向实际子类的虚表,而子类虚表中存放的虚函数是派生类的虚函数,此时拿到的虚函数就是派生类自己的虚函数,由此实现 多态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值