C++ -- 多态(1)

简介: 1. 多态的概念通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。2. 多态的定义和实现2.1 满足条件必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

1. 多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

2. 多态的定义和实现

2.1 满足条件

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.2 虚函数

学习多态之前,必须了解一个东西就是虚函数,被virtual修饰的类成员函数称为虚函数。

class A {
public:
  virtual void fun() { cout << "A::fun()" << endl; }
};

2.3 见见多态

#include <iostream>
using namespace std;
class Person {
public:
  virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
  virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
  p.BuyTicket();
}
int main()
{
  Person ps;
  Student st;
  Func(ps);
  Func(st);
  return 0;
}
//运行结果:
//买票-全价
//买票-半价

是否满足多态:满足继承前提,Func()函数中通过基类的引用p调用函数,被调用的函数BuyTicket()是虚函数,且派生类对基类的虚函数进行了重写。

2.4 虚函数的重写(覆盖)

什么是虚函数的重写(覆盖)?派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。上述例子中派生类中的BuyTicket()函数就是一个虚函数重写了基类的BuyTicket()虚函数。上述例子中派生类不带virtual修饰成员函数是否构成虚函数重写?构成重写。

#include <iostream>
using namespace std;
class Person {
public:
  virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
  void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
  p.BuyTicket();
}
int main()
{
  Person ps;
  Student st;
  Func(ps);
  Func(st);
  return 0;
}
//运行结果:
//买票-全价
//买票-半价

注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写,这里叫做接口继承(因为继承后基类的虚函数被继承下来了(继承了参数,也就是拷贝了一份基类的虚函数的参数)在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。这里如果只是把基类中的virtual修饰关键字去掉能否构成虚函数重写?不能,因为不满足派生类对基类的虚函数进行重写了,看下面:

#include <iostream>
using namespace std;
class Person {
public:
  void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
  virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
  p.BuyTicket();
}
int main()
{
  Person ps;
  Student st;
  Func(ps);
  Func(st);
  return 0;
}
//运行结果
//买票-全价
//买票-全价

为什么不满足多态会出现上面的情况呢?不满足多态了,就是看调用对象的类型了,这里p.BuyTicket()中的p对象是person类型的,所以两次调用都是调用的基类的BuyTicket()函数。小结:满足多态看调用对象,不满足多态看调用对象类型。如果两个函数都不加virtual修饰呢?

#include <iostream>
using namespace std;
class Person {
public:
  void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
  void BuyTicket() { cout << "买票-半价" << endl; }
};
int main()
{
  Student s;
  s.BuyTicket();
  return 0;
}
//运行结果:买票-半价

首先不构成多态,不满足多态满足条件的任何一条,但是这里是继承,观察得到这里就构成了继承中的重定义。再回过头来看上一个例子中为啥运行结果是:买票-全价 买票-全价;原因就是不满足多态,但是构成了继承的重定义(隐藏),但是这里是基类对象去调用的基类成员函数,这里虽然构成隐藏,但是这里没有用子类对象去调用子类的成员函数BuyTicket(),也就不构成隐藏关系,只有子类调用才会体现隐藏关系。

2.5 虚函数重写的两个例外

2.5.1 析构函数重写(虚函数名不同)

#include <iostream>
using namespace std;
class Person {
public:
  virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
  virtual ~Student() { cout << "~Student()" << endl; }
};
int main()
{
  Person* p1 = new Person;
  Person* p2 = new Student;
  delete p1;
  delete p2;
  return 0;
}
//运行结果:
//~Person()
//~Student()
//~Person()

上述中满足多态两个条件:通过基类Person的指针调用虚函数,同时也Person()构成了虚函数,并且子类的虚函数Studen()对父类进行了重写,这里的虚函数重写不是应该满足派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同吗,但是这里的函数名是不同的,这是因为可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,这里如果把基类的析构函数中的virtual关键字去掉呢?

#include <iostream>
using namespace std;
class Person {
public:
  ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
  virtual ~Student() { cout << "~Student()" << endl; }
};
int main()
{
  Person* p1 = new Person;
  Person* p2 = new Student;
  delete p1;
  delete p2;
  return 0;
}
//运行结果:
//~Person()
//~Person() (程序崩溃)

这里的原因就是没有构成多态,导致看对象类型调用函数,因为p1和p2都是Person类型,所以都是调用的~Person()函数。

2.5.2 协变(返回值类型不同)

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(不常用)

#include <iostream>
using namespace std;
class A {};
class B : public A {};
class Person {
public:
  virtual A* f() { 
    cout << "Person() -> f()" << endl;
    return new A; }
};
class Student : public Person {
public:
  virtual B* f() { 
    cout << "Student() -> f()" << endl;
    return new B; }
};
void fun(Person& p)
{
  p.f();
}
int main()
{
  Person p;
  fun(p);
  Student s;
  fun(s);
  return 0;
}

2.6 例题

2.6.1 无变更

#include <iostream>
using namespace std;
class A
{
public:
  virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
  virtual void test() { func(); }
};
class B : public A
{
public:
  void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
  B* p = new B;
  p->test();
  return 0;
}
//请问结果是什么?A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

解析:

微信图片_20230524024900.png

2.6.2 变更1

#include <iostream>
using namespace std;
class A
{
public:
  virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
  virtual void test() { func(); }
};
class B : public A
{
public:
  void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
  A* p = new B; //B* --> A* 对象赋值转换
  p->test(); //最终和无变更例子一样输出 B->1
  return 0;
}

2.6.3 变更2

#include <iostream>
using namespace std;
class A
{
public:
  virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
};
class B : public A
{
public:
  void func(int val = 0) { std::cout << "B->" << val << std::endl; }
  virtual void test() { func(); }
};
int main(int argc, char* argv[])
{
  B* p = new B;
  p->test();
  return 0;
}

解析:并不满足多态

微信图片_20230524025023.png

2.7 C++11 override 和 final

2.7.1 override

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

class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

2.7.2 final

修饰虚函数,表示该虚函数不能再被重写


#include <iostream>
using namespace std;
class Car
{
public:
  virtual void Drive() final {}
};
class Benz :public Car
{
public:
  virtual void Drive() { cout << "Benz-舒适" << endl; } //声明为final函数无法被Benz:Drive重写
};
int main()
{
  Car* p = new Benz;
  p->Drive();
  return 0;
} 

2.8 重载&&重写&&重定义

微信图片_20230524025145.png

3. 抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

#include <iostream>
using namespace std;
class Car //抽象类
{
public:
  virtual void Drive() = 0; //纯虚函数
};
class Benz :public Car
{
public:
  virtual void Drive() //虚函数重写
  {
    cout << "Benz-舒适" << endl;
  }
};
class BMW :public Car
{
public:
  virtual void Drive()
  {
    cout << "BMW-操控" << endl;
  }
};
int main()
{
  Car* pBenz = new Benz;
  pBenz->Drive();
  Car* pBMW = new BMW;
  pBMW->Drive();
  return 0;
}
//输出结果:
//Benz-舒适
//BMW-操控



































相关文章
|
2月前
|
存储 编译器 数据安全/隐私保护
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
34 1
|
3月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
56 2
C++入门12——详解多态1
|
8月前
|
C++
C++中的封装、继承与多态:深入理解与应用
C++中的封装、继承与多态:深入理解与应用
182 1
|
3月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
93 1
|
5月前
|
存储 编译器 C++
|
6月前
|
存储 编译器 C++
【C++】深度解剖多态(下)
【C++】深度解剖多态(下)
60 1
【C++】深度解剖多态(下)
|
6月前
|
存储 编译器 C++
|
6月前
|
机器学习/深度学习 算法 C++
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
|
6月前
|
Java 编译器 C++
【C++】深度解剖多态(上)
【C++】深度解剖多态(上)
61 2
|
6月前
|
程序员 C++
【C++】揭开C++多态的神秘面纱
【C++】揭开C++多态的神秘面纱