- static的使用可以分为两类,一类是用在普通变量和函数上,另一类是用在类中。
- 普通变量分为全局变量和局部变量。声明为静态全局变量是在全局区分配内存,并且只在当前文件可见,在文件之外是不可见的。其他文件定义同名变量不会发生冲突。变量的值只在第一次执行时进行初始化。声明为静态局部变量时与全局变量类似,只是作用域为局部作用域。
- 静态普通函数,只在当前文件中可见,其他文件中定义同名函数不会发生冲突。
- static用在类中,首先是静态成员变量,在类中声明,类外初始化。所有对象共享一份数据。
- 然后是静态成员函数:所有对象共享同一个函数,静态成员函数只能访问静态成员变量。
- 多态分类两大类:静态多态和动态多态。静态多态是重载和模板。动态多态:也叫运行时多态,是通过继承和虚函数实现的。在具有继承关系的子类中,子类重写父类的虚函数,通过父类引用或指针指向子类对象时,产生不同的行为叫做多态。
- 多态的核心在于虚函数表指针,每个对象都有一个虚函数表指针,虚函数表指针指向一张虚函数表,表中记录了虚函数的入口地址,如果子类重写虚函数后,这个地址就会替换掉。多态的好处在于更方便程序的扩展,坏处在于每个对象多了一个4字节的指针,同时每次查询虚函数表需要耗时。**
C++11有3个智能指针,分别是unique_ptr, shared_ptr和weak_ptr
- unique_ptr独享指针的所有权,无法进行拷贝构造赋值的操作,只能通过move函数进行所有权的转换。
- shared_ptr共享对象,它使用引用计数来保存当前有多少个智能指针在引用这个对象,当引用计数降为0时,对象会被销毁。
- weak_ptr称为弱引用,用于辅助shared_ptr正常工作,主要解决shared_ptr可能会产生的环形引用问题。weak_ptr不会增加对象的引用计数,共享指针可以直接赋值给弱指针,同时弱指针可以使用lock函数来获取shared_ptr对象。
- 有异常也能正确析构,因为是局部变量。
const int *
int* const
- 只能用来修饰类的构造函数,阻止构造函数进行隐式转换。
- malloc和new都是在堆上开辟内存,进行动态的管理。
- new是操作符,malloc是库函数
- malloc只负责开辟内存,没有初始化功能。new不但能开辟内存,还可以初始化。
- malloc必须指定开辟内存的大小,并且返回类型为void*,因此malloc的返回值一般都需要进行类型转换。new可以自动计算出所需内存的大小,并且返回指定类型的指针。
- malloc和new如果操作是内置数据类型两者基本类似,不同在于申请失败时。malloc申请失败时返回NULL。new申请失败抛出异常。
- 自定义类型时,new先调用operator new函数申请空间,然后在申请的空间上执行构造函数。
- 从高地址到低地址
- 环境变量和命令行参数
- 栈区
- 共享区
- 堆区
- 未初始化数据段.bss
- 初始化数据段.data
- 代码段.text
- 指针保存的是所指对象的地址,而引用是所指对象的别名。指针需要通过解引用间接访问对象的值,引用可以直接访问。
- 指针可以有多级指针,而引用最多两级。并且两个取地址符是右值引用。右值引用是为了减少深拷贝的次数。
- 指针可以不初始化,即使初始化以后也可以改变。而引用必须初始化,同时初始化以后不许改变。
- 引用的本质是指针常量。指针常量不可以修改指向,但是可以修改指向的值。常量指针刚好与之相反。
- 首先,vector的基类是三根指针,分别是start/finish/end_of_storage用来指示当前分配到的空间所用的起始位置,终止位置和容量尾部。然后,当finish指针到达end_of_storage的位置时,操作系统会寻找当前容量大小2倍的连续内存空间,并且将旧内存中的数据拷贝到新内存,然后释放旧内存。其次,如果重新分配了内存,原来的迭代器就会失效。频繁的开辟新内存比较耗时。如果可以预知使用的大小,可以使用reserve函数,预先开辟足够大的空间。或者使用swap函数收缩内存空间。
- 主要分为四个步骤
- 预编译阶段:对g++编译器指定-E参数,生成.i文件。这个阶段的主要工作是将所有的宏展开,去掉所有的条件预编译指令,将所有的头文件包含进来,删除注释等。
- 编译阶段:对g++编译器指定-S参数,生成.s汇编文件。这个阶段的主要工作是对代码的语法,语义和词法等进行分析。
- 汇编阶段: 对g++编译器指定-c参数,生成.o二进制文件。
- 链接阶段:将各个模块之间的相互引用处理好。把所有的静态库用到的目标文件装入程序中,并进行统一编址,然后进行重定位,即逻辑地址到物理地址的转换。
- 静态库:命名方式为lib开头加上自定义的静态库名,然后以.a结尾。静态库实际上是一组目标文件的集合,再链接阶段与调用的程序生成可执行文件。静态库的优点在于:**代码加载速度快,发布程序时,不需要提供对应的库;**缺点时:可执行文件体积大,**同时如果静态库有修改,调用的程序需要重新编译,**而编译的耗时比较久。
- 动态库:命名方式为lib开头加上自定义的动态库名,然后以.so结尾。动态库首先生成与位置无关的目标文件,然后再运行时加载到内存。优点是:动态库可以共享,节省了系统资源,动态库进行修改后,无需重新编译。缺点是加载速度比静态链接慢,发布程序时,需要提供动态库。
- windows
- 静态库:xxx.lib
- 动态库:yyy.ddl
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)的。
- 数组必须事先设定固定的长度,不能动态的增减,可能会造成资源浪费。链表可以动态的增减。
- 由于编译器默认的拷贝构造函数只是简单的位拷贝,可能会导致内存的重复释放。解决浅拷贝的办法通常使用深拷贝,即自己实现拷贝构造函数,在堆上重新分配内存。
- union最大成员所占的整数倍,同时能容纳其他的成员。union中变量共用内存,应以最长的为准。
- struct按照成员的声明顺序,依次安排内存,偏移量为成员大小的整数倍,最后结构体的大小为最大成员所占大小的整数倍。在C++中,空结构体和空类的内存所占大小为1个字节。C中空结构体所占大小为0。
- 为什么要有内存对齐:
- 硬件原因:加速CPU的访问速度。因为CPU和内存数据交换的基本单位是块,块的大小为2的n次方字节。内存未对齐可能需要多次访问内存。2. 平台原因:不是所有的平台都支持任意地址的数据访问。
#include <iostream>
using namespace std;
typedef union{
long long i; //8 bytes
int k[5]; //4 bytes 最长的成员不是20
char c; // 1 byte
}UDATE;
//联合体共用内存 最长成员为8字节 结果要为8的倍数 同时要能容纳其他成员,即大于等于20字节 所以为24字节
struct data{
int cat; // 4 bytes
UDATE cow; //24 bytes 但是需要先拆开来 最长成员为8字节
double dog; //8 bytes
}too;
//结构体顺序考虑,结果为最大成员的整数倍,如果后一个成员的长度的开始位置不是整数倍需要填充字节
//cat占4个字节 填充4个字节
//起始位置为8 满足整数倍 cow占用24字节
//起始位置为32 满足整数倍 doule占用4字节
//所以结构体总共占用40字节,同时40也是8的倍数。
UDATE temp;
int main(){
cout<<sizeof(temp)<<" "<< sizeof(struct data)<<endl; //24 40
return 0;
}
- 用来告诉编译器不要对该变量做任何优化,编译器每次操作该变量时,一定要从内存中取出,而不是使用寄存器中与存在的值,因为值可能已经发生了改变。
应用场景:
- 并行设备的硬件寄存器(如状态寄存器)。
- 多线程中共享变量。
- 主要作用:虚拟内存到物理内存的地址映射。 设置修改内存访问级别。
- 主存容量有限
- 分隔进程,保证进程空间彼此不受干扰
- 基于局部性原理进行页面替换
虚拟内存的大小由计算机的地址总线决定
cache名字和TLB命中没有必然联系,是两种独立的机制。
CPU和Cache之间交换的单位是字节,Cache和内存之间交换的单位是块。
-
gdb可以用于分析coredump文件。coredump文件含有进程被终止时内存/CPU寄存器和各种函数调用栈的信息。
-
产生coredump文件的原因:
- 内存访问越界
- 多线程使用了线程不安全的函数
- 多线程读写的数据未加锁保护
- 栈溢出
- core文件没有符号表信息,必须结合可执行文件才可调试
-
全特化:模板参数被指定未确定的类型
-
偏特化:模板参数没有被全部确定,需要编译器在编译时进行确定。只能偏特化类模板,不能偏特化函数模板。
-
别名模板和变量模板属于语法糖
- 在编译时计算出运行时需要的常数,类型和代码的方法。
-
右值引用指向要被销毁的对象。右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
-
move函数将左值转换为右值,调用move函数后源对象只能赋值或销毁。
override在子类中标记某个函数,表示想要覆盖已有的虚函数,如果没有覆盖,编译器会报错。
加作用域运算符调用特定类的虚函数
- 优点:减少函数调用的开销,包括寄存器值的保存和实参的拷贝等。
- 缺点:增加函数体积,可能导致cache装不下,从而减少了cache的命中率。
inline只是一个请求,编译器有权拒绝。
调用场景:
- 一个对象以值传递传参
- 一个对象以值传递的方式从函数返回
- 一个对象通过另一个对象初始化
-
占有一个字节
-
有构造,析构,拷贝,赋值运算符,取地址运算符。
-
构造函数可以被重载,析构函数不可以被重载且不能带参数。
-
explicit取消隐式转换,类中构造函数默认是implicit
-
explicit关键字的作用是防止类构造哈桑农户的隐式自动转换,只对有一个参数的构造函数有效。
- 栈连续,堆不一定连续。
- 申请方式不同。栈由操作系统自动分配,堆需要程序员自己申请。
- 生长方向不同。栈由高地址向地址生长,是一块连续的内存区域。堆由地址向高地址生长,是不连续的内存区域。在一个链表中记录空间内存地址。
- 分配速度。栈由系统分配,速度较快。堆使用new分配,速度较慢,且容易产生内部碎片。
- static可以用来修饰函数和变量。修饰全局变量和局部变量时都是放在静态区,static变量只初始化一次,在程序结束时销毁,全局和局部的区别在于作用域不同。static可以修饰普通成员函数,表明这个函数只在本文件中有效。static修饰类成员变量是,这些变量为这个类所共享,static修饰类成员函数时,也是所有对象共享这个函数,该函数中没有this指针。同时static类成员函数中只能调用static修饰的函数。
- 存放的static修饰的全局变量和局部变量,const修饰的变量以及字符串。
- 数据段存放的是代码的二进制指令。静态区是变量。
- vector是动态数组,分配连续的内存,2倍扩容。
- list双向表,插入删除效率高。
- map和set红黑树,有序的容器。
- stack和queue底层可能都是数组实现
- unordered_map和unordered_set哈希表。
- array栈上分配的数组执行效率快。
- tuple元组多数据类型的集合。
- 分治
- 递归
- 交换
- 基于交换和分治的算法, 平均nlogn 最坏n平方
- 主动关闭方发送FIN标志位,自身状态进入FIN_WAIT_1被动关闭方收到FIN后,发送ACK确认,自身进入CLOSE_WAIT状态,当主动关闭方收到ACK后,进入FIN_WAIT_2状态,此时主动关闭方只能接收数据,不能发送数据,因为TCP是全双工的,所以要等待被动关闭方关闭后才结束。此时进行了两次挥手,双方进入半关闭状态。当被动关闭方需要关闭时,发送FIN标志位,发出后自身状态进入LAST_ACK状态,如果对方收到FIN标志位后,发送ACK应答,自身进入TIME_WAIT状态,等待2MSL后关闭连接。等待的2MSL是报文在网络传输中一个来回的长度。确保最后一个ACK能被对方收到。
- static可以修饰普通函数变量和类成员函数和变量。
- static修饰普通变量时,分为全局变量和局部变量,两者都保存在静态区,并且只初始化一次,在整个程序运行期间一直存在。全局变量和局部变量的区别是作用域不同。
- static修饰普通函数时,说明此函数只在本文件中可见,防止多个文件的同名冲突。
- static修饰类中的成员变量时,必须在类中声明,在类外初始化,初始化的时候分配内存,所有的static成员变量为所有对象共享。
- static修饰类中的成员函数时,只能调用static的变量和函数,没有this指针,所有对象共享这个函数,可以使用类名直接调用。
变量分为全局变量和局部变量,static修饰全局变量时,表示这个变量只在本文件中可见
const用于限定变量指针和函数不可改变,方便编译器做类型检查。
- cons修饰变量时必须初始化。const全局变量通常放在静态区,const局部变量放在栈区。
- const修饰成员函数时,函数中的成员变量不可更改,如果要修改成员变量需要声明为mutable
- const修饰指针有两种,常量指针和指针常量,常量指针是指针的指向的值不可改变,而指针的指向可以改变。指针常量是指向不可变,而值可变。
- const明确指定类型,编译器对类型做检查,而define没有类型也不 检查。
- const分配内存,而define不分配。
- const在编译期处理,而define在预编译期进行宏替换。define的宏替换不加括号会产生严重的影响。
- 指针保存的是所指对象的地址,而引用是所指对象的别名。指针通过解引用间接访问所指的对象,而引用直接访问。
- 指针可以有多级,而引用最多两级。当有两个取地址符时,是右值引用,右值引用可以减少深拷贝的次数。
- 指针定义时可以不初始化,即使初始化后也可以改变。而引用定义时必须初始化,初始化后不可以改变。
- 引用的本质是指针常量,编译器帮助转换。指针常量的指向不可以改变,值可以变。
- 内联函数是一个函数,在编译期插入到调用的地方,而define在预处理期进行替换。
- 内联函数避免了函数调用时的压栈和参数拷贝等操作,提高了性能。
- 内联函数对参数有类型检查。define不加括号容易出错。
- malloc是库函数,new是运算符
- malloc只分配内存不初始化,而new不仅分配内存也初始化。new分配内存以后自动调用构造函数。
- malloc分配内存时必须指定内存大小,而new可以自动计算。malloc分配完成后返回的是void*类型,需要强转,而new返回的是对应类型的指针。
- malloc分配内存失败时返回NULL,而new分配内存失败时抛出bad_alloc异常。
- grep文件查找
- awk按行查找
- sed按列查找
- socket是linux文件的一种类型。socket通过绑定IP和端口可以实现再进程间通信。创建一个socket文件描述符指向两个缓冲区,所以可以实现全双工通信。
- 连接的文件描述符比较多,但是活跃的文件描述符比较少时,比select和poll效率更高。
epoll三个主要的函数:
- epoll_create()
- epoll_ctl()
- epoll_wait()
- ET称为边沿触发,在调用epoll_wait函数监听的时候,如果有满足的事件,则该函数进行返回,返回值是满足监听事件的文件描述符数量。ET模式下,只会响应一次,即使部分文件描述符的事件没有进行处理。当发送接收缓冲区状态发生改变时,触发读写事件。边沿触发模式要求一直读写,直到读写完或者返回EAGAIN。
- LT称为水平触发,如果epoll_wait有满足条件的事件,会一直返回。增加了epoll_wait函数的调用次数。所以通常在并发服务器上,多使用ET模式。如果系统中有大量不需要读写的就绪文件描述符,而它每次都会返回,这样会大大降低检索自己关心的就绪文件描述符的效率。接收缓冲区不为空,有数据可读,读事件一直触发。发送缓冲区不为满,有数据可写,则写事件一直触发。
- 阻塞模式,程序在读取数据时无法知道什么时候读完,会一直阻塞在read函数上,即使已经读完了数据。
- 非阻塞模式,程序使用while循环读取数据,读完以后read会返回,errno被设置为EAGAIN。
- 阻塞IO
- 非阻塞IO
- IO多路复用
- 信号驱动IO
- 异步IO
前4个都是同步IO。IO多路复用也是阻塞的,但是没有阻塞在send和recv上,而是阻塞在select,poll和epoll上。
- 同步:进程在数据由内核空间复制回进程缓冲区时不能干别的事。
- 异步:在数据准备完成,由内核缓冲区拷贝到进程缓冲区后通知进程,在等待通知的这段事件里,进程可以干别的事情。