心口如一,犹不失为光明磊落丈夫之行也。——梁启超
😏1. 语言基础
内存分配
代码区:存放程序的二进制代码
常量存储区:存储常量,一般不能改
全局/静态存储区:分为初始化和未初始化的两个相邻区域
堆:开发者管理,需要手动 new malloc delete free 进行内存分配与回收,可能会出现内存泄漏和空闲碎片的情况
指针参数传递和引用参数传递
指针参数传递本质是值传递,传递一个地址值;而引用传递传递的是实参变量的地址
指针传递可以改变其指向的对象,而引用对象不能被修改
四种强制转换
包括:static_cast, dynamic_cast, const_cast, reinterpret_cast
面向对象的三大特性并举例
封装:就是把客观事物封装成抽象的类,并且类可以把⾃⼰的数据和⽅法只让信任的类或者对象操作,对不可信的进⾏信息隐藏。⼀个类就是⼀个封装了数据以及操作这些数据的代码的逻辑实体。在⼀个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种⽅式,对象对内部数据提供了不同级别的保护,以防⽌程序中⽆关的部分意外的改变或错误的使⽤了对象的私有部分。
继承:是指可以让某个类型的对象获得另⼀个类型的对象的属性的⽅法。它⽀持按级分类的概念。继承是指这样⼀种能⼒:它可以使⽤现有类的所有功能,并在⽆需重新编写原来的类的情况下对这些功能进⾏扩展。通过继承创建的新类称为“⼦类”或者“派⽣类”,被继承的类称为“基类”、“⽗类”或“超类”。继承的过程,就是从⼀般到特殊的过程。要实现继承,可以通过“继承”和“组合”来实现。
继承概念的实现⽅式有两类:
-实现继承:实现继承是指直接使⽤基类的属性和⽅法⽽⽆需额外编码的能⼒。
-接⼝继承:接⼝继承是指仅使⽤属性和⽅法的名称、但是⼦类必需提供实现的能⼒。
多态:就是向不同的对象发送同⼀个消息,不同对象在接收时会产⽣不同的⾏为(即⽅法)。即⼀个接⼝,可以实现多种⽅法。
多态与⾮多态的实质区别就是函数地址是早绑定还是晚绑定的。如果函数的调⽤,在编译器编译期间就可以确定函数的调⽤地址,并产⽣代码,则是静态的,即地址早绑定。⽽如果函数调⽤的地址不能在编译器期间确定,需要在运⾏时才确定,这就属于晚绑定
#define 和别名 typedef 的区别
执⾏时间不同, typedef 在编译阶段有效, typedef 有类型检查的功能;#define是宏定义,发⽣在预处理阶段,不进⾏类型检查;功能差异, typedef ⽤来定义类型的别名,定义与平台⽆关的数据类型,与 struct的结合使⽤等。
#define 不只是可以为类型取别名,还可以定义常量、变量、编译开关等。 作⽤域不同,#define 没有作⽤域的限制,只要是之前预定义过的宏,在以后的程序中都可以使⽤。 ⽽ typedef 有⾃⼰的作⽤域。
struct和class的区别
在 C 语言中,结构体(struct)默认是没有构造函数的,需要使用初始化函数或赋值语句给结构体变量成员赋值。
而在 C++ 中,结构体也可以像类一样拥有构造函数和析构函数。例如:
// 声明一个结构体
struct Person {
std::string name;
int age;
// 构造函数
Person(std::string n, int a) : name(n), age(a) {}
// 析构函数
~Person() {}
};
int main() {
// 创建结构体变量并初始化
Person p1("张三", 20);
return 0;
}
在 C++ 中,结构体和类的区别已经很小了,它们之间的主要区别在于默认访问权限(struct 的默认访问权限为 public,而 class 的默认访问权限为 private),以及继承时的默认继承方式(struct 的默认继承方式为 public,而 class 的默认继承方式为 private)等。因此,在 C++ 中,我们可以将结构体作为一种类来看待。
++i 和 i++ 的区别
前置加加不会产⽣临时对象,后置加加必须产⽣临时对象,临时对象会导致效率降低。
++i实现:
int& int::operator++ (){
*this +=1;
return *this;
}
i++实现:
const int int::operator(int) {
int oldValue = *this;
++(*this);
return oldValue;
}
⼤端⼩端模式
⼤端模式:是指数据的⾼字节保存在内存的低地址中,⽽数据的低字节保存在内存的⾼地址端。
⼩端模式,是指数据的⾼字节保存在内存的⾼地址中,低位字节保存在内存的低地址端。
直接读取存放在内存中的⼗六进制数值,取低位进⾏值判断:
int a = 0x12345678;
int *c = &a;
c[0] == 0x12 ⼤端模式
c[0] == 0x78 ⼩段模式
回调函数的作⽤
当发⽣某种事件时,系统或其他函数将会⾃动调⽤你定义的⼀段函数。
回调函数就相当于⼀个中断处理函数,由系统在符合你设定的条件时⾃动调⽤。为此,你需要做三件事:1、声明;2、定义;3、设置触发条件,就是在你的函数中把你的回调函数名称转 化为地址作为⼀个参数,以便于系统调⽤。
左值引用与右值引用的区别?右值引用的意义?
左值引用(T&):绑定到具名变量(左值),可以修改其值。例如:int x = 10; int& ref = x;。
右值引用(T&&):绑定到临时对象(右值),通常用于实现移动语义和完美转发。例如:int&& temp = 20;。
右值引用的意义:
移动语义:通过右值引用,可以将资源的所有权从一个对象转移到另一个对象,而不是复制资源,从而提高性能。例如:std::vector 的移动构造函数和移动赋值操作符。
完美转发:结合 std::forward,右值引用帮助实现函数模板中传递参数的原样转发,无论是左值还是右值。
在C++11用过哪些特性?
自动类型推导(auto):简化了变量声明。
范围基于循环(range-based for):更简洁地遍历容器。
智能指针(std::unique_ptr 和 std::shared_ptr):管理动态内存。
右值引用(T&&)和移动语义:提升性能,减少不必要的复制。
线程支持(std::thread):基础线程库。
lambda 表达式:定义匿名函数对象。
constexpr:编译时常量计算。
初始化列表({}):统一初始化语法。
nullptr:类型安全的空指针常量。
静态断言(static_assert):编译时检查条件。
讲讲C++开发经常用到的4种层式结构、B+树、时间轮、跳表、LSM-Tree
B+树:
结构:自平衡的树数据结构,所有叶子节点在同一层,内部节点作为索引。
特点:支持高效的范围查询和排序操作。常用于数据库索引和文件系统。
优点:查询、插入和删除操作的时间复杂度为 O(log N),适合大规模数据存储和检索。
时间轮:
结构:一个圆形的时间轮,每个槽代表一个时间段。通过轮转槽来处理定时任务。
特点:高效的定时任务调度机制,适合处理大量的定时事件。
优点:能够在 O(1) 时间内处理定时任务,减少了复杂度。
跳表:
结构:一种多层级的链表,每一层都是前一层的子集,能够快速跳过不必要的节点。
特点:提供类似于平衡树的性能但实现较简单。
优点:查询、插入和删除操作的时间复杂度为 O(log N),实现相对简单,适用于内存中的数据结构。
LSM-Tree(Log-Structured Merge Tree):
结构:结合了内存和磁盘存储,数据首先写入内存中的内存表(MemTable),然后批量合并到磁盘中的 SSTables(Sorted String Tables)。
特点:优化了写操作性能,适合写密集型应用。
优点:写操作高效,支持高吞吐量的写入,适合大规模数据存储和检索,如在现代数据库和日志系统中应用广泛。
请简述智能指针种类以及使用场景
智能指针是 C++ 中用来自动管理动态分配内存的工具,避免了手动管理内存可能带来的问题(如内存泄漏和悬空指针)。C++ 标准库提供了几种常用的智能指针类型,主要包括 std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
std::unique_ptr 是一种拥有唯一所有权的智能指针,指向一个动态分配的对象。它不允许多个 unique_ptr 实例指向同一个对象,也不支持拷贝操作,但可以进行移动操作。
std::shared_ptr 是一种共享所有权的智能指针。多个 shared_ptr 实例可以指向同一个对象,引用计数机制会跟踪有多少 shared_ptr 实例指向该对象,当最后一个 shared_ptr 被销毁时,对象才会被释放。
std::weak_ptr 是一种弱引用智能指针,用于打破 std::shared_ptr 之间的循环引用问题。它不影响引用计数,因此不会阻止对象的销毁。
C++的new delete 和 malloc free的区别
new 和 delete:更适用于 C++,提供了对象构造和析构的支持,更加安全和高效。
malloc 和 free:更适用于 C 语言,处理原始内存分配,不适合 C++ 对象的管理。
C++的static的作用
静态局部变量:在函数调用之间保持其值,并且仅初始化一次。
静态成员变量:属于类而不是类的实例,所有实例共享。
静态成员函数:属于类而不是类的实例,可以通过类名直接调用。
静态全局变量和函数:作用域限制在定义它们的文件内,防止命名冲突。
git rebase
git rebase 主要用于将一系列的提交(commit)应用到另一个分支的基础上,从而改变提交的历史。与 git merge 不同,git rebase 会生成新的提交历史,而 git merge 则会保留所有原有的提交历史。
Protobuf的向后兼容
兼容性设计:protobuf 设计支持向后和向前兼容性,这意味着你可以在不破坏现有应用程序的情况下,逐步演化数据结构。这使得协议的演进变得更加灵活和可管理。
字段的添加和删除:你可以在消息中添加新字段而不会影响旧的客户端或服务器,因为 protobuf 会根据字段编号来处理数据,允许旧的代码忽略未知字段。
支持 RPC 框架:protobuf 是许多 RPC 框架的首选序列化格式,例如 gRPC,它本身就是基于 protobuf 的。这样的集成简化了 RPC 实现,使得系统的设计和实现更加一致。
😊2. 标准库
STL介绍
STL ⼀共提供六⼤组件,包括容器,算法,迭代器,仿函数,配接器和配置器,彼此可以组合套⽤。容器通过配置器取得数据存储空间,算法通过迭代器存取容器内容,仿函数可以协助算 法完成不同的策略变化,配接器可以应⽤于容器、仿函数和迭代器。
1.容器: 各种数据结构,如 vector, list, deque, set, map,⽤来存放数据, 从实现的⻆度来讲是⼀种类模板。
2.算法: 各种常⽤的算法,如 sort(插⼊,快排,堆排序), search(⼆分查找), 从实现的⻆度来讲是⼀种⽅法模板。
3.迭代器: 从实现的⻆度来看,迭代器是⼀种将 operator*,operator->,operator++,operator–等 指针相关操作赋予重载的类模板,所有的 STL 容器都有⾃⼰的迭代器。
4.仿函数:从实现的⻆度看,仿函数是⼀种重载了operator()的类或者类模板。 可以帮助算法实 现不同的策略。
5.配接器:⼀种⽤来修饰容器或者仿函数或迭代器接⼝的东⻄。
6.配置器:负责空间配置与管理,从实现的⻆度讲,配置器是⼀个实现了动态空间配置、空间管理,空间释放的类模板。
频繁调⽤ push_back() 的影响
向 vector 的尾部添加元素,很有可能引起整个对象 存储空间的重新分配,重新分配更⼤的内 存,再将原数据拷⻉到新空间中,再释放原有内存,这个过程是耗时耗⼒的,频繁对 vector 调⽤ push_back()会导致性能的下降。 在 C++11 之后, vector容器中添加了新的⽅法: emplace_back() ,和 push_back() ⼀样的是都是在容器末尾添加⼀个新的元素进去,不同的是emplace_back() 在效率上相⽐ 较于 push_back() 有了⼀定的提升。 emplace_back() 函数在原理上⽐push_back() 有了⼀定的改进,包括在内存优化⽅⾯和运⾏效率⽅⾯。内存优化主要体现在使⽤了就地构造(直接在容器内构造对象,不⽤拷⻉⼀个 复制品再使⽤)+强制类型转换的⽅法来实现,在运⾏效率⽅⾯,由于省去了拷⻉构造过程, 因此也有⼀定的提升。
vector中的push_back()和emplace_back()的区别、以及使用场景
push_back() 适用于已经存在的对象或需要先构造对象的情况。如果对象已经在其他地方构造完成并且可以拷贝或移动,它是简单直接的选择。
emplace_back() 适用于直接构造复杂对象或避免拷贝/移动的场景。它可以提高性能,特别是当你向 vector 中添加大量对象时,因为它省去了构造后拷贝的开销。
使用 emplace_back() 可以减少不必要的拷贝开销,并且对于构造过程复杂的对象,它能够更有效率。
😆3. 四件套
TCP三次握⼿和挥⼿
三次握⼿过程:
第⼀次:客户端发含SYN位, SEQ_NUM = S的包到服务器。(客-> SYN_SEND)
第⼆次:服务器发含ACK,SYN位且ACK_NUM = S + 1, SEQ_NUM = P的包到客户机。(服-> SYN_RECV)
第三次:客户机发送含ACK位, ACK_NUM = P + 1的包到服务器。(客 -> ESTABLISH,服 -> ESTABLISH)
四次挥⼿过程:
第⼀次:客户机发含FIN位, SEQ = Q的包到服务器。(客 -> FIN_WAIT_1)
第⼆次:服务器发送含ACK且ACK_NUM = Q + 1的包到服务器。(服-> CLOSE_WAIT,客-> FIN_WAIT_2)此处有等待
第三次:服务器发送含FIN且SEQ_NUM = R的包到客户机。(服-> LAST_ACK,客-> TIME_WAIT)此处有等待
第四次:客户机发送最后⼀个含有ACK位且ACK_NUM = R + 1的包到客户机。(服-> CLOSED)
线程⽐进程具有哪些优势
1)线程在程序中是独⽴的,并发的执⾏流,但是,进程中的线程之间的隔离程度要⼩;
2)线程⽐进程更具有更⾼的性能,这是由于同⼀个进程中的线程都有共性:多个线程将共享同⼀个进程虚拟空间;
3)当操作系统创建⼀个进程时,必须为进程分配独⽴的内存空间,并分配⼤量相关资源;
😆4.有目的的刻意练习
- 定义明确的目标
- 练习过程保持专注(归类整理、思考迁移)
- 获取反馈(无论是正的还是负的)
- 走出舒适区,在实际场景中验证知识或能力(学以致用)
😆5. 类设计与实现最佳实践
- 尽可能尝试使用新的C++标准
- 使用命名空间模块化代码
- 抽象:仅向外部世界提供关于数据的基本信息,隐藏背景细节或实现
- 类越小越好:具有多行代码的类型应该被划分为一组较小的类型
- 每个类尽量提供最少的方法
- 加强低耦合:低耦合可以通过使用抽象类或泛型类和方法来实现
- 加强高内聚
- 只注释代码不能表达的内容
- 尽量不要用重复的代码
- 不变性有助于多线程编程
以上。