- 基础知识
- 面向对象
- STL
- 多线程
- 三大特征:封装,继承,多态。
- 为什么要面向对象?C语言是结构化设计,无法解决重用,扩展和维护的问题。
- 封装:将数据和操作数据的方法封装到一起。
- 继承:直接继承已有类的属性和方法,减少代码重复。
- 多态:对相同的方法做出不同的响应,提高代码灵活性。
- 构造与析构
- 拷贝构造与拷贝赋值
- 移动构造与移动赋值
- malloc只分配内存不初始化;new不仅分配内存也初始化,new分配内存以后自动调用构造函数。
- malloc分配内存时必须指定内存大小,而new可以自动计算。malloc分配完成后返回的是void*类型,需要强转,而new返回的是对应类型的指针。
- malloc分配内存失败时返回NULL,而new分配内存失败时抛出bad_alloc异常。
- 多态分类两大类:静态多态和动态多态。
- 静态多态是重载和模板。动态多态:也叫运行时多态,是通过继承和虚函数实现的。
- 在具有继承关系的子类中,子类重写父类的虚函数,通过父类引用或指针指向子类对象时,产生不同的行为叫做多态。
- 多态的核心在于虚函数表指针,每个对象都有一个虚函数表指针,虚函数表指针指向一张虚函数表,表中记录了虚函数的入口地址,如果子类重写虚函数后,这个地址就会替换掉。
- 多态的好处:在于更方便程序的扩展。
- 坏处在于每个对象多了一个4字节的指针,同时每次查询虚函数表需要耗时。
- unique_ptr独享指针的所有权,无法进行拷贝构造赋值的操作,只能通过move函数进行所有权的转换。
- shared_ptr共享对象,它使用引用计数来保存当前有多少个智能指针在引用这个对象,当引用计数降为0时,对象会被销毁。
- weak_ptr称为弱引用,用于辅助shared_ptr正常工作,主要解决shared_ptr可能会产生的环形引用问题。weak_ptr不会增加对象的引用计数,共享指针可以直接赋值给弱指针,同时弱指针可以使用lock函数来获取shared_ptr对象。
- static的使用可以分为两类,一类是用在普通变量和函数上,另一类是用在类中。
- 普通变量分为全局变量和局部变量。声明为静态全局变量是在全局区分配内存,并且只在当前文件可见,在文件之外是不可见的。其他文件定义同名变量不会发生冲突。变量的值只在第一次执行时进行初始化。声明为静态局部变量时与全局变量类似,只是作用域为局部作用域。
- 静态普通函数,只在当前文件中可见,其他文件中定义同名函数不会发生冲突。
- static用在类中,首先是静态成员变量,在类中声明,类外初始化。所有对象共享一份数据。
- 然后是静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量。
- 指针保存的是所指对象的地址,而引用是所指对象的别名。指针需要通过解引用间接访问对象的值,引用可以直接访问。
- 指针可以有多级指针,而引用最多两级。并且两个取地址符是右值引用。右值引用是为了减少深拷贝的次数。
- 指针可以不初始化,即使初始化以后也可以改变。而引用必须初始化,同时初始化以后不许改变。
- 引用的本质是指针常量。指针常量不可以修改指向,但是可以修改指向的值。常量指针刚好与之相反。
- 首先,vector的基类是三根指针,分别是start/finish/end_of_storage用来指示当前分配到的空间所用的起始位置,终止位置和容量尾部。然后,当finish指针到达end_of_storage的位置时,操作系统会寻找当前容量大小2倍的连续内存空间,并且将旧内存中的数据拷贝到新内存,然后释放旧内存。其次,如果重新分配了内存,原来的迭代器就会失效。频繁的开辟新内存比较耗时。如果可以预知使用的大小,可以使用reserve函数,预先开辟足够大的空间。或者使用swap函数收缩内存空间。
int* const p = &a
- 指针常量必须初始化,一旦初始化完成,就不能再修改它的值,即指针的指向不可变。
- 引用的本质是指针常量
- 声明是告诉编译器有这个变量和函数的存在,但是需要到其它地方去寻找。
- 定义包含了声明,但是声明不包含定义,定义时才分配存储空间。
- 共同点:C++中,可以用struct和class定义类,都可以继承。
- 不同点:struct默认继承权限和默认访问权限时public class类的默认继承权限和访问权限时private。
- const可以用于限定变量,指针和函数不可改变,同时明确指定了类型,可以方便编译器做类型检查,也增加了代码的可读性。
- const修饰变量必须初始化。如果是全局的const变量,通常放在静态区。在局部声明的const变量放在栈区。
- const修饰成员函数时,函数中的成员变量不可改变,除非该变量特别声明为mutable
- const可以用来修饰指针,称为常量指针const int *p 指针的指向可以改变,但是不能改变指针指向的值。
- const修饰常量的指针叫做指针常量,int* const p 指针的指向不可以修改,指针指向的值可以修改。指针常量必须初始化。
- const可以明确指定数据类型,而宏定义没有数据类型。
- define宏是在预处理阶段展开,const常量是在编译运行阶段使用。
- define宏不分配内存,变量定义分配内存。
- 引入同一模块在其他文件中定义的全局变量和函数。
- 如果在C++里调用了C库定义函数,那么需要使用
extern "C"
标识这个函数,告诉编译器使用C的方式进行编译,防止C++的编译方式导致命名重整,无法找到对应的C函数。命名重整的原因在于C++支持函数重载,而C不支持,所以C++编译时增加了函数参数的标识符。
- 解决同名冲突
- 返回对象本身
- this指针的本质是指针常量,指针的指向不可以修改。
- 将左值强制转换为右值引用,右值引用可以减少一次对象的析构和对象的构造。
- 右值引用可以减少深拷贝的次数。
- 段错误通常发生在访问非法内存地址的时候。系统会发送一个SIGSEGV11号信号告诉当前进程,进程采取默认的捕获方式,即终止进程。
- 野指针
- 试图修改字符串常量的内容
- 让编译器能够根据初始值的类型推断变量的类型。当处理复杂类型,比如STL中的类型时,优势最明显。
auto p = vt.begin()
- static_cast低风险的转换,比如整数转浮点数,字符型转整形。
- const_cast去掉const关键字的转换,可以去掉带const的指针和引用。
- dynamic_cast使具有继承关系的基类转换为派生类,如果不可以转换则返回NULL。
- reinterpret_cast指针或引用的转换,风险较高。
- 运行时类型识别
- run time type identification
- 常常结合typeid()和dynamic_cast实现。可以根据当前调用的指针是何种类型,经过dynamic_cast转换后,调用非虚函数。
- dynamic_cast只能用于指针和引用的转换,要转换的类型中必须包含虚函数,转换成功返回子类的地址,失败返回NULL。
- typeid返回一个type_info对象的引用。
- 虚函数是通过虚函数表指针来调用的,而虚函数表指针存在对象内存空间。当一个对象调用构造函数时,该对象还没有实例化,即没有分配内存空间,所以虚函数表指针无法找到。
- 析构函数不是虚函数容易引起内存泄漏。
Animal *animal = new Cat();
- 为了实现多态的动态绑定,通常将基类指针指向派生类对象。
- 当指针销毁时,如果析构函数不是虚函数,根据析构函数在继承中的调用顺序,则派生类对象的析构函数将不会被执行,造成内存泄漏。
- 析构函数抛异常,则异常点之后的的程序不会执行,如果异常点之后有释放资源的操作,则这部分资源无法释放,导致内存泄漏。noexcept
- 不再需要使用的内存单元,没有及时释放。
- memcheck和valgrind检测内存泄漏的工具。
- 使用RAII资源获取就是初始化和智能指针。
- 一些内存的单元已被释放,之前指向它的指针还在被使用。
- vector是动态数组,在内存中分配一块连续的内存空间,因此可以使用下标进行快速的随机访问。但是删除和插入需要移动大量的元素。
- list是双向链表,在内存中是不连续的空间,由指针将不同的地址连接在一起。list的插入和删除操作都是O(1)的。
- 数组必须事先设定固定的长度,不能动态的增减,可能会造成资源浪费。链表可以动态的增减。
- 全特化:模板参数被指定未确定的类型
- 偏特化:模板参数没有被全部确定,需要编译器在编译时进行确定。只能偏特化类模板,不能偏特化函数模板。
- 别名模板和变量模板属于语法糖
- 右值引用指向要被销毁的对象。右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
- move函数将左值转换为右值,调用move函数后源对象只能赋值或销毁。
- override在子类中标记某个函数,表示想要覆盖已有的虚函数,如果没有覆盖,编译器会报错。 加作用域运算符调用特定类的虚函数
- 优点:减少函数调用的开销,包括寄存器值的保存和实参的拷贝等。
- 缺点:增加函数体积,可能导致cache装不下,从而减少了cache的命中率。
- inline只是一个请求,编译器有权拒绝。
调用场景:
- 一个对象以值传递传参
- 一个对象以值传递的方式从函数返回
- 一个对象通过另一个对象初始化
- 占有一个字节
- 有构造,析构,拷贝,赋值运算符,取地址运算符。
- 构造函数可以被重载,析构函数不可以被重载且不能带参数。
-
explicit取消隐式转换,类中构造函数默认是implicit
-
explicit关键字的作用是防止类构造哈桑农户的隐式自动转换,只对有一个参数的构造函数有效。
- 栈连续,堆不一定连续。
- 申请方式不同。栈由操作系统自动分配,堆需要程序员自己申请。
- 生长方向不同。栈由高地址向地址生长,是一块连续的内存区域。堆由地址向高地址生长,是不连续的内存区域。在一个链表中记录空间内存地址。
- 分配速度。栈由系统分配,速度较快。堆使用new分配,速度较慢,且容易产生内部碎片。
- static可以用来修饰函数和变量。修饰全局变量和局部变量时都是放在静态区,static变量只初始化一次,在程序结束时销毁,全局和局部的区别在于作用域不同。static可以修饰普通成员函数,表明这个函数只在本文件中有效。static修饰类成员变量是,这些变量为这个类所共享,static修饰类成员函数时,也是所有对象共享这个函数,该函数中没有this指针。同时static类成员函数中只能调用static修饰的函数。
存放的static修饰的全局变量和局部变量,const修饰的变量以及字符串。
- 数据段存放的是代码的二进制指令。静态区是变量。