1.程序运行知识
1.1 内存布局和分配方式
C程序的内存布局如下:
- 静态存储区:存储全局变量和static变量,通常在程序编译期间已经分配好了。
- BSS段:存放未初始化的static变量和全局变量
 - Data段:存放初始化过的static变量和全局变量
 - Text段:存储程序的二进制代码,程序代码区。
 
 - 堆:程序运行时通过malloc申请的内存区存放在堆中,需要使用free来释放该内存空间,生存期在malloc和free之间。
 - 栈:执行函数时,函数的局部变量存储在栈中,执行结束后自动释放该内存区域,栈内存分配运算内置与处理器指令集中。
 
C++程序的内存布局与C程序布局类似,区别是C++不再区分全局变量和静态变量是否已经初始化,全部存储在静态存储区;另外堆中存放new/delete申请释放的资源,而malloc和free申请的资源存放在自由存储区。
1.2 内存溢出原因
- 栈溢出:越界访问造成,例如局部变量数组越界访问或者函数内局部变量使用过多,超出了操作系统为该进程分配的栈的大小,还有递归函数层次过多超过了栈大小。
 - 堆溢出:程序申请了资源但忘记释放该资源,造成内存泄露,累积泄露内存过多会造成内存溢出。
 
1.3 内存泄露和检测
- 
C++内存泄漏检测内存泄露是指程序中动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况。
 - 
动态内存泄露检测:检查new/delete的资源是否正确释放,检查程序运行期间内存是否一直在增长,使用内存检测工具来检测泄露情况。
 
1.4 程序生成过程
- 预处理阶段:根据文件中的预处理指令来修改源文件的内容。如#include指令,作用是把头文件的内容添加到.cpp文件中。
 - 编译阶段:将其翻译成等价的中间代码或汇编代码。
 - 汇编阶段:把汇编语言翻译成目标机器指令。
 - 链接阶段:例如,某个源文件中的函数可能引用了另一个源文件中定义的某个函数;在程序中可能调用了某个库文件中的函数。
 
1.5 预编译
- 定义:预编译又称为预处理 , 是做些代码文本的替换工作。处理 # 开头的指令 , 比如拷贝 #include 包含的文件代码, #define 宏定义的替换 , 条件编译等。
 - 功能:宏定义,文件包含,条件编译。
 
1.6 头文件的作用
- 保存程序的声明。
 - 通过头文件可以来调用库函数。因为有些代码不能向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,编译器会从库中提取相应的代码。
 - 如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
 
2. 基本数据类型和用法知识
2.1 struct与class的区别
- 默认情况下,struct的成员变量是public的,而class是private的。
 - struct保证成员按照声明顺序在内存中存储,而class不能保证。
 - 默认情况下,struct是public继承,而class是private继承。
 
2.2 struct与union的区别
- struct中各个成员变量是独立的,union中的成员变量共享同一片内存区域,内存区域长度由成员变量中长度最大的一个决定。
 - struct不能保证分配的是连续内存,但union分配的是连续内存。
 
2.3 const和define的用途以及区别
- const用途:用来定义常量、修饰函数参数、修饰函数返回值,可以避免被修改,提高程序的健壮性。
 - define用途:是宏定义,在编译的时候会进行替换,这样做的话可以避免没有意义的数字或字符串,便于程序的阅读。
 - 区别:const定义的数据有数据类型,而宏常量没有数据类型。编译器可以对const常量进行类型检查。而对宏定义只进行字符替换,没有类型安全检查,所以字符替换时可能出错。
 
2.4 枚举和define的区别
- #define 是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
 - 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
 - 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
 
2.5 内联函数和宏的区别
- 内联函数在编译时展开,宏在预编译时展开。
 - 在编译的时候内联函数可以直接被嵌入到目标代码中,而宏只是一个简单的文本替换,内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具备这样的功能。inline函数是函数,宏不是函数。
 
2.6 new/delete和malloc/free的区别
- new/delete用调用构造函数来实例化对象和调用析构函数释放对象申请的资源。
 - malloc/free用来申请内存和释放内存,但是申请和释放的对象只能是内部数据类型。
 - malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
 
2.7 delete和delete[]的区别
- delete只会调用一次析构函数,delete[]会调用每一个成员的析构函数。
 - delete与new配套,delete []与new []配套,用new分配的内存用delete删除用new[]分配的内存用delete[]删除。
 
2.8 指针和引用的概念和区别
- 
指针指向一块内存,指针保存的是内存的地址;引用是变量的别名,本质是引用该变量的地址。解引用是取指针指向的地址的内容,取地址是获得变量在内存中的地址。
 - 引用在创建的同时必须初始化,保证引用的对象是有效的,所以不存在NULL引用。
 - 指针在定义的时候不必初始化,所以,指针则可以是NULL,可以在定义后面的任何地方重新赋值。
 - 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用。
 - 指针在任何时候都可以改变为指向另一个对象。
 - 引用的创建和销毁并不会调用类的拷贝构造函数。
 - 因为不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,所以比指针安全。由于const 指针仍然存在空指针,并且有可能产生野指针,所以还是不安全。
 - 程序会给指针变量分配内存区域,而引用不需要分配内存区域。
 - 返回引用时,在内存中不产生被返回值的副本。
 
2.9 memset,memcpy和strcpy的区别
- memset用来对一段内存空间全部设置为某个字符。
 - memcpy是内存拷贝函数,可以拷贝任何数据类型的对象。
 - strcpy只能拷贝字符串,遇到’\0′结束拷贝。
 
2.10 指针在16位机,32位机,64位机中分别占多大内存
- 16位机:2字节。
 - 32位机:4字节。
 - 64位机:8字节。
 
2.11 字符指针,浮点数指针和函数指针哪个占用内存更大
- 一样大,指针的占用内存大小只和机器相关。
 
2.12 如何引用一个全局变量
- 在同一文件中:直接引用。
 - 咋不同文件中:直接引用头文件;使用extern声明变量。
 
2.13 变量声明和定义的区别
- 变量声明:告诉编译器有某个类型的变量,但不会为其分配内存。
 - 变量定义:位该类型的变量分配内存。
 
2.14 野指针,未初始化指针和空指针的区别
- 野指针:指向一个已删除的对象或无意义地址的指针。
- 原因:指针变量没有被初始化,或者指针p被free或者delete之后,没有置为NULL。
 
 - 空指针:空指针表示“未分配” 或者“尚未指向任何地方” 的指针。
 - 区别:空指针可以确保不指向任何对象或函数; 而未野指针或初始化指针则可能指向任何地方。
 
2.15 常量指针和指针常量的区别
- 常量指针:是一个指向常量的指针。可以防止对指针误操作而修改该常量。
 - 指针常量:是一个常量,且是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。但是指针常量的内容是可以改变。
 
2.16 指针函数和函数指针的区别
- 指针函数:返回值是指针的函数。
 - 函数指针:一个指向函数的指针。函数名被括号括起来,并且加有指针符号。
 
2.17 const char*, char const*, char* const的区别
- char * const cp;//cp是常指针,指向char类型的数据
 - const char * cp;//cp是char类型的指针,指向const char
 - char const * p;//C++里面没有const*的运算符,所以const属于前面的类型。
 
2.18 static全局变量与普通的全局变量的区别
- 全局变量在整个工程文件内都有效。
 - 静态全局变量只在定义它的文件内有效。
 - 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。
 
2.19 static局部变量和普通局部变量的区别
- 静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失,直到程序运行结束后才释放。
 - 普通局部变量在定义它的函数内有效,这个函数返回会后失效。
 - static局部变量会自动初始化,而局部变量不会。
 
2.20 sizeof用在不同对象上的区别
- sizeof是C语言的一种单目操作符,并不是函数。sizeof以字节的形式返回操作数的大小。
 - 若操作数具有类型char、unsigned char或signed char,其结果等于1。
 - 当操作数是指针时,sizeof依赖于系统的位数。
 - 当操作数具有数组类型时,其结果是数组的总字节数。
 - 联合类型操作数的sizeof是其最大字节成员的字节数。
 - 结构类型操作数的sizeof是这种类型对象的总字节数。
 - 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。
 
2.21 sizeof与strlen的区别
- sizeof是运算符,计算数据所占的内存空间;strlen()是一个函数,计算字符数组的字符数。
 - sizeof可以用类型作参数;strlen()只能用char*作参数,必须是以‘/0’结束。
 - 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
 - sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现建立的最大对象的字节大小。
 
2.22 空指针指向了内存的什么地方
- 
标准并没有对空指针指向内存中的什么地方这一个问题作出规定,一般取决于系统的实现。我们常见的空指针一般指向 0 地址,即空指针的内部用全 0 来表示。空指针的“逻辑地址”一定是0,对于空指针的地址,操作系统是特殊处理的。并非空指针指向一个0地址的物理地址。在实际编程中不需要了解在我们的系统上空指针到底是一个 0指针还是非0地址,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。
 
2.23 有一个char * 型指针刚好指向一些int 型变量, 我想跳过它们。 为什么((int *)p)++; 不行?
- 类型转换的实质“把这些二进制位看作另一种类型, 并作相应的对待”。
 - ((int *)p)++是一个转换操作符, 根据定义它只能生成一个右值(rvalue)。
 - 而右值既不能赋值, 也不能用++ 自增。
 - 正确的做法:p = (char *)((int *)p + 1);。
 
3. 面向对象知识
3.1 面向对象三个基本特点
- 封装:将客观事物抽象成类,每个类对自身的数据和方法。封装可以使得代码模块化,目的是为了代码重用。
 - 继承:子类继承父类的方法和属性,继承可以扩展已存在的代码,目的是为了代码重用。
 - 多态:通过继承同一个基类,产生了相关的不同的派生类,与基类中同名的成员函数在不同的派生类中会有不同的实现,也就是说:一个接口、多种方法。
 
3.2 多态的作用
- 可以隐藏实现的细节,使得代码模块化;方便扩展代码;
 - 可以实现接口重用。
 
3.3 空类默认的成员函数
- 默认构造函数
 - 析构函数
 - 复制构造函数
 - 赋值运算符
 
3.4 类的成员函数重载、覆盖和隐藏的概念和区别
- 重载是指再同一个作用域内,有几个同名的函数,但是参数列表的个数和类型不同。
 - 函数覆盖是指派生类函数覆盖基类函数,函数名、参数类型、返回值类型一模一样。派生类的对象会调用子类中的覆盖版本,覆盖父类中的函数版本。
 - 隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
 - 覆盖和隐藏的区别:
- 派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
 - 派生类的函数与基类的函数同名,参数也相同。基类函数有virtual,是覆盖,没有virtual就是隐藏。
 
 
3.5 基类和子类的构造、析构顺序
- 定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数
 - 先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了
 
3.6 深拷贝与浅拷贝的区别
- 
深拷贝意味着拷贝了资源和指针
 - 
浅拷贝只是拷贝了指针,没有拷贝资源
 
3.7 构造函数的特点
- 
构造函数只在建立对象的时候自动被调用一次
 - 
构造函数必须是公共的,否则无法生成对象
 - 
构造函数只负责为自己的类构造对象
 
3.8 析构函数的特点
- 函数名称固定:~类名( )
 - 没有返回类型,没有参数
 - 不可以重载,一般由系统自动的调用
 
3.8 公有继承、私有继承、受保护的继承
- 公有继承时,派生类对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有和受保护成员;公有继承时基类受保护的成员,可以通过派生类对象访问但不能修改。
 - 私有继承时,基类的成员只能被直接派生类的成员访问,无法再往下继承。
 - 受保护继承时,基类的成员也只被直接派生类的成员访问,无法再往下继承。
 
3.9 类成员中只能使用构造函数的初始化列表而不能赋值的有哪些
- const成员
 - 引用成员
 
3.10 函数模板与类模板的区别
- 函数模板是模板的一种,可以生成各种类型的函数实例,函数模板的实例化是由编译程序在处理函数调用时自动完成的
 - 类模板的实例化必须由程序员在程序中显式地指定。
 
3.11 引用与多态的关系
- 引用就是对象的别名。
 - 引用主要用作函数的形参。
 - 引用必须用与该引用同类型的对象初始化: 引用是除指针外另一个可以产生多态效果的手段。
 - 一个基类的引用可以指向它的派生类实例。
 
3.12 static成员变量和static成员函数
- static数据成员独立于该类的任意对象而存在。
 - tatic数据成员(const static数据成员除外)在类定义体内声明,必须在类外进行初始化。
 - static数据成员定义放在cpp文件中,不能放在初始化列表中。
 - static成员函数在类的外部定义。
 - Static成员函数没有this形参。
 - 可以直接访问所属类的static成员,不能直接使用非static成员。
 - 因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。
 - static成员函数也不能被声明为虚函数。
 
3.13 static总结
- 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。
 - 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问。
 - 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内。
 - 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
 - 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
 
3.13 const总结
- 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了。
 - 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const。
 - 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值。
 - 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量。
 - 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
 
4. STL标准库
4.1 STL
- 容器:主要的七种容器 vector,list,deque,map,multimap,set,multiset。
 - 算法
 - 迭代器
 
参考文献
[1] c++面试知识,知乎。
来源:https://www.cnblogs.com/burningTheStar/p/8688515.html