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) {
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();
0x100002fe0 <+64>: movq -0x10(%rbp), %rax
0x100002fe4 <+68>: movl $0xf, 0x8(%rax)
0x100002feb <+75>: movq -0x10(%rbp), %rax
0x100002fef <+79>: movq (%rax), %rcx
0x100002ff2 <+82>: movq %rax, %rdi
0x100002ff5 <+85>: callq *(%rcx)
10. 从内存角度分析找虚函数的调用的过程?
Animal *cat1 = new Cat();
cat1->m_age = 15;
cat1->speak();
cat1->run();
`Cat::speak:
-> 0x100003160
`Cat::run:
-> 0x1000031a0
40 40 00 00 01 00 00 00 转成地址 0x100004040 (按照理论这个应该是虚表地址)
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 ;