C++ 面试备忘录
1. 函数调用的内部机理:
比如main函数中调用一个Add(a,b)。
1.首先会将两个实际参数a和b压入调用栈,从而向函数内传递数据。
2.用CALL指令调用一个函数。处理器从当前代码区跳转到被调用函数的入口处。
3.保存现场。具体来说,进入被调用函数入口后,调整栈帧。栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中保存了该函数的返回地址和局部变量。
4.从调用栈中取出实际的参数。执行被调用函数Add(a,b);
5.return 关键词将计算结果nRes返回,将结果数据移动到eax寄存器。
6.被调函数执行完成后,恢复之前保存的执行现场。主函数继续向后执行。
2. 函数递归调用。
函数内部调用自己本身。会设置一个特殊条件,作为递归调用终止的依据。
由函数调用的内部机理可知:
1.递归函数的运行机理:也是从底层一步步执行到上层。
2.递归函数需要大量保护现场的操作,性能比较低效,一般能用循环解决的,用循环解决。
递归函数的主要用处:
1. 可以将问题分解为更小的相似的子问题。并且无法用循环解决。
2.举例:找出一个数组连续和值最大的数据序列。
3. 内联函数
内联函数:一种用空间换时间的思想。
通过函数调用的内部机理,可以知道在函数调用的时候,会消耗掉大量性能。因此可以实现把一些短小的函数放入主调函数中,这样即不用去执行调用函数。
实现机理:用inline关键字去声明一个函数,编译时,编译器会自动把这个函数变回主调函数的执行语句,提升了性能。
4.类中问题详解
4.1 对象的内存模型。
首先需要了解的几个基础知识:
1.C++程序的内存格局:
1.全局数据区。存放全局变量,静态变量和常量
2.代码区。类成员函数和非成员函数存放在代码区
3.栈区。为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区
4.堆区。new 或者malloc分配的内存空间
2.变量名不做存储,变量名只是用于方便人阅读。
3.类和对象要区分开。类的声明中声明的类成员变量是决定了对象的内存数量以及如何解释内存的为,以及对象执行的操作和方法,即是不实际占用内存的,只是在编译时使用。定义一个对象就实际占用内存了。类定义成员函数时会存放在代码区。
4.初始化。 初始话针对的是对象的初始化,所以一般在类构造函数使用初始化列表进行初始化。
总结起来,可以初始化的情况有如下四个地方:
1、在类的定义中进行的,只有const 且 static 且 integral 的变量。
2、在类的构造函数初始化列表中, 包括const对象和Reference对象。
3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。
4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。
下面可以开始讲解实际的内存模型知识:
对象的内存模型:1.类的成员函数存放在代码区。成员函数即包括(静态成员函数、const 成员函数、普通成员函数)(不占对象的内存空间)。静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,因而可以说它们都是属于类的,但是类为什么只能直接调用静态类成员函数,而非静态类成员函数(即使函数没有参数)只有类对象才能调用呢?原因是类的非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
2.static成员变量。static成员变量是只能在类中声明,在类外定义。所有对象共用这个静态变量。(不占对象的内存空间)
3.普通成员变量。每个对象都有一份专属的成员变量内存。(占用对象的内存空间)const 变量也是占用内存空间。const其实质是只读量,编译器在编译程序时,只不过对这个地址进行限定,不能修改。所以const如果属于全局变量,放在全局区,局部变量放在栈区。在对象中,也是占用对象内存的。
4.虚函数(实现多态)。会在类对象的前四个字节设置虚表的地址。虚函数表是可以看做一个函数指针数组。通过这个虚函数表可以实现动态绑定。
5.空的类占用内存空间是1.。c++要求每个实例在内存中都有独一无二的地址
int a = 0; 全局初始化区 char *p1; 全局未初始化区 main() { int b;// 栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; 123456/0"; //在常量区,p3在栈上。 static int c =0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 }
5. 派生类对象的模型:
派生类对象的内存空间,包含基类的成员变量和自己类的成员变量和一个指向虚表的指针。
私有继承的特性和组合是一致的,从组合的理解来说更容易理解这个内容。不同的是,组合是生成了另一个类的对象。但派生的情况下,没有生成基类对象。由此也可以知道组合的情况下,也比派生多了一个指向虚表的指针。
6. 各种基本类型所占字节
基本类型:char、short, int ,long, C++ 11 新增 long long. 每种类型都有有符号版本和无符号版本。
c++采用的是确保最小长度原则,具体数字和电脑型号有关。
short至少16位。
int 至少和short一样长。
long至少32位,且至少与int一样长
long long至少64位,且至少与long一样长
通过 #include<climits>文件可得到系统的具体型号。
这个文件比较有用的的INT_MAX,SHRT_MAX,LONG_MAX,LLONG_MAX,CHAR_BIT常用的一些符号常量。用于比较大小时使用。
7.大端小端
大端模式:就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
小端模式:低位字节放在内存的底地址端,高位字节放在内存的高地址端。
大端模式符合我们常用的习惯。从高位字节到低位字节。
各自优势:
小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。
判断大小端函数:
int main() { union u { short i; char a[2]; }u; u.a[0] = 0x11; u.a[1] = 0x22; std::cout << std::hex << u.i << std::endl; }
8. 符号常量
声明符号常量的三种方法:
1.#define
2.const
3.enum
重点讲解一下枚举:
枚举不仅声明了符号常量,更是定义了一种数据常量。
enum enumType{Ming,Li,Yang};
上面这个语句声明了一个 新的数据类型 enumType,实现了3个符号常量。
也可以用这个数据类型声明一个变量,变初始化。
enumType fa=Ming;
9.数据成员指针和函数成员指针
指向数据成员的指针格式如下:
<类型说明符><类名>::*<指针名>
指向成员函数的指针格式如下:
<类型说明符>(<类名>::*<指针名>)(<参数表>)
1.对于普通指针变量来说,其值是它所指向的地址,0表示空指针。
2.而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示