C++入门06 --多态,虚函数,虚函数表,纯虚函数,抽象类

多态

  • 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态;
  • 多态是面向对象的非常重要的一个特性;
    • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果;
    • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数;
#include <iostream>

using namespace::std;

class Animal {

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

class Dog : public Animal{

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

class Cat : public Animal{

public:
    void run(){
        cout << "Cat run()" << endl;
    }
};

class Pig : public Animal{

public:
    void run(){
        cout << "Pig run()" << endl;
    }
};

void liu(Animal *a){
    a->run();
}

int main(int argc, const char * argv[]) {
    
    liu(new Dog());
    liu(new Cat());
    liu(new Pig());
    
    return 0;
}
  • 由于用Animal *a来接收形参,那么执行run方法是,调用的都是Animal类中的方法,并没有实现根据不同的对象类型,调用对应的函数方法;

虚函数

  • C++中多态时通过虚函数来实现的;

  • 虚函数:被virtual关键字修饰的函数,称为虚函数;

  • 只要在父类中声明为虚函数,子类中的重写的函数也自动变成虚函数,也就是说子类中的函数可以省略virtual关键字;

  • 多态实现的要素:

    • 子类重写父类的成员函数;
    • 父类指针指向子类对象;
    • 利用父类指针调用子类的成员函数,且成员函数为虚函数;从基类开始虚,从中间类开始虚,也不能实现多态的功能;
#include <iostream>

using namespace::std;

class Animal {

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

class Dog : public Animal{

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

class Cat : public Animal{

public:
    void run(){
        cout << "Cat run()" << endl;
    }
};

class Pig : public Animal{

public:
    void run(){
        cout << "Pig run()" << endl;
    }
};

void liu(Animal *a){
    a->run();
}

int main(int argc, const char * argv[]) {
    
    liu(new Dog());
    liu(new Cat());
    liu(new Pig());
    
    return 0;
}
  • 上述的代码实现了多态的功能,即不同的子类对象调用属于自己的函数方法;

虚表

  • 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫做虚函数表;
#include <iostream>

using namespace::std;

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

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

int main(int argc, const char * argv[]) {
    
    Animal *cat = new Cat();
    cat->m_age = 3;
    cat->speak();
    cat->run();
    
    cout << cat << endl;
    
    return 0;
}
  • 当前测试环境是在MacOS,即x64环境,则指针占用8个字节;
  • 当Animal没有虚函数时,cat实例对象有m_age,m_life有两个成员变量,所以cat实例对象占用8个字节;
  • 当Animal有虚函数时,即有virtual关键字修饰函数时,cat实例对象会占用16个字节,前面8个字节存储的是Cat类的虚函数表的地址;
  • cat实例对象的内部布局如下所示:
Snip20210813_147.png
  • 可通过汇编分析证明上面的描述:
Snip20210813_148.png
  • movl $0x3, 0x8(%rax):rax存储的是cat实例对象的内存地址,由于虚函数地址占了前8个字节,所以m_age的偏移量为8;
  • movq (%rax), %rcx:取出cat实例对象的内存地址中前8个字节内容即虚函数的地址,存入rcx寄存器;
  • callq *(%rcx):取出虚函数地址的前8个字节内容,即speak函数内存地址,然后调用speak函数;
  • 同理callq *0x8(%rcx),从虚函数地址的第8个字节开始的后8个字节的内容,即run函数内存地址,然后调用run函数;

总结:

  • 所有cat实例对象,不管在栈区,全局区,堆区都共用一份虚表,也就是说每个类都有自己独立的一份虚表;
  • cat实例对象的前8个字节存储了虚表的内存地址,虚表中存储着cat类的所有虚函数;
  • 若cat类存储虚函数,且cat子类没有实现run方法,那么虚表中会存储父类Animal的run方法,所以会调用Animal的run方法,这也就是说调用目标方法,如果子类没有实现,就调用父类的实现;

子类调用父类的成员函数

#include <iostream>

using namespace::std;

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

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

int main(int argc, const char * argv[]) {
    
    Animal *cat = new Cat();
    cat->m_age = 3;
    cat->speak();
    cat->run();
    
    cout << cat << endl;
    
    cout << sizeof(Animal) << endl;
    
    return 0;
}
  • 直接在子类的函数中,调用父类的函数;

虚析构函数

  • 含有虚函数的类,应该将析构函数声明为虚函数即虚析构函数;
  • delete父类指针时,才会调用子类的析构函数,保证析构的完整性,否则会造成内存泄漏;
#include <iostream>

using namespace::std;

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

class Cat : public Animal{
public:
    int m_life;
    
    void speak(){
        Animal::speak();
        cout << "Cat speak()" << endl;
    }
    
    void run(){
        Animal::run();
        cout << "Cat run()" << endl;
    }
    
    ~Cat(){
        cout << "~Cat()" << endl;
    }
};

int main(int argc, const char * argv[]) {
    
    Animal *cat = new Cat();
    cat->m_age = 3;
    cat->speak();
    cat->run();
    
    delete cat;
    
    cout << cat << endl;
    
    cout << sizeof(Animal) << endl;
    
    return 0;
}
  • virtual ~Animal():父类Animal的析构函数声明为虚函数,否则Animal *cat = new Cat(),不会调用Cat类的析构函数,造成内存泄漏;

纯虚函数

  • 纯虚函数:没有函数体且初始化为0的虚函数,用来定义借口规范;
class Animal {
public:
    virtual void speak() = 0;
    
    virtual void run() = 0;
};

抽象类

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

推荐阅读更多精彩内容