随着知识点越来越多,记忆难免会混乱、遗忘,一个好的知识点总结能有效帮助回忆。
C语言¶
1.数组和指针¶
- 数组指针
- 指针数组
- 函数指针
- const和指针
- sizeof和指针和数组
- strlen和字符数组
void*
指针:- 隐式转换抹去实例类型信息,强制转换变为其他类型。
- 由于无类型信息,不可解引用(取内容),地址无法自增。
- NULL,是宏,是强制转换的
(void*)0
指针(C++改为了0,避免了重载识别为int类型)
2.库函数的模拟实现¶
- memcpy(void,const void,size):提供拷贝任何数组形式的内存的。
- const作用是防止源字符串被修改
- 判空,转换成char,循环赋值。
- memmove(void,const void,size):相比cpy,新增了内存重叠的判定。
- 先判空(assert),用ret指向dst(dst后面要自增发生变化)。
- 防止指向同字符串不同位置(dst地址<=src或者强制转换到
char*
后,dst>=src+len)。 - 如果不重叠,则从低地址开始复制(强制转换到char,dst自增,src自增)
- 如果重叠,就从高地址开始倒过来赋值(类似于合并数组)
- 返回ret。
- strstr(const char,const char):是否是子串,是的话返回开始的首地址,否则返回NULL
- str2是否为NULL?是的话直接返回str1地址
- 双重while循环遍历,第二重循环遍历子串为空(遍历完了)时直接返回首地址。
- strcpy/strlen/strcmp 出现相对少,上面三个要求轻松手撕
3.数据存储¶
- 基本类型大小,循环越界与死循环问题。
- 整型存储规则,原(符号位正负?)反(负数规则)补(区分0)。IEEE浮点数(考的少)
- 大小端及如何判断(考的很多):存在原因?代码判断(强转或联合体)?端序转换(位)?
- 类型提升和截断:截断因素(类型和大端小端),补位规则(补0和符号)
4.编译链接¶
- 预编译--宏(考察很多,轻松实现宏函数)
- 预编译、编译、汇编、链接的过程
C++¶
1.⭐⭐⭐面向对象基本概念¶
封装概念¶
- 数据,方法以及对外提供接口。
- private,protected,public级别。同类对象无权限隔离,可以访问private。
继承概念¶
- (继承中)隐藏特点?隐藏后调用基类?
- 公共继承、私有继承:权限问题?转换会发生什么?。
⭐⭐⭐多态¶
概念
静态绑定¶
- 重载定义?特点? 函数签名?
- 模版泛型?特化,偏特化。为什么放.h?\
- (好玩的)lambda模版参数包展开c++17(结合ECS拷贝组件使用)
- 调用虚函数的静态绑定?
虚函数virtual¶
- 继承构造出多态的两个条件?this的隐式多态? 虚函数重写条件?两个例外(协变和析构)?
- 析构为什么需要虚函数?什么是指针的切割?
- 接口继承,实现重写?override和final的作用?
- 虚表指针vptr:数量,大小,产生时机,类型
- 虚表vtbl:和类对应的关系,数量,位置,存储内容(RTTI,函数指针),虚函数存放(重写和未重写?)?数组最后?
- 纯虚函数?抽象类?有啥不同?= 0
- 多继承重写的三个虚函数地址?未重写的虚函数的地址?
- 虚继承内存排布?为什么要虚继承?菱形继承内存模型?
- 虚基表:虚基表指针,虚表内存模型改变,偏移量,多份
- 构造和析构中调用虚函数调用情况:存储角度、实用角度,调用后的情况
- 虚函数的静态绑定机制
- 虚函数能否内联:编译时(理论,实践),运行时(加final?)。
- 派生类为什么不能调用基类?
2.⭐⭐⭐ 智能指针¶
【C++】详谈C++智能指针的前世今生-CSDN博客
- 深拷贝、浅拷贝、野指针、悬空指针、RAII设计思想
- 创造的原因:RAII->auto_ptr(多次析构、copy+NULL)->boost(scoped_ptr那三个)->C11(正式引入,标准废除autoptr)->C14(控制块)->C17(移除auto_ptr)
- 智能指针共性:RAII、operator*
和operator->
- unique:
- 删除拷贝赋值、删除拷贝构造。
- sharedptr:
- 两个指针:一个资源,一个控制块
- 控制块内容:强引用计数、弱引用计数、其他数据(删除器、分配器等)
- make_shared和(new)构建:
- 仿照make_pair,一个是构建资源后拷贝,一个是直接分配内存块在里面new 那块资源。(资源是否分散两次构建的问题)
- 不能使用定制删除器和数组new【】
- 线程安全:原子自增自减(加锁)。同时修改等问题(外部加锁)
- 循环引用问题(小tip,为什么不能弱循环引用)
- 拷贝赋值怎么做的?自检、旧控制块释放(包含自减)、swap、新控制块计数增加。
- 释放过程怎么做的:自减,为0删除指针、计数
- 构造、拷贝构造、拷贝赋值,引用块自增。
- 析构,引用块自减。
- 弱引用计数有啥用?延长生命周期。share拷贝weak不会+,weak拷贝weak、shared才会加+。
- 定制删除器:存在del的参数,底层是函数包装器function,支持仿函数、lambda、函数指针操作。
- boost本来有智能指针数组的,不过c11没加,所以提供了这个
- 方便实现delete【】。
- (超进阶)代码实现(尽量能讲STL源码):线程安全保障,
- shared_ptr循环引用解决:weak,手动释放指针打破,鸵鸟策略。
- weak_ptr单独使用:另一引用被销毁时,weak_ptr自动失效,避免野指针。
- shared_ptr线程安全问题:
- (进阶)unique_ptr实现,shared_ptr 实现
- (补充)一个引擎如何将智能指针改成自己的指针:别名功能,符号重构
- 智能指针内存泄漏场景
3.⭐⭐⭐ new/delete、malloc/free,STL二级分配¶
malloc和free是库函数,需要头文件支持
- malloc:可用内存块(结构体:可用标识+size+前一块大小+空闲链表指针)与空闲链表。遍历链表找不到则请求延时,整理内存片段合并。内存分配成果返回==void*
,失败返回NULL
- 小内存brk(小于等于128kb),大内存直接mmap映射独立内存页。剩下的部分切成新空闲块加入链表。
- free:通过当前指针参数-内存块结构体大小获取内存块指针,设置为可用并释放size大小的内存。
new和delete是C++关键字,是操作运算符,支持operator重载实现:
- new运算符:
1.operator new
:(可单独拿出来使用,不)自动计算内存,调用malloc,失败返回bad_alloc()异常,成功则严格的返回指针==void*
2.调用构造函数:获取内存指针,通过placement new
机制调用构造函数
- nothrow new:不返回badalloc异常,而是nullptr
- delete的过程:调用析构,operator delete
调用free()
删除对应的指针
- new[]
:会多分配8的内存,记录数组长度n
- delete[]
:根据记录的n值,逐个调用析构。误用delete会造成内存泄漏
- 什么时候使用delete this?主动释放资源,完成某个特定任务后销毁自身,异步回调
- 注意:必须在堆上分配、必须是最后操作、避免析构调用、确保该对象不会再被访问
- 成员函数delete this的后果?
STL的两级分配器:小于128B内存池(链表管理)技术,大于128Bmalloc()。 C++的内存池技术:优化内存碎片问题,针对小对象,申请一定数量内存块(通常8B),构成链表。申请后改内存块从空闲链表去除。
4.⭐⭐⭐ STL数据结构¶
Vector¶
- push_back
- emplaca_back:万能引用,完美转发,容器数据获取,迭代器判断,扩容判断,内部断言判断,编译期条件分支选择(无异常且默认构造,扩展区域内存保护并使用自定义构造),分配内存,传入指针和对象进行构造,迭代器失效,设置模版类型的返回值,迭代器移动,返回返回值。
- vector扩容reserve:memcpy,memmove。
map和set 红黑树¶
- avl是什么
unordered_map和set 哈希表¶
5.⭐⭐左值右值,移动构造,万能引用,完美转发¶
- 左值:类型--引用,右值引用。
- 右值:值类别--(无地址)纯右值,(无持久地址)将亡值(区分一下右值引用)
- std::move:本身不移动,源码只有静态强制转换为右值引用类型返回,但实际上是将亡值。实际内存操作在移动拷贝和移动赋值中实现
6.⭐⭐类型转换¶
string、char,char{}类型万物互转 隐式转换条件:值类型-低精度转高精度,有符号无符号混合转无符号,赋值表达式,传参和返回值 非基本类型:NULL转任意,任意转void,void转任意 指针/引用: 1.派生转基类(不改变const或volatile属性)2.非常量转为常量 3.类的隐式转换:单参数构造函数创建,Initialize_list多参数构造函数创建。类型转换运算符自定义。 显式转换条件: - const:不能去除变量的常量性,只能去除指针或引用的。移除const和volatile - static:静态转换(编译期确定),基本的隐式转换工作。不然一般编译期会给警告精度损失。指针地址不会改变。 - dynamic:根据RTTI,进行向上,向下(更加的安全),横向(指针偏移)的转换。必须有虚函数。也可以把类转void。 - reinterpret:比特位的简单拷贝和重新解释。如不同类型指针和引用互转,指针整数间互转。
7.⭐函数调用过程¶
堆栈,
8.拷贝构造、移动构造、拷贝赋值、移动赋值代码实现¶
9.⭐Lambda和仿函数¶
- 底层实现?仿函数是什么?
- 值引用、引用,auto&&推导
- 捕获、闭包概念
10.C11 function<>和std::bind和元组std::tuple¶
- function函数包装器:传入返回值和形参
- bind::目标函数,变量,占位符
11.⭐⭐内联函数和宏¶
都可以实现替换的目的 - 引用库#include - 宏定义 # define X Y - 条件编译 inline函数失效 - 代码体积过大(十行) - 递归函数 - 复杂控制流 - 虚函数 - 含指针或引用调用 - 动态库(地址无法确定) 为什么尽量以const、enum、inline替代宏:记号表追踪问题、作用域、取地址问题,函数检查和难以预料的行为。
11.⭐⭐static和全局变量和extern'C'和全局静态。¶
- 全局(+static)静态和全局变量区别:作用域为单个文件
- 函数默认为extern,加了static就不能被外部访问了。头文件不要用
- static不需要初始化,默认为0值。使用时才会分配到内存。
- extern‘C’:使代码按照C语言方式编译,主要是为了区分C++的重载底层函数命名。
- 可以结合动态链接,和C#(Unity)进行相互调用。
- 全局静态变量
- cpp定义全局变量,.h文件extern一下,其他想用只需要引用这个有文件
- 定义一个静态变量,提供get和set函数
- 提供一个类的静态成员变量,直接通过类获得。
12.⭐⭐内存分配和二进制问题¶
- 结构体的大小,union的大小,类的大小,空类大小。
- enum和enumClass(C11)
- 内存对齐的意义
- 无符号整数溢出导致的死循环
- 栈溢出的原因:递归、还有什么?
13.⭐const¶
- const作用于函数:
- 常量指针和指针常量
- 修改方式
- constexpr常量表达式
14.⭐⭐指针和引用区别¶
指针传递:本质是值传递,传递的是地址值的副本,可以多级。 引用:本质为指针常量, - 必须初始化绑定 - 不能更改绑定,对外没有内存地址 - 不能多级。 - 返回值?和值传递的区别?(对象生命周期问题) - 传参相比值传递如何?
15.内存大小、内存对齐、sizeof原理¶
- int类型、long、指针
- 对齐方式
- sizeof原理:编译期计算。
15.⭐内存泄漏的定位¶
16.C++普通函数与成员函数区别¶
普通函数: - 作用域为全局或命名空间,类外定义,访问全局变量或传入参数。 - 无封装性。不依赖对象,独立存在于代码区。无法继承,不支持多态(但可以重载) 成员函数: - 作用域为类,类内定义,类外需要作用域解析符(::)。可访问所有类成员,调用隐含this指针。 - 支持封装,通过对象(.)或对象指针(->)调用(除了静态成员函数)。支持继承多态与重载。 - 代码区仅一份副本,默认内联。
17.auto和decltype¶
18.⭐STL sort实现¶
- 默认快速排序
- 数据量过小:插入排序
- 避免递归深度过深,堆排序
- 能对哪些容器进行排序?
19. emplace_back设计思想¶
20.友元函数,友元类¶
21.Constexpr 编译期常量¶
- 替代#define和const,具备类型安全和作用域
- 强制编译期计算,避免运行时开销,传参到函数直接获得答案替换成常量。(支持模版元编程简化逻辑,允许编译期构造)
- C11单行,C14局部变量与循环,C17编译条件分支,C20允许虚函数和动态类型分配。
- 默认的隐式inline,可以但没必要一起写,关注编译期。inline关注运行时。
22.迭代器是什么¶
- 各容器迭代器的差别
23.volatile是什么?¶
类型修饰符,告诉编译期每次必须取地址,防止一些过度的优化(如循环直接把之前的值拿过来用)导致的问题。