用于博主自己查漏补缺喜欢的关注一波持续更新。。有错的话监督一下emplace_back()在末尾添加元素如vectorlistdeque中emplace_back()直接在容器内存空间构造对象push_back()需要一个已经构造好的对象。与pop_back()常一起。。c11新标准引入三个新成员emplace_front,emplace和emplace_back这些操作构造而不是拷贝元素相比push_backpushinsert等函数更好的避免内存的拷贝和移动emplace(p,t)在迭代器p指向的元素之前创建一个值为t的元素返回指定新添加元素的迭代器vector提供了emplace_back和emplacedeque提供三种方法list提供三种方法封装、继承、多态封装将具体实现过程方法和数据封装成一个函数只通过接口进行访问。继承子类继承父类的特征和行为复用了基类的全体数据成员变量和成员函数。基类的构造函数、析构函数、静态数据成员、静态成员函数不能被派生类继承。多态不同继承类的成员函数对同一信息事件做出不同行为响应基类的指针指向或绑定到派生类的对象使得基类指针呈现不同的表现形式。提高了代码的可替代性和可扩展性。虚函数的实现用virtual声明类的成员函数称之为虚函数虚函数用于实现多态子类继承父类子类以父类的指针或引用的身份出现。。类内定义了虚函数就有虚函数指针这个指针指向虚函数表表内是两个虚函数的地址重写的函数在虚函数表中的地址变了没重写两个地址子类和父类是一样的。。虚函数表每个包含虚函数的类都有一个虚函数表存储类中所有虚函数指针的数组。每个多态类都有一个自己的虚函数表包含指向该类的虚函数实现指针虚表指针指向对应类的虚函数表虚函数表内指针指向虚函数实现虚表指针指向该对应类的虚函数表由于虚表指针需要通过this指针调用所有static修饰静态成员函数不能被virtual修饰父类的构造函数不能为虚函数对象构造顺序问题当派生类对象被创建时构造函数的调用顺序从基类开始逐层向下调用派生类的构造函数。虚函数主要目的是支持运行时多态。基类构造函数执行时派生类对象部分还没有被构造也就没有多态所以调用虚函数可能导致未定义行为虚函数表的初始化虚函数调用通过虚函数表来查找而虚函数表由类实例化对象的vptr指针指向该虚表指针需要调用构造函数完成初始化如果构造函数是虚函数调用构造函数就要去找vptr但此时vptr还没有完成初始化因为先调用基类的构造函数再调用派生类的构造函数导致无法构造对象。。析构函数可以并且经常被定为虚函数使用父类指针指向子类时当父类析构函数为非虚函数时只会调用父类的析构函数子类的析构函数不会被调用从而造成内存泄漏。而把父类的析构函数定为虚函数之后再使用父类执行指向子类并且删除该对象时会先调用派生类的析构函数再调用基类的析构函数虚函数和纯虚函数虚函数有函数体实现可以被实例化纯虚函数没函数体不能被实例化一个类有纯虚函数则为抽象类该类不能实例化。在虚函数的函数声明后面加上0即声明为纯虚函数。class test(){ //虚函数 virtual void test_virtual(){ //函数体 }; //纯虚函数,无函数体且添加0声明 virtual void test_virtual 0; }纯虚函数仅定义一个接口而具体实现延迟到派生类中完成。抽象类包含至少一个纯虚函数抽象类无法被直接实例化只能通过其派生类来实例化。即子类可以有虚函数实现此函数父类无法实现。。什么的抽象类一定是父类不能是子类。。仅声明。。static关键字在类中static修饰成员变量成员变量所有类共享无论实例化多少次静态成员变量在内存只存在一份static修饰成员函数函数全局可调用不再与类特定实例相关联在类外static修饰局部变量延长生命周期直至程序调用结束才销毁static修饰全局变量仅在文件内可见避免重名冲突内存分区在linux下操作系统会为每个进程分配虚拟的地址空间代码区存放代码数据区存放全局变量和静态变量等堆区用于程序员动态分配的内存程序结束时由系统自动释放回收与数据结构中的堆做区分分配方式类似于链表。如newmalloc等栈区先进后出用于存储局部变量函数的参数值高地址向低地址写入内存映射区域于堆栈之间通过mmap系统调用将其他对象文件映射到该进程地址空间区域内。如果堆区空间不够需要新开辟空间时也是将堆顶指针向高地址移动从而在内存映射区开辟空间。栈由系统自动管理大小固定是连续的内存区域无碎片化有静态分配和动态分配堆由程序员手动管理可变大小非连续内存区域有碎片化只动态分配linux使用虚拟地址空间大大增加了进程的寻址空间缺页中断即虚拟空间地址对应的物理地址不在物理内存中检查访问的虚拟空间是否合法查找/分配一个物理页填充物理页内容读取磁盘或直接置0或什么也不干。。建立映射关系进程分配内存两种方式分别由两个系统调用完成brk和mmapbrk将数据段最高地址指针往高地址推不考虑共享内存即往堆推上去mmap是在进程的虚拟地址空间中堆栈之间称文件映射区找一块空闲的虚拟内存两种分配方式分的都是虚拟内存不是物理内存进程调用malloc标准库malloc调用brk向操作系统申请内存操作系统执行brk系统调用windows下用virtualAlloclinux下用brk和mmapmalloc动态申请内存实际不是从堆中获取内存而是找到一块空闲虚拟地址空间本质malloc是一块内存池。。mallocnewmalloc/free是C的标准库函数new/delete是C运算符malloc失败返回空new失败抛异常malloc/free不会调用构造析构new/delete会调用构造析构malloc返回无类型的指针要强转new返回有类型指针malloc/free不可重载new/delete可重载分配后不够用realloc扩容new不可以扩容需显式指出所需内存尺寸new无需指定内存块大小malloc 封装了 mmap系统函数调用和brk系统函数调用。而new封装了类先调用operate new 分配空间再调用指定对象的构造函数进行初始化。内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放造成系统资源的浪费导致程序运行缓慢甚至系统崩溃的严重后果。使用智能指针管理资源在释放对象数组时使用delete []尽量避免在堆上分配内存。关于delete和delete[]delete用于释放由new创建的单个对象delete[]用于释放由new创建的数组对象一般配对使用为什么delete不用于释放数组对象delete[]不用于释放单个对象delete在使用时调用指针所指向对象的析构函数调用free函数回收指针所指向的内存delete[]也是两步调用指针所指向数组中每个对象的析构函数调用free函数回收指针所指向的内存使用delete只有第一个数组的第一个对象析构函数被调用之后的对象都不会调用析构函数析构函数用于对象生命周期结束时执行清理操作像动态分配的内存、关闭文件、释放资源delete是C自带运算符对于类对象删除时调用类的析构函数释放内存后确保内存内容被释放并合并成一块可用的空间只能释放new分配空间free是C标准库函数仅释放空间不会释放类或结构体对象的空间仅简单将内存地址修改为空闲状态并不能保证合并成一块可用空间如果频繁执行malloc和free容易产生大量小块内存碎片只能释放malloc分配的空间野指针和悬空指针都是指向无效内存区域不安全不可控的指针访问行为将会导致未定义行为。野指针指的是没有被初始化过的指针。悬空指针指针最初指向的内存已经被释放了的指针。指针在对象free或者delete之后没有及时置空。避免对指针进行初始化用合法的可访问的内存地址来对指针初始化野指针指针用完释放内存将指针赋值nullprt悬空指针。避免野指针比较简单但悬空指针比较麻烦cpp中引入了智能指针用于自动管理释放内存本质就是避免悬空指针的产生。final标识符和override标识符用于声明不想被继承的类或函数保障了函数的完整性。放在虚函数后面虚函数无法被重写表示阻止虚函数的重载。。class test final{ //无法被继承 };浅拷贝与深拷贝浅拷贝只简单的复制对象的成员变量值包括指针成员变量的地址值原始对象和拷贝对象共享相同的资源一个对象修改了资源另一个也会受影响。共用地址深拷贝会复制对象的所有内容并重新分配资源彼此不受影响匿名函数lambda表达式即没有名字的函数。本质是一个对象定义时会创建一个栈对象因为是临时对象所以存储在栈区内部通过重载符号实现函数调用的外表免去函数的声明和定义。这样匿名函数仅在调用函数的时候才会创建函数对象调用后立即释放匿名函数比非匿名函数更节省空间[capture list](parameters)-return_type{body} capture list:捕获上下文中变量可以为空或包含一个或多个变量 parameters函数参数列表 return_type:函数返回类型可省略编译器会自动推导 body函数体。。智能指针智能指针是类模板原理是利用类在销毁时会自动调用类的析构函数从而在析构函数里面将内存释放简化了内存管理。是C中用于管理动态分配的内存资源的一种工具。与传统指针相比智能指针自动内存管理避免内存泄漏减少程序出错的可能性等std::unique_ptr独享它指向的对象同时只有一个unique_ptr指向同一个对象当这个unique_ptr被销毁时指向的对象也随之被销毁。具有排他性。通过std::move()转移所有权。。#includememory std::unique_ptrint ptr(new int(42));std::shared_ptr是一种共享所有权的智能指针多个std::shared_ptr可以共同拥有一个对象。内部用引用计数来跟踪对象的引用数量当引用计数为零时资源被释放。即创建多个指针指向同一个对象。。std::weak_ptr循环引用是两个或多个对象相互引用形成一个闭环使他们无法被自动释放从而导致内存泄漏。在使用shared_ptr时如果两个对象相互持有shared_ptr就会导致引用计数永远无法归零资源无法释放。是一种弱引用的智能指针它不增加对象的引用计数不共享对象的所有权。主要用于解决shared_ptr循环引用导致的内存泄漏问题。使用lock()方法获得一个与std::shared_ptr共享对象的指针如果对象销毁则返回空指针与shared_ptr一起使用避免循环引用。。shared_ptr改成weak_ptr,打破循环引用weak_ptr不增加引用计数。。左值引用和右值引用最常见的左值引用左值是一个在内存有固定地址可以通过标识符访问的对象。左值引用主要用于传递和操作具有持久性的对象如局部变量或函数返回的左值。。int x42;//x就是左值42即右值int lrefx;//即lref是x的左值引用右值引用是对右值的引用。为一个临时变量取别名。右值即不占用持久存储的临时对象通常是表达式的结果或字面值。即临时对象或将要销毁的对象。右值引用使用符号表示主要用于实现移动语义和完美转发通过将资源从临时对象“窃取”到另一个对象来提高效率。int rref42;//右值引用指向42移动语义即以移动而非深拷贝的方式初始化含有指针成员的类对象。移动语义将其他对象通常是临时对象拥有的内存资源“移为己用”。换句话说以浅拷贝方式复制指针然后将原指针置为空指针。传统上通过复制构造函数和赋值运算符进行对象的拷贝操作这可能会涉及深拷贝即将数据从一个对象复制到另一个对象。移动语义允许在不复制数据的情况下将资源例如内存、文件句柄等从一个对象转移到另一个对象通过将右值引用Rvalue Reference绑定到临时对象来实现。移动语义通过 std::move() 函数来实现它将左值转换为右值引用从而可以使用移动构造函数或移动赋值运算符。移动语义主要用于提高性能减少不必要的对象拷贝操作。完美转发即一个能根据参数类型调用相应函数的接口当参数是左值引用调用相应函数且传递的参数是左值引用当参数是右值引用调用相应函数且传递的参数是右值引用。完美转发允许函数接受任意数量和类型的参数并将它们转发给另一个函数同时保留原始参数的值和类型。在 C11 之前如果想要编写一个可以转发参数的函数模板必须为每一种可能的情况都编写一个重载版本这导致了代码的重复和不够灵活。完美转发通过引入右值引用和模板参数推导来解决这个问题使用 std::forward() 函数来实现参数的转发。完美转发主要用于编写泛型代码例如实现通用的包装器类或工厂函数。这两个至今未明白左值lvalue左值是指可以出现在赋值运算符的左边的表达式也就是可以取地址的表达式。具体来说左值是指有名称且在内存中有确定位置的表达式可以通过取地址操作获取其在内存中的位置。例如变量、函数返回的左值引用以及数组元素都是左值。右值rvalue右值是指不能出现在赋值运算符的左边的表达式也就是临时生成的、没有名称的值。具体来说右值是指无法取地址的临时对象或字面常量例如常量、临时变量、以及表达式的结果。在 C11 中右值引用的引入进一步扩展了右值的概念右值引用可以绑定到临时对象并允许移动语义的实现。左值引用和指针的区别是否初始化指针无需初始化引用必须初始化性质不同指针是一个变量引用是对被引用的对象取的一个别名占用内存单元不同指针有自己的空间地址引用和被引用对象占同一个空间。。define和const的区别编译阶段define是在编译预处理阶段进行简单的文本替换const是在编译阶段确定其值安全性define定义的宏常量没有数据类型只是进行简单替换不会进行类型安全检查const定义的常量有类型要进行类型判断内存占用define定义的宏常量在程序中使用多少次就会进行多少次替换内存有多个备份占用的是代码段的内存const定义常量占用静态存储区域的空间程序运行过程中只有一份。调试define定义的宏常量不能调试因为预编译时就已经替换const定义的常量是可以进行调试的。一个程序运行的步骤预编译将头文件编译进行宏替换输出.i文件编译将其转化为汇编语言文件主要做词法分析语义分析以及检查错误检查无误后将代码翻译成编译语言生成.s文件汇编汇编器将汇编语言文件翻译成机器语言生成.o文件二进制文件链接将目标文件和库链接到一起生成可执行文件锁的底层原理最常用的就是互斥锁还有很多不同的锁如自旋锁、读写锁、乐观锁等不同种类的锁适用于不同的场景。互斥锁和自旋锁对于加锁失败后互斥锁加锁失败后线程会释放 CPU给其他线程自旋锁加锁失败后线程会忙等待直到它拿到锁互斥锁加锁失败会释放 CPU 让给其他线程线程 B 释放掉了 CPU加锁的代码就会被阻塞。内核会在合适的时机唤醒线程当这个线程成功获取到锁后于是就可以继续执行开销成本是两次线程上下文切换的成本线程的上下文切换的是什么当两个线程是属于同一个进程因为虚拟内存是共享的所以在切换时虚拟内存这些资源就保持不动只需要切换线程的私有数据、寄存器等不共享的数据。如果被锁住的代码执行时间很短应该选用自旋锁不应用互斥锁。自旋锁是通过 CPU 提供的CAS函数Compare And Swap锁的底层是通过CASatomic机制实现。CAS机制Compare And Swap比较相同再交换可以比较和交换操作转换成原子操作CAS操作依赖于三个值内存中的值V旧的预估值X要修改的新值B如果旧的预估值X等于内存中的值V就将新的值B保存在内存之中。就是每个线程从主内存复制一个变量副本后进行操作然后对其进行修改修改完之后再刷新回主内存前。再取一次主内存的值看拿到的主内存的新值与当初保存的快照值是否一样如果不一样说明有其他线程修改本次修改放弃重试。atomic机制如下原子操作原子操作是指不会被线程调度机制打断的操作这种操作一旦开始就一直运行到结束中间不会有任何切换到另一个线程。原理是在X86的平台下CPU提供了在指令执行期间对总线加锁的手段CPU中有一根引线#HLOCK pin连接到北桥如果汇编语言的程序在程序中的一条指令前面加上了前缀“LOCK”经过汇编之后的机器码就使CPU在执行这条指令的时候把#HLOCKpin的电平拉低持续到这条指令结束的时候放开从而把总线锁住这样别的CPU就暂时不能够通过总线访问内存了保证了多处理器环境中的原子性。互斥锁、自旋锁、读写锁都属于悲观锁悲观锁认为并发访问共享资源时冲突概率可能非常高所以在访问共享资源前都需要先加锁。相反的如果并发访问共享资源时冲突概率非常低的话就可以使用乐观锁它的工作方式是在访问共享资源时不用先加锁修改完共享资源后再验证这段时间内有没有发生冲突如果没有其他线程在修改资源那么操作完成如果发现有其他线程已经修改过这个资源就放弃本次操作。class和struct的区别默认继承权限不同class默认继承的是private继承struct默认是public继承除此之外几乎没区别class可定义类模板而struct不行C保留struct是为了向下兼容C。。类的声明周期全局对象在main 开始前被创建main退出后被销毁。静态对象在第一次进行作用域时被创建在main退出后被销毁。局部对象在进入作用域时被创建在退出作用域时被销毁。New创建的对象直到内存被释放程序员手动/程序执行完由操作系统释放的时候都存在。vector、list、deque的优缺点vector动态数组通过索引访问数据O(1)内存中连续存储适合缓存友好的操作能够提高性能容器末尾插入和删除操作非常快支持自动扩容处理大数据量方便deque双端操作高效支持两端进行快速插入和删除操作支持通过索引访问元素不需要整体重分配deque是分段存储的每段内存块是连续的但各段不连续所以即使扩容也不需要整体重分配内存queue队列和deque双端队列deque是集大成。。list双向链表每个节点都包含指向前后指针因此任何位置进行插入删除都是高效的不需要移动元素由于链表插入删除不涉及移动其他元素适用频繁插入删除的场景不支持下标随机访问指针占用的大小64位操作系统上占8字节32位的占4字节是固定的。我们平时所说的计算机多少位是指计算机CPU中通用寄存器一次性处理、传输、暂时保存的信息的最大长度。即CPU在单位时间内能一次处理的二进制的位数因此CPU所能访问的内存所有地址由多少位组成而8比特位表示1字节就可以得出在不同位数的机器中指针的大小。。迭代器和指针的区别迭代器不是指针而是一个类模板通过重载了指针的一些操作符模拟了指针的一些功能迭代器返回的是对象应用而不是对象的值。指针能够指向函数而迭代器不行迭代器只能指向容器迭代器用来遍历C标准模板库容器中的部分或全部元素每个迭代器对象代表容器中的确定的地址。string::iterator its.begin(); //此处的begin()函数有两个重载函数分别是 //iterator begin(); //const_iterator begin() const; //调用他们的对象分别是可以修改和不可修改的 while(it!s.end()){ cout*it; it; }还有逆向遍历string::reverse_iterator r_its.rbegin(); while(r_it!s.rend()){ cout*r_it; r_it; }所有容器都可以使用迭代器去访问修改对于string下标完全够用迭代器没有突出优点但对于其他容器对于以链表形式连接的数据结构如listmap/set等就不能用下标[]方式去访问元素了所以就需要采用迭代器来访问这些容器里面的元素。。关于迭代器失效像vector大多发生在改变vector容量删除vector中元素等情况因迭代器是类似指针的功能能够遍历整个数组改变容量时迭代器指向的vector中的元素就有可能发生变化删除后所以每次扩容之前把迭代器位置记录下来。。map和unordered_mapSTL分成序列式容器像vector、list、deque和关联式容器map、set。。关联式容器里面存储的是key,value键值对一一对应树型结构的关联式容器主要map、set、multimap、multiset。底层用平衡搜索树红黑树容器中元素是一个有序的序列pair是一种简单数据结构用于存储两个相关联的对象key用pair.first表示value是pair.secondsetset是按照一定次序存储的容器set中的元素不能修改它总是被const修饰的但可以插入和删除set中的value和key是相同的且每个value的值是唯一的不存在两个相同的value值set的底层是通过红黑树实现的set与map/multimap不同map/multimap中存储的是真正的键值对key,valueset中只放value但在底层实际存放的是由value,value构成的键值对。在set插入一个元素时只需要传一个value值不需要构造键值对。set中的元素默认按照小于来比较。set中的元素不能改变。set中的元素不能重复可用于去重。set的迭代器遍历set中的元素是中序可以得到有序序列。set中查找某个元素的时间复杂度是Olog n。set和unordered_setmap和unordered_map使用上几乎没有区别主要区别在内部实现的数据结构不同set内部实现的是一个红黑树的平衡二叉树但unordered_set内部实现是一个哈希表同理map和unordered_map也一样。。哈希表和红黑树哈希表基于哈希函数该函数将键Key映射到存储桶Bucket的索引位置。通过这种方式哈希表直接访问存储桶实现快速的数据查找。然而不同的键可能会被映射到同一个存储桶这就是哈希冲突需要特定的冲突解决方法。红黑树是一种自平衡的二叉搜索树。每个节点除了包含键值对还有一个额外的颜色属性红色或黑色。通过颜色属性和一系列的规则如根节点是黑色、红色节点的子节点必须是黑色等来保证树的高度平衡从而保证操作的时间复杂度。map内部实现是一个红黑树平衡二叉树内部所有的元素都是有序的而hashmap则是内部实现了一个哈希表。map优点有序性插入、删除、查找的时间复杂度都是logNmap缺点空间占用率高unordered_map优点查找效率非常高O(1)缺点哈希表的建立比较费时间。。详细可以看这里https://blog.csdn.net/2401_82607598/article/details/159800532?fromshareblogdetailsharetypeblogdetailsharerId159800532sharereferPCsharesource2401_82607598sharefromfrom_linkvector扩容resize和reserver的区别使用resize改变的是vector的大小(size)可能会添加或删除元素如果新的大小比当前大小大则会插入新元素默认值初始化如果相比较小则会移除多余的元素。使用reserve用于预先分配vector的容量改变的是vector的容积capacity不会改变当前元素的数量仅仅是为了优化内存使用和性能。vector扩容为了避免重复扩容做了哪些机制当vector容量不够时本身内存会以1.5或者2倍增长以减少扩容次数 引入了reserve可以自定义vector最大容量。移动构造和拷贝构造的区别移动构造函数用于创建一个新的对象该对象是从现有对象通过“偷取”资源得到的而不是深拷贝。这通常通过将原对象的指针或资源指向新对象同时将原对象的指针或资源置空来实现。这种方式避免了不必要的深拷贝操作提高了性能特别是在处理临时对象时。属于浅拷贝。移动构造函数本质上是基于指针的拷贝实现对堆区内存所有权的移交在一些特定场景下可以减少不必要的拷贝。比如用一个临时对象或者右值对象初始化类实例时我们可以使用move函数将一个左值对象转变为右值对象拷贝构造则是将传入的对象复制一份然后放进新的内存中是一种深拷贝。lamda表达式匿名函数捕获列表捕获的方式有哪些如果是引用捕获要注意什么按值捕获和引用捕获按引用捕获是将外部变量的引用存储在Lambda表达式的闭包中[] 表示按引用捕获所有外部变量。这样当Lambda表达式执行时它将直接访问原始变量。这种方式在某些情况下可能导致问题例如当回调执行时原始变量已经失效例如原始变量是栈上的局部变量而回调在该变量离开作用域后执行。按值捕获是将外部变量的值复制到Lambda表达式的闭包中。这样当Lambda表达式执行时它将使用这个复制的值而不是原始变量的值。这种方式可以避免在回调执行时原始变量已经失效的问题。默认的引用捕获可能会导致悬挂引用引用捕获会导致闭包包含一个局部变量的引用或者形参的引用如果一个由lambda创建的闭包的生命周期超过了局部变量或者形参的生命期那么闭包的引用将会空悬。解决方法是对个别参数使用值捕获。哈希碰撞哈希冲突的处理方式开放定址法去寻找一个新的空闲的哈希地址。再哈希法同时构造多个哈希函数等发生哈希冲突时就使用其他哈希函数直到不发生冲突为止虽然不易发生聚集但是增加了计算时间。链地址法将所有的哈希地址相同的记录都链接在同一链表中。建立公共溢出区将哈希表分为基本表和溢出表将发生冲突的都存放在溢出表中。详细的https://blog.csdn.net/2401_82607598/article/details/159800532?fromshareblogdetailsharetypeblogdetailsharerId159800532sharereferPCsharesource2401_82607598sharefromfrom_linkunordered_map 的扩容过程当unordered_map 中的元素数量达到桶的负载因子0.75时会重新分配桶的数量通常会按照原有桶的数量*2的方式进行扩容但是具体的增长策略也可以通过修改容器中的max_load_factor成员变量来进行调整并将所有的元素重新哈希到新的桶中。负载因子 假设哈希表中已经映射存储了N个值哈希表的大小为M那么负载因子N/M负载因子有些地方也被翻译成载荷因子/装载因子等它的英文为load factor。负载因子越大哈希冲突地概率就越高高间利用率就会越高负载因子越小哈希冲突的概率就越低空间利用率就会越低。赋值的区别string A 123456; string B A;执行第一句会创建临时常量123456并赋值给A常量用后会被销毁。而第二句是调用string类的拷贝构造函数用A的值来初始化BAB享有各自独立的存储空间。push_back() 和emplace_back()的区别push_back()接受一个已构造的对象并将其拷贝或移动到向量的末尾使用push_back()时新元素是先在调用处构造然后再复制或移动到向量内部。可能会涉及额外的拷贝或移动操作特别是对于复杂对象这些操作可能会比较昂贵。需要调用构造函数和转移构造函数。emplace_back() 直接在向量的末尾原地构造对象它接收构造函数的参数并直接在向量内部调用构造函数创建对象这样可以避免不必要的拷贝或移动直接在目标位置构造对象从而提高性能。实现了零拷贝。只需要调用构造函数。const用法常量指针和指针常量char const * 和const char * 等价 const char * 是指向常量的指针const修饰的是指针指向的对象即指针指向的该对象是常量不可修改。 char * const 定义的是一个指针常量表示该指针不可修改。 *前面是被指对象的约束* 后面加则是该指针的约束区别以下指针类型int *p[10]; //表示指针数组强调数组概念是一个数组变量数组大小为10数组内每个元素都是指向int类型的指针变量 int (*p)[10]; //表示数组指针强调是指针只有一个指针类型变量指向的是一个int类型大小为10的数组 int *p(int); //是函数声明函数名是p参数是int返回值是int * int (*p)(int); //是函数指针强调是指针该指针指向的函数具有int类型参数并且返回值是int类型的public、protected、private 访问和继承权限的区别public:变量和函数在类的内部外部都可以访问。protected的变量和函数只能在类的内部和其派生类中访问private修饰的元素只能在类内访问。内部指由派生类中新增的成员函数对从基类继承来的成员的访问。外部在派生类外部通过派生类的对象对从基类继承来的成员的访问。继承权限 权限取较小的。public继承private成员不可见public继承protected成员protectedprotected继承public成员protectedprivate继承 protected成员privateprivate继承public成员private四种强制类型转换reinterpret_cast将指针或引用转换为不相关的类型如果使用不当会导致未定义的行为一般认为是不安全的。const_cast用途用于移除对象的“const”或“volatile”修饰符允许对其进行修改。它只能用于指针或引用类型的转换。const int i 10; int* p const_castint*(i); // 移除 const 修饰符 *p 20; // 修改 i 的值static_cast静态转换用于在相关类型之间进行转换通常用于基本类型转换和具有继承关系的类之间的指针或引用的转换。是最常用的显式类型转换方式。int i 10; double d static_castdouble(i); // 将 int 转换为 doubledynamic_cast动态转换主要用于带有多态性的指针或引用的向下转换如从基类指针转换为派生类指针。它在运行时进行类型检查如果转换失败则返回nullptr或抛出bad_cast 异常对于引用。class Base { virtual void foo() {} }; class Derived : public Base {}; Base* b new Derived(); Derived* d dynamic_castDerived*(b); // 向下转换 if (d) { // 转换成功 }构造函数、拷贝构造函数、移动构造函数构造函数用于初始化拷贝构造函数用已经构造的对象a来初始化对象b移动构造函数用已经构造的对象a初始化完对象b构造的对象a空间还在就冗余了所以采用直接将已经构造的对象a移动给对象b减少了请求内存的开销。拷贝构造函数的参数是一个左值引用但是移动构造函数的初值是一个右值引用。继承和组合的优缺点继承是 is a的关系如果student 继承person则说明student is s person。优点 子类可以重写父类的方法来方便地实现父类的拓展。缺点父类的内部细节对子类可见子类从父类继承的方法在编译时就确定下来了所以无法在运行期间改变从父类继承的方法的行为如果对父类方法做了修改的话则子类的方法必须做出相应的修改导致子类和父类高耦合违背了面向对象的思想。组合是 has a的关系设计类的时候把组合的类的对象加入到该类中作为自己的成员变量优点当前对象只能通过所包含的那个对象去调用其方法所以所包含的对象的内部细节对当前对象是不可见的当前对象与包含的对象是一个低耦合关系如果修改包含对象的类中代码不需要修改当前对象类的代码当前对象可以在运行时动态的绑定所包含的对象可以通过set方法给所包含对象赋值。缺点容易产生过多的对象为了能组合多个对象必须仔细对接口进行定义。优先使用组合而不是继承。实际尽量多去用组合组合的耦合度低代码维护性好。不过也不太那么绝对如果 类之间的关系就适合继承(is-a)那就用继承另外要实现多态也必须要继承。如果 类之间的关系 既适合用继承(is-a)也适合组合(has-a)就用组合。异常处理机制C三个异常处理核心关键字try、throw、catch三个配合使用try用于包围可能抛出异常的代码如果在try块中发生了异常程序会立即跳转到相应的catch块。try( //可能抛出异常的代码 }throw用于抛出异常可以抛出任何类型的异常对象throw exception_object;catch用于捕获和处理异常它紧跟在try块后面并且可以有多个catch块来捕获不同类型的异常。catch(exception_type exception_object){ //处理异常的代码 }#include iostream #include stdexcept // 包含标准异常类的头文件 using namespace std; // 一个可能抛出异常的函数计算除法 double divide(double a, double b) { // 检测异常条件除数为0 if (b 0) { // 抛出异常throw 后面是异常对象可以是自定义值/标准异常 throw invalid_argument(除数不能为0); // 标准异常类带错误信息 } return a / b; } int main() { double num1 10, num2 0; // 1. try块包裹可能抛出异常的代码 try { double result divide(num1, num2); cout 计算结果 result endl; } // 2. catch块捕获并处理对应类型的异常 catch (const invalid_argument e) { // 匹配throw抛出的异常类型 cout 捕获到异常 e.what() endl; // what()获取异常描述 } // 3. 万能catch捕获所有类型的异常可选 catch (...) { cout 捕获到未知异常 endl; } // 异常处理后程序继续执行 cout 程序未崩溃正常结束 endl; return 0; }为什么要用异常处理对比传统的 “返回错误码”比如用 - 1 表示失败异常处理的优势错误和业务逻辑分离不用在代码里到处写if (返回值 -1)错误处理集中在 catch 块代码更整洁能跨函数传递throw 抛出的异常可以穿过多层函数调用直接被上层的 catch 捕获比如函数 A 调用 BB 调用 CC 抛出异常A 里的 catch 能接住强制处理错误如果抛出异常但没被 catch程序会调用terminate()直接崩溃避免 “隐性错误”比如返回错误码但程序员忘了判断。C 异常处理核心是try包裹风险代码throw抛出异常catch捕获处理能避免程序崩溃优雅处理运行时错误优先使用标准异常类如invalid_argument、out_of_range让代码更规范异常处理适合处理 “意外错误”而非正常逻辑判断核心优势是错误与业务代码分离。指针传递、值传递和引用传递指针传递和引用传递都能修改原变量的值。而值传递是形参传递给实参不能修改原变量的值。如果数据对象是数组则选择指针传递因为可以通过操作指针来指向数组不同的元素。类对象一般使用引用传递因为数据较大引用传递不会创建副本效率更高。指针传递本质上是值传递传递的是一个地址值。引用传递和指针传递都是在被调函数栈空间上的一个局部变量但是他们是不同的任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数如果改变被调函数中的指针地址它将应用不到主调函数的相关变量如果想通过指针参数传递来改变主调函数中的相关变量地址那就得使用指向指针的指针或者指针引用。指针可以改变其指向的对象而引用对象则不能。回调函数一般回调函数是自定义的函数调用回调函数的中间函数为操作系统提供当发生某种事件的时候就会调用回调函数。当函数作为另一个函数的参数时被调用的函数称为回调函数。当发生某种事件时系统或其他函数会自动调用自定义的回调函数。回调函数就相当于一个中断处理函数由系统在符合设定的条件时自动调用。通过函数指针进行调用。我们一般很少使用函数指针我们一般使用函数都是直接使用函数调用 。函数指针是指向函数的指针变量。通常我们说的指针变量是指向一个整型、字符型或数组等变量而函数指针是指向函数。 函数指针可以像一般函数一样用于调用函数、传递参数。 函数指针的定义方式为函数返回值类型 (* 指针变量名) (函数参数列表);函数指针的定义就是将“函数声明”中的“函数名”改成“*指针变量名”int (*Fun1)(int)一般我们typedef 函数返回值类型 (* 指针变量名) (函数参数列表);typedef int (*Fun1)(int);//声明也可写成int (*Fun1)(int x)但习惯上一般不这样。 typedef int (*Fun2)(int, int);//参数为两个整型返回值为整型 typedef void (*Fun3)(void);//无参数和返回值 typedef void* (*Fun4)(void*);//参数和返回值都为void*指针函数指针调用函数int Func(int x); /*声明一个函数*/ int (*p) (int x); /*定义一个函数指针*/ p Func; /*将Func函数的首地址赋给指针变量p*/ p Func; /*将Func函数的首地址赋给指针变量p*/函数指针作为某个函数的参数既然函数指针变量是一个变量当然也可以作为某个函数的参数来使用的。#include stdio.h #include stdlib.h typedef void(*FunType)(int); //前加一个typedef关键字这样就定义一个名为FunType函数指针类型而不是一个FunType变量。 //形式同 typedef int* PINT; void myFun(int x); void hisFun(int x); void herFun(int x); void callFun(FunType fp,int x); int main() { callFun(myFun,100);//传入函数指针常量作为回调函数 callFun(hisFun,200); callFun(herFun,300); return 0; } void callFun(FunType fp,int x) { fp(x);//通过fp的指针执行传递进来的函数注意fp所指的函数有一个参数 } void myFun(int x) { printf(myFun: %d\n,x); } void hisFun(int x) { printf(hisFun: %d\n,x); } void herFun(int x) { printf(herFun: %d\n,x); }回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时我们就说这是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的用于对该事件或条件进行响应。如果代码立即被执行就称为同步回调如果过后再执行则称之为异步回调。int Callback_1(int a) /// 回调函数1 { printf(Hello, this is Callback_1: a %d , a); return 0; } int Callback_2(int b) /// 回调函数2 { printf(Hello, this is Callback_2: b %d , b); return 0; } int Callback_3(int c) /// 回调函数3 { printf(Hello, this is Callback_3: c %d , c); return 0; } int Handle(int x, int (*Callback)(int)) /// 注意这里用到的函数指针定义 { Callback(x); } int main() { Handle(4, Callback_1); Handle(5, Callback_2); Handle(6, Callback_3); return 0; }更多例子更详细解说可以看这个推荐https://blog.csdn.net/qq_41854911/article/details/121058935?fromshareblogdetailsharetypeblogdetailsharerId121058935sharereferPCsharesource2401_82607598sharefromfrom_linkint Callback() /// 回调函数 { // TODO return 0; } int main() /// 主函数 { // TODO Library(Callback); /// 库函数通过函数指针进行回调 // TODO return 0; }内存池通常我们通过new/malloc申请内存这样的缺点在于所申请的内存块大小不定当频繁使用时会造成大量的内存碎片进而降低性能。内存池则是在真正使用内存之前就先申请分配一定数量的、大小相等一般情况的内存块留作备用当有新的内存需求时就从内存池中分出一部分内存块若内存块不够再继续申请新的内存这样做的一个显著优点是尽量避免了内存碎片使得内存分配效率得到提升。数据结构与算法1、逻辑结构顺序存储链式存储2、常用数据结构类型线性表、单链表、静态链表、循环链表、双向链表栈先进后出和队列先进先出、循环队列串树二叉树、线索二叉树、哈夫曼树、红黑树图最小生成树、最短路径、拓扑排序、关键路径3、算法查找排序4、递归算法5、红黑树平衡二叉树 map的底层实现五条性质1、每个节点要么是红色要么是黑色2、根节点是黑色的3、叶子节点是黑色的空节点4、所有红色节点的两个孩子都是黑色的5、任意节点到其可到达的叶子节点之间有相同数量的黑色节点。左节点比根节点小右节点比根节点大。并且动态调整树的平衡节点的旋转算法维护平衡。