文章目录
- 基本语言
- 1. static关键字的作用
- 2. C++和C的区别
- 5. c++四种cast转换
- 6. C/C++指针和引用的区别
- 8. C++的四个智能指针
- 11. 野指针
- 13. 智能指针是否存在内存泄漏
- 17. 析构函数是否必须是虚函数?C++默认的析构函数为什么可以不是虚函数?
- 18. 函数指针
- 20. C++中的析构函数
- 21. 静态多态、动态多态
- 22. 重载和覆盖
- 25. 虚函数的实现
- 26. 举例:在main函数执行前先运行的函数
- 30. shared_ptr的实现
- 33. const修饰成员函数
- 35. 隐式类型转换
- 37. C++函数栈空间最大值:1M,可以调整
- 38. extern "C":调用C函数,因为C函数没有重载
- 40. RTTI:运行时类型检查
- 41. 虚函数表具体是怎样实现运行时多态
- 42. C语言是怎么进行函数调用的?
- 43. C语言参数压栈顺序:右到左
- 44. C++如何处理返回值:生成临时变量把它的引用作为函数参数传入函数内
- 45. C++中拷贝赋值函数的形参能否进行按值传递?
- 容器和算法
- 类和数据抽象
- 面向对象与泛型编程
- 编译与底层
- C++11
- 附录
基本语言
1. static关键字的作用
- 全局静态变量:静态存储区,整个程序运行期间都在,未经初始化的全局静态变量会被自动初始化为0,作用域:全局静态变量在声明他的文件之外是不可见的,从定义开始,到文件结尾结束.
- 局部静态变量:静态存储区,同上,作用域:仍为局部作用域,但是离开作用域后并没有销毁。
- 静态函数:函数的定义和声明默认情况下都是extern的,但是静态函数只是在声明他的文件中可见,不能被其他文件所用。函数的实现使用static修饰,那么这个函数只能在本cpp内使用,不会同其他cpp中同名函数冲突,不要在头文件中声明static全局函数,不要在cpp内声明非static的全局函数,若要在多个cpp中复用该函数,则要把它的声明提到头文件中,否则cpp内部声明需要加上static修饰.
- 类的静态成员:在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态成员不会破坏影藏。对于多个对象来说,静态数据成员只存储一处,供所有对象共用.
- 类的静态函数:静态成员函数和静态数据成员一样,静态成员函数中不能直接引用类中的非静态成员,只能引用类中的静态成员.
2. C++和C的区别
- 设计思想上:C++是面向对象的语言,C是面向过程的结构化编程语言
- 语法:C++具有重载,继承,多态三种特性,C++相比C,增加许多类型安全的功能,必须强制类型转换,C++支持泛型编程,模板类、函数模板
5. c++四种cast转换
c++中四种cast:static_cast,dynamic_cast,const_cast,reinterpret_cast
-
const_cast:将const常量转变为非const
-
static_cast:用于各种隐式转换,必须非const转const,void*转指针,static_cast能用于多态向上转化,但是向下转化是不安全的
-
dynamic_cast:用于动态类型转换,只能用于含有虚函数的类,用于类层次间的向上和向下转化,只能转指针和引用.
- 向上转换:子类向基类的转换
- 向下转换:基类向子类的转换,判断在执行该语句时变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换
-
reinterpret_cast:几乎所有的都可转,int转指针,可能会出问题,因此尽量少用
-
C++不用C的强制类型转换:C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易错误
6. C/C++指针和引用的区别
- 指针有自己的一块空间,引用只是别名
- 使用sizeof看一个指针大小是4/8,而引用则是被引用对象的大小
- 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,而对引用的修改会直接改变引用所指的对象
- 可以有const指针,但是没有const引用
- 指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能被改变;
- 指针可以多级指针(**p),而引用只能一级
- 指针和引用使用++运算符的意义不一样
- 返回动态内存分配的对象或者内存需要使用指针
8. C++的四个智能指针
- 为什么要用智能指针:申请的空间在函数结束时忘记释放,会造成内存泄漏。智能指针是一个类,当超出类的作用域时,类会自动调用析构函数,析构函数会自动释放资源,智能指针的作用原理是在函数结束时,自动释放内存空间
- auto_ptr:c++98方案,c++11弃用,采用所有权模式
- unique_ptr:替换auto_ptr,实现独占式拥有或者严格拥有,保证在同一个时间内只有一个智能指针可以指向该对象
- shared_ptr: 实现共享式拥有,多个智能指针指向同一个对象,采用计数的机制表明资源被几个指针共享
- weak_ptr:是一种不控制对象生命周期的智能指针,指向一个shared_ptr管理的对象,进行对象的内存管理的是强引用的shared_ptr,weak_ptr只是提供了对管理对象的一种访问
11. 野指针
指向一个已经删除的对象或者未申请访问受限内存的指针
13. 智能指针是否存在内存泄漏
- 循环引用:当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,引用计数失效,导致内存泄漏.
- 解决方案:引入weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,不会对对象内存进行管理(类似一个普通指针),但是可以检测所管理的对象是否已经被释放,从而避免非法访问
17. 析构函数是否必须是虚函数?C++默认的析构函数为什么可以不是虚函数?
- 将来可能被继承的父类的构造函数设置为虚函数,这样可以保证new一个子类,然后使用基类指针指向子类对象,释放基类指针的时候可以释放子类的空间,防止内存泄漏
- C++默认的析构函数不是虚函数,是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,这样会浪费内存。因此C++默认的析构函数不是虚函数,只有当需要当做父类时,设置为虚函数。
18. 函数指针
- 概念:C在编译时,每个函数都有一个入口地址,入口地址就是函数指针所指向的地址。
- 用处:回调函数里会用到
- 形式:char* (*func)(char *p),定义一个函数指针func
20. C++中的析构函数
类析构顺序:派生类本身的析构函数->对象成员析构函数->基类析构函数
21. 静态多态、动态多态
静态多态通过重载和模板实现,在编译时确定,动态多态通过虚函数和继承实现,动态绑定,在运行时确定。其中虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销
22. 重载和覆盖
- 重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域内
- 重写:子类继承了父类,父类中的函数是虚函数,子类重新定义了这个虚函数
25. 虚函数的实现
在虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向虚函数表,表中存放着虚函数的地址,实际的虚函数在代码段(.text)中,当子类继承父类的时也会继承它的虚函数表。当子类重写父类中虚函数时,会将其继承到的虚函数表中的地址替换为重新写的函数地址。虚函数会增加访问内存的开销,降低效率。
26. 举例:在main函数执行前先运行的函数
__attribute((constructor))void before()
{
...
}
30. shared_ptr的实现
核心是引用计数,什么时候销毁底层指针,赋值,
33. const修饰成员函数
函数调用不会对对象做任何更改
35. 隐式类型转换
- 内置类型,低精度向高精度转换
- 单个参数的构造函数的对象构造,函数调用直接使用该参数传入,编译器自动调用构造函数生成临时对象
37. C++函数栈空间最大值:1M,可以调整
38. extern “C”:调用C函数,因为C函数没有重载
40. RTTI:运行时类型检查
在C++层面主要体现在dynamic_cast和typeid,vs中虚函数表的-1位置存放了指向type_info的指针,对于存在虚函数的类型,typeid和dynamic_cast都会查询type_info
41. 虚函数表具体是怎样实现运行时多态
子类若重写父类虚函数,虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在VS中,对象的对象模型的头部存放指向虚函数表的指针。
42. C语言是怎么进行函数调用的?
每个函数调用都会分配函数栈,在栈内进行函数执行过程,调用前,先把返回地址压栈,然后把当前函数的esp指针压栈.
43. C语言参数压栈顺序:右到左
44. C++如何处理返回值:生成临时变量把它的引用作为函数参数传入函数内
45. C++中拷贝赋值函数的形参能否进行按值传递?
不能。如果这样的话,调用拷贝构造函数的时候,首先要将实参传递给形参,传递的时候,又要调用拷贝构造函数,如此往复,无法完成拷贝,爆栈.
容器和算法
1. map和set的区别,又怎么实现?
由RB-tree实现
2. STL的allocator
stl分配器封装stl容器在内存管理上的底层细节,内存配置和释放如下:
- new 运算分两个阶段:调用::operator new配置内存,调用对象构造函数构造对象内容
- delete 运算分两个阶段:调用对象析构函数,调用::operator delete释放内存
为了精密分工,stl allocator将两个阶段操作区分开来,内存配置有alloc::allocate()负责,内存释放由alloc::deallocate负责;对象构造由::construct()负责,对象析构由::destroy负责,为了提高内存管理效率,sgi stl采用两级配置器,第一级空间配置器:malloc(),realloc(),free()函数进行内存空间的分配和释放,第二级空间配置器采用内存池技术,通过空闲链表管理内存.
3. stl基本组成
容器,迭代器,仿函数,算法,分配器,配接器,关系:分配器给容器分配存储空间,算法通过迭代器获取容器中的内容,仿函数协助算法完成各种操作,配接器用来套接适配仿函数.
8. stl迭代器的作用,指针?
- 迭代器模式又称游标模式,用于提供一种方法顺序访问聚合对象中各个元素,又不需要暴露该对象的内部表示。仅限于底层聚合支持类:stl的list,vector,stack
- 迭代器与指针的区别:迭代器重载了指针的操作符(->,*,++,–)
类和数据抽象
1. C++类成员的访问权限
public,protected,private:
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected 成员可以被派生类访问。
2. c++ struct与class区别
struct的默认继承权限和默认访问权限是public,而class是private,class定义模板类参数: template <class T, int i>
3. C++类定义引用成员必须通过成员函数初始化列表初始化
面向对象与泛型编程
1. 右值引用,左值
右值引用是c++11中引入的新特性,实现语义传递和精确传递。目的:
-
消除两个交互时不必要的对象拷贝,节省计算存储资源;
-
能够更简洁明确地定义泛型函数
-
左值:能对表达式取地址,表达式结束后仍然持久对象
-
右值:不能…
左值/右值区别:
- 左值可寻址,右值不行
- 左值可以被赋值,右值不可以被赋值
- 左值可变,右值不可变
编译与底层
1. C++源文件编译过程
- 预处理阶段:对源文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件
- 编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
- 汇编阶段:将编译阶段生成的汇编文件转化为机器码,生成可重定向目标文件
- 链接阶段:将多个目标文件及所需要的库链接成最终的可执行目标文件.
2. include头文件的顺序以及双引号""和<>的区别
""查找路径:
- 当前头文件目录
- 编译器设置的头文件路径(编译器可以使用-I显示指定搜索路径)
- 系统变量:CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定头文件路径
<>查找路径:
- 编译器设置的头文件路径(-I显式设置)
- 系统变量:CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定头文件路径
3. malloc原理,brk系统调用,mmap系统调用
malloc:为减少内存碎片和系统调用的开销,malloc采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。malloc采用隐式链表将堆区分成连续、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构管理所有空闲块,即使用一个双向链表将空闲块连接起来,每个空闲块记录一个连续未分配的地址。
当进行内存分配时,malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配,当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。
malloc在申请内存,当申请内存小于128K,使用brk在堆区分配,否则,用mmap在映射区分配。
4. C++ 内存管理
c++中,虚拟内存分为代码段(.text),数据段(.data),.bss段,堆区,文件映射区,栈区
- 代码段:只读存储区,文本区。只读存储区存储字符串常量,文本区存储程序的机器码
- 数据段:已经初始化的全局变量和静态变量
- bss段:未初始化的全局变量+静态变量
- 堆+栈
- 映射区:存储动态链接库以及调用mmap函数进行文件映射
5. 段错误
访问非法地址、使用野指针、修改字符串常用内容
6. 内存泄漏
堆内存泄漏、系统资源内存泄漏(bitmap,handle,SOCKET等系统资源),没有将基类的析构函数设置为虚函数
11. reactor模型组成
reactor模型要求主线程只负责监听文件描述符上是否有事件发生,有的话就立即将该线程通知工作线程,主线程不做其他工作,读写数据、接收新连接和处理客户请求均由工作线程完成
12. 单线程方式处理高并发:IO复用异步回调
在单线程模型中,采用IO复用来提高单线程处理多个请求的能力,然后在事件驱动中基于异步回调处理事件.
14. select,epoll区别,原理,性能,限制
- IO多路复用:IO复用模型在阻塞IO模型上多了一个select函数,select函数有一个参数是文件描述符集合,即对这些文件描述符进行监听,当某个文件描述符就绪之后,对它进行处理;IO多路复用(select,poll,epoll),select/epoll的好处在于单个process就可以同时处理多个网络连接的IO,即不停轮询所负责的所有的socket,当某个socket数据到达时候就通知用户进程.
C++11
1. C++11最常用的新特性
- auto:编译器可以根据初始值自动推导类型
- nullptr:特殊类型的字面值,而NULL一般宏定义为0,在遇到重载时可能出问题
- 智能指针
- 初始化列表:按照定义的顺序进行初始化
- 右值引用:基于右值引用可以实现移动语义和完美转发,消除对象交互时不必要的对象拷贝
- atomic:原子操作,用于多线程资源互斥
- 新增stl容器以及tuple
2. C++11的可变参数模板,右值引用,lambda表达式
- 可变参数模板
暂略
附录
1. C++ 类默认的8个成员函数(C++11)
class A {
public:
// 默认构造函数
A();
// 默认拷贝构造函数
A(const A&);
// 默认析构函数
~A();
// 默认重载赋值运算符函数
A& operator = (const A&);
// 默认重载取地址运算符函数
A* operator & ();
// 默认重载取地址运算符const函数
const *A operator & () const;
// 默认移动构造函数
A(A&&);
// 默认重载移动赋值操作符
A& operator = (const A&&);
};
贝构造函数
A(const A&);
// 默认析构函数
~A();
// 默认重载赋值运算符函数
A& operator = (const A&);
// 默认重载取地址运算符函数
A* operator & ();
// 默认重载取地址运算符const函数
const *A operator & () const;
// 默认移动构造函数
A(A&&);
// 默认重载移动赋值操作符
A& operator = (const A&&);
};