C++多态【上】

在这里插入图片描述

1.多态的介绍

1.1概念

在这里插入图片描述
简单来说,同样一种行为,不同对象完成时会呈现不同的状态。比如:

  1. 吃东西 牛吃草 狼吃羊 猫吃鱼
  2. 买票 学生票 军人票 普通票

1.2分类

静态多态: 函数重载 和 运算符重载 — 编译阶段确定函数地址
动态多态: 虚函数 — 运行阶段确定函数地址

2.多态的实现

2.1虚函数的介绍

1.定义

被virtual修饰的类的成员函数
在这里插入图片描述

2.重写

1.通常情况

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

2.特例
  1. 子类重写父类的虚函数【不加virtual也构成重写–继承基类的虚函数后实现】
  2. 协变:返回值是父子关系的指针或引用–返回值不同也构成重写

对照组:

class Animal
{
public:
   virtual void express()
   {
        cout << "我在疯狂动物叫" << endl;
   }
};

class Dog :public Animal
{
public:
    virtual void express()
    {
        cout << "我在疯狂狗叫" << endl;
    }

};
void func(Animal& animal)
{
    animal.express();
}
int main() 
{
    Animal animal;
    func(animal);
    Dog dog;
    func(dog);
    return 0;
}

实验组1:

class Animal
{
public:
    virtual Animal* express()
    {
        cout << "我在疯狂动物叫" << endl;
        return this;
    }

};

class Dog :public Animal
{
public:
    virtual Dog* express()
    {
        cout << "我在疯狂狗叫" << endl;
        return this;
    }

};
void func(Animal& animal)
{
    animal.express();
}
int main()
{
    Animal animal;
    func(animal);
    Dog dog;
    func(dog);
    return 0;
}

实验组2:

class Ox  //牛
{

};
class Bull :public Ox//公牛
{

};
class Animal
{
public:
    virtual Ox* express()
    {
        cout << "我在疯狂动物叫" << endl;
        return nullptr;
    }

};

class Dog :public Animal
{
public:
    virtual Bull* express()
    {
        cout << "我在疯狂狗叫" << endl;
        return nullptr;
    }

};
void func(Animal& animal)
{
    animal.express();
}
int main()
{
    Animal animal;
    func(animal);
    Dog dog;
    func(dog);
    return 0;
}

2.2多态构成条件

  1. 指向子类对象的基类指针或引用调用虚函数
  2. 派生类重写基类的虚函数
class Animal
{
public:
   virtual void express()
   {
        cout << "我在疯狂动物叫" << endl;
   }
};

class Dog :public Animal
{
public:
    virtual void express()
    {
        cout << "我在疯狂狗叫" << endl;
    }

};
void func(Animal& animal)
{
    animal.express();
}
int main() 
{
    Animal animal;
    func(animal);
    Dog dog;
    func(dog);
    return 0;
}

在这里插入图片描述

例题

在这里插入图片描述

class A
{
public:
	virtual void func(int value = 1) 
	{ 
		cout << "A->" << value << endl; 
	}
	virtual void test() 
	{ 
		func();
	}
};
class B : public A
{
public:
	void func(int value = 0)
	{ 
		cout << "B->" << value << endl; 
	}
};
int main()
{
	A* pb = new B;  //B -> 1
	
	pb->test();
	return 0;
}

2.3破坏多态的情况

1.父类没有虚函数 子类有对应虚函数重写

class Dad
{
public:
	void Cook()
	{
		cout << "佛跳墙" << endl;
	}

	virtual void Work()
	{
		cout << "Work" << endl;
	}
	int _a = 0;
};

class Son : public Dad
{
public:
	virtual void Cook()
	{
		cout << "方便面" << endl;
	}

	int _b = 0;
};

void Test(Dad& p)
{
	p.Cook();
}

int main()
{
	Dad dad;
	Test(dad);

	Son son;
	Test(son);

	return 0;
}

父类没有虚函数表 编译时就已经确定了函数地址 即不论父类或子类对象调用的都是父类的成员函数

2.父类有虚函数 子类没有重写对应虚函数

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
using namespace std;

class Dad
{
public:
	virtual void Cook()
	{
		cout << "佛跳墙" << endl;
	}

	virtual void Work()
	{
		cout << "Work" << endl;
	}
	int _a = 0;
};

class Son : public Dad
{
public:
	/*virtual void Cook()
	{
		cout << "方便面" << endl;
	}*/

	int _b = 0;
};

void Test(Dad& p)
{
	p.Cook();
}

int main()
{
	Dad dad;
	Test(dad);

	Son son;
	Test(son);

	return 0;
}

父类有虚函数 运行时才确定函数地址 只不过子类没有重写 虚函数表存放的均为父类的虚函数地址 所以均调用父类的成员函数

2.4虚析构和纯虚析构

1.虚析构

1.一般情况:调用完子类析构后自动调用父类析构

class Person 
{
public:
	Person()
	{
		cout << "Person()" << endl;
	}

    ~Person() 
	{
		cout << "~Person()" << endl;
	}

	//int* _ptr;
};

class Student : public Person 
{
public:
	Student()
	{
		cout << "Student()" << endl;
	}
	// 析构函数名底层为:destructor -- 构成虚函数重写
	~Student() 
	{
		cout << "~Student()" << endl;
	}
};

int main()
{
	Student s;

	return 0;
}

在这里插入图片描述
2.没有虚析构导致错误的场景

class Person 
{
public:
	Person()
	{
		cout << "Person()" << endl;
	}

	~Person() 
	{
		cout << "~Person()" << endl;
	}

	//int* _ptr;
};

class Student : public Person 
{
public:
	Student()
	{
		cout << "Student()" << endl;
	}
	// 析构函数名底层为:destructor -- 构成虚函数重写
	~Student() 
	{
		cout << "~Student()" << endl;
	}
};

int main()
{
	Person* ptr1 = new Person;
	delete ptr1;

	Person* ptr2 = new Student;
	delete ptr2;
    
	return 0;
}

在这里插入图片描述
3.成功样例

class Person 
{
public:
	Person()
	{
		cout << "Person()" << endl;
	}

	virtual ~Person() 
	{
		cout << "~Person()" << endl;
	}

	//int* _ptr;
};

class Student : public Person 
{
public:
	Student()
	{
		cout << "Student()" << endl;
	}
	// 析构函数名底层为:destructor -- 构成虚函数重写
	virtual ~Student() 
	{
		cout << "~Student()" << endl;
	}
};

int main()
{
	Person* ptr1 = new Person;
	delete ptr1;

	Person* ptr2 = new Student;
	delete ptr2;                  //ptr2 -> destructer();
                                  //operator delete(ptr2);
	return 0;
}

在这里插入图片描述

2.纯虚析构

class Dad 
{
public:

    Dad()
    {
        cout << "Dad 构造函数调用!" << endl;
    }

    //虚析构函数
    //virtual ~Dad()
    //{
    //    cout << "Dad 虚析构函数调用!" << endl;
    //}
    //**虚析构和纯虚析构都必须有函数【实现】
    //纯虚析构函数
    virtual ~Dad() = 0;

    virtual void Name() = 0;

};

Dad::~Dad()
{
    cout << "Dad 纯虚析构函数调用!" << endl;
}

class Son : public Dad
{
public:
    Son(string name)
    {
        cout << "Son 构造函数调用!" << endl;
        _name = new string(name);
    }
   
    ~Son()
    {
        cout << "Son 析构函数调用!" << endl;
        if (this->_name != NULL) 
        {
            delete _name;
            _name = NULL;
        }
    }

    virtual void Name()
    {
        cout << *_name << "是son的名字" << endl;
    }
public:
    string* _name;
};

int main() 
{

    Dad* dad = new Son("Mike");
    dad->Name();

    delete dad;

    return 0;
}

在这里插入图片描述

3.虚析构/纯虚析构存在的意义

  1. 虚析构语法:virtual ~类名(){}
    纯虚析构语法: virtual ~类名() = 0; 类名::~类名(){}
  2. 二者目的皆是能够【delete指向子类对象的父类指针】时正确调用析构函数。
    区别在于:纯虚析构适用于:当前基类作为一个抽象类,不想要实例化对象,只作为子类的父类,并且可以强制子类重写析构函数。
    但是使用虚析构和纯虚析构需要注意:二者必须有函数实现–虚析构在类内即可完成函数实现–纯虚析构需要在类外完成。
    如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

2.5总结及拓展

1.final:修饰父类虚函数,表示该虚函数不能再被重写

在这里插入图片描述

2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

在这里插入图片描述

在这里插入图片描述

3.重载、覆盖(重写)、隐藏(重定义)

重载:

两个函数在同一作用域
函数名相同参数不同(类型个数顺序 )

重写(覆盖):

两个函数分别在基类和派生类的作用域
R_函数名/参数/返回值都必须相同(协变例外)
两个函数必须是虚函数

重定义(隐藏):子类和父类中有同名成员,直接访问时将屏蔽父类同名成员

两个函数分别在基类和派生类的作用域
函数名相同
两个基类和派生类的同名函数不构成重写就是重定义

2.6抽象类

通常多态中父类虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类称为抽象类
抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

1.概念

包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。
派生类只有重写纯虚函数后才能实例化对象。

  1. 纯虚函数规范了派生类必须重写
  2. 纯虚函数体现出了接口继承

2.接口继承和实现继承

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

3.代码演示

class Plant
{
public:
	virtual void Breath() = 0;
};
class Rose :public Plant
{
public:
	virtual void Breath()
	{
		cout << "Rose-疏影暗香" << endl;
	}
};
class Grass :public Plant
{
public:
	virtual void Breath()
	{
		cout << "Grass-生生不息" << endl;
	}
};
void Test()
{
	Plant* pRose = new Rose;
	pRose->Breath();

	Plant* pGrass = new Grass;
	pGrass->Breath();
}
int main()
{
	Test();
	return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿猿收手吧!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值