1. C 和 C++ 的区别
a. C 是面向过程、结构化的语言; C++ 是面向对象、模块化的语言,主要特点是封装、继承、多态。
b. 动态管理内存的方法不同,C 中用 malloc / free 分配、释放内存; C++中还有 new / delete。
c. C++ 中有引用, C中没有。
d. C++ 支持函数重载,C 中不支持。
2. C++ 不是类型安全的,两个不同类型的指针之间可以强制转换 (reinterpret cast),C#是类型安全的。
3. malloc / free 和 new / delete 区别
malloc / free 是标准库函数,且malloc对开辟的内存空间大小严格要求,无法满足动态对象的内存分配回收要求;
new / delete 是C++运算符,可以调用构造 / 析构函数,完成内存的动态分配和回收。
4. delete 和 delete[] 的区别
与 new 和 new[] 对应。
delete只调用一次析构函数(删除一个对象指针);
detele会调用每一个成员的析构函数,然后调用operate delete释放空间,用于删除数组。
5.构造函数和析构函数
析构函数的调用次序: 先调用派生类的析构函数,再调用基类的析构函数,与构造函数刚好相反;
基类的析构函数不是虚函数产生的问题:
a. 编译器实施静态绑定,在删除指向派生类的指针时只会调用基类的构造函数,派生类的析构函数会无法调用,析构不完全导致内存泄漏;
构造函数不是虚函数的原因:
a. 创建一个对象的时候需要确定其类型,而虚函数是在运行的时候确定其类型的;而构造一个对象的时候,由于对象还没有创建成功,编译器无法知道对象的类型;
b. 虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数申明为虚函数,由于对象还没有创建,没有为其分配内存空间,无法存储虚函数的指针;
6. “多态”
"一种接口,多个方法"。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
C++ 实现多态的方法:虚函数,抽象类,覆盖,模板(重载和多态无关)。
7. “虚函数”
基类中冠以关键字"virtual"的成员函数,提供一种接口,允许在派生类中对基类的虚函数重新定义。
8. “纯虚函数”
在基类中仅为其派生类保留一个函数名字,作为接口存在,不具备函数的功能,一般不能被直接调用。
从基类继承来的纯虚函数,在派生类中仍然是虚函数。
-“抽象类”:若一个类至少有一个纯虚函数,则这个类就叫抽象类,抽象类用于派生其他类,不可直接创建实例。
9. 虚函数的实现
a. 每个含虚函数的类都有一个与之对应的虚函数表,其内部存放着该类所有虚函数对应的函数指针(地址);
b. 类的实例不包含虚函数表,只有虚指针;
c. 派生类会生成一个兼容基类的虚函数表;
10.引用和指针的区别
引用:给变量的别名
指针:指向变量的地址
a.引用不占用内存单元,也无法给其分配内存空间;指针占用一定的内存单元;
b.不存在指向空值的引用;但存在指向空值的指针;
b.引用初始化后不能再改变;指针初始化后可以改变其指向的地址;
d.引用只有一级;指针可以有多级;
e.引用传递参数相当于对实参直接操作,不需要额外分配内存;指针传递参数属于值传递,指针本身的值不会修改,需要从栈中分配内存给参数。
11. 数组和指针的区别
a. 数组只能在静态存储区 / 栈中被创建,而指针可以随时指向任意类型的内存块;
b. 用sizeof可以计算出数组的容量(字节数),却只能计算出指针自身所占内存大小;
12. struct /class 的区别
不申明的话struct默认的变量是public的;class默认是private的
13. struct / union 的区别
a. union所有成员共用一段内存,其大小等于其中最大元素所占内存;struct每个成员各自占用一段内存,其大小等于各个成员占用内存大小之和;
b. 对union的成员赋值会自动覆盖其其他元素的值;对struct成员赋值不会影响其他成员的值;
14. bool, int, float, 指针类型的变量分别与“零”比较
bool: if(a) / if(!a)
int: if (0 == a)
float: const EXPRESSION EXP = 0.000001;
if ( a < EXP && a >- EXP )
pointer: if ( a == NULL) / if ( a != NULL)
15. 常引用: const 类型标识符 &引用名 = 目标变量名;
既利用引用提高了效率,又保护了传递给函数的数据不被函数改变。
int a = 5;
const int &ra = a;
a = 3; // right
ra = 3; // wrong,不可以给常引用赋值
16. 引用作为对象的返回值好处及注意事项
好处:在内存中不产生被返回值的副本
注意:
a. 不可以返回局部变量的引用(生存周期和局部变量一致,局部变量被回收后其引用也不存在了)
b. 不可以返回函数内部new分配的内存的引用(若返回值仅作为临时变量出现,则new分配的内存无法回收)
c. 可以返回类成员的引用,但最好加上const
17. overload / override 的区别
overload: 重载,允许存在多个同名函数,其各自的参数列表不同(参数个数或类型);
编译器根据函数不同的参数表对同名函数的名称做修饰,就成了不同的函数。
override: 覆盖,子类重新定义父类虚函数的方法;
当子类重新定义了父类的虚函数后,父类指针根据赋给他的不同的子类指针,动态调用属于子类的虚函数。
重载属于“早绑定”,编译期间就已经确定函数的地址了。
覆盖输入“晚绑定”,编译期间无法确定调用子类的虚函数的地址。
18. override 和 overwrite 的区别
override:覆盖,派生类覆盖基类的虚函数,实现接口重用;
特征是基类和派生类中的函数名、参数相同,基类必须有virtual关键字;
overwrite:重写,派生类屏蔽了与其同名的基类函数;
特征是基类和派生类中的函数名相同,参数不同或者基类中没有virtual关键字;
19. 只能使用参数初始化列表的情况:当类中含有const、reference成员变量时,以及基类的构造函数。
20. main函数执行前会执行的代码:全局对象的构造函数;
21.内存分配的几种方式和区别
a. 静态存储区分配:全局变量、static静态变量
内存在编译期间就已经分配好的,并且在整个程序运行期间都存在;
b. 从栈上创建:函数执行时,其值传递的参数和局部变量从栈上创建,函数执行后被自动释放(编译器自动执行),栈内存分配 运算内置于处理器指令集
c. 从堆上分配:又叫动态内存分配,程序运行时用malloc / new 申请内存,使用free / delete释放内存,动态内存的生命周期由程 序员决定;频繁分配释放会使内存不连续、产生碎片;
栈的生长空间向下,地址越来越小;堆的生长空间向上,地址越来越大;
22. const 和 #define 的区别
const作用:定义变量、修饰函数参数、修饰函数返回值;被修饰的对象都被强制保护,防止意外更改,提升程序鲁棒性;
const修饰变量(成员变量),则为常量不可以修改;const修饰成员函数,则该函数不可以修改类中数据成员,不会调用其他非const成员函数;
a. const常量的有数据类型,#define没有;编译器会对const修饰的常量进行类型安全检查,对宏常量只进行字符替换;
b. #define 可以定义简单的函数,const不可以;const常量可以被调试,宏常量不可以;
23. int(*s[10])(int) 表示函数指针数组,每个指针指向一个int func(int params) 的函数。
24. char str1[] = "abc"; char str2[] = "abc";
str1 == str2 : false ==> 分别指向各自的内存;
char *str3 = "abc"; char str4* = "abc";
str3 == str4 : true ==> 指针变量,指向相同的常量区;
25. 将程序跳转至指定的内存运行:
(void(*)())0x100000: 将0x100000强制转换为函数指针;
*((void(*)())0x100000)(): 取其值;
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000);
26. 全局变量、局部变量的区别及如何实现:
a. 生命周期不同:全局变量的生命周期和主程序一致,局部变量在局部函数内部、甚至再局部循环体内部,推出循环就不存在了;
b. 使用方式不同:通过申明后,全局变量可以再程序的各个部分被使用;局部变量只能被局部使用;
c. 分配内存的位置不同:全局变量分配在静态存储区的全局数据段上;局部变量分配在堆栈中;操作系统和编译器通过检查内存分配的位置区分全局变量和局部变量;
27. STL: Standard Template Library 包括容器和算法
a. 容器:存放数据的地方,分为序列式容器和关联式容器;
序列式容器:其内部元素不一定有序,但可以被排序;e.g vector / list / queue / heap / priority-queue;
关联式容器:内部为平衡二叉树,每个元素都有键值和值(key/value) e.g map / set / hashtable / hash_set;
b. 算法:排序 / 复制 及各个容器特定的算法;
c. STL的迭代器提供了一种方法,使其可以按顺序访问容器元素又不会暴露其内部的结构;
28. static作用 / 与const的区别
a. 函数体内:static修饰的局部变量的作用范围为该函数体,其内存只被分配一次,因此其值在下次调用的时候维持了上一次的值;
b. 模块内:static修饰全局变量 / 全局函数,可以被模块内所有函数访问,而不可以被模块外的其他函数访问,适用范围限制在起申明的模块内部;
c. 类内:修饰成员变量,表明该变量为整个类所有,对类的所有对象只有一份拷贝;
d. 类内:修饰成员函数,表明该函数为整个类所有,不接受this指针,只能访问类中的static成员变量;
const 强调值不可修改;static强调对所有类的
29. STL map / set 原理
底层通过红黑树实现,是一种特殊的二叉查找树,接近平衡;
a. 每个节点是黑色或者红色的;
b. 根节点为黑色,叶子节点为黑色(NULL);
c. 若一个节点为红色,则其全部子节点为黑色;
d. 从一个节点到该节点的子孙节点的所有路径包含数目相同的黑色节点;
30. “内存泄漏”, 面对内存泄漏和指针越界的措施方法:
动态分配所开辟的空间,在使用完毕后未手动释放,导致一直占用该内存,即为内存泄露;
原因:
a. 内存的构造函数和析构函数中的new和delete没有配套;
b. 释放对象数组时没有使用delete[], 而是使用了delete;
方法:malloc / free 要配套,对指针赋值的时候应该注意指针是否需要释放;使用的时候记得指针的长度,防止越界;
31. 定义和申明的区别
申明是告诉编译器变量的类型和名称,不会为变量分配内存空间;
定义需要分配内存空间,同一个变量可以被申明多次,但是只能被定义一次;
32. C++文件编译与执行的四个阶段:
a. 预处理:根据文件中的预处理指令来修改源文件中的内容;
b. 编译:编译成汇编代码;
c. 汇编:把汇编代码翻译成目标机器指令;
d. 链接:连接目标代码生成可执行程序;
33. STL中unordered map 和 map 的区别:
map 是STL的一个关联容器,提供键值对的数据管理,底层通过红黑树实现,实际上是二叉排序树和非严格的二叉平衡树,所以map内部是有序的;
unordered map 和 map 类似,也是存储键值对,可以通过key快速索引到value,但unordered map不会根据key进行排序;unordered map 是一个冗余的哈希表,存储时根据key的hash值判断元素是否相同;
34. C++ 内存管理
C++内存被分为五个区:栈、堆、自由存储区、全局 / 静态存储区、常量区;
栈:存放函数参数和局部变量,编译器自动分配和回收;
堆:new关键字动态分配的内存,有程序员手动进行释放,否则程序结束后系统自动释放;
自由存储区:有malloc分配的内存,和堆相似,由对应的free释放;
全局/静态存储区:存放全局变量和静态变量;
常量区:存放常量,不允许被修改;
35. 静态绑定和动态绑定
是C++多态的一种特性
静态类型:对象在申明时采用的数据类型,在编译时确定;
动态类型:当前对象所指向的类型,在运行期间决定,对象的动态类型可变,静态类型不可变;
静态绑定:绑定的对象是静态对象,函数依赖于对象的静态类型,在编译时确定;
动态绑定:绑定的对象是动态对象,函数依赖于对象的动态类型,在运行时确定;
虚函数使用动态绑定;
36. 引用是否能实现动态绑定?
可以。因为引用(或指针)既可以指向基类对象又可以指向派生类对象;用引用调用的虚函数在运行时确定,是引用(或指针)所指的对象的实际类型所定义的;
37. 深拷贝和浅拷贝
如果一个类拥有资源,当其发生复制过程时,如果资源重新分配了就是深拷贝;反之就是浅拷贝;
38. 调用拷贝构造函数的情况(三种)
a. 用类的一个对象去初始化类的另一个对象时;
b. 当函数的参数是类的对象的时候,即值传递的时候;如果是引用传递则不会调用;
c. 当函数的返回值是类的对象或引用的时候;
系统自动生成构造函数的情况:未定义类的普通构造函数; 拷贝构造函数;
39. C++四种强制转换
类型转换机制分为隐式类型转换和显示类型转换(强制类型转换);
a. static_cast:编译时期的静态类型检查 static_cast<type>(expression)
b. dynamic_cast:基类指针/引用安全向下转换到派生类指针/引用,提供运行时的类型检查;不能转换则返回NULL,基类中必须有虚函数,保证多态; dynamic_cast<source>(expression)
c. const_cast:去除const属性,转换为volatile属性’
d. reinterpret_cast:为了将一种数据类型转换为另一种数据类型;
40. 调试程序的方法
Windows下使用VS的debug功能,Linux下使用gdb,设置断点和监视器;
安装一些调试插件,例如VS里的ImageWatch;
41. extern "C" 作用:为了能够正确实现C++调用C语言代码;
42. typedef 和 #define 区别
#define 是预处理命令,预处理时进行简单的替换,不做类型检查;
typedef是编译时处理的,在自己的作用域内给已经存在的类型一个别名;
43. volatile的作用:解决变量在“共享”环境下容易出现读取错误的问题;
44. 野指针及其成因
野指针不是NULL指针,是未初始化或未清零的指针,它指向的内存地址不是程序员所期望的,可能指向了受限制的内存;
成因:
a. 指针变量未被初始化;
b. 指针指向的内存被释放了,但是指针没有置为NULL;
c. 指针超过了变量的作用范围;
45. 线程安全和线程不安全
线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时进行保护,其他线程不能进行访问知道该进程访问结束;
线程不安全:不提供数据访问保护,有可能多个线程先后更改数据所得到的数据就是脏数据;
46. 栈溢出的原因和方法
原因:
a. 函数调用层次过深,每调用一次函数参数、局部变量信息时就压一次栈;
b. 局部变量体积过大;
解决方法:
a. 增加栈内存的数目;
b. 使用堆内存;如把数组定义改为指针,动态申请内存;也可以把局部变量变成全局变量或静态变量(statci)
47. C++11 新特性
auto:自动类型推导;
decltype:从一个变量或表达式中得到类型;
nullptr:解决原来C++中NULL二义性问题,因为NULL实际代表0;
lambda表达式:定义创建匿名函数;
扩展了STL,并入了TR1(Technical Report 1)程序库;
48. lambda表达式
sort(v.begin(), v.end(), [](int a, int b) -> bool { return a < b; });
[capture list] (params list) mutable exception-> return type { function body }
- capture list:捕获外部变量列表
- params list:形参列表
- mutable指示符:用来说用是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
49. vector 和 list 区别
vector和数组都拥有连续内存空间,vector 内存不够时会以2倍的的内存大小重新申请一块更大的内存,将原数据拷贝过去并释放旧空间;很好的支持随机存取,vector<int>::iterator 支持 + / += / < 等操作符;
list 是由双向链表实现,内存空间不连续,只能通过指针访问,随机存取效率低,但插入删除效率高;
50. C语言函数调用过程
a. 从栈空间分配存储空间;
b. 从实参的存储空间复制值到形参栈空间;
c. 运算;
51. 友元类和友元函数 friend
友元提供了不同类的成员函数、类的成员函数和一般函数之间的数据共享机制;
通过友元一个不同的函数或另一个不同类的成员函数可以访问类中私有和保护成员;
提高了程序运行效率,同时也破化了类的封装性和数据的隐藏性,导致程序的可维护性变差;
友元函数:可以访问类的私有成员的非成员函数,是定义在类外的普通函数,不属于任何类,但需要在类的定义中申明;
友元类:友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类的私有成员和保护成员;
注意:
a. 友元关系不可以被继承;
b. 友元关系是单向的;
c. 友元关系不具有传递性;
52. C++线程的几种锁机制
a. 互斥锁:用于控制多个线程互斥访问共享资源;
b. 条件锁:条件变量,线程因为某个条件满足时可以使用条件变量使该程序处于阻塞状态;
c. 自旋锁:发生阻塞时,让CPU不断循环请求这个锁;
d. 读写锁:“读者/写者”问题的拓展;
e. 递归锁