1.在有继承关系的父子类中,构建和析构一个子类对象时,父子构造函数和析构函数的执行顺序分别是怎样的?

A:构造从上至下,析构从下而上。

2.在有继承关系的类体系中,父类的构造函数和析构函数一定要申明为 virtual 吗?如果不申明为 virtual 会怎样?

A:构造函数不需要,析构函数一定要。

3.什么是 C++ 多态?C++ 多态的实现原理是什么?

A:C++中虚函数的作用主要是为了实现多态。在继承层次中,父类的指针可以具有多种形态——当它指向某个子类对象的时候,通过它能够调用到子类改写的函数,而不是父类本身。

这是运行期多态,即父类指针只有在程序运行时才能知道所指的真正类型是什么。

4.什么是虚函数?虚函数的实现原理是什么?

5.什么是虚表?虚表的内存结构布局如何?虚表的第一项(或第二项)是什么?

6.菱形继承(类 D 同时继承 B 和 C,B 和 C又继承自A)体系下,虚表在各个类中的布局如何?如果类B和类C同时有一个成员变了m,m如何在D对象的内存地址上分布的?是否会相互覆盖?

另外,时至今日,你一定要熟悉 C++11/14/17 常用的语言特性和类库,这里简单地列一下:
统一的类成员初始化语法与std::initializer_list
注解标签(attributes)
final/override/=default/=delete
语法auto
关键字Range-based
循环语法结构化绑定stl
容器新增的实用方法
std::thread
线程局部存储 thread_local
线程同步原语 std::mutex、std::condition_variable 等
原子操作类
智能指针类
std::bind/std::function

C++的内存分布

代码区:函数的二进制代码

全局数据区:全局变量和static变量

堆:动态内存分配

栈:局部变量

常量区:字符/文字常量

const

1.顶层const与底层const?

指向常量的指针:

不能改变指向的内容。const放在类型名前后都可以,const int 和 int const是等价的。声明指向常量的指针也就是底层const。

但是指向常量的指针不代表指向的内容一定是常量,只是不能改变指向的内容。

1
2
3
int num = 1;
int const *p_num = #
const int* p_num = #

指针常量:

指针本身是常量,声明时必须初始化,存储的地址不能改变。声明时const必须放在指针符号*后面。声明常量指针就是顶层const。

1
2
int num = 1;
int *const p_num = # //顶层const

“effective c++”第三条讲到: 只需要判断const是在 * 的左边还是右边即可。左边则是修饰被指物,即被指物是常量,不可以修改它的值;右边则是修饰指针,即指针是常量,不可以修改它的指向;在左右两边,则被指物和指针都是常量,都不可以修改。

*const在 * 左边,变量为const*

*const在 * 右边,指针为const*

2.函数传递中的const:

普通形参加不加const限定符对实参没有影响,引用形参和指针形参前面没有const限定符时,实参必须是非const的,而前面有const限定符时对实参也没有什么影响。

原因在于实参的传递方式不同,函数中的形参是普通形参的时,函数只是操纵的实参的副本,而无法去修改实参,实参会想,你形参反正改变不了我的值,那么你有没有const还有什么意义吗?引用形参和指针形参就下不同了,函数是对实参直接操纵,没有const的形参时实参的值是可以改变的。

3.const成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A
{
public:
A(int N = 0);
void Fun();
private:
int n;
};

A::A(int N):n(N){}
void A::Fun()
{
cout << n << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.Fun();
const A &b = a;
b.Fun();//const引用的对象调用非const成员函数
//const A a;
//a.Fun();//const对象调用非const成员函数
cin.get();
return 0;
}

当使用const对象调用非const成员函数时编译会报错:error C2662: ‘A::Fun’ : cannot convert ‘this’ pointer from ‘const A’ to ‘A &’

报错原因:因为const对象在调用成员函数时会隐含的把实参把中*this修改成const class * const this,以导致非const成员函数在接收时还是使用了class *const this接收,结果就是把const的指针赋给非const的指针

修正方法:以前错误只需在对象的成员函数后面加上const即可

extern

Dynamic_cast

一般认为子类对象大小>=父类对象大小。因为子类可以扩展父类,可以增加成员变量。如果一个子类增加了成员变量,那么它的对象的内存空间会大于父类对象。这时一个实际指向父类的指针,如果被强制转化为子类对象指针,当使用这个指针时可能会导致越界访问非法内存。相反,为何子类指针可以转换为父类指针?因为父类指针需要的,子类对象都有,不会出现非法内存访问。

这就是dynamic_cast不一定成功的原因。如果一个实际指向子类对象的指针被转换成了父类指针,然后再用dynamic_cast转换回来,一定能成功,而一个实际指向父类对象的指针,被dynamic_cast转换为子类指针,一定会失败。

Static

类的静态成员是所有对象共享的,一开始就分配了,以后固定而不会再分配空间。

静态函数不允许调用类的普通变量,只能访问类的静态变量

全局变量、静态全局变量、静态局部变量——内存的全局数据区,局部变量——栈区

  • 全局变量在整个工程文件中都有效
  • 静态全局变量只在定义它的文件有效
  • 静态局部变量只在定义它的函数内有效,只分配一次内存,函数返回后变量不会消失。
  • 全局变量和静态变量如果没有手动初始化,则编译器初始化为0,局部变量不知道

staic成员变量在类外初始化,因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这就矛盾了。

this

  • this指针的一般形式为(假如类类型是A,当然与类类型相关):A * const ,也就是说this指针是一个常量指针(指针的地址值为常量)
  • 在类的非const成员函数中,this的类型为一般形式,即 A * const
  • 在类的const成员函灵数,this的类型为:const A * const,即指向常量对象的常量指针

const_cast转换是去掉所指向对象的常量性,使得const成员函数可以修改数据成员或调用非const成员函数(本来是不允许的);但不推荐用这样的方法,如果只是修改数据成员,请用mutable关键字。