C++ Day08 面向对象 C 初始化列表、重写、多态、虚函数、虚表

439 阅读4分钟
1. 什么是初始化列表?它的本质是什么?
  • 初始化列表如下:
Student(int age, int height) : m_age(age), m_height(height) {}
  • 本质如下:
Student(int age , int height) {
 m_age = age;
 m_height = height;
}
2. C++ 中构造函数之间相互调用要怎么调用?new Student(10,20);,这句代码是在什么时候分配内存的?
  • 要在初始化列表中调用
Student():Student(10, 20) {//doSomething}
  • new Student(10,20); ① 会先调用 new 函数 ② new 函数内部调用 malloc 函数,完成会返回堆空间申请的地址 ③ 再调用构造函数
3. 子类的构造函数默认会调用父类的构造函数吗?顺序如何?如何做到子类显示调用指定父类的构造函数呢?
  • 子类会默认调用父类无参的构造函数;先调用父类的构造函数,再调用子类的构造函数
  • Student(int age , int height): People(10) {}
4. 子类的析构函数会自动调用父类的析构函数吗?顺序如何?
  • 子类的析构函数会自动调用父类的析构函数
  • 优先调用子类的析构函数,子类析构函数执行完再调用父类的
5. 为什么用父类的指针,指向子类对象是安全的?反过来就不安全了呢?
  • 因为子类包含了父类的一切
  • 而子类相比父类,通常会多一些数据
6. 什么是重写(覆盖、覆写、override)?什么是重载?
  • 重写:子类覆盖掉父类的默认实现
  • 重载:函数名一样,函数参数类型或数目不同
7. C++的类中方法,默认支持多态吗? C++要支持多态需要用什么关键字?父类声明为虚函数,子类还需要再声明虚函数吗?
  • 不支持,太离谱了
  • 关键字:virtual
  • 子类会继承父类虚函数的特性,一虚到底
8. 观察下面代码,请问 Cat 的实例占用内存大小?再简述 Animal *cat = new Cat(); cat->speak(); 寻找 speak 方法的过程?
struct Animal {
 int m_age;
 virtual void speak() {
     cout << "Animal:speak" << endl;
 };
 virtual void run() {
     cout << "Animal:run" << endl;
 };
};


struct Cat : Animal {
 int m_life;
 void speak() {
     cout << "Cat:speak" << endl;
 };
 void run() {
     cout << "Cat:run" << endl;
 };
};
  • 如果不存在虚函数占据 4+4= 8 字节存在虚函数 需要另外加一个指针大小的内存,用于存储虚表的内存地址
  • 寻找虚函数 speak 的过程:① cat 是一个指针,指向 cat 堆对象 ② cat 堆对象内部的首部 8 个字节是虚表的地址,可以找到虚表 ③ 找到虚表,虚表里面有虚函数的地址值
9. 从汇编分析找虚函数的调用的过程?
  • 如下代码:
Animal *cat1 = new Cat();
cat1->m_age = 15;
cat1->speak();
cat1->run();
  • 上述代码对应的汇编
// cat1->m_age = 15 可以确定 rax 就是 cat1 的堆空间地址 , 0x10(%rbp) 也是 cat1 的堆空间地址
0x100002fe0 <+64>:  movq   -0x10(%rbp), %rax
0x100002fe4 <+68>:  movl   $0xf, 0x8(%rax)


0x100002feb <+75>:  movq   -0x10(%rbp), %rax // 取 cat1堆对象的 前 8 个字节, 赋值给 rax (也就是拿虚表地址值)
0x100002fef <+79>:  movq   (%rax), %rcx // 取虚标的前 8 个字节,赋值给 rcx
0x100002ff2 <+82>:  movq   %rax, %rdi   // 将函数调用者 cat1 作为参数,传递给 rdi
0x100002ff5 <+85>:  callq  *(%rcx)      // 调用 虚函数 speak
10. 从内存角度分析找虚函数的调用的过程?
  • 如下代码:
Animal *cat1 = new Cat();
cat1->m_age = 15;
cat1->speak();
cat1->run();
  • 先通过 call 函数,拿到两个地址
`Cat::speak:
->  0x100003160
`Cat::run:
->  0x1000031a0
  • 再通过内存分析,分析虚表内容
// cat 的内存地址,前 8 个字节
40 40 00 00 01 00 00 00  转成地址 0x100004040 (按照理论这个应该是虚表地址)

// 用 0x0100004040 找到 16 个字节(按照理论,前 8 个是 Cat::speak 地址;后 8 个是 Cat::run 的地址)
60 31 00 00 01 00 00 00  转成地址 0x100003160
A0 31 00 00 01 00 00 00  转成地址 0x1000031A0
  • 上下能对应上,从内存分析也能圆满解释
11. 虚函数的作用?所有 Cat 的实例,共用一个虚表吗?
  • 虚函数作用:将函数调用的具体地址延后到运行时决定,让 C++ 有了动态性
  • 是的,所有的 Cat 对象共用同一份虚表。(这边存疑:全局区和栈区的Cat对象有多态性吗?)
12. 如果 C++中想调用父类的成员函数怎么做?简单粗暴
void speak() {
Animal::speak();
}
13. 虚析构函数什么时候使用?
  • 虚析构函数:如果存在父类指针指向子类对象的情况,应该讲虚构函数声明为虚函数(虚析构函数)
  • delete 父类指针时,才会调用子类的析构函数,保证析构的完整性
14. 如何定义纯虚函数?
  • 没有函数体且初始化为 0 的虚函数,用来定义接口规范
  • virtual void speak = 0 ;