- 博客(55)
- 收藏
- 关注
原创 list
的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。迭代到该位置,在这段位置上迭代需要线性的时间开销;最大的缺陷是不支持任意位置的随机访问,比如:要访问。还需要一些额外的空间,以保存每个节点的相关联信息。个元素,必须从已知的位置。
2026-11-07 20:18:33
274
原创 vector
就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。倍,具体增长多少是根据具体的需求定义的。
2026-11-07 19:50:02
12
原创 Any类简要实现
在C语⾔中,通⽤类型可以使⽤void*来管理,但是在C++中,boost库和C++17给我们提供了⼀个通⽤类型any来灵活使⽤,如果考虑增加代码的移植性,尽量减少第三⽅库的依赖,则可以使⽤C++17特性中的any,或者⾃⼰来实现。Any是经典依靠类型擦除 (Type Erasure)实现,依靠“父类虚函数 + 子类继承”,可以在同一个容器里保存任意类型对象(类似std::any底层原理)。,虚函数会调用对应 placeholder 的 clone,新建一个同类型对象,返回。,只是都继承自 holder。
2026-10-10 15:15:15
204
原创 传输层协议
一旦监听到客户端发来的连接请求(SYN 同步报文段),服务器就将该连接放入内核半连接队列,状态切换为 [LISTEN -> SYN_RCVD],同时向客户端回复 SYN+ACK 报文。如果我们需要传输的数据超过64K,就需要分包, 多次发送并在接收端手动拼装;就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;就会将窗口大小设置成一个更小的值通知给发送端。较粗的实线表示客户端的状态变化情况;可能会收到来自上一个进程的迟到的数据,
2026-09-28 23:34:01
145
原创 自定义协议、序列化与反序列化
连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工。你怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。接收到数据的时候再按照相同的规则把字符串转化回结构体;的序列化和反序列化时,确实存在不同的做法和工具类。实际数据什么时候发,发多少,出错了怎么办,由。其实,协议就是双方约定好的结构化的数据。
2026-09-06 21:53:07
155
原创 TCP socket
之后和这个客户端收发数据 (read/recv/write/send),客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;,所以需要第三个参数 addrlen指定结构体的长度;服务器程序所监听的网络地址和端口号通常是固定不变的。直到与某个客户端建立了连接时才确定下来到底用哪个。就会出现端口号被占用导致不能正确建立连接;引入系统部分的线程池,进行简单的业务处理。而connect 的参数是对方的地址。绑定一个固定的网络地址和端口号;端口号为 SERV_PORT,客户端要连接服务器就会遇到麻烦;
2026-09-01 21:42:32
219
原创 socket udp
在IP数据包头部中有两个IP地址分别叫做源IP地址和目的IP地址端口号(port)是传输层协议的内容端口号是一个2字节16位的整数;端口号用来标识一个进程告诉操作系统当前的这个数据要交给哪一个进程来处理;IP地址端口号能够标识网络上的某一台主机的某一个进程;一个端口号只能被一个进程占用端口号 vs pid。
2026-08-08 20:41:01
200
原创 网络基础一
独立模式计算机之间相互独立;网络互联多台计算机连接在一起完成数据共享;局域网LAN:计算机数量更多了通过交换机和路由器连接在一起;广域网WAN:将远隔千里的计算机都连在一起;所谓局域网和广域网只是一个相对的概念,现在默认一个家庭就是一个局域网。
2026-08-03 23:39:31
163
原创 基于读写锁的读者写者问题
相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。一旦有写者等待资源,后续所有新来的读者全部阻塞排队;必须等所有等待 + 正在执行的写者全部完成后,读者才能继续读。第一个到达的读者需要加锁,阻止写者进入;最后一个读完的读者释放锁,写者才有机会写入。只要有读者正在读,后续新来的读者全都可以插队进入读取;只要有读者正在读,后续新来的读者全都可以插队进入读取;✅ 写者不会饿死,写入响应快 ❌ 大量读者堆积等待,读者饥饿。:正在读的读者数量,初值 = 0。
2026-07-30 23:41:40
224
原创 string模拟实现
这里有一个点:string类的结束条件并不在于'\0',而是在于size的大小,所以,在遇到string的拷贝或扩容时,我们不能用strcpy或strncpy,因为这两个函数的结束条件都是遇到'\0'为之,所以,我们要使用memcpy。以上代码是string类的模拟实现,实现了string的最常用最基本的成员函数。
2026-01-28 21:36:20
223
原创 string
2. string类对象的容量操作 size(重点) 返回字符串有效字符长度 length 返回字符串有效字符长度 capacity 返回空间总大小 empty (重点) 检测字符串释放为空串,是返回true,否则返回false clear (重点)
2026-01-28 21:30:36
985
原创 模板初阶
大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。,那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。2. 代码的可维护性比较低,一个出错可能所有的重载均出错。如果在C++中,也能够存在这样一个。类模板实例化与函数模板实例化不同,如何实现一个通用的交换函数呢?,对于字符类型也是如此。,对于模板函数的使用,
2026-01-23 12:24:07
779
原创 类和对象下
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。首先先初始化a2,但在初始化列表里a2的值取决于a1,为随机值,a1的值为1,所以答案选D。如果B是A的友元,C是B的友元,则不能说明C时A的友元。
2026-01-18 23:40:58
755
原创 类和对象中
而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。
2025-11-26 21:49:01
717
原创 排序算法
1.归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。2.时间复杂度:O(N*logN)3.空间复杂度:O(N)4.稳定性:稳定非比较排序之前的排序算法说到底还是去比较数字的大小,称为比较排序,那么自然也有非比较排序。计数排序:思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。操作步骤:1.统计相同元素出现次数2.根据统计的结果将序列回收到原来的序列中。
2025-11-05 15:46:05
782
原创 链式二叉树
当创建的二叉树不是完全二叉树时,使用数组模拟实现二叉树会造成大量空间的浪费,所以在这里我们可以通过链表来模拟实现二叉树。遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第。学习二叉树结构,最简单的方式就是遍历。分别又称为先根遍历、中根遍历和后根遍历。
2025-08-13 13:08:05
404
原创 栈和队列
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出。进行数据插⼊或删除的⼀端称为栈顶,另⼀端称为栈底。不含元素的栈称为空栈。,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。栈是⼀种只允许在⼀端进行数据插⼊和删除操作的线性表。入队列:进行插入操作的一端称为。出队列:进行删除操作的一端称为。栈的实现一般可以使用。
2025-08-03 14:07:41
251
原创 树和二叉树
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。向下调整建堆效率比较高,从最后一个节点的父亲结点开始往前向下调整建堆,因为最后的左右孩子都可以看作堆,满足向下调整算法。删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。:一个节点含有的子树的根节点称为该节点的子节点;:一棵树中,最大的节点的度称为树的度;
2025-08-03 14:05:03
1004
原创 复杂度讲解
【事前分析法】算法设计好后,根据算法的设计原理,只要问题规模确定,算法中基本语句执⾏次数和需求资源个数基本也就确定了。比如求n− 1 +n,可以设计三种算法:需要开辟⼀个大小为N的空间。不需要开辟空间,直接求和。需要循环n次,ret+ =n语句会执行n次 ,而且随着问题规模的增长,执行次数也会增长。算法执行所需资源的个数与问题规模有关。因此可以根据算法执行过程中对空间的消耗来衡量算法的好坏,这就是空间复杂度。不论问题规模为多少, 语句只会执行1次。
2025-05-24 17:59:43
869
原创 双向链表专题
注意:这⾥的“带头”跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严。带头链表面的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨。谨,但是为了同学们更好的理解就直接称为单链表的头节点。带头:指的是链表中有哨兵位节点,该哨兵位节点即头节点。顺序表和双向链表的优缺点分析。遍历循环链表避免死循环。
2025-05-12 20:01:36
262
原创 单链表专题
图中指针变量 plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。链表中每个节点都是独立申请的(即需要插入数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。当我们想要从第⼀个节点走到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
2025-05-10 14:01:52
371
原创 顺序表专题
数据结构是由“数据”和“结构”两词组合而来。什么是数据?常见的数值1、2、3、4.....、教务系统里保存的用户信息(姓名、性别、年龄、学历等等)、网页里肉眼可以看到的信息(文字、图片、视频等等),这些都是数据。什么是结构?当我们想要大量使用同⼀类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将⼤量的数据组织在⼀起,结构也可以理解为组织数据的方式。想要找到草原上名叫“咩咩”的羊很难,但是从羊圈里找到1号羊就很简单,羊圈这样的结构有效将。
2025-04-23 19:08:55
497
原创 预处理详解
(假定某个程序中声明了⼀个某个长度的数组,如果机器内存有限,我们需要⼀个很小的数组,但是另外⼀个机器内存大些,我们需要⼀个数组能够大些。当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。如果⼯程比较大,有公共使用的头文件,被⼤家都能使用,⼜不做任何的处理,那么后果真的不堪设想。这里我们想想,写⼀个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。⼀个头文件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。
2025-04-19 15:30:32
729
原创 编译和链接
1.翻译环境和运行环境1.翻译环境和运行环境在 ANSI C 的任何⼀种实现中,存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令(⼆进制指令)。第2种是执行环境,它用于实际执行代码。2.翻译环境那翻译环境是怎么将源代码转换为可执行的机器指令的呢?这里我们就得展开讲解⼀下翻译环境所做的事情。其实翻译环境是由编译和链接两个大的过程组成的,而编译又可以分解成:预处理(有些书也叫预编译)、编译、汇编三个过程。⼀个C语言的项目中可能有多个.c 文。
2025-03-14 22:51:02
734
原创 文件操作2
ANSI C 标准采用“缓冲文件系统” 处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每⼀个正在使用的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。的作用是:当文件读取结束的时候,判断读取结束的原因是否是:遇到文件尾结束。⼆进制文件的读取结束判断,判断返回值是否小于实际要读的个数。函数的返回值直接来判断文件的是否结束。fgetc 判断是否为。
2025-03-13 22:47:13
395
原创 文件操作
每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进⾏持久化的保存,我们可以使用文件。
2025-03-12 17:49:28
997
原创 动态内存管理
1.为什么要有动态内存分配1.为什么要有动态内存分配但是上述的开辟空间的⽅式有两个特点:空间开辟大小是固定的。数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满足了。C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。2.malloc和free2.1malloc。
2025-03-10 20:33:40
2042
原创 ⾃定义类型:联合和枚举
上述的结构其实设计的很简单,⽤起来也⽅便,但是结构的设计中包含了所有礼品的各种属性,这样使得结构体的⼤⼩就会偏⼤,⽐较浪费内存。所以我们就可以把公共属性单独写出来,剩余属于各种商品本⾝的属性使⽤联合体起来,这样就可以介绍所需的内存空间,⼀定程度上节省了内存。联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩(因为联合⾄少得有能⼒保存最⼤的那个成员)。联合体的特点是所有成员共⽤同⼀块内存空间。代码1输出的三个地址⼀模⼀样,代码2的输出,我们发现将i的第4个字节的内容修改为55了。
2025-03-09 13:24:06
462
原创 自定义类型:结构体
1.结构体类型的声明1.1结构体回顾结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。1.1.1结构的声明1.1.2结构体变量的创建和初始化1.2结构的特殊声明在声明结构的时候,可以不完全的声明。上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。警告:编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。1.3结构的⾃引⽤。
2025-03-08 19:42:27
1001
原创 数据在内存中的存储
1.整数在内存中的存储1.整数在内存中的存储在讲解操作符的时候,我们就讲过了下⾯的内容:整数的2进制表⽰⽅法有三种,即有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是,最⾼位的⼀位是被当做符号位,剩余的都是数值位。正整数的原、反、补码都相同。原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。补码:反码+1就得到补码。(补码取反+1得到原码)对于整形来说:数据存放内存中其实存放的是⼆进制的补码。为什么呢?
2025-03-03 19:39:37
1614
原创 C语言内存函数
1.memcpy使用和模拟实现1.memcpy使用和模拟实现函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到'\0'的时候并不会停下来。如果source和destination有任何的重叠,复制的结果都是未定义的。对于重叠的内存,交给memmove来处理。这个函数不在乎内存中存放的是什么数据,不同于strcpy是针对字符串的。1、memcpy的模拟实现2.memmove使用和模拟实现。
2025-03-01 19:13:24
267
原创 字符函数和字符串函数2
这个头⽂件中说明的,C语⾔程序启动的时候就会使⽤⼀个全局的变量 errno 来记录程序的当前错误码,只不过程序启动 的时候errno是0,表⽰没有错误,当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会将对应的错误码,存放在 errno 中,⽽⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串⼀般都是临时拷⻉的内容并且。可能是0 1 2...,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标。
2025-03-01 15:45:51
301
原创 字符函数和字符串函数1
C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的。这些函数的使⽤都需要包含⼀个头⽂件是ctype.h在这里介绍一下islower函数,其他的非常相似。islower是能够判断参数部分的c是否是⼩写字⺟的。通过返回值来说明是否是⼩写字⺟,如果是⼩写字⺟就返回⾮0的整数,如果不是⼩写字⺟,则返回 0。练习:写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。是小写返回非0,执行下面那条转大写(即ASCII码值-32)的语句。
2025-02-28 21:14:09
672
原创 深入理解指针6
在学习操作符的时候,已经学习了sizeof,sizeof计算的是变量所占内存空间的大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。
2025-02-25 13:59:43
890
原创 深入理解指针5
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
2025-02-18 16:38:10
181
原创 深入理解指针4
在指针变量中我们知道有一种指针类型为字符指针char*代码const char* p = "abcdef";特别容易让同学们以为是把字符串abcdef放到字符指针p中,但是本质是把字符串首字母的地址放到了p中,在上面就是a的地址。
2025-02-02 15:08:39
983
原创 深入理解指针3
这里我们使用&arr[0]拿到了数组第一个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址。如果数组名是首元素地址,那上面的代码应该如何理解?输出结果是40,如果arr是地址名,那输出的应该是4或8才对。其实数组名是数组首元素的地址是对的,但是有两个例外。sizeof(数组名)sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。&数组名 这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
2025-01-31 20:43:55
756
原创 深入理解指针2
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量,而const可以使变量不可被修改(在C语言中是变量,但具有常属性,在C++中是常量)。此时我们不能直接对n修改,而绕过n,使用n的地址去修改n就可以做到,虽然这样打破了语法规则。
2025-01-30 15:26:33
755
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅