三次握手与四次挥手
三次握手和四次挥手是计算机网络中鼎鼎大名的面试题,今天主播也来回答下 三次握手首先是握手的过程 1.客户端随机生成序列号,然后把序列号seq=x放进SYN报文,发送给服务端,客户端进入syn_sent阶段 2.服务端接收到SYN报文之后,确认ack=x+1,以及自己的seq=y,发送syn+ack报文,服务端进入syn_rcvd阶段 3.客户端收到syn+ack报文之后,将ack=y+1,发送ack报文,自身进入established阶段 4.服务端收到ack报文,也进入established阶段 tips:第三次握手是可以携带数据的,前两次不可以(因为通信能力和连接还未确定可靠,而第三次携带一些数据可以减少RTT次数,比如发送http请求) 那么为什么需要三次握手呢?1. 确保双向通信能力TCP 是全双工协议,允许数据在客户端和服务器之间双向传输。三次握手通过以下步骤验证双方的发送和接收能力: 第一次(SYN):客户端发送 SYN 包(seq=x),证明客户端能发送数据,服务器能接收。 第二次(SYN-ACK):服务器响应...
从源文件到可执行文件(编译之路)
从源文件到可执行文件(编译之路)被面试官狠狠拷打的亡羊补牢罢了 编译过程C++程序从源代码到可执行文件需要经过四个主要阶段: 预处理(Preprocess) 编译(Compilation) 汇编(Assembly) 链接(Linking) 预处理(Preprocess)这一步由预处理器完成,对源程序中的伪指令(以#开头的指令)和特殊符号进行处理。主要工作包括: 将所有的#define删除,并进行宏展开 处理所有条件编译指令,如#if、#ifdef、#ifndef、#else、#elif、#endif等 处理#include预编译指令,将被包含的头文件内容插入该位置,递归处理多重包含 处理其他宏指令,包括#error、#warning、#line、#pragma 删除所有注释(C++的//,C语言的/**/),通常用空格替代 添加行号和文件标识,便于调试和错误定位 保留所有的#pragma编译器指令 处理预定义的宏:如__DATE__、__FILE__等 预处理后的文件通常有.i或.ii(C++)扩展名,可以通过g++ -E source.cpp -o...
静态编译与动态编译
静态编译和动态编译是两种编译的形式,静态编译简单来说就是全部编译完毕,编译器将代码直接编译成二进制码,链接器把静态库(.a)编译进去。而动态编译也叫延时编译,只将代码编译成中间码,就是将编译过程延迟到程序运行时进行。 相比较而言,两种编译各有优点,c++使用较多的是静态编译 1. 静态编译原理 编译器(如 GCC、Clang)将源代码(C/C++ 等)翻译为目标机器的二进制代码。 链接器将目标代码与静态库(.a 文件)链接,生成完整可执行文件。 所有符号解析和地址分配在编译时完成。 优点 性能高效:运行时直接执行机器码,无编译开销,速度快。 独立性强:可执行文件包含所有依赖,运行时无需额外库(除系统级动态库)。 优化充分:编译器可在编译时进行全局优化(如内联、死代码消除)。 可预测性:资源需求固定,适合嵌入式系统。 缺点 文件较大:因包含所有依赖,可执行文件体积大。 不可动态更新:修改代码需重新编译和部署。 兼容性问题:针对特定平台编译,跨平台需重新编译。 应用场景 嵌入式系统:资源受限,需独立运行(如微控制器程序)。 高性能应用:游戏引擎、操作系统内核(如...
宏与inline函数
宏定义 vs 内联函数宏定义(Macro)和内联函数(Inline Function)是 C/C++ 中用于代码复用和性能优化的两种机制,但它们的实现方式、行为和适用场景有显著差异。以下是详细对比,结合 C++ STL 容器(如 std::vector)的上下文,分析两者的定义、原理、优缺点及应用场景。 1. 宏定义 (Macro Definition)定义宏定义是使用预处理器指令 #define 定义的代码片段,由预处理器在编译前进行文本替换。宏可以是简单的常量、表达式或函数式宏。 原理 预处理器操作:在编译前,预处理器将宏调用替换为宏定义的内容,类似于文本“复制粘贴”。 无类型检查:宏是纯文本替换,不涉及语法或类型检查,编译器直接处理替换后的代码。 示例:12#define SQUARE(x) ((x) * (x))int a = SQUARE(5); // 替换为 ((5) *...
STL再探
看了半天的手撕STL最后面试答的依托答辩,真服了,重新看一下吧 C++ STL 容器的底层实现C++ 标准模板库(STL)中的容器是高效、灵活的数据结构实现,用于存储和管理数据集合。STL 容器分为三大类:序列容器、关联容器、无序关联容器,以及一类特殊的容器适配器。 1. 序列容器 (Sequence Containers)序列容器以线性方式存储元素,支持按顺序访问。底层实现通常基于数组或链表。 1.1 std::vector 底层实现:动态数组(Dynamic Array) 元素存储在连续内存中。 使用三个指针管理:begin(指向数组开头)、end(指向最后一个元素的下一位置)、capacity(指向分配内存的末尾)。 当插入元素导致大小超过容量时,重新分配更大的内存(通常按 1.5x 或 2x 增长),复制现有元素,并释放旧内存。 关键特性: 随机访问:O(1)(通过索引)。 末尾插入/删除:均摊 O(1)(可能触发重新分配)。 中间插入/删除:O(n),需移动后续元素。 内存分配使用...
什么是placement_new?
昨天面试完,感觉自己有好多不足啊,亡羊补牢,女娲补天,哈哈哈 在 C++ 中,placement new 是一种特殊的 new 运算符变体,允许在预分配的内存地址上构造对象,而不分配新的内存。与标准 new 不同,placement new 仅负责调用构造函数,不负责分配内存。这使得它在需要精确控制内存分配的场景(如内存池、自定义容器或嵌入式系统)中非常有用。 1. 定义Placement new 是 new 运算符的一种重载形式,其核心思想是将对象的构造与内存分配分离。标准 new 运算符的工作流程是: 通过 operator new 分配内存。 调用对象的构造函数初始化内存。 而 placement new 跳过第一步,直接在用户提供的内存地址上调用构造函数。其标准语法定义在 <new> 头文件中: 1void* operator new(std::size_t size, void* ptr) noexcept; size:要构造的对象大小(通常由编译器自动传递)。 ptr:用户提供的内存地址,用于构造对象。 2. 语法Placement new...
mysql中的索引
最近有点摆烂,两门考试和六级压力有点大。没怎么更新,唉,还有就是女人()。 胡乱投了简历,也约上面试了,开始女娲补天呜呜呜。公司是图数据库方面的,所以主播恶补一下mysql。 众所周知,数据库中的数据是按照表的形式存储的,可以想象成excel表,那么为了方便查询,我们给我们要查询的列,叫做索引,常见的索引一般是id,比如我们就查询id为1的人的姓名,年龄等,比较方便,更可以将id设置为主键(唯一且不为空) 1. 聚簇索引 (Clustered Index) 定义:聚簇索引是表数据的物理存储顺序与索引顺序一致的索引。每个表只能有一个聚簇索引,通常由主键定义。 就是按照索引的顺序存放数据,id从0到1000,数据就这么存放,不看其他的列 特点: 表数据按聚簇索引的键值物理排序存储。 数据和索引存储在同一结构中(通常是B+树)。 查询效率高,尤其是范围查询和主键查找。 用途:用于快速检索数据,适合频繁查询的列(如主键)。 示例:1CREATE TABLE users (id INT PRIMARY KEY, name...
迭代器
...
完美转发
C++ 中的“完美转发”(Perfect Forwarding)。核心含义: 完美转发是指在函数模板中,将接收到的参数以其原本的值类别(左值或右值)和 const/volatile 属性转发给另一个函数。 简单来说,就是让一个“中间”函数(通常是模板函数)能够透明地将参数传递给它调用的“目标”函数,就好像参数是直接传递给目标函数一样,不会因为经过中间函数而改变参数的左值/右值属性或 const/volatile 属性。 为什么需要完美转发? 考虑一个常见的场景:你有一个泛型函数(比如一个包装器、一个工厂函数、一个日志记录函数),它接收任意类型的参数,然后将这些参数传递给另一个实际执行操作的函数。 例如: 123456789101112131415161718192021222324252627282930313233343536373839404142void target_function(int& arg) { std::cout << "Called with lvalue: "...
移动语义的方方面面
主播刚刚考完计算机组成原理哈,然后浅浅荒废了几天的时间,leetcode也没刷,八股也没看,不过计算机组成原理终究是过了,姑且算对计算机有了更深入的一点点了解,过几天的信号与系统和电动力学才是折磨。 碎碎念结束。 ———————————————————————————————————————————————————————————— c++11中引入了一个经常见到的函数,std::move,初次见面是在unique_ptr那一节中,后来在移动构造函数中也有见面,它的作用可以简单的理解为实现将左值转换为右值。 那么我们首先介绍一下什么是左值和右值。 左值和右值左值,可以理解为有内存地址的值,与之相对,右值就是没有内存地址的值。 从硬件上说,左值由内存存储,右值由寄存器存储,所以右值就是马上就要消亡的值,生命周期一般只有所在的那一行代码 常见的右值形式包括: 1.int x = 5;里面的5等常量 2.调用函数的返回值 3.算术表达式或者逻辑表达式 而左值就是我们常说的变量及所有可以被左赋值的值 从而我们得到了左值引用和右值引用 123int a=1;int...