C++初始化列表 多态

初始化列表

特点

  • 一种便捷的初始化成员变量的方式
  • 只能在构造函数中
      Person(int age, int height): m_age(age), m_height(height) { }

初始化列表与默认参数配合使用

     Person(int age = 0, int height = 0): m_age(age),   m_height(height) { }

如果函数的声明和实现是分离的

  • 初始化列表只能写在函数的实现中
  • 默认参数只能写在函数的声明中

父类的构造函数

  • 子类的构造函数默认会调用父类的无参构造函数
  • 如果子类的构造函数显示地调用了父类的有惨构造函数,就不会再去默认调用父类的无参构造函数
  • 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数

继承体系下的构造函数示例

struct Person {
    int m_age;
    
    Person(): Person(0) {}
    
    Person(int age): m_age(age) { }
};


struct Student: Person {
    int m_no;
    Student(): Student(0, 0) {
        
    }
    
    Student(int age, int no): Person(age), m_no(no) {
        
    }
};

父类指针、子类指针

  • 父类指针可以指向子类对象,是安全的(继承方式必须是public)
  • 子类指针指父类对象是不安全的
class Person {
public:
    int m_age;
};

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

void test() {
    Person *person = new Student();
    // 可以访问m_score, m_age; 安全
    person->m_age = 10;
    
    Student *stu = (Student*)new Person();
    // 访问不到 m_score, 不安全
    stu->m_score = 10;
}

多态

  • 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态

特性

  • 同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
  • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数

多态的要素

  • 有继承关系
  • 子类重写父类的成员函数(override)
  • 父类指针指向子类对象
  • 利用父类调用重写的成员函数

多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态个动态多态的区别

  • 静态多态的函数地址早绑定-编译阶段确定函数地址
  • 动态多态的函数地址晚绑定-运行阶段确定函数地址
class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

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

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

void test() {
    Dog *dog = new Dog();
    dog->run();
    
    Animal * animal0 = new Dog();
    animal0->run();
    
    Animal * animal1 = new ErHa();
    animal1->run();
}

虚函数

  • C++中的多态通过虚函数(virtual function)来实现
  • 虚函数:被virtual修饰的成员函数
  • 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类可以省略virtual关键字)

虚表

  • 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的函数地址,这个虚表也叫虚函数表
class Animal {
public:
   virtual void run() {
       cout << "Animal::run()" << endl;
   }
     virtual void speak() {
       cout <<"Animal::speak()" << endl;
   }
};

class Cat: public Animal {
public:
    int m_life;
    Cat(): m_life(0) {}
    
    void run() override {
        cout << "Cat::run()" << endl;
    }
void speak() override {
        // 先执行父类的成员函数
        Animal::speak();、
        // 再执行自己的一些操作
        cout << "Cat::Speak()" << endl;
    }
};

虚表的x86环境图

image.png
  • 由上图可知,所有的Cat对象只对应一份虚表。
  • 具有虚函数的类,在对象创建时,会在其内存布局的时候,拿出4个字节的内存,用来存储该对象对应的函数虚表地址(vfptr: 虚函数指针),x86环境中,一个虚函数地址占用4个字节。

虚表的汇编分析

   // 调用Cat::run
   // 取出cat指针变量里面存储的地址值
   // eax里面存放的是cat对象的地址
   mov eax, dword ptr [cat]
   // 取出Cat对象的前面4个字节给edx
   // edx里面存储的是虚表的地址
   mov edx, dword ptr [eax]
   // 取出虚表中的前面4个字节给eax
   // eax存放的就是Cat::run的函数地址
   mov eax, dword ptr [edx]
   call eax
   
   // 调用Cat::speak
   // 取出cat指针变量里面存储的地址值
   // eax里面存放的是cat对象的地址
   mov eax, dword ptr [cat]
   // 取出Cat对象的前面4个字节给edx
   // edx里面存储的是虚表的地址
   mov edx, dword ptr [eax]
   // 取出虚表中的后面4个字节给eax
   // eax存放的就是Cat::speak的函数地址
   mov eax, dword ptr [edx]
   call eax

虚析构函数

  • 含有虚函数的类,应该将析构函数声明为虚函数(虚析构函数)
  • delete 父类指针时,才会调用子类的析构函数,保证析构的完整性
  • 虚析构或纯纯虚析构就是用来解决父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 拥有纯虚析构函数的类属于抽象类
class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    virtual void speak() {
        cout <<"Animal::speak()" << endl;
    }
    
   // 虚析构
    virtual ~Animal() {
        
    }
  // 纯虚析构
  virtual ~Animal() = 0;

};


class Cat: public Animal {
public:
    int m_life;
    Cat(): m_life(0) {}
    
    void run() override {
        cout << "Cat::run()" << endl;
    }
    
    void speak() override {
        // 先执行父类的成员函数
        Animal::speak();
        // 再执行自己的一些操作
        cout << "Cat::Speak()" << endl;
    }
    
    ~Cat() {
        cout << "Cat::~Cat()" << endl;
    }
};

纯虚函数

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