【多态】初识多态

图片名称

博主首页: 有趣的中国人

专栏首页: C++进阶


    本篇文章主要讲解 多态 的相关内容

      1. 多态的概念

      1.1 概念

      多态就是多种形态,就是当不同的对象执行某个行为,会产生不同的状态

      比如买票,普通人购买是全面票,学生购买是学生票.

      2. 多态的定义及实现

      2.1 多态的构成条件

      多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了PersonPerson对象买票全价,Student对象买票半价。

      多态构成需要有两个条件:

      1. 虚函数构成重写;
      2. 父类的指针或者引用来调用虚函数。
      在这里插入图片描述

      2.2 虚函数

      虚函数virtual修饰的类成员函数称为虚函数。

      class Person 
      {
      public:
      	virtual void BuyTicket() { cout << "买票-全价" << endl;}
      };
      

      2.3 虚函数的重写

      重写:派生类有一个和基类相同的函数,即要满足三同:
      1. 函数名相同 2. 参数列表相同 3. 返回值相同
      这时就称子类的虚函数重写了基类的虚函数。
      class Person
      {
      public:
      	virtual void buyTickets()
      	{
      		cout << "Person::全价票" << endl;
      	}
      };
      class Student : public Person
      {
      public:
      	virtual void buyTickets()
      	{
      		cout << "Student::半价票" << endl;
      	}
      };
      
      void test1()
      {
      	Person* p = new Person;
      	Person* st = new Student;
      	p->buyTickets();
      	st->buyTickets();
      	delete p;
      	delete st;
      }
      
      void test2(Person& p)
      {
      	p.buyTickets();
      }
      
      int main()
      {
      	test1();
      	Person p;
      	Student st;
      	test2(p);
      	test2(st);
      	return 0;
      }
      


      虚函数的三个例外

      虚函数有三个例外:
      1. 协变
      2. 析构函数
      3. 派生类的virtual关键字可以省略


      接下来是详细分解:


      协变

      我们之前要求虚函数要满足返回值的类型相同。

      但是协变规定:虚函数返回值可以不同但是,返回值必须保证是父子关系的指针或者引用,即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用。


      这里的基类对象和派生类对象的指针或者引用, 可以是其他类的,也可以是自己本身的类,如以下代码所示:
      class A {};
      class B : public A {};
      class Person
      {
      public:
      	//virtual A* buytickets() 
      	virtual Person* buytickets() 
      	{ 
      		cout << "Person::全价票" << endl; 
      		return nullptr;
      	}
      };
      
      class Student : public Person
      {
      public:
      	//virtual B* buytickets()
      	virtual Student* buytickets()
      	{
      		cout << "Student::学生票" << endl;
      		return nullptr;
      	}
      };
      
      int main()
      {
      	Person* p = new Person;
      	Person* st = new Student;
      	p->buytickets();
      	st->buytickets();
      	return 0;
      }
      

      析构函数

      当父类的指针指向子类,在delete的时候,delete会根据指针的类型调用对应的析构函数。
      因此,如果说派生类中有动态开辟的空间,不会被释放,会导致内存泄漏。

      比如这段代码:

      class Person
      {
      public:
      	virtual void buytickets()
      	{
      		cout << "Person::全价票" << endl;
      	}
      	~Person()
      	{
      		cout << "~Person()" << endl;
      	}
      };
      
      class Student : public Person
      {
      public:
      	virtual void buytickets()
      	{
      		cout << "Student::学生票" << endl;
      	}
      	~Student()
      	{
      		delete[] a;
      		cout << "~Student()" << endl;
      	}
      protected:
      	int* a = new int[10];
      };
      
      int main()
      {
      	Person* p1 = new Person;
      	// 父类指针指向子类
      	Person* p2 = new Student;
      	delete p1;
      	delete p2;
      	return 0;
      }
      

      在这里插入图片描述

      所以,为了解决这个问题,如果基类和派生类构成重写,指向父类的指针调用父类的析构,指向子类的指针调用子类的析构函数,就可以解决这个问题了,但是子类和父类的析构函数函数名称不相同,是否能构成重写呢?


      是可以的,析构函数经过编译器的内部处理,函数名会被修饰为destructor,因此,可以构成重写,只需要加上virtual即可。


      因此一般推荐具有继承的类的析构函数要构成重写,也就是加上virtual关键字
      代码如下:

      class Person
      {
      public:
      	virtual void buytickets()
      	{
      		cout << "Person::全价票" << endl;
      	}
      	virtual ~Person()
      	{
      		cout << "~Person()" << endl;
      	}
      };
      
      class Student : public Person
      {
      public:
      	virtual void buytickets()
      	{
      		cout << "Student::学生票" << endl;
      	}
      	virtual ~Student()
      	{
      		delete[] a;
      		cout << "~Student()" << endl;
      	}
      protected:
      	int* a = new int[10];
      };
      
      int main()
      {
      	Person* p1 = new Person;
      	Person* p2 = new Student;
      	delete p1;
      	delete p2;
      	return 0;
      }
      

      这里为什么会出现两次Person的析构上一篇文章讲过,因为析构要保证先子后父,所以派生类的析构函数会自动在最后调用父类的析构函数。

      在这里插入图片描述

      关于virtual(不重要)

      在重写基类虚函数时,派生类的虚函数可以不加virtual关键字,也构成重写,但是一般不推荐这样写。

      class Person
      {
      public:
      	virtual void buytickets()
      	{
      		cout << "Person::全价票" << endl;
      	}
      	virtual ~Person()
      	{
      		cout << "~Person()" << endl;
      	}
      };
      
      class Student : public Person
      {
      public:
      	// 不加virtual也可以
      	void buytickets()
      	{
      		cout << "Student::学生票" << endl;
      	}
      	// 不加virtual也可以
      	~Student()
      	{
      		delete[] a;
      		cout << "~Student()" << endl;
      	}
      protected:
      	int* a = new int[10];
      };
      
      int main()
      {
      	Person* p1 = new Person;
      	Person* p2 = new Student;
      	p1->buytickets();
      	p2->buytickets();
      	delete p1;
      	delete p2;
      	return 0;
      }
      

      在这里插入图片描述

      2.4 override & final

      从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了overridefinal两个关键字,可以帮助用户检测是否重写。


      final

      1. final修饰虚函数,表示该虚函数不能再被重写
      class Car
      {
      public:
      	virtual void Drive() final {}
      };
      class Benz :public Car
      {
      public:
      	// 这里不能重写了
      	virtual void Drive() {cout << "Benz-舒适" << endl;}
      };
      
      1. final 修饰类,改类不能被继承
      class Car final
      {
      public:
      	virtual void Drive() final {}
      };
      // 不能继承
      class Benz :public Car
      {
      public:
      	// 这里不能重写了
      	virtual void Drive() { cout << "Benz-舒适" << endl; }
      };
      

      override

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

      class Car
      {
      public:
      	virtual void Drive() {}
      };
      
      class Benz : public Car
      {
      public:
      	// 检查是否重写成功
      	virtual void Drive() override{ cout << "Benz-舒适" << endl; }
      };
      

      2.5 重写、重载、隐藏的区别

      重载:两个函数在同一个作用域,函数名相同,参数列表不同,构成重载(下图有点错误)。

      重写:两个函数分别在派生类和基类作用域,三同,两函数必须是虚函数。

      隐藏:两函数必须在派生类和基类作用域,函数名相同构成重写。

      也就是说:两个基类和派生类的同名函数不构成重写就是隐藏。

      在这里插入图片描述

      • 24
        点赞
      • 14
        收藏
        觉得还不错? 一键收藏
      • 6
        评论

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

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

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

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

      抵扣说明:

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

      余额充值