245 - BM25
一、BM25 算法核心原理:从公式到 C++ 实现关注点BM25(Best Matching 25)是基于概率检索模型的改进算法,核心是在 TF-IDF 的基础上增加文档长度归一化和参数可调性,解决 “长文档过度匹配” 的问题。理解原理时,需重点关注与 C++ 实现强相关的设计点。 1.1 核心公式与参数意义BM25 的单术语 - 文档相关性评分公式如下: $score(q, d) = IDF(q) \times \frac{TF(q, d) \times (k_1 + 1)}{TF(q, d) + k_1 \times (1 - b + b \times \frac{len(d)}{avg_len})}$ 其中关键参数与 C++ 实现的关联的: $TF(q,d)$在文档$d$中的词频,需存储在倒排索引中,用float类型平衡精度与内存; $len(d)$文档$d$的长度(术语数),需在文档元数据中记录,用uint32_t节省内存; $avg_len$所有文档的平均长度,预处理阶段计算后全局缓存,避免重复计算; $k$词频饱和系数(通常取...
C++ 实现 JWT 工具类封装
一、JWT概念JWT(JSON Web Token)是一种用于在网络上安全传输信息的紧凑、自包含的方式。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),通过点分隔的字符串形式呈现。在身份验证和信息交换场景中应用广泛,因为它可以验证信息的完整性和真实性。 1.1 准备工作在开始之前,我们需要确保系统中安装了libjwt库,这是一个轻量级的 JWT 实现库。在 Ubuntu/Debian 系统上,可以使用以下命令安装: 1sudo apt-get install libjwt-dev 1.2 jwt.cc1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889#include <jwt.h>#include...
双栈 TCP 回声服务器 -- v3.0
要将双栈 TCP 回声服务器从线程池模式改造为Reactor 模式,核心是基于I/O 多路复用(epoll) 实现 “事件驱动” 的高效 I/O 处理 —— 通过单线程(或主线程)监听多个 Socket 的 I/O 事件,事件触发时再分发到对应处理器,避免线程上下文切换开销,更适合高并发场景。 一、Reactor 模式核心原理与组件Reactor 模式(反应器模式)是一种事件驱动架构,核心思想是 “将 I/O 事件从业务逻辑中分离”,通过以下组件实现: 组件 作用 Reactor(反应器) 核心调度器:运行事件循环,通过 I/O 多路复用(epoll)等待事件,分发事件到处理器 EventDemultiplexer 事件多路分离器:封装 epoll,负责注册 / 删除事件、等待事件触发(本文用 epoll) EventHandler(处理器) 事件处理接口:定义handleRead/handleWrite/handleError等统一接口,子类实现具体逻辑(如...
简单echo服务器 -- v2.0
在原有双栈兼容基础上,新增线程池替代进程、文件日志、超时处理、自定义协议四大核心优化,解决进程开销高、排查难、资源占用、粘包等问题,适用于高并发场景。 一、核心优化方案设计 优化功能 实现思路 线程池优化 设计固定大小线程池(基于pthread),复用线程处理客户端连接,避免频繁创建销毁进程的开销 文件日志系统 实现线程安全的日志类,记录时间戳、日志级别、事件详情,写入本地文件(如echo_server.log) 超时处理 通过setsockopt设置SO_RCVTIMEO/SO_SNDTIMEO,为recv/send设置超时(默认 5 秒) 自定义协议 定义 “4 字节长度头 + 数据” 格式,解决 TCP 粘包问题(长度头用网络字节序传输) 二、通用工具类实现(日志 + 线程池)1....
timefd定时器封装
导言在 Linux 系统开发中,定时器是一个非常常见的需求。除了传统的setitimer、alarm等接口,Linux 还提供了一种基于文件描述符的定时器机制 ——timerfd。这种机制将定时器事件转化为文件描述符的可读事件,非常适合与 I/O 多路复用(如poll、epoll)结合使用。 一、简介timerfd是 Linux 内核 2.6.25 版本后引入的接口,它将定时器功能抽象为一个文件描述符:当定时器到期时,该文件描述符会变为可读状态,我们可以通过read操作获取到期次数,从而处理定时事件。 相比传统定时器,timerfd的优势在于: 可以无缝集成到 I/O...
简单echo服务器 -- IPv4/IPv6 双栈兼容
一、核心技术:IPv4/IPv6 双栈兼容的关键设置要实现双栈兼容,需理解四个核心概念:hints.ai_family=AF_UNSPEC、hints.ai_flags=AI_PASSIVE、getaddrinfo函数、INET6_ADDRSTRLEN宏。它们共同解决了 IPv4 与 IPv6 协议差异带来的适配问题。 1. hints.ai_family = AF_UNSPEC:协议无关的地址解析hints是getaddrinfo的查询条件结构体,ai_family指定地址族(协议类型): AF_INET:仅解析 IPv4 地址(对应struct sockaddr_in); AF_INET6:仅解析 IPv6 地址(对应struct sockaddr_in6); AF_UNSPEC:不限制协议,同时解析 IPv4 和 IPv6 地址。 为什么选AF_UNSPEC 现代服务器需同时响应 IPv4 和 IPv6...
getaddrinfo 查找网络IP
一、网络地址解析的必要性在网络通信中,应用程序通常需要通过域名(如www.example.com)而非直接使用 IP 地址(如93.184.216.34)来定位目标主机,同时需要通过服务名(如http)而非直接使用端口号(如80)来指定通信端口。这种抽象带来了以下核心需求: 用户友好性:人类更容易记忆域名而非数字 IP 协议兼容性:需同时支持 IPv4 与 IPv6,避免硬编码地址类型 服务灵活性:服务端口可能动态分配,通过服务名解析更可靠 网络拓扑适应:同一域名可能对应多个 IP(负载均衡场景),需支持多地址选择 传统地址解析方法(如gethostbyname、getservbyname)存在明显局限性:仅支持 IPv4、无法统一处理主机名与服务名、线程安全性差。getaddrinfo作为 POSIX 标准接口,完美解决了这些问题,是现代 C++ 网络编程的首选地址解析方案。 二、getaddrinfo 函数getaddrinfo是一个统一的地址解析接口,可同时处理主机名→IP 地址、服务名→端口号的解析,并返回可直接用于socket调用的地址结构。 2.1...
C++ 断言(assert)机制
一、assert 宏的基本语法与工作机制断言是 C++ 标准库提供的调试工具,核心通过头文件(兼容 C 的<assert.h>)中的assert宏实现,其本质是条件检查宏,仅在调试阶段生效。 1.1 核心语法assert宏接收一个布尔表达式作为参数,语法如下: 12345678910111213#include <cassert> // 必须包含的头文件int main() { int* ptr = new int(10); // 检查指针是否非空(调试阶段生效) assert(ptr != nullptr); delete ptr; ptr = nullptr; // 此时断言会失败(指针已置空) assert(ptr != nullptr); return 0;} 1.2 工作机制与 NDEBUG 宏assert的行为完全由 **NDEBUG宏 **("No Debug")控制,这是 C++...
基于Trie树的词频统计与前缀匹配
一、为什么需要 Trie 树?—— 先搞懂核心价值在开始写代码前,我们先明确 Trie 树的 “不可替代性”: 数据结构 插入 / 查询复杂度 前缀匹配能力 内存效率(重复前缀) 适用场景 哈希表(unordered_map) 平均 O (1) 不支持 低(存完整字符串) 单键精准查询(如缓存) 红黑树(map) O(log n) 支持(遍历) 低 有序键值对查询 Trie 树 O (k)(k 为字符串长度) 原生支持 高(前缀共享) 前缀相关操作(自动补全) 简单说:如果你的需求涉及 “前缀”(如输入 “app” 要提示 “apple”“application”),Trie 树是最优解之一。 本文实现的 Trie 树将包含以下核心功能: 单词插入(自动统计重复单词的出现次数) 词频查询(返回单词出现次数,0 表示不存在) 前缀匹配(返回所有以指定前缀开头的单词,支持字典序 / 词频排序) 单词删除(智能回收无用节点,不破坏共享前缀) 整体清空(安全释放所有内存,避免泄漏) 二、代码结构设计 ——...
Trie 树核心原理
一、Trie 树定位Trie 树(前缀树 / 字典树)是专为字符串设计的树形结构,核心价值是通过 “前缀共享” 减少内存冗余,同时实现 O (k)(k 为字符串长度)的插入 / 查询效率,尤其适合 “前缀相关场景”(如自动补全、拼写检查)。 二、核心结构:节点设计与前缀共享1. 节点结构体Trie 的最小单元是节点,需存储子节点映射和单词结尾标记,C++ 中用结构体实现最简洁: 123456789struct TrieNode { // 子节点:两种实现方案(按需选择) // 方案1:数组(仅适用于固定小字符集,如小写字母,速度快) TrieNode* children[26] = {nullptr}; // 方案2:哈希表(字符集不确定时用,如含数字/符号,灵活) // unordered_map<char, TrieNode*> children; bool isEnd = false; //...
C/C++ 中两种结构体 typedef 定义的差异与实践
导言在 C 和 C++ 编程中,结构体(struct)是组织复杂数据的核心工具,而 typedef 则常用于简化类型名、提升代码可读性。实际开发中,我们常会见到两种结构体 + typedef 的定义方式:typedef struct Person{} Person; 与 typedef struct {} Person;。这两种写法看似相似,却因语言特性(C/C++ 差异)和结构体标签(tag)的存在与否,在使用场景、功能限制上有显著区别。 一、基础认知:结构体标签与 typedef 的作用在深入差异前,需先明确两个核心概念: 结构体标签(tag):紧跟 struct 后的标识符(如 struct Person 中的 Person),是结构体的 “原生名称”,仅在 struct 关键字后生效。 typedef 别名:通过 typedef 为类型(包括结构体)定义的简化名称(如 Person),可直接作为类型名使用。 C 和 C++ 对 “结构体标签” 的处理规则不同,这是两种定义方式差异的根源 ——C 语言中,结构体必须通过 struct 标签 或...
C++/MySQL/Redis 锁机制 - 1
导言在并发编程与分布式系统中,锁机制是保障数据一致性的核心技术。不同技术栈因运行环境(本地进程 / 数据库 / 分布式集群)差异,锁的实现逻辑、核心特性与适用场景存在显著区别。 一、C++ 锁机制:本地进程内的并发控制C++ 作为系统级编程语言,其锁机制基于操作系统内核态同步原语(如互斥量、信号量)与用户态原子操作实现,核心解决单进程内多线程共享内存的线程安全问题。C++11 及以后通过 <thread>、 <mutex>、 <atomic> 等标准库提供统一锁接口,同时支持自定义锁实现。 1. 互斥锁(std::mutex):C++ 基础悲观锁核心定义C++ 标准库中的基础悲观锁,通过操作系统互斥量(Mutex)实现,保证同一时间只有一个线程进入临界区,其他竞争线程会阻塞等待,直到锁释放。是解决本地线程并发冲突的 “通用方案”。 底层实现 依赖操作系统内核态同步原语(如 Linux 的 pthread_mutex_t、Windows 的...
std::tuple 的使用
一、tuple 核心定位与基本特性std::tuple(定义于 头文件)是 C++17 标准库中用于打包多个异构数据类型的轻量级容器,其核心价值在于: 无需定义自定义结构体 / 类,即可承载任意数量的不同类型数据; 配合 C++17 新特性(如类模板参数推导 CTAD、结构化绑定),大幅简化异构数据的创建与访问; 无动态内存分配,内存开销与手动定义的结构体相当,性能高效。 关键区别: 与 std::array:array 仅支持同构类型(如 array<int, 3>),tuple 支持异构类型(如 tuple<int, string, double>); 与 std::pair:pair 仅支持最多 2 个元素,tuple 无元素数量限制。 二、tuple 基本用法(创建与访问)1. 创建方式(C++17 CTAD 特性重点)C++17 引入类模板参数推导(CTAD),创建 tuple 时无需显式指定模板参数,编译器会自动推导类型。 创建方式 代码示例 说明 CTAD 直接初始化 tuple t1(42,...
自定义对象支持 C++ 范围循环(Range-based for)的实现
导言范围循环(C++11 引入)是现代 C++ 中遍历容器的便捷方式,其核心依赖迭代器协议与begin/end 接口。 一、范围循环的底层实现原理C++ 标准规定,对于表达式for (range_declaration : range_expression),编译器会自动将其展开为以下逻辑(伪代码): 123456789// 1. 获取范围的起始与结束迭代器auto __begin = begin(range_expression);auto __end = end(range_expression);// 2. 遍历逻辑:依赖迭代器的 !=、++、* 操作for (; __begin != __end; ++__begin) { range_declaration = *__begin; // 解引用获取元素 loop_statement; // 循环体} 关键依赖接口要支持范围循环,自定义对象需满足: 存在可被调用的 begin() 和 end()...
Final/Override/Default/Delete 关键字整理
导言整理CppGuide社区内容,Final/Override/Default/Delete 均为C++ 关键字,ANSI C(如 C89、C99、C11)标准不支持这些特性。以下解析基于 C++(面向对象扩展,常与 C 语言结合使用),关联 C 语言的内存管理、代码安全思想,所有代码需用 C++ 编译器(如 g++、clang++)编译,ANSI C 编译器(如 gcc)均不支持。 一、Final 关键字:限制继承与重写1. 语义定义与作用域 作用 1:修饰类时,禁止该类被继承(作用域为整个类) 作用 2:修饰虚函数时,禁止子类重写该虚函数(作用域为单个虚函数) C 语言类比:C 中通过结构体封装 + 函数指针模拟多态时,需手动规范 “继承”(如不允许其他结构体包含父结构体模拟继承),但 Final 是 C++ 编译期强制约束,比 C 的代码规范更可靠。 2. 代码实例 1:Final 修饰类(禁止继承)123456789101112131415161718192021222324252627#include...
C++ 中 struct 与 class 的核心差异与应用场景
导言在 C++ 编程中,struct与class是定义复合数据类型的核心语法元素,二者既共享大部分 OOP(面向对象编程)特性,又因设计初衷不同存在关键差异。 一、语法定义与核心共性struct源于 C 语言的结构化数据设计,class则是 C++ 为支持 OOP 引入的特性。在 C++ 标准(ISO/IEC 14882)中,二者功能上高度重合,仅在默认行为上存在差异。 1.1 核心共性 成员定义能力:均可包含数据成员(如int x)和成员函数(如void print()),支持静态成员(static)和友元(friend)。 OOP 特性支持:均支持构造函数、析构函数、拷贝 / 移动语义、继承、多态(虚函数)。 内存布局规则:数据成员的对齐(alignment)、填充(padding)逻辑完全一致,由编译器根据平台(如 32 位 / 64 位)和类型大小决定。 模板与容器适配:均可作为 STL 容器(如std::vector)的元素类型(需满足容器要求,如可拷贝性)。 1.2...
C++ 类间关系与功能复用
导言在面向对象编程的世界里,类与类之间的关系设计和功能复用机制是构建高质量软件的基石。理解这些概念不仅有助于写出结构清晰的代码,更能提升系统的可维护性和扩展性。本文将结合实例,深入探讨 C++ 中类间的五大关系(继承、组合、聚合、关联、依赖),并分享对功能复用的理解与实践经验。 一、对类间关系的本质理解类间关系本质上反映了现实世界中事物之间的联系,是对客观世界的抽象。在面向对象设计中,我们通过类间关系来建模这些联系,使软件系统更贴近现实逻辑。 类间关系并非孤立存在,它们之间存在着从强耦合到弱耦合的渐变过程:继承 > 组合 > 聚合 > 关联 > 依赖。这种耦合度的差异,决定了它们在不同场景下的适用性。 让我们以基础类 A 为核心,通过具体代码来理解这些关系: 123456789class A{public: friend class E; void func() { cout << "hello,world" << endl; ...
C++ 使用 bind / mem_fn 了解函数对象与可调用实体
一、核心概念辨析在开始代码实现前,需先明确三个核心概念的区别: 概念 定义 典型示例 可调用实体 (Callable Entity) 所有可以通过()语法调用的对象或表达式的统称 函数指针、lambda 表达式、仿函数、bind返回对象 函数对象 (Function Object) 具有operator()成员函数的类实例(仿函数) 自定义struct Add { int operator()(int a, int b); } 可调用对象 (Callable Object) 除函数指针外的可调用实体,强调 "对象" 属性 lambda 表达式、std::bind返回值、std::mem_fn返回值 二、完整代码实现以下代码基于 C++11...
图形计算程序
引言在传统的 C++...
C++ 文件读取再整理
一、文件读取核心概念与基础流程1.1 文件操作的三要素文件读取本质是 "数据在外部存储与内存间的传输过程",需关注三个核心要素: 流对象:C++ 标准库通过std::ifstream(输入文件流)提供文件读取接口,是连接程序与外部文件的桥梁 流状态:通过good()/eof()/fail()/bad()四个状态标志判断操作有效性 数据缓冲区:操作系统与标准库均会维护缓冲区,减少磁盘 IO 次数(默认缓冲区大小通常为 4KB 或 8KB) 1.2 基础文件读取流程(标准范式)所有文件读取操作都遵循 "打开 - 读取 - 关闭" 的核心流程,标准实现代码如下: 123456789101112131415161718192021222324252627282930313233343536373839#include <fstream>#include <iostream>#include <string>int main() { // 1....
C++ 中 std::bind 与 std::function
一、std::function —— 可调用对象的 "万能容器"1.1 概念解析:什么是 std::function?std::function 是 C++11 标准库 头文件中引入的通用可调用对象封装器,其核心作用是将各种不同类型的可调用实体(函数指针、成员函数指针、lambda 表达式、函数对象)统一到一个类型安全的容器中。 可以将其类比为 "函数的通用接口转换器"—— 无论原始可调用对象的类型如何,只要签名(返回值类型 + 参数类型列表)匹配,就能被 std::function 封装并统一调用。 1.2 实现原理:类型擦除(Type Erasure)std::function 本质是通过类型擦除技术实现的多态封装,核心流程如下: 定义一个抽象基类(如 function_base),包含纯虚函数 operator()(对应目标签名)和析构函数; 为每个具体的可调用对象类型,实现一个模板派生类(如 function_impl),继承自 function_base,并在 operator()...
C++核心语法整理
C++核心语法整理C与C++类与对象C++输入输出流友元与运算符重载关联式容器继承多态模板移动语义与资源管理C与C++ C++程序介绍 g++编译器安装 sudo apt install g++ 源文件名称 .cc .cpp C++程序模板设置 /home/st/.vim/plugged/prepare-code/snippet hello world程序分析 #include C++标准库中头文件 没有.h cin 标准输入流 默认输入设备 从键盘接收数据 cout 标准输出流 默认输出设备 屏幕 int main(int argc , char *argv[]){} 返回值为int argc 命令行参数的个数 argv 具体的命令行参数 vim中启动鼠标 编辑.vimrc文件 子主题 命名空间 命名空间是什么, 有什么作用? C++中的一种避免名字冲突的机制 ...
243 - func(int) & func(int x)
一、核心区别:参数名的「存在意义」先明确最本质差异:**func(int){}**省略参数名,**func(int x){}**指定参数名x。这一区别在函数「声明」和「定义」场景中影响截然不同,且仅在 C/C++ 等少数语言中合法(Python、Java 等需强制指定参数名)。 二、分场景深度解析1. 函数声明阶段:几乎无差异在头文件或函数原型声明中,两者作用完全一致 ——仅告知编译器「函数接收一个 int 类型参数」,参数名不影响函数签名。 示例: 123// 以下两种声明等效,编译器均识别为「接收int、返回void」的函数void func(int); // 省略参数名(常用)void func(int x); // 带参数名(可选,仅作注释提示) 正如中关村在线问答指出的:声明只需说明参数类型,参数名「没什么用」。编译器处理时,会忽略声明中的参数名,仅记录函数名和参数类型序列。 2. 函数定义阶段:可用性天差地别函数定义(实现)时,参数名的有无直接决定「能否在函数体内使用该参数」: func(int...
STL标准模板库内容整理
...
C++ 实现高效单词转换工具
一、需求与设计分析1.1 核心需求根据 C++ Primer 11.3.6 练习要求,工具需满足以下功能: 规则加载:从map.txt读取替换规则(每行格式:待替换单词 替换后的短语) 文本处理:读取file.txt中的待转换文本,将匹配规则的单词替换为对应短语 结果输出:将替换后的文本写入output.txt 灵活性:支持通过命令行参数自定义规则文件、输入文件和输出文件路径 鲁棒性:处理文件打开失败、格式错误等异常情况 1.2 示例输入输出 规则文件(map.txt):定义替换映射 12345678brb be right backk okay?y whyr areu youpic picturethk thanks!l8r later 待转换文本(file.txt):包含缩写词的原始文本 123where r uy dont u send me a pick thk l8r 预期输出(output.txt):替换后的标准文本 123where are youwhy dont you send me a pictureokay? thanks!...
《STL 源码剖析》读书笔记
导言作为具备一定工程实践经验的中级软件工程师,在日常软件开发工作流中,C++ 标准模板库(Standard Template Library,STL)的容器与算法体系已成为不可或缺的编程工具。vector 的动态内存管理机制、map 的有序键值对存储特性,以及 sort 算法的高效执行范式,这些看似常规的编程操作,实则蕴含着深邃的计算机科学设计思想。通过研读侯捷所著《STL 源码剖析》,得以系统性解构 STL 的底层实现逻辑,深刻体会到 "知其然更知其所以然" 在软件工程领域的重要价值。 一、数据结构:从应用层到实现层的认知跃迁在数据结构层面,STL 核心容器的底层实现机制在书中得到细致解析。以 vector 容器为例,其动态数组特性不仅体现在可扩展的存储容量上,更在于其内存管理策略的精妙设计。当容器容量不足时,vector 采用指数级扩容策略(通常为原容量的 2 倍或 1.5 倍,依具体实现而定),通过重新分配内存空间、元素迁移及旧内存释放的流程,在空间复杂度与时间复杂度之间实现了高效平衡。这种 "以空间换时间"...
迭代器与迭代适配器
引言:迭代器的核心价值在 C++ 标准模板库 (STL) 中,迭代器扮演着 "胶水" 的角色,它连接了容器与算法,使算法能够独立于具体容器类型工作。这种抽象机制带来了极大的灵活性 —— 同一个排序算法可以作用于向量 (vector)、链表 (list) 或数组 (array),只需它们提供兼容的迭代器。 迭代适配器则是在基础迭代器之上的增强,通过包装现有迭代器,提供反向遍历、插入操作等特殊行为,进一步扩展了迭代器的能力。本文将系统解析迭代器的分类、实现原理及迭代适配器的应用场景。 一、迭代器基础:概念与分类1.1 迭代器的本质迭代器本质上是一种泛化的指针,它重载了*、->、++等运算符,使开发者能够以统一的方式访问容器中的元素,而不必关心容器的内部实现细节。 1234567891011121314151617181920212223#include <vector>#include <list>#include <iostream>//...
函数对象
一、函数对象的本质函数对象(也称为仿函数,Functor)是*重载了函数调用运算符***operator()**的类或结构体的实例。这种特殊的设计使它能够像普通函数一样被调用,同时又具备对象的所有特性。 12345678910111213141516// 一个简单的函数对象类struct Add { // 重载函数调用运算符 int operator()(int a, int b) const { return a + b; }};// 使用方式int main() { Add add; int result = add(3, 5); // 像函数一样调用对象 // 也可以直接使用临时对象 int result2 = Add()(10, 20); return 0;} 从本质上讲,函数对象是一个带行为的对象,而普通函数是一段可执行代码。这种本质差异决定了它们在功能和适用场景上的不同。 二、函数对象与普通函数的核心区别2.1...
C++ Lambda 表达式
导言在现代 C++ 开发中,lambda 表达式(匿名函数)已经成为编写简洁高效代码的重要工具。尤其在配合 STL 算法(如for_each)时,lambda 表达式能够消除编写命名函数或函数对象的额外开销,使代码更加紧凑直观。 一、Lambda 表达式的基本语法lambda 表达式的完整语法结构如下: 123[capture](parameters) mutable noexcept -> return_type { // 函数体} 各组成部分的含义: [capture]:捕获列表,定义 lambda 表达式可以访问的外部变量 (parameters):参数列表,与普通函数的参数列表类似 mutable:可选修饰符,允许修改按值捕获的变量 noexcept:可选修饰符,指定函数不会抛出异常 -> return_type:返回类型,当函数体只有 return 语句时可省略 {}:函数体,包含具体的执行逻辑 1.1 最简单的 Lambda 表达式最简化的 lambda...
unordered_map存放自定义类具体实现
一、引言上一篇文章介绍了unordered_map存放自定义类型的六种方法的理论框架,本文将通过完整可运行的代码示例,详细展示每种方法的具体实现细节。这六种方法是通过 2 种哈希实现方式与 3 种相等性比较方式组合而成,每种组合都有其独特的实现要点。 二、基础准备首先定义基础的Point类和测试函数,作为六种方法的共同基础: 12345678910111213141516171819202122232425262728293031323334353637383940#include <iostream>#include <unordered_map>#include <string>#include <functional>// 自定义点类型class Point {private: int x; int y;public: Point(int x_ = 0, int y_ = 0) : x(x_), y(y_) {} int getX() const {...
unordered_map 存放自定义类型的六种方法
引言std::unordered_map是 C++ 标准库中提供的无序关联容器,与std::map不同,它通过哈希表实现,因此需要两个关键组件:哈希函数(用于计算键的哈希值)和相等性比较函数(用于判断两个键是否相等)。当使用自定义类型作为unordered_map的键时,我们需要显式提供这两种组件。 一、核心概念std::unordered_map的模板定义如下: 1234567template< class Key, class T, class Hash = std::hash<Key>, // 哈希函数类型 class KeyEqual = std::equal_to<Key>, // 相等性比较类型 class Allocator = std::allocator<std::pair<const Key, T>>> class unordered_map; Hash类型必须满足Hash概念:Hash对象的operator()接受const...
C++ 容器的选择
一、关联式容器与无序关联容器的核心区别关联式容器(如set、map、multiset、multimap)和无序关联式容器(如unordered_set、unordered_map、unordered_multiset、unordered_multimap)是 C++ STL 中两种不同的数据结构,核心区别在于底层实现和特性: 关联式容器:基于红黑树(一种自平衡二叉搜索树)实现,元素按照键(key)的有序性存储,默认通过less比较键的大小。 无序关联式容器:基于哈希表实现,元素存储顺序与键的大小无关,依赖哈希函数计算存储位置,通过键的哈希值快速访问元素。 二、如何选择:关联式容器 vs 无序关联式容器选择需根据具体场景的需求,主要从以下维度判断: 2.1 有序性需求 需要元素有序:优先选择关联式容器。例如: 需遍历元素时按键的大小排序(如set遍历默认升序); 需频繁执行范围查询(如map::lower_bound、map::upper_bound获取键在[a, b]之间的元素)。 无需有序性:优先选择无序关联式容器,其插入、查找、删除的平均效率更高。 2.2...
STL 容器的成员函数与相关函数
引言C++ 标准模板库(STL)提供了一系列功能丰富的容器,这些容器不仅封装了数据结构,还提供了大量成员函数用于操作数据。此外,STL 还包含许多与容器配合使用的非成员函数,它们扩展了容器的功能,使操作更加灵活。 一、通用成员函数几乎所有 STL 容器都提供了一组基础的通用成员函数,用于获取容器信息、修改容器状态等。 1.1 基本信息函数12345678910111213141516171819202122#include <iostream>#include <vector>#include <list>int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; std::list<std::string> lst = {"apple", "banana", "cherry"}; // 容器大小相关 std::cout...
map 存放 pair<Point,string> 的三种解决方案
引言在 C++ 中使用std::map存储自定义类型作为键时,需要确保该类型能够被正确比较大小,因为std::map是一个有序关联容器,其内部通过比较操作来组织元素。本文将介绍三种方法来解决map中存放pair<Point, string>元素的问题,其中Point是一个自定义点类型。 核心概念std::map要求其键类型必须支持比较操作(默认使用<运算符)。对于自定义类型Point,我们需要通过以下三种方式之一提供比较能力: 为Point类重载<运算符 定义一个比较结构体(仿函数) 为Point准备std::less的特化模板 代码示例首先定义基础的Point类: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include <math.h>#include <iostream>#include <set>using std::cout;using...
迭代器与指针
引言在 C++ 中,迭代器 (iterator) 和指针 (pointer) 是两个密切相关但又有所区别的概念。它们都可以用来访问内存中的数据,都支持类似的操作符 (如*和->),但应用场景和功能范围却有显著差异。本文将深入解析迭代器与指针的关系、区别及各自的应用场景。 核心概念指针的本质指针是 C++ 从 C 语言继承而来的概念,是一个变量,其值为另一个变量的内存地址。指针直接指向内存中的某个位置,可以是: 普通变量的地址 数组元素的地址 动态分配内存的地址 函数的地址 迭代器的本质迭代器是 C++ 标准库提供的一种抽象,它模拟了指针的行为,为各种容器提供了统一的访问接口。迭代器可以看作是...
C++ 算法:remove_if 与 partition
一、remove_if 算法原理与实现细节1.1 核心功能与特性remove_if是 C++ 标准库中用于条件过滤的算法,它能移除容器中满足特定条件的元素。需要特别注意的是: 执行的是逻辑删除而非物理删除 不会改变容器的大小(size) 返回指向新的逻辑末尾的迭代器 1.2 底层实现逻辑123456789101112131415// 模拟标准库remove_if实现逻辑template<class ForwardIt, class UnaryPredicate>ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) { // 跳过不符合删除条件的元素 first = std::find_if(first, last, p); if (first != last) { // 用后面的元素覆盖需要删除的元素 for (ForwardIt i = std::next(first); i != last; ++i)...
C++ 智能指针与容器组合使用:std::unique_ptr 与 std::vector
一、基础概念与设计原理在 C++ 开发中,std::unique_ptr与std::vector组合是动态内存管理的常用方案,既灵活又安全。unique_ptr独占所有权的特性,使其与容器存储场景天然适配,容器全权负责指针生命周期。 二、完整实现示例1. 定义基础类定义Point类,包含虚析构函数以支持多态: 123456789101112131415161718#include <iostream>#include <vector>#include <memory> class Point {private: int x_; int y_;public: Point(int x = 0, int y = 0) : x_(x), y_(y) { std::cout << "Point构造: (" << x_ << "," << y_ << ")" <<...
C++ 中使用splice( )函数实现LRU算法
导言LRU(Least Recently Used,最近最少使用)算法是一种常用的缓存淘汰策略,其核心思想是:当缓存空间满时,优先淘汰最近最少使用的元素。在 C++ 中,结合list容器的splice()函数可以高效实现 LRU 算法,因为splice()能以 O (1) 的时间复杂度调整元素位置,非常适合维护元素的访问顺序。 一、LRU 算法的核心需求实现 LRU 算法需要支持以下操作: 访问元素:如果元素存在于缓存中,将其标记为 “最近使用”;如果不存在,需要插入新元素。 插入元素:当缓存未满时直接插入;当缓存已满时,删除 “最近最少使用” 的元素后再插入新元素。 维护顺序:始终保持元素的排列顺序与访问时间相关(最近使用的在前端,最少使用的在末端)。 二、数据结构设计为了高效实现 LRU,我们需要两种数据结构配合: list容器:用于存储缓存元素,最近使用的元素放在链表头部,最少使用的放在尾部。 unordered_map容器:用于快速查找元素在list中的位置(存储元素键与list迭代器的映射),支持 O (1)...
C++ 容器中的 sort () 与 splice ()
一、sort ():容器元素的排序利器sort()是 C++ 标准库中用于排序的函数,其核心功能是对容器中的元素进行升序或自定义规则排序。不过,并非所有容器都支持sort(),它仅适用于随机访问迭代器的容器(如vector、deque、array等),而像list、set等容器则有自己的排序方式。 1. 基本用法sort()的函数原型如下(以vector为例): 1234567#include <algorithm> // 需包含算法库// 升序排序(默认)sort(begin_iterator, end_iterator);// 自定义排序规则sort(begin_iterator, end_iterator, comparator); 其中,begin_iterator和end_iterator指定排序的范围(左闭右开),comparator是一个自定义的比较函数或 lambda 表达式,用于定义排序规则。 2. 实战示例 默认升序排序: 12345678910111213#include <vector>#include...
C++ 容器中の erase
一、erase 方法的功能定位vector 的 erase 方法是 STL 中用于从容器中移除元素的核心函数,其主要功能是: 从 vector 中删除单个元素或一段连续范围内的元素 调整容器大小以反映元素数量的变化 维护剩余元素的连续性和内存布局 返回一个迭代器,指向被删除元素的下一个元素 erase 方法是破坏性操作,会改变容器的状态和内部布局,同时可能影响现有迭代器的有效性。 二、迭代器参数的语法特征与使用场景vector 的 erase 方法有两种重载形式,分别适用于不同的删除场景: 2.1 单个元素删除1iterator erase(iterator position); 参数:指向要删除元素的迭代器 返回值:指向被删除元素下一个元素的有效迭代器 使用场景:已知要删除元素的确切位置时 2.2 范围元素删除1iterator erase(iterator first, iterator...
C++ list
一、内部实现机制 vector:基于动态数组实现,元素存储在连续的内存空间中,通过单一指针管理内存块 list:基于双向链表实现,每个元素包含数据域和两个指针域(前驱和后继),元素分散存储在内存中 二、核心性能差异 操作 vector list 随机访问(按索引访问) O (1),高效支持 O (n),不支持直接索引访问 头部插入 / 删除 O (n),需移动所有元素 O (1),只需调整指针 尾部插入 / 删除 O (1)(平均) O(1) 中间插入 / 删除 O (n),需移动插入点后的所有元素 O (1),只需调整附近元素的指针 内存分配 可能需要整体扩容(复制所有元素) 每次插入新元素单独分配内存 迭代器类型 随机访问迭代器 双向迭代器 缓存利用率 高(连续内存,缓存局部性好) 低(元素分散,容易缓存失效) 三、功能差异 vector: 支持[]运算符和at()方法进行随机访问 提供reserve()方法预分配内存 元素在内存中连续存储,可直接获取数据指针(data()方法) 适合与 C API...
C++ deque
导言deque(双端队列)是另一种常用的序列容器,与 vector 相比,它在两端的插入删除操作上有独特优势。以下是 deque 高效操作的策略和特性: 一、deque 的特性与优势deque 与 vector 的核心区别在于内存布局: deque 采用分段连续内存结构,由多个固定大小的内存块组成 支持 O (1) 时间复杂度的两端插入和删除操作 不需要像 vector 那样在扩容时复制所有元素 二、高效插入元素 两端插入效率最高deque 在头部和尾部的插入操作都是 O (1) 时间复杂度,这是它相比 vector 的主要优势: 123456789std::deque<int> dq;// 尾部插入dq.push_back(10);dq.emplace_back(20); // 更高效,直接构造// 头部插入(vector不擅长的操作)dq.push_front(5);dq.emplace_front(3); // 直接在头部构造 中间插入的特点与 vector 类似,deque 的中间插入仍需移动元素,时间复杂度为 O (n),但实际性能可能略优于...
C++ Vector
一、vector 容器概述vector 是 C++ 标准模板库(STL)中 最用的序列容器之一,它基于动态数组实现,能够存储同类型元素并支持高效的随机访问。自 C++98 标准首次引入以来,vector 凭借其灵活的内存管理和优秀的性能表现,成为了 C++ 开发者处理动态数据集合的首选工具。 与静态数组相比,vector 的核心优势在于自动内存管理—— 它会根据元素数量动态调整内部存储空间,无需开发者手动分配和释放内存。这种特性使得 vector 在处理元素数量不确定的场景时尤为便捷,同时保持了数组的随机访问效率。 vector 的核心特性可以概括为: 连续的内存空间分配,支持随机访问 动态扩容机制,自动管理内存 尾部插入 / 删除操作效率高 中间插入 / 删除操作可能导致大量元素移动 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566#include...
C++ 循环引用
一、智能指针循环引用问题的处理1.1 循环引用的产生与危害循环引用是shared_ptr使用过程中最常见的问题之一,当两个或多个shared_ptr形成引用闭环时就会产生循环引用。这种情况下,每个shared_ptr的引用计数都无法降到 0,导致其所管理的对象无法被释放,最终造成内存泄漏。 典型的循环引用场景: 双向链表节点相互引用 父对象持有子对象的shared_ptr,子对象同时持有父对象的shared_ptr 观察者模式中,观察者与被观察者相互持有shared_ptr 1.2 循环引用示例12345678910111213141516171819202122232425262728293031#include <memory>#include <iostream>class B; // 前向声明class A {public: std::shared_ptr<B> b_ptr; A() { std::cout << "A constructed\n";...
C++ 智能指针
一、引言引入智能指针(Smart Pointer)是 C++ 为解决动态内存管理问题而设计的工具,其核心目的是自动管理动态分配的内存(堆内存),避免手动管理内存时常见的错误(如内存泄漏、野指针、二次释放等),提高代码的安全性和健壮性。 二、智能指针基础2.1 具体解决的问题手动使用new分配内存和delete释放内存时,容易出现以下问题,而智能指针通过自动化机制解决了这些问题: 内存泄漏 手动管理时,若忘记调用delete释放已分配的内存,或因异常、分支跳转等导致delete未执行,会造成内存泄漏(内存被占用且无法回收)。 智能指针通过RAII(资源获取即初始化)...
C++ 用RAII实现智能指针
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980#ifndef SMART_PTR_H#define SMART_PTR_H// 基于RAII的智能指针实现,模拟unique_ptrtemplate <typename T>class SmartPtr {private: T* m_ptr; // 指向管理的资源public: // 构造函数:获取资源 explicit SmartPtr(T* ptr = nullptr) : m_ptr(ptr) {} // 析构函数:释放资源(RAII核心) ~SmartPtr() { delete m_ptr; // 自动释放资源 m_ptr = nullptr; ...
C++ 资源管理
前言在 C++ 开发中,资源管理是确保程序稳定性、安全性和性能的核心环节。本指南系统介绍 C++ 资源管理的核心概念、实践方案与最佳实践,涵盖从基础内存管理到复杂资源类型的完整生命周期控制策略。 一、资源管理核心要素C++ 程序中需要管理的七类核心资源: 内存资源:动态分配的堆内存、缓存区等 文件资源:文件句柄、目录访问权限等 网络资源:套接字、连接句柄、端口等 图形资源:窗口句柄、设备上下文、纹理等 数据库资源:连接对象、事务句柄、游标等 线程资源:线程对象、互斥体、条件变量等 硬件资源:设备句柄、IO 端口、外设连接等 所有这些资源都遵循相同的管理原则:获取资源后必须确保其被正确释放,无论程序正常执行还是发生异常。 二、资源管理基本原则2.1 RAII:资源获取即初始化RAII(Resource Acquisition Is Initialization)是 C++ 资源管理的基石原则,其核心思想是: 资源的获取在对象的构造函数中完成 资源的释放在对象的析构函数中完成 利用 C++...
C++ 移动语义:从原理到实践
导言在 C++11 标准所引入的一系列创新性语言特性中,移动语义作为一种重要的语言机制,通过重新定义对象生命周期内资源转移的行为范式,显著提升了程序运行时的资源管理效率。该机制有效规避了传统临时对象拷贝操作引发的冗余资源复制开销,为构建高性能 C++ 应用程序提供了理论基础与实现路径。 一、移动语义的核心概念1.1 左值与右值的重新认知要理解移动语义,首先需要重新审视 C++ 中的值类别。在 C++11 之前,我们通常简单地将表达式分为左值(lvalue)和右值(rvalue),而 C++11 则将值类别体系进行了扩展: 左值:指可以取地址的表达式,通常有标识符,如变量名、数组元素等 右值:无法取地址的表达式,又细分为: 纯右值(prvalue):如字面量、临时对象、返回非引用类型的函数调用 将亡值(xvalue):即将被销毁的对象,通常是通过 std::move 转换的左值 1234int a = 42; // a是左值,42是纯右值int b = a + 5; // a+5的结果是纯右值std::string s =...
C++ 类模板
一、模板类基本概念1.1 什么是类模板类模板是 C++ 泛型编程的核心机制,允许我们定义一个通用的类结构,该结构能与多种数据类型一起工作,而无需为每种类型重复编写代码。 类模板不是一个具体的类,而是类的 "蓝图",编译器会根据实际使用的类型参数生成具体的类实例(模板实例化)。 1234567891011121314151617181920212223242526// 简单的类模板示例template <typename T>class Box {private: T content;public: // 构造函数 Box(T value) : content(value) {} // 获取存储的值 T getContent() const { return content; } // 设置新值 void setContent(T newValue) { content = newValue; }};//...
C++ 函数模板
一、概述1.1 函数模板的定义函数模板是 C++ 泛型编程的核心,它允许定义带类型参数的函数原型,经编译器实例化后生成不同数据类型的函数,本质上是编译期指令。 1.2 函数模板的优势函数模板的核心优势体现在: 代码复用:类型参数化使一套模板适配多种数据类型,减少重复代码 类型安全:利用静态类型检查,规避编译阶段类型转换错误 高效执行:实例化函数直接嵌入,无运行时额外开销 1.3 应用场景函数模板常用于 STL 容器与算法设计、链表 / 栈等数据结构的类型无关实现,以及排序、查找等通用算法的多态封装。 二、基础语法2.1 模板声明函数模板的语法结构遵循以下范式: 1234template <typename T>返回类型 函数名 (参数列表) { // 函数体实现} 其中,typename关键字用于声明类型参数,class关键字在此语境下具备完全等价语义;T作为类型参数占位符,可根据实际需求替换为任意合法标识符。 2.2 简单示例:交换函数以数据交换操作为例,其模板实现如下: 123456template...
C++ 函数定义与调用中的符号体系
引言在 C++ 函数的定义与调用过程中,< >、( )、[ ]和{ }等符号具有明确的语义边界和使用规范。理解这些符号的准确含义和应用场景,对于编写正确、高效的 C++ 代码至关重要。 一、< >在函数模板中的应用尖括号< >主要用于函数模板的参数列表,用于指定模板类型参数或非类型参数。 1.1 函数模板定义中的< >在函数模板定义中,< >用于声明模板参数列表: 1234template <typename T> // 模板参数列表T max(T a, T b) { return (a > b) ? a : b;} 这里的声明了一个类型参数T,使函数能够接受任意类型的参数。 1.2 函数模板调用中的< >在调用函数模板时,可以显式指定模板参数: 12int result1 = max<int>(3, 5); // 显式指定模板参数为intdouble result2 = max<double>(3.2, 5.7); //...
C++虚基类与虚函数的内存布局
一、40字节对象大小的组成结构在标准C++中,一个包含虚基类和虚函数的类实例会由以下组件构成: 内存结构分解1234[对象地址] ├── 虚函数表指针 (8字节) → 用于多态调用├── 偏移量表 (16字节) → 处理虚基类偏移└── 数据成员 (16字节) → 本类的实际数据 关键点: 虚函数表指针会占用8字节(64位系统)或4字节(32位系统) 虚基类引入的偏移量表通常需要16字节(包含两个虚基类指针) 本类数据成员占用16字节(假设包含两个double类型成员) 总内存大小 = 虚函数表指针 + 偏移量表 + 数据成员 二、虚基类继承关系考虑以下类继承结构: 1234567class Figure { virtual void draw() = 0; }; // 虚基类class Space { virtual void calc() = 0; }; // 虚基类class Circle : virtual public Figure, virtual public Space { double...
C++ 纯虚函数与抽象类
一、什么是面向对象中的抽象在面向对象编程中,抽象是一种将复杂事物简化的方法,它关注对象的本质特征而非具体实现细节。想象一下,当我们谈论 "交通工具" 时,我们不会具体指明是汽车、自行车还是飞机,而是关注它们共同的特性:能够运输人和物,可以移动到不同地点等。 以游戏开发为例,不同角色(战士、法师、刺客)都具备移动、攻击、获取经验等行为,将这些共性抽象出来,就能构建出一个通用的角色概念。这种抽象思维在软件设计中非常有价值,它能帮助我们: 建立清晰的系统架构,将问题分解为合理的模块 定义通用接口,使不同实现可以互换使用 提高代码复用性,减少重复开发 便于团队协作,不同开发者可以基于相同接口并行工作 二、纯虚函数:定义接口的特殊函数纯虚函数是一种特殊的虚函数,它只有声明而没有具体实现,专门用于定义接口规范。在语法上,它的声明需要在函数原型后加上 "=0"。例如: 1234class AbstractClass {public: virtual double getArea() = 0; //...
C++ 虚析构函数详解:从原理到实践
一、内存管理与析构函数的基础核心原理 在C++中,对象的内存分配与释放是通过构造函数和析构函数完成的。当创建一个对象时: 构造函数负责初始化资源(如内存申请、文件打开) 析构函数负责释放资源(如内存回收、文件关闭) 资源管理规律 无资源类:普通类对象的析构无需特殊处理,编译器自动调用 有资源类:需要显式定义析构函数来处理资源释放 继承体系:当基类可能被继承时,必须考虑析构顺序问题 在单继承场景下,析构函数的调用遵循 "先派生类、后基类" 的顺序,这确保了资源释放的安全性。然而,当引入多态(使用基类指针指向派生类对象)时,普通析构函数就会暴露出严重的缺陷。 问题的引出考虑以下场景: 我们有一个基类Base和派生类Derived 使用Base*类型的指针指向Derived类的对象 当通过基类指针删除对象时,会发生什么? 典型场景 123456789class Base {public: ~Base() { cout << "Base析构" << endl;...
C++ 虚函数访问控制
一、虚函数的访问控制虚函数的访问控制(public、protected、private)会影响其在派生类中的重写和调用规则,这是容易混淆的知识点。 1. public 虚函数基类中 public 的虚函数在派生类中可以被重写为 public 或 protected,但不能重写为 private(在 C++11 前允许,C++11 后被禁止): 123456789101112131415161718192021222324252627282930313233343536373839class Base {public: virtual void publicFunc() { cout << "Base::publicFunc" << endl; }};class Derived : public Base {public: // 正确:重写为public void publicFunc() override { cout <<...
C++ 虚函数与多态实现机制
一、虚函数核心概念框架1.1 虚函数定义虚函数是通过virtual关键字声明的成员函数,允许派生类重写基类的行为。其本质是为实现运行时多态服务,通过动态绑定机制,在程序运行时决定调用哪个类的函数实现。 类比理解:想象一个图书馆管理系统,每个书架都有一个统一的借书接口。当借书时,系统根据实际书架类型( Hardcover/Book/Reference)选择对应的借书规则。 1.2 多态实现四要素 基类指针/引用 虚函数声明 派生类重写 动态绑定调用 关键概念:虚函数定义、多态特性、静态与动态绑定差异 示例: 12345678910111213141516171819202122232425262728293031323334353637#include <iostream>using namespace std;class Animal {public: virtual void speak() { cout << "Animal speak" <<...
C++多态机制解析:重载、重写与隐藏
一、概念C++ 的多态机制主要通过三个核心概念实现,它们在编译和运行时有着截然不同的处理方式: 概念 定义 绑定时机 核心特征 函数重载 同一作用域内,函数名相同但参数列表不同的函数 编译时 静态多态,基于参数列表区分 函数重写 派生类中重新定义基类中的虚函数,函数签名完全相同 运行时 动态多态,基于对象实际类型调用 函数隐藏 派生类中定义的函数遮蔽基类中同名函数,无论参数是否相同 编译时 名称遮蔽,基类函数被隐藏 注:函数签名包括函数名、参数类型和顺序,不包括返回值类型 二、函数重载解析函数重载是 C++ 实现静态多态的基础机制,允许在同一作用域内定义多个同名函数,通过参数列表的差异进行区分。 重载的实现原理编译器在编译阶段会对重载函数进行名称修饰(Name Mangling),根据函数名和参数列表生成唯一的内部名称,因此重载函数在底层实际上拥有不同的标识符。 重载示例代码123456789101112131415161718192021222324252627282930313233343536373839#include...
派生类对象复制控制
一、复制控制的核心概念与场景复制控制机制是C++中处理对象创建、复制和销毁的核心手段。在继承体系中,当派生类对象被复制时,需要特别关注以下关键点: 复制场景 拷贝构造函数调用:当用已存在的对象初始化新对象时 赋值操作符调用:当用一个对象赋值给另一个对象时 析构函数调用:当对象生命周期结束时 典型问题 浅拷贝导致的指针悬挂(dangling pointer) 资源泄漏(resource leak) 自赋值(self-assignment)引发的异常 二、派生类复制的构造函数调用链12345678910111213class Base {public: Base() { /* 基类构造 */ } Base(const Base& other) { /* 基类拷贝构造 */ } ~Base() { /* 基类析构 */ }};class Derived : public Base {public: Derived() : Base() {...
C++ 继承机制中的类型转换
一、对象赋值的双向可行性分析1.1 基类到派生类的转换 隐式转换: 不允许直接赋值(如 Base b = d;) 显式转换: 需使用构造函数或类型转换运算符 123456789101112class Base {public: Base() {} explicit Base(int val) : data(val) {}private: int data;};class Derived : public Base {public: Derived(int val) : Base(val) {}}; 内存影响: 派生类对象包含基类数据成员,赋值操作不会改变大小,但会丢失派生类特有数据 1.2 派生类到基类的转换 隐式转换: 允许(如 Base b = d;) 显式转换: 可使用static_cast或构造函数 123Derived d;Base b = d; // 隐式转换Base& br =...
C++ virtual 继承机制详解(非多态场景)
一、virtual 继承的核心作用在C++中,virtual关键字用于解决多重继承时的菱形继承问题。当多个派生类共享同一基类时,如果不使用虚继承,会导致基类对象被多次实例化,造成内存浪费和指针混淆。 1.1 问题场景考虑经典菱形继承结构: 12345678910111213141516class A {public: int a;};class B : virtual public A {public: int b;};class C : virtual public A {public: int c;};class D : public B, public C {}; 在这种情况下,D对象会包含两个A子对象(一个来自B,一个来自C),导致: 内存重复占用(每个A子对象占用相同内存空间) 指针访问歧义(需要明确使用B::a或C::a) 1.2...
C++ 关联容器(map 与 set)解析
一、关联容器的底层数据结构特性C++ 标准库中的map和set均属于关联容器,其底层实现基于红黑树(Red-Black Tree)—— 一种自平衡的二叉搜索树。这种数据结构具有以下核心特性: 有序性:元素按照键值的比较规则进行排序存储 自平衡性:通过特定的旋转和着色操作,保证树的高度始终保持在 O (log n) 级别 迭代效率:支持高效的顺序访问和范围查询 红黑树的平衡机制确保了所有基本操作(插入、删除、查找)都能在O(log n) 的时间复杂度内完成,这使得关联容器在需要频繁查找和有序遍历的场景中表现优异。 二、map 与 set 的存储机制差异虽然map和set共享相同的底层实现机制,但它们的存储方式存在本质区别: 特性 set map 存储内容 仅存储键(key) 存储键值对(key-value) 元素唯一性 键值唯一,不允许重复 键值唯一,不允许重复 访问方式 只能通过键访问元素 可通过键访问对应的值 模板参数 std::set<Key, Compare, Allocator> std::map<Key,...
C++ Map 容器
一、核心数据结构:红黑树C++ 标准库中std::map的底层实现采用红黑树(Red-Black Tree),这是一种自平衡的二叉搜索树(BST)。红黑树通过特定的规则保证树的高度始终维持在 O (log n) 级别,从而确保各种操作的高效性。 1具体见前一篇 C++ 标准库中的 set 容器 二、map 容器的核心组件2.1 迭代器实现std::map的迭代器是双向迭代器,其实现本质上是红黑树节点的指针封装,并提供了遍历树的操作。 2.2 内存管理机制std::map通常使用内存池(memory pool)或分配器(allocator)管理节点内存,而非频繁调用new和delete: 采用 slab 分配策略,预先分配一批节点内存 节点释放时不直接归还给系统,而是放入空闲列表 下次分配时优先从空闲列表获取,减少系统调用开销 三、核心操作实现3.1...
C++ 标准库中的 set 容器
一、set 容器的本质与底层数据结构C++ 标准库中的std::set是一种关联式容器,其核心特性是自动排序和唯一性。与序列式容器(如 vector、list)不同,set 中的元素是按照特定的排序规则进行存储的,这使得 set 具有高效的查找、插入和删除操作。 std::set的底层实现采用了红黑树(Red-Black Tree) 这一自平衡二叉搜索树数据结构。红黑树通过一系列规则保证了树的平衡性,从而确保了基本操作(插入、删除、查找)的时间复杂度均为 O (logN),其中 N 为树中元素的数量。 在 C++ 标准库中,红黑树的实现通常被封装在_Rb_tree类模板中,而 set 则是该类模板的一个特化应用,专门用于存储键值对中的键(在 set 中,键和值是同一个概念) 二、红黑树的结构解析2.1 红黑树节点的构成红黑树的每个节点通常包含以下几个部分: 键(key):即存储的元素值,用于节点间的比较 左孩子指针(left child):指向当前节点的左子节点 右孩子指针(right...
C++ 标准库中 pair 的实现原理与应用解析
一、pair 的模板定义与类型参数设计1.1 基本模板定义C++ 标准库中的std::pair是一个模板类,用于存储两个异构对象作为一个单元。其基本定义如下: 123456789101112namespace std { template <class T1, class T2> struct pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; // 构造函数及其他成员函数... };} 这个定义展示了pair的核心特征: 两个模板类型参数T1和T2,分别指定了两个成员的类型 公开的成员变量first和second,分别存储第一个和第二个元素 类型别名first_type和second_type,用于获取元素类型 1.2 类型推导机制在 C++11...
C++ 短字符串优化(SSO)
引言:字符串性能的关键挑战在 C++ 开发中,std::string作为最常用的容器之一,其性能直接影响整体程序效率。传统字符串实现采用动态内存分配策略,无论字符串长度如何,都需要在堆上分配空间,这会带来: 内存分配 / 释放的开销 缓存局部性不佳 小字符串场景下的效率低下 短字符串优化(Short String Optimization, SSO)正是为解决这些问题而生的关键技术,已成为现代 C++ 标准库(如 libstdc++、libc++)的标配实现策略。 一、SSO 的核心原理1.1 传统字符串实现的缺陷传统std::string(C++11 之前)通常采用 "胖指针" 结构: 123456// 传统字符串实现(概念模型)struct String { char* data; // 指向堆内存的指针 size_t length; // 字符串长度 size_t capacity; // 已分配容量}; 这种结构对短字符串极不友好,例如存储 "hello"...
C++ 写时复制 (Copy-on-Write)
一、写时复制核心概念写时复制 (简称 COW) 是一种资源管理优化技术,其核心思想是:当多个对象需要共享同一资源时,直到其中一个对象需要修改资源前,都不需要真正复制资源,仅在修改时才创建资源的私有副本。 这种机制通过延迟复制操作,减少了不必要的内存分配和数据拷贝,从而提高程序性能,尤其适用于: 频繁复制但很少修改的场景 内存资源宝贵的环境 大型数据结构的共享访问 二、写时复制实现三要素1. 共享数据存储需要一个独立的共享数据结构,存储实际的数据内容。 1234567template <typename T>struct SharedData { T* data; // 实际数据指针 size_t size; // 数据大小 size_t ref_count; // 引用计数 // 其他元数据...}; 2. 引用计数机制通过引用计数跟踪当前有多少对象共享该资源: 当新对象共享资源时,引用计数 + 1 当对象销毁或不再共享时,引用计数 - 1 当引用计数为 0...
C++ 中->和*运算符重载的全面解析
引言在 C++ 编程中,->和*运算符最初设计用于指针操作。然而,通过运算符重载机制,我们可以让自定义类型也支持这些操作,从而实现类似指针的行为,同时添加额外功能。 一、为什么需要重载->和*运算符1. 扩展指针功能的核心需求原生指针 (T*) 虽然简洁高效,但存在明显局限: 无法自动管理资源生命周期 缺乏访问控制机制 不支持额外的调试信息 不能实现代理或间接访问模式 通过重载->和*,我们可以创建 "智能指针" 或 "代理对象",在保持指针操作语法的同时,添加所需功能。 2. 关键应用场景资源自动管理智能指针通过运算符重载实现自动内存管理: 1234567891011121314151617181920// 简化的shared_ptr实现思路template<typename T>class SharedPtr {private: T* ptr; int* ref_count; void release() { if...
C++ 中的 Pimpl 模式:封装实现细节的艺术
一、什么是 Pimpl 模式?Pimpl 模式(Pointer to Implementation)通过私有指针隔离类的接口与实现,是 C++ 封装原则的高级应用。其核心目标为:隐藏实现细节、提升编译效率、保障二进制兼容及简化接口。 二、Pimpl 模式的实现机制传统类设计将接口与实现混在头文件,Pimpl 模式则通过以下方式分离: 头文件声明含私有指针的公共接口类 源文件定义包含实现细节的实现类 接口类借指针间接访问实现类成员 传统设计 Pimpl 模式设计 接口与实现一体 接口类仅含指针 头文件暴露所有细节 头文件隐藏实现 实现变更需重编译所有依赖 仅需重编译源文件 三、Pimpl 模式的代码实现3.1 头文件(widget.h)123456789101112131415161718192021222324252627#include <memory>#include <string>// Widget 类是对外暴露的公共接口类class Widget {public: // 构造函数,用于创建...
C++ 文件读取技术详解:get ()、getline () 与流提取运算符
一、核心函数原型与参数解析1.1 get () 函数家族get()函数是 C++ 中最基础的字符读取工具,有多个重载版本: 1234567891011// 读取单个字符(包含空白字符)int get();// 读取字符到指定缓冲区,最多n-1个字符,遇到分隔符停止istream& get(char* s, streamsize n);// 带自定义分隔符的版本istream& get(char* s, streamsize n, char delim);// 读取字符到字符对象istream& get(char& c); 关键特性: 不会跳过空白字符(空格、制表符、换行符等) 读取失败时返回 EOF(-1) 保留分隔符在输入流中(不提取) 1.2 getline () 函数getline()专为读取完整行设计,主要有两种形式: 123456789// 从输入流读取一行到字符数组istream& getline(char* s, streamsize n);// 带自定义分隔符的版本istream&...
CONST AUTO 的应用
一、const auto迭代器的适用场景1. 只读访问容器元素时当你只需要读取容器元素而不需要修改它们时,应该使用const auto声明迭代器: 1234567891011121314151617#include <vector>#include <iostream>void printData(const std::vector<int>& data) { // 函数参数为const引用,只能使用const迭代器 for (const auto it = data.begin(); it != data.end(); ++it) { std::cout << *it << " "; // 只读访问 // *it = 100; // 编译错误,不允许修改 } std::cout << std::endl;}int main() { ...
auto 关键字在 C++ 迭代器遍历中的应用
一、auto 关键字简化迭代器遍历在 C++11 之前,迭代器遍历代码往往冗长且可读性差: 类型声明冗长,增加代码量和阅读负担 容易出现类型不匹配错误 当容器类型改变时,需要修改所有相关迭代器声明 代码不够直观,隐藏了真正的业务逻辑 C++11 引入的auto关键字彻底改变了迭代器的使用方式,让我们能够完全摆脱冗长的传统迭代器声明。auto能够自动推导迭代器类型,使代码更加简洁、可读且不易出错。 1.1 基本迭代模式1234567891011121314151617181920#include <vector>#include <iostream>int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 使用auto声明迭代器 for (auto it = numbers.begin(); it != numbers.end(); ++it) { std::cout...
继承与多态
一、继承机制:代码复用的基石1.1 继承的概念与本质继承是面向对象编程中实现代码复用的核心机制,它允许一个类(派生类)获取另一个类(基类)的成员变量和成员函数。这种关系类似于现实世界中的 "is-a" 关系,例如 "狗是一种动物"。 在 C++ 中,继承通过类定义时的冒号语法实现: 12345678910111213class Animal {public: void speak() { std::cout << "The animal makes a sound." << std::endl; }};class Dog : public Animal {public: void bark() { std::cout << "The dog barks." << std::endl; }}; 在上述示例中,Dog...
C++中class与struct的异同点深度解析
一、基础语法差异1.1 访问修饰符差异 class 默认成员访问权限为 private 声明的成员变量与函数仅对类内部可见,需使用 public/protected 显式开放 struct 默认成员访问权限为 public 结构体成员对所有作用域开放,需使用 private/protected 显式限制 12345678910class MyClass { int privateVar; // 默认私有public: void publicFunc() {}};struct MyStruct { int publicVar; // 默认公有 void publicFunc() {}}; 1.2 成员函数特性 class 支持抽象方法(纯虚函数) 通过 = 0 定义的虚函数使类成为抽象类,如 virtual void func() = 0; struct 不支持抽象方法 ...
C++ 析构函数详解:原理、实现
一、析构函数基础概念1.1 析构函数的定义析构函数是 C++ 面向对象编程中用于对象销毁时进行资源清理的特殊成员函数。当对象生命周期结束时,析构函数会被自动调用,负责释放对象所占用的资源。 语法特征: 函数名与类名相同,前面加波浪号~ 没有返回值,也不指定 void 没有参数,因此无法重载 不能被显式调用(编译器自动调用) 123456789101112class MyClass {public: // 构造函数 MyClass() { std::cout << "构造函数被调用" << std::endl; } // 析构函数 ~MyClass() { std::cout << "析构函数被调用" << std::endl; }}; 1.2...
C++ 左值与右值:语义解析与实战应用
一、左值与右值的核心定义在 C++ 中,表达式根据其特性被分为左值(lvalue)和右值(rvalue),这一分类直接影响着变量的存储、引用绑定和资源管理。根据 C++17 标准( IO/IEC 14882:2017),左值是指 "可以取地址且具有身份 (identity) 的表达式",而右值则是 "非左值的表达式",通常是临时的、不具有持久身份的对象。 1.1 左值的核心特征左值具有以下关键特性: 可以通过&运算符获取地址S 具有持久性,在表达式结束后仍然存在 可以出现在赋值运算符的左侧 1234int x = 10; // x是左值int* ptr = &x; // 合法,左值可以取地址x = 20; // 合法,左值可以出现在赋值左侧 1.2 右值的核心特征右值具有以下关键特性: 不能通过&运算符获取地址 临时性,通常在表达式结束后销毁 不能出现在赋值运算符的左侧 1234int y = x + 5; // x + 5是右值// int* ptr = &(x +...
C++ 变量作用域与存储持续度完全解析
导言:变量类型的四维属性体系在 C++ 中,每个变量都具有四个核心属性,这些属性共同决定了变量的行为特征: 作用域 (Scope):变量在程序中可见的区域 存储持续度 (Storage Duration):变量在内存中存在的时间 链接属性 (Linkage):变量在不同编译单元间的可见性 生命周期 (Lifetime):变量从创建到销毁的时间段 一、作用域类型详解1.1 块作用域 (Block Scope)块作用域是由花括号{}界定的区域,包括函数体、循环体、条件语句体等。 12345678910void function() { int a = 0; // 块作用域开始 if (a == 0) { int b = 1; // 内部块作用域 // a和b在此可见 } // b的作用域结束 // 仅a可见,b不可见} // a的作用域结束 1.2 函数作用域 (Function...