zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++多态与虚函数

C++ 函数 多态
2023-06-13 09:11:58 时间

Contents

本文文学习笔记总结讲得比较浅显,更深入内容可以参考C++ Primer。

C++ 多态概念

多态字面意思是一个事物有多种形态,在 C++ 程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。

在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

多态可以分为编译时的多态和运行时的多态。

  • 编译时的多态:主要是指函数的重载(包括运算符的重载)、对重载函数的调用,在编译时就能根据实参确定应该调用哪个函数,因此叫编译时的多态;
  • 运行时的多态:和继承、虚函数等概念有关,这篇文章主要涉及运行时的多态。

C++ 多态实例

程序运行时的动态即动态多态性,这里要研究的问题是:当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个派生类的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是“一个接口,多种 方法”。下面通过实例代码展示这个问题:

//
// Created by ZHG52 on 2019/9/7.
//
#include <iostream>
using namespace std;
// 定义基类People
class People{
public:
    People(char *name, int age);
    void display();  // 构造函数
protected:
    char *m_name;
    int m_age;
};
// 定义构造函数
People::People(char *name, int age):m_name(name), m_age(age){}
// 类成员函数定义
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一个无业游民"<<endl;
}

// 派生类Teacher
class Teacher:public People{
public:
    Teacher(char *name, int age, int salary);
    void display();  // 构造函数
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary):People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名工程师,每月有"<<m_salary<<"元的收入."<<endl;
}

int main(){
    People *p = new People("王志刚", 23);
    p -> display();
    p = new Teacher("赵宏佳", 45, 2000);
    p -> display();
    return 0;
}

运行程序输出如下:

王志刚今年23岁了,是一个无业游民  赵宏佳今年45岁了,是一个无业游民

直观上来将,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,但是程序运行结果却告诉我们,当基类指针 p 指向派生类 Teacher 的对象时,虽然使用了 Teacher 的成员变量,但是却没有使用它的成员函数,导致输出结果不伦不类(赵宏佳本来是一名工程师,输出结果却显示人家是个无业游民),不符合我们的预期。 

也就是说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。 

为了消除这种问题,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。将 display() 声明为虚函数:

//
// Created by ZHG52 on 2019/9/7.
//
#include <iostream>
using namespace std;
// 定义基类People
class People{
public:
    People(char *name, int age);
    virtual void display();  // 构造函数
protected:
    char *m_name;
    int m_age;
};
// 定义构造函数
People::People(char *name, int age):m_name(name), m_age(age){}
// 类成员函数定义
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一个无业游民"<<endl;
}

// 派生类Teacher
class Teacher:public People{
public:
    Teacher(char *name, int age, int salary);
    virtual void display();  // 构造函数
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary):People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名工程师,每月有"<<m_salary<<"元的收入."<<endl;
}

int main(){
    People *p = new People("王志刚", 23);
    p -> display();
    p = new Teacher("赵宏佳", 45, 2000);
    p -> display();
    return 0;
}

程序输出如下:

王志刚今年23岁了,是一个无业游民  赵宏佳今年45岁了,是一名工程师,每月有2000元的收入.

C++ 多态总结

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。 

C++ 提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数,从而实现一个接口,多种方法。如果没有多态,我们只能访问成员变量。

注意:多态的特性的 virtual 修饰,不单单对基类和派生类的普通成员函数有必要,而且对于基类和派生类的析构函数同样重要

虚函数

虚函数字面意思是在基类中使用关键字 virtual 声明的函数。虚函数和纯虚函数都是为了多态性服务,使得基类指针或引用可以指向变化多端的子类以实现变化多端的版本。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。

多态应用

在军事类定义各种团体的应用,代码如下:

#include <iostream>
using namespace std;

//军队
class Troops{
public:
    virtual void fight(){ cout<<"Strike back!"<<endl; }
};

//陆军
class Army: public Troops{
public:
    void fight(){ cout<<"--Army is fighting!"<<endl; }
};
//99A主战坦克
class _99A: public Army{
public:
    void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
};
//武直10武装直升机
class WZ_10: public Army{
public:
    void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
};
//长剑10巡航导弹
class CJ_10: public Army{
public:
    void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
};

//空军
class AirForce: public Troops{
public:
    void fight(){ cout<<"--AirForce is fighting!"<<endl; }
};
//J-20隐形歼击机
class J_20: public AirForce{
public:
    void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
};
//CH5无人机
class CH_5: public AirForce{
public:
    void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
};
//轰6K轰炸机
class H_6K: public AirForce{
public:
    void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
};

int main(){
    Troops *p = new Troops;
    p ->fight();
    //陆军
    p = new Army;
    p ->fight();
    p = new _99A;
    p -> fight();
    p = new WZ_10;
    p -> fight();
    p = new CJ_10;
    p -> fight();
    //空军
    p = new AirForce;
    p -> fight();
    p = new J_20;
    p -> fight();
    p = new CH_5;
    p -> fight();
    p = new H_6K;
    p -> fight();

    return 0;
}

程序运行结果如下:

Strike back!
--Army is fighting!
----99A(Tank) is fighting!
----WZ-10(Helicopter) is fighting!
----CJ-10(Missile) is fighting!
--AirForce is fighting!
----J-20(Fighter Plane) is fighting!
----CH-5(UAV) is fighting!
----H-6K(Bomber) is fighting!

这个例子中的派生类比较多,如果不使用多态,那么就需要定义多个指针变量,很容易造成混乱;而有了多态,只需要一个指针变量 p 就可以调用所有派生类的虚函数。从这个例子中也可以发现,对于具有复杂继承关系的大中型程序,多态可以增加其灵活性,让代码更具有表现力。

参考资料

C++多态和虚函数快速入门教程