虚函数和多态

来源:互联网 发布:淘宝花呗店铺出售 编辑:程序博客网 时间:2024/05/17 22:49

  • 虚函数和多态
    • 成员函数重写
    • 继承遇到函数重写
    • 虚函数和多态的含义
    • 多态的应用
      • 多态的作用
      • 什么时候父类成员函数加virtual关键字
      • 还支持引用类型
      • 父类类型本身没有多态效果
  • 抽象基类和纯虚函数
  • 语法细节
  • 其它参考

虚函数和多态

成员函数重写

子类继承了父类的所有成员函数,但是父类的成员函数不一定适合子类,子类可以重载或者重写(覆盖)父类的成员函数。

重载我们在之前的章节中已有介绍,即函数名相同,参数不同。
重写(覆盖)的意思是函数名和参数表完全一样,即函数的原型完全一样。

比如,void Human::introduce()函数的功能是输出人类的各成员变量的值。但是它并不合适Student对象,因为该函数没有输出Student类增加的年级成员变量grade的值。因此Student类可以新增一个更适合自己的版本,即重写(覆盖)该函数。

class Student : public Human{     public:         void set_grade(int g){ grade = g; }         int get_grade(){ return grade; }         int grade;             void introduce();  // 新增introduce成员函数};void Student::introduce(){    // 与Human::introduce()函数实现相同的功能    cout << "大家好,我是" << name << endl;    if(is_male){         cout << "男性" << endl;    }else{         cout << "女性" << endl;    }    cout << age << "岁" << endl;    if(id.length() == 0){                    cout << "身份证号未知" << endl;    }else{        cout << "身份证号:" << id << endl;    }      // 新增grade成员的输出    cout << "年级:" << grade << endl;}

注意,与重载不同,重写(覆盖)只能发生在继承关系中。单独的一个类中的两个成员函数,或者类外部的两个函数是不能有相同的函数原型的。

class foo{public:    void member(){};    void member(){}; // 错误,一个类中不能出现原型完全一样的成员函数};void fun(int x){}void fun(int x){}  // 错误,不能出现原型完全一样的函数

重写(覆盖)的意义在于子类可以增加一些成员函数,这些成员函数与父类的成员函数有相同的接口(函数原型),不同的实现。使它们更适合子类。换句话说即成员函数功能的细化。

重写(覆盖)的意义在于子类的”新版本“的成员函数优化了/细化了父类的一些”老版本“成员函数。

注意,父类的”老版本“的被重写的函数也被子类继承了,在子类中存在”老版本“和”新版本“的两种版本。在子类中要使用”老版本“的函数需要使用范围运算符::。否则,默认是”新版本“的。即重写和重载一样,在子类中都会自动隐藏父类的版本的函数。

下面是void Student::introduce()另一种正确的写法。

void Student::introduce(){    // 与Human::introduce()函数实现相同的功能,因此可以调用它    Human::introduce();      // 新增grade成员的输出    cout << "年级:" << grade << endl;}

而下面的void Student::introduce()的实现会造成void Student::introduce()递归调用。

void Student::introduce(){    introduce();  // 错误,调用了Student::introduce自己,形成了递归调用,函数无法结束    // 新增grade成员的输出    cout << "年级:" << grade << endl;}

继承遇到函数重写

前面说过,继承反映的是子类与父类之间“是一个”的关系,比如说对象赋值:

Human p;Student s;s.init("周润发", true, 30, "123456", 1);p = s; // 把Student类对象s中包含的Human部分的各成员的值赋给p的各成员

再比如说,指针操作

Human *p_human = NULL;Student s;s.init("周润发", true, 30, "123456", 1);p_human = &s;p_human->introduce();

因为,定义p_human时指定它的类型是Human *,因此它认为自己指向的是Human对象。于是,p_human->introduce()调用的是void Human::introduce()方法。

不过,等等,这里我们是不是忽略了什么。

显然,p_human实际上指向的是Student类对象s,而Student重写了introduce方法,为什么p_human->introduce()调用的不能是”新版本“的introduce呢,它更适合Student对象啊。

在这种场景下,要想p_human->introduce()调用的是”新版本“的void Student::introduce(),那么introduce必须是虚函数。

虚函数和多态的含义

如果想在父类中定义一个成员函数留待子类中进行细化,我们必须在它前面加关键字virtual ,以便可以使用指针对指向相应的对象进行操作。

class Human{    public:        void init(string n, int a, bool m, string i){            name = n;            age = a;            is_male = m;            id = i;        }        virtual void introduce();  // 虚函数    private:        string name;        int age;        bool is_male;        string id;};void Human::introduce(){   cout << "大家好,我是" << name << endl;    if(is_male){         cout << "男性" << endl;    }else{         cout << "女性" << endl;    }    cout << age << "岁" << endl;    if(id.length() == 0){                    cout << "身份证号未知" << endl;    }else{        cout << "身份证号:" << id << endl;    }  }class Student : public Human{    public:        void init(string n, int a, bool m, string i, int g){            Human::init(n, a, m, i);            grade = g;        }        void introduce();    private:        int grade;  // 年级};void Student::introduce(){    Human::introduce();    cout << "年级:" << grade << endl;}class Soldier : public Human{    public:        void init(string n, int a, bool m, string i, string r){            Human::init(n, a, m, i);            rank = r;        }        void introduce();    private:        string rank;  // 军衔};void Soldier::introduce(){    Human::introduce();    cout << "军衔:" << rank << endl;}int main(){    Human *p_human = NULL;    Student s;    s.init("周润发", true, 30, "12345", 1);    Soldier s2;    s2.init("刘伯承", true, 30, "23456", "元帅");    p_human = &s;    p_human->introduce();    p_human = &s2;    p_human->introduce();    system("pause");    return 0;}

父类成员函数是虚函数,那么通过父类指针来调用,调用的版本由指针指向的对象来决定。这就体现了多态的含义。即一种接口,多种实现。

  • 一种调用形式:p_human->introduce()
  • 多种调用结果:实际被调用的可能是下面三个版本的introduce之一
    void Human::introduce()
    void Student::introduce()
    void Soldier::introduce()

大家可以去掉Human类中的virtual关键字,对比一下程序运行结果。

多态的应用

多态的作用

多态有什么作用呢?它帮助我们达到“软件复用”。

那在程序里,可能编写了很多处理Human类的代码,比如下面的函数,

...  // Human、Student、Soldier类的定义void introduce_triple(Human *p){    int i;    for(i = 0; i < 3; i++){        p->introduce();    }}

会让Human的进行3遍自我介绍。

那这个函数能否处理学生对象呢,即当p指向学生对象,p->introduce();是否可以调用学生类的introduce。在introduce函数是虚函数的情况下是可以的。

int main(){    Student s;    s.init("周润发", true, 30, "12345", 1);    Soldier s2;    s2.init("刘伯承", true, 30, "23456", "元帅");    introduce_triple(&s);    introduce_triple(&s2);    system("pause");    return 0;    }

举一个生活中的例子,TCL出了某款型号的彩电比如“栩栩如生”系列第一代产品,还有附带的遥控器可以来遥控电视。随后,后续又推出了该系列第二代、第三代产品。这时,大家可能会想,控制第一代的遥控器也可以控制它们就好了。如果可以,那遥控器就可以复用了。

什么时候父类成员函数加virtual关键字

什么时候需要将父类的成员函数修饰为虚函数,即加virtual关键字呢?就看这个函数是否是适应于子类的,

如果是成员函数适合子类的就不需要加virtual关键字。

如果子类可能会细化这个函数,那就需要在父类中加virtual关键字。

比如void Human::set_age(int age)int Human::get_age()这两个函数是用来设置和读取年龄的。显然,Human类的子类不需要重写这个函数。那么这两个函数就不需要设置为虚函数。

还支持引用类型

多态必须通过父类指针调用才可以起作用,或者引用也可以,如下面的代码

...  // Human、Student、Soldier类的定义void introduce_triple(Human &p)  // 父类的引用{      int i;    for(i = 0; i < 3; i++){        p.introduce();    }}int main(){    Student s;    s.init("周润发", true, 30, "12345", 1);    Soldier s2;    s2.init("刘伯承", true, 30, "23456", "元帅");    introduce_triple(s);    introduce_triple(s2);    system("pause");    return 0;    }

父类类型本身没有多态效果

如果不是指针和引用,而是父类类型,那么最终调用的是父类的版本,没有多态的效果。如下面代码:

...  // Human、Student、Soldier类的定义void introduce_triple(Human p)  // 父类类型本身{     int i;    for(i = 0; i < 3; i++){        p.introduce();    }}int main(){    Student s;    s.init("周润发", true, 30, "12345", 1);    Soldier s2;    s2.init("刘伯承", true, 30, "23456", "元帅");    introduce_triple(s);    introduce_triple(s2);    system("pause");    return 0;    }

抽象基类和纯虚函数

shape类的例子

语法细节

虚函数特性是被继承的,子类重写虚函数,在类的定义里可加virtual关键字也可不加。当然加上更明显的说明此函数是虚函数。

...class Student : public Human{    public:        void init(string n, int a, bool m, string i, int g){            Human::init(n, a, m, i);            grade = g;        }        virtual void introduce(); // 正确,也可以不加virtual关键字    private:        int grade;  // 年级};

类定义中指出是某个成员函数是虚函数,如果该函数的实现写在类定义外边,不能加virtual关键字了。

virtual void Student::introduce() // 错误,不能在这里加virtual关键字{    Human::introduce();    cout << "年级:" << grade << endl;}

其它参考

多态介绍可参考4.4 多态 (Polymorphism)

0 0