• 理解多态,让不同的“人”做同一件事情会产生不同的结果


    以下代码在vs2019下的x86程序中,涉及指针为4bytes。


    多态的概念

    多态:多种形态。具体一些,就是不同的对象去做同样的行为,会产生出不同的状态。

    例如:买车票的时候,当你是学生的时候,可能就会半价;当你是其他人的时候可能就是全价了。

    //举例,这个代码可以暂时不用看懂
    class Person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "普通票---全价" << endl;
    	}
    };
    class Student : public Person
    {
    	virtual void BuyTicket()
    	{
    		cout << "学生票---半价" << endl;
    	}
    };
    void func(Person& p)
    {
    	p.BuyTicket();
    }
    int main(void)
    {
    	Person p1;
    	Student s1;
    	//不同的对象做同一件事,得到的状态不同
    	func(p1); 
    	func(s1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述


    多态分为:静态的多态、动态的多态。
    静态的多态:是在编译时实现的。例如函数重载,看上去像是在调用同一个函数有着不同的行为。
    动态的多态:是在运行时实现的。基类的指针或者引用去调用同一个函数,传不同类型的对象会调用不同的函数。

    构成多态的条件

    1、多态是基于继承的,只有在有继承关系的体系中才能构成多态。
    2、必须通过基类的指针或者引用去调用虚函数。
    3、被调用的成员必须是虚函数,且派生类必须对基类的虚函数进行重写。

    在这里插入图片描述

    虚函数:被virtual关键字修饰的非静态类成员。(ps:在返回值前面加virtual)

    虚函数和重写

    虚函数的重写:派生类中有一个和基类基本相同的函数,这里的基本相同指,同样是虚函数并且返回值、函数名、参数都相同。而函数体内的内容不同,类似于将这个函数重新改写了,这种情况称为派生类的虚函数重写了基类的虚函数

    构成多态的一个例外:返回值不同,其他条件满足,并且这两个函数的返回值是继承关系(父子关系)的指针或者引用,这个时候也构成多态。这种情况称为协变

    
    //协变也构成多态
    //协变:返回值可以不同,但是必须是继承关系(父子关系)
    class Person
    {
    public:
    	virtual Person* BuyTicket()
    	{
    		cout << "普通票---全价" << endl;
    		return nullptr;
    	}
    };
    class Student : public Person
    {
    	virtual Student* BuyTicket() //派生类中可以不是虚函数
    	{
    		cout << "学生票---半价" << endl;
    		return nullptr;
    	}
    };
    void func(Person& p)
    {
    	p.BuyTicket();
    }
    int main(void)
    {
    	Person p1;
    	Student s1;
    
    	func(p1);
    	func(s1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    构成多态的另一个例外:基类成员是虚函数、而派生类成员可以不是虚函数,也就是不用virtual修饰,但是其他条件还是需要满足的。这样也构成多态,虽然派生类成员没有被virtual修饰,但它是先继承了基类虚函数的属性,再完成的重写,那么它也算是虚函数。

    class Person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "普通票---全价" << endl;
    	}
    };
    class Student : public Person
    {
    	void BuyTicket() //派生类可以不是虚函数
    	{
    		cout << "学生票---半价" << endl;
    	}
    };
    void func(Person& p)
    {
    	p.BuyTicket();
    }
    int main(void)
    {
    	Person p1;
    	Student s1;
    
    	func(p1);
    	func(s1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    存在着一个场景,析构函数必须是虚函数:动态申请基类、派生类的对象,如果都交给了基类指针管理,那么析构函数需要是虚函数。

    //存在着一种场景,析构函数必须是函数
    //基类、派生类对象,都交给了基类指针管理。
    class Person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "普通票---全价" << endl;
    	}
    	~Person()
    	{
    		cout << "~Person" << endl;
    	}
    };
    class Student : public Person
    {
    	virtual void BuyTicket()
    	{
    		cout << "学生票---半价" << endl;
    	}
    	~Student()
    	{
    		cout << "~Student" << endl;
    	}
    };
    void func(Person& p)
    {
    	p.BuyTicket();
    }
    int main(void)
    {
    	//派生类对象交给了基类指针管理
    	Person* p2 = new Person;
    	Person* s2 = new Student;
    
    	delete p2;
    	delete s2;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    运行结果:p2和s2都是调用基类的析构函数去清理资源。
    在这里插入图片描述
    派生类对象地址赋值给基类指针,虽然被切片了,但还是需要调用派生类自己的析构函数去清理资源。所以这并没有正确地调用析构函数,我们需要的是,S2调用Student自己的析构函数。析构函数的函数名,编译时会被统一处理成destructor,并且它不需要参数和返回值,所以认为析构函数函数名、参数、返回值相同。那要使得析构函数构成多态,那么可以在用virtual修饰这两个析构函数,或者修饰基类的析构函数就可以了。

    class Person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "普通票---全价" << endl;
    	}
    	virtual ~Person() //析构函数修饰成虚函数
    	{
    		cout << "~Person" << endl;
    	}
    };
    class Student : public Person
    {
    	virtual void BuyTicket()
    	{
    		cout << "学生票---半价" << endl;
    	}
    	~Student()
    	{
    		cout << "~Student" << endl;
    	}
    };
    void func(Person& p)
    {
    	p.BuyTicket();
    }
    int main(void)
    {
    	//派生类对象交给了基类指针管理
    	Person* p2 = new Person;
    	Person* s2 = new Student;
    
    	delete p2;
    	delete s2;
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述
    这里调用了两次基类的析构函数,是因为,派生类的析构函数调用之后,会自动调用一次基类的析构函数去析构被继承下来的基类部分。

    关键字override 、final

    final:修饰虚函数,表示该虚函数不能被重写。
    修饰方法:在基类虚函数参数括号后,函数体之前使用。

    //关键字final
    class Car
    {
    public:
    	virtual void Drive() final 
    	{}
    };
    class Benz :public Car
    {
    public:
    	virtual void Drive() 
    	{ cout << "Benz-舒适" << endl; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    【补充】
    使用final修饰类,那么会使得该类不能被继承。修饰方法:在基类类名之后,类体之前使用。

    //关键字final
    class Car final
    {
    public:
    	virtual void Drive() 
    	{}
    };
    class Benz :public Car
    {
    public:
    	virtual void Drive() 
    	{ cout << "Benz-舒适" << endl; }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述


    override:修饰虚函数,检查派生类的虚函数是否重写了基类的某个虚函数,如果没有重写编译报错。
    修饰方式:在派生类虚函数参数括号后面,函数体之前使用。

    //关键字override
    //使用override修饰派生类的虚函数,判断该虚函数是否重写,如果没有重写则会编译报错
    class Car
    {
    public:
    	virtual void Drive()
    	{}
    };
    class Benz :public Car
    {
    public:
    	virtual void Drive(int i) override
    	{
    		//cout << "Benz-舒适" << endl;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    重载、重写(覆盖)、重定义(隐藏)区分

    • 重载:
      1、两个函数在同一作用域中
      2、要求函数名相同。参数个数、类型或者顺序不同

    • 重写(覆盖):
      1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域。
      2、函数名、参数、返回值都必须相同(协变除外,返回值可以不同,但是一定是父子关系)
      3、两个函数都是虚函数,或者基类中的那个函数是虚函数。

    • 重定义(隐藏)
      1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域
      2、两个函数的函数名、参数相同
      3、两个基类和派生类的同名函数不构成重写就是重定义

  • 相关阅读:
    数据可视化加定语
    Prometheus系列第十篇一核心之micrometer源码分析一micrometer-registry-prometheus核心实现
    nginx修改配置文件不生效
    Android中级——ListView和RecycleView解析
    改变世界的开发者丨以梦为码,华工小哥的致青春
    统计学考研笔记:季度指数
    Pika v3.5.1发布!
    Python语言学习:Python语言学习之列表/元祖/字典/集合的简介、案例应用之详细攻略
    全球与中国双壳鼻夹板市场:增长趋势、竞争格局与前景展望
    win11系统点开图片几秒后就显示“此处没有任何要显示的内容
  • 原文地址:https://blog.csdn.net/qq_56870066/article/details/125568613