C++面向对象-多态

父类和子类指针

1 父类指针可以指向子类对象,这是安全的,开发中经常用到,继承方式必须是public方式。
2 子类指针指向父类是不安全的,因为子类指针可能访问到父类以外的数据,而子类对象并没有创建。

class Person {
public:
    int m_age;
};

class Student : public Person {
public:
    int m_score;
};

int main() {
    //父类指针指向子类对象,安全
    Person *p = (Person *) new Student();
    p->m_age = 10;

    //子类指针指向父类对象,不安全
    Student *s = (Student *) new Person();
    //指向父类对象的子类指针访问了自己的成员变量,指针指向超出了范围
    s->m_score = 10;
    return 0;
}

多态

多态是面向对象一个非常重要的特性,同一操作作用于不同的对象,产生不同的执行结果,在运行时,可以识别出真正的对象类型,调用对应子类的函数。产生多态的条件如下:子类重写父类的成员函数,父类指针指向子类对象,利用父类指针调用重写的成员函数,这个成员函数必须是由Virtual修饰的成员函数,父类只要声明了Virtual,子类自动转为Virtual函数

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

class Dog : public Animal {
public:
    void run() {
        cout << "Dog::run()" << endl;
    }
};

class ErHa : public Dog {
public:
    void run() {
        cout << "ErHa::run()" << endl;
    }
};

int main() {
    
    Dog *dog0 = new Dog();
    dog0->run(); //调用dog下的run函数
    
    Dog *dog1 = new ErHa();
    dog1->run();//调用ErHa下的run函数
    

    return 0;
}

以上就是虚函数实现的列子,我们再进一步,为什么不加Virtual 就不能实现多态了,加了Virtual 就能实现多态呢,多态的实现是靠虚表实现的。我们先看看这几个类的大小

    //8
    cout << sizeof(Dog) << endl;
    //8
    cout << sizeof(ErHa) << endl;
    //8
    cout << sizeof(Animal) << endl;

这几个类sizeof大小是8,我们这是64位的,其实这个新增加的大小就是虚表的地址,指向虚表,而且这个虚表地址在类的最前面,而虚表里面存着本类虚函数的地址,从而进行真正的调用,每一个类只有一份虚表,多个对象共享一份虚表,无论这个对象是在堆还是栈还是全局对象。我们可以通过反汇编和内存调试也能看出来,这里我就不演示了。当父类实现了虚函数,而子类没有实现该虚函数的时候,我们来看看这个情况:

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
};

class Dog : public Animal {
public:
    int m_age; //dog的age
};

int main() {
    
    Animal *animal = new Animal();
    animal->run();会调用Animal::run()
    animal->speak();会调用Animal::speak()
    
    Dog *dog0 = new Dog();  
    dog0->run(); //会调用Animal::run()
    dog0->speak(); //会调用Animal::speak()

    return 0;
}

当子类没有重写父类虚函数的时候,它也会调用父类的虚函数,其底层实现也是通过虚表查找到函数调用,也就是子类即时没有虚函数,也有自己的虚表,我反汇编看到此时父类子类的虚表地址一样,可能不同的平台有不同的处理,虚表没有继承一说。如果子类想调用父类的虚函数方法的时候,应该显示调用,注意C++ 没有super等类似关键字,正确调用如下:

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
};

class Dog : public Animal {
public:
    int m_age; //dog的age
    
    void run() {
        Animal::run(); //直接用类::显示调用
        cout << "Dog::run()" << endl;
    }
    void speak() {
        Animal::speak(); //直接用类::显示调用
        cout << "Dog::speak()" << endl;
    }
};

int main() {
    Dog *dog0 = new Dog();
    dog0->run();
    dog0->speak();

    return 0;
}

含有虚虚函数实现的父类时候,父类的析构函数也需要声明为virtual函数,此时析构函数变成了虚析构函数,这样才能够保证销毁对象的时候父类子类析构函数调用,保证析构的完整性,子类可不加(virtaul)。

纯虚函数

没有函数体,且初始化为0的虚函数,用来定义接口规范

class Animal {
public:
    virtual void speak() = 0;
    virtual void run() = 0;
};

含有纯虚函数的类是抽象类,不可以实例化,不能创建对象,抽象类成也可以包含其它非纯虚函数,以及其他成员变量,抽象类的指针可以指向子类对象,如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类

多继承

C++允许一个类继承多个类,可以拥有多个类的特性,多继承增加了设计的复杂性,不建议使用。

#include <iostream>
using namespace std;

class Student {
public:
    int m_score;
    Student(int score = 0) :m_score(score) { }
    void study() {
        cout << "Student::study() - score = " << m_score << endl;
    }
    ~Student() {
        cout << "~Student" << endl;
    }
};

class Worker {
public:
    int m_salary;
    Worker(int salary = 0) :m_salary(salary) { }
    void work() {
        cout << "Worker::work() - salary = " << m_salary << endl;
    }
    ~Worker() {
        cout << "~Worker" << endl;
    }
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
    Undergraduate(
                  int score = 0,
                  int salary = 0,
                  int grade = 0) :Student(score), Worker(salary), m_grade(grade) {
        
    }
    void play() {
        cout << "Undergraduate::play() - grade = " << m_grade << endl;
    }
    ~Undergraduate() {
        cout << "~Undergraduate" << endl;
    }
};

int main() {
    {
        Undergraduate ug;
        ug.m_score = 100;
        ug.m_salary = 2000;
        ug.m_grade = 4;
        ug.study();
        ug.work();
        ug.play();
    }
    
    cout << sizeof(Undergraduate) << endl;
    
    return 0;
}

注意:这种多继承,父类的成员变量在子类前面,先继承谁,谁的成员就在前面。多继承的构造函数一样需要使用初始化列表调用父类构造函数,

父类如果都含有虚函数,子类多继承多个父类后,子类对象会产生多个虚函数表,顺序跟继承顺序有关。

#include <iostream>
using namespace std;

class Student {
public:
    virtual void study() {
        cout << "Student::study()" << endl;
    }
};

class Worker {
public:
    virtual void work() {
        cout << "Worker::work()" << endl;
    }
};

class Undergraduate : public Student, public Worker {
public:
    void study() {
        cout << "Undergraduate::study()" << endl;
    }
    void work() {
        cout << "Undergraduate::work()" << endl;
    }
    void play() {
        cout << "Undergraduate::play()" << endl;
    }
};

int main() {

    //含有16个字节,因为有2张虚表
    cout << sizeof(Undergraduate) << endl;
    
    Student *stu = new Undergraduate();
    stu->study();
    
    Worker *worker = new Undergraduate();
    worker->work();
    
    return 0;
}

同名成员

C++允许同名成员函数,同名成员变量,子类不会覆盖,访问的时候加上类名表示作用域,如下所示:

#include <iostream>
using namespace std;

class Student {
public:
    int m_age;
};

class Worker {
public:
    int m_age;
};

class Undergraduate : public Student, public Worker {
public:
    int m_age;
};

int main() {
    Undergraduate ug;
    ug.m_age = 10;
    ug.Student::m_age = 20;
    ug.Worker::m_age = 30;
    ug.Undergraduate::m_age = 40;

        //这里等于12
    cout << sizeof(Undergraduate) << endl;

    return 0;
}

虚继承

虚继承是为了解决菱形继承带来的成员变量冗余,重复。而且最底层子类因为二义性无法访问基类的的成员变量。我们先来看看菱形继承:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
};

class Student :  public Person {
public:
    int m_score;
};

class Worker :  public Person {
public:
    int m_salary;
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
};

int main() {
     Undergraduate ug;
     ug.m_grade = 10;
     ug.m_score = 20;
     ug.Student::m_age = 20;
     ug.Worker::m_age = 30;
     cout << sizeof(Undergraduate) << endl; //20
     return 0;
}

这里我们看到Undergraduate的大小是20,因为这样继承Undergraduate的父类两个类都有m_age成员变量,而且访问的时候我们需要通过作用域去访问,直接访问会报错。这种继承方式,基类的成员变量在最底层子类就冗余了,没有必要,为了解决这种问题,可以使用虚继承。加上virtual关键字:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
};

class Student :virtual public Person {
public:
    int m_score;
};

class Worker :virtual public Person {
public:
    int m_salary;
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
};


class Person1
{
    
};

int main() {
     Undergraduate ug;
     ug.m_grade = 10;
     ug.m_score = 20;
     ug.m_age = 30;

     return 0;
}

此时三个类比如:Student、Worker、Undergraduate都有了虚函数表指针,此时成员变量m_age在最底层子类只有一份内存。此时Person类被称为虚基类

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352