uacs2024

导航

C++多态和虚函数

C++多态和虚函数

#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, 8200);
    p -> display();
    return 0;
}

结果

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

我们直观上认为,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,这符合人们的思维习惯。但是本例的运行结果却告诉我们,当基类指针 p 指向派生类 Teacher 的对象时,虽然使用了 Teacher 的成员变量,但是却没有使用它的成员函数,导致输出结果不伦不类(赵宏佳本来是一名老师,输出结果却显示人家是个无业游民),不符合我们的预期。
换句话说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数
为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++增加了虚函数。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

更改上面的代码,将 display() 声明为虚函数:

#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, 8200);
    p -> display();
    return 0;
}

结果:

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

本例仅仅是在 display() 函数声明前加了一个virtual关键字,将成员函数声明为了虚函数(Virtual Function),这样就可以通过 p 指针调用 Teacher 类的成员函数了

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

同样是p->display();这条语句,当 p 指向不同的对象时,它执行的操作是不一样的。同一条语句可以执行不同的操作,看起来有不同表现方式,这就是多态。

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

所以虚函数是根据指针的指向来调用的,指针指向哪个类的对象就调用哪个类的虚函数

借助引用也可以实现多态

由于引用类似于常量,只能在定义的同时初始化,并且以后也要从一而终,不能再引用其他数据,所以本例中必须要定义两个引用变量,一个用来引用基类对象,一个用来引用派生类对象。从运行结果可以看出,当基类的引用指代基类对象时,调用的是基类的成员,而指代派生类对象时,调用的是派生类的成员。

不过引用不像指针灵活,指针可以随时改变指向,而引用只能指代固定的对象,在多态性方面缺乏表现力,所以以后我们再谈及多态时一般是说指针。

多态的用途

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

虚函数的注意事项

1) 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加

2) 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数

3) 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数

4) 只有派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。

5) 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义

6) 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。

 

构成多态的条件

封装、继承和多态是面向对象的三大特征。而多态是指通过基类的指针既可以访问基类的成员,也可以访问派生类的成员

构成多态的条件:

    • 必须存在继承关系;
    • 继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)
    • 存在基类的指针,通过该指针调用虚函数
#include <iostream>
using namespace std;
//基类Base
class Base{
public:
    virtual void func();
    virtual void func(int);
};
void Base::func(){
    cout<<"void Base::func()"<<endl;
}
void Base::func(int n){
    cout<<"void Base::func(int)"<<endl;
}
//派生类Derived
class Derived: public Base{
public:
    void func();
    void func(char *);
};
void Derived::func(){
    cout<<"void Derived::func()"<<endl;
}
void Derived::func(char *str){
    cout<<"void Derived::func(char *)"<<endl;
}
int main(){
    Base *p = new Derived();
    p -> func();  //输出void Derived::func()
    p -> func(10);  //输出void Base::func(int)
    p -> func("http://c.biancheng.net");  //compile error
    return 0;
}

在基类 Base 中我们将void func()声明为虚函数,这样派生类 Derived 中的void func()就会自动成为虚函数。p 是基类 Base 的指针,但是指向了派生类 Derived 的对象。

语句p -> func();调用的是派生类的虚函数,构成了多态。

语句p -> func(10);调用的是基类的虚函数,因为派生类中没有函数覆盖它。

语句p -> func("http://c.biancheng.net");出现编译错误,因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。

什么时候声明虚函数

首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。

posted on 2024-03-13 17:11  ᶜʸᵃⁿ  阅读(3)  评论(0编辑  收藏  举报