美文网首页
C++初始化列表 多态

C++初始化列表 多态

作者: lieon | 来源:发表于2020-10-30 16:47 被阅读0次

初始化列表

特点

  • 一种便捷的初始化成员变量的方式
  • 只能在构造函数中
      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() { }
};

相关文章

  • C++初始化列表 多态

    初始化列表 特点 一种便捷的初始化成员变量的方式 只能在构造函数中 初始化列表与默认参数配合使用 如果函数的声明和...

  • C++初始化列表

    引言 用c++的人都知道,c++的构造函数具有初始化列表,初始化列表有什么作用?什么情况下必须使用初始化列表...

  • [C++之旅] 11 初始化列表

    [C++之旅] 11 初始化列表 初始化列表的特性 初始化列表先于构造函数执行 初始化列表只能用于构造函数 初始化...

  • C++的初始化列表和列表初始化

    C++的初始化列表和列表初始化 初始化列表 初始化列表是声明在构造函数中来实现的,相当于初始化,而不是复制操作 初...

  • EOS插件初始化机制讲解

    EOS插件初始化机制讲解 EOS插件初始化采用的技术 EOS插件初始化采用技术有C++多态技术、函数模板及代理设计...

  • 深刻剖析之c++博客文章

    三大特性 封装、继承、多态 多态 C++ 虚函数表解析C++多态的实现原理 介绍了类的多态(虚函数和动态/迟绑定)...

  • C++ 面试知识点

    不定期整理一些C++的知识点 C++是静态类型,即在编译阶段检查类型 引用和const变量必须被初始化 多态是通过...

  • C++初始化列表

    本文主要说明成员初始化列表的注意事项。 I、上帝视角看初始化列表 构造函数可以有两种构造形式,一是在构造函数体内对...

  • c++ 初始化列表

    初始化列表可以改变private里const 的值

  • 多态的C++实现

    多态的C++实现 1 多态的原理 什么是多态?多态是面向对象的特性之一,只用父类指针指向子类的对象。 1.1 多态...

网友评论

      本文标题:C++初始化列表 多态

      本文链接:https://www.haomeiwen.com/subject/mhrdvktx.html