1. 搜索器概述在 C++17 之前,如果我们想在一个字符串中查找子串,通常使用 std::string::find。虽然它很简单,但有几个明显的局限性:
算法单一:C++ 标准库只提供了简单的朴素算法(逐个字符比较)。
无法指定算法:如果你想用更高效的算法(如 KMP 或 Boyer-Moore),必须自己实现或寻找第三方库。
无法用于容器:find 是 std::string 的成员函数,不能直接用于 std::vector<char> 或其他容器。
接口不统一:字符串查找和泛型算法(如 std::search)的接口不一致。
C++17 引入了搜索器概念。它允许我们把 搜索算法 和 搜索操作 分离开来。现在,标准库里直接提供了Boyer-Moore 和 Boyer-Moore-Horspool 这两个高性能算法!
C++17 在 <functional> 头文件里引入了三种搜索器,每种都基于不同的算法:
std::default_searcher:
算法:就是标准库原来用的那种朴素算法。
用途:默认选项,短字符串用它足够了。
std::boyer ...
1. 读写分离在多线程编程中,数据的并发访问通常分为两类场景:
写-写互斥:多个线程同时修改数据会导致冲突,必须串行化。
读-读并发:多个线程同时读取数据通常是安全的,不需要互斥。
C++11 提供了 std::mutex,它是一个独占锁。即使在只读场景下,同一时间也只允许一个线程访问数据。这在读多写少的场景下会严重限制性能。
C++14 引入了一种读写锁std::shared_timed_mutex,也被称为共享独占互斥量。它允许多个线程同时读共享数据,但写数据时必须独占访问。相比普通的 std::mutex,它在读多写少的场景下能显著提高性能。此外,它支持超时机制,即可以设定等待锁的最长时间。
C++17 引入了 std::shared_mutex,这也是一种读写锁,主要目的是为了瘦身和高性能。大多数情况下,我们不需要超时功能(超时逻辑非常复杂且容易出错)。std::shared_mutex 去掉了时间相关的接口,实现上通常比 std::shared_timed_mutex 更轻量,占用内存更少,速度更快。
和std::shared_timed_mutex 一样,std::sh ...
1. std::shared_ptr<T[]>在 C++ 的世界里,手动管理内存(new 和 delete)是噩梦的开始。忘了 delete 会导致内存泄漏(程序越跑越慢),重复 delete 会导致程序崩溃。
std::shared_ptr 是 C++11 引入的救世主,到了 C++17,它变得更加成熟。可以把 shared_ptr 想象为一个拿着绳子(指针)拽着气球(堆内存对象)的人。
引用计数:气球上挂了一个标签,写着 “几根绳子拴着我”。
赋值/拷贝:相当于多接了一根绳子,计数 +1。
重置/销毁:相当于解开一根绳子,计数 -1。
释放:当最后一根绳子解开,气球飞走(自动执行 delete,内存被释放)。
在创建std::shared_ptr的时候,永远优先使用 std::make_shared,不要直接用 new。
1234567891011121314151617181920#include <memory>#include <iostream>class Widget {public: Widge ...
1. 为什么我们需要新的转换在 C++17 之前,C++ 程序员在处理字符和数字转换时经常面临两个痛苦的选择:
C 语言风格函数 (atoi, strtol, sprintf):
类型不安全(容易崩溃),不支持 std::string,功能受限,无法区分错误(比如输入 123abc 会被当成 123 处理)。
C++ 流 (std::stringstream/std::to_string):
std::stringstream - 极其慢(涉及内存分配、状态设置),代码冗长(写一行转换要三五行代码),不仅影响性能,还容易写错。
std::to_string - 可能抛出bad_alloc异常,并且无法控制精度、格式等
C++17 引入了 <charconv> 头文件,提供了一组全新的函数( std::to_chars 和 std::from_chars)。这些函数不仅速度极快(被认为是目前通用 C++ 库中最快的),而且极其方便(不分配内存,不抛异常)。
123456789101112131415161718#include <sstre ...
1. 什么是并行算法在 C++17 之前,当我们使用标准库算法(如 std::sort, std::accumulate)时,程序是串行执行的。
想象一下我们要洗一盆子的脏衣服:
串行执行:一件一件地洗,洗完第一件再洗第二件。即使有十个空闲的水槽,也只用了一个。
并行执行:有五个朋友(CPU 核心),大家一人抓几件衣服同时洗,速度自然快得多。
以前,如果想利用多核加速排序或遍历,必须手动写复杂的线程代码,不仅难写,还容易出错。C++17 为几乎所有的标准算法(约 70 个)引入了执行策略参数。现在,只需要在调用算法时多加一个参数,编译器和标准库就会自动利用多核 CPU 来帮我们加速计算,完全不需要自己写线程代码。
要使用并行算法,需要包含头文件:
1234#include <algorithm> // 算法头文件#include <execution> // 并行执行策略头文件// 执行策略位于std::execution命名空间using namespace std::execution;
C++17 定义了三种主要的执行策略,执行策略是一个 ...
1. std::byte 是什么?在 C++17 之前,当我们需要处理“字节”级别的数据(例如网络协议、文件二进制流、硬件寄存器)时,我们通常只有两个选择,但它们都有缺陷:
使用 char 或 unsigned char:
缺陷:char 太聪明了。它是字符类型,不仅代表数值,还代表 ASCII 码。
坑点:std::cout << my_char 会打印出字符 A 而不是数值 65。如果想把它当成纯二进制数据用,很容易和文本处理混淆。
使用 uint8_t(unsigned char 的别名来自头文件 <cstdint>):
缺陷:它是一个整数类型。
坑点:编译器和 IDE 会认为它是数字,允许你进行加减乘除 uint8_t a = 10; a = a + 5;。这在处理二进制位时往往会导致逻辑错误(我们通常只做位操作,很少把字节当数字加减)。
C++17 的解决方案是引入一个新的类型 std::byte用于在类型系统中明确表示字节数据。它不是字符,也不是整数,只是一个纯粹的字节表示。
它不是字符(不会打印出乱码)。
它不是整数(不能直接加减乘除 ...
1. std::as_const 概述在 C++ 编程中,我们经常需要重载成员函数,分别提供 const 版本和非 const 版本,以便在对象是只读(const)或可变(non-const)时采取不同的行为。最经典的例子是容器类中的下标运算符 operator[]:
123456789101112131415161718class MyVector {public: // 非 const 版本:用于读写 int& operator[](size_t index) { return data[index]; } // const 版本:用于只读 const int& operator[](size_t index) const { return data[index]; }private: std::vector<int> data;};
为了让代码更加简洁,开发者通常希望 非 const 版本的函数能够直接调用 ...
1. std::string_view 的登场在 C++17 之前,当我们需要编写一个函数来处理字符串时,通常会面临一个艰难的选择:
使用 const std::string&
优点:安全,避免拷贝。
缺点:如果参数是一个字符串字面量(如 "hello")或者是一个 C 风格字符串(如 char*),编译器必须隐式地创建一个临时的 std::string 对象。这意味着会发生内存分配,这是非常昂贵的操作!
使用 const char*
优点:速度快,没有内存分配,可以接受字面量。
缺点:不安全,不能获取字符串长度(必须调用 strlen),不能直接使用 STL 算法,一旦涉及到 std::string 成员就麻烦了。
std::string_view 是 C++17 引入的一个只读引用包装器。我们可以把它想象成指向字符串的窗户:它只负责观察字符串的数据,而不拥有字符串的数据。
std::string_view 类 API 在线查询
std::string_view 的构造函数设计得非常宽容,下面这些类型都可以隐式转换为 string_view: ...
1. 解决没有值的尴尬作为 C++ 使用者,我们经常会经历以下这些场景:
查找失败:在一个std::map或std::vector里查找某个元素,如果没找到,此时该返回什么?
返回 -1?如果容器里存的是负数怎么办?
返回 nullptr?这个只能用于指针,而且得在堆上分配内存,万一忘了 delete 就内存泄漏了。
抛出异常?如果没找到是很常见的情况,抛出异常太重了,影响性能。
无效配置:读取一个配置文件,某个键可能不存在。我们不得不定义一个魔法值(比如 INT_MIN 或空字符串)来表示无效,这会让代码里充满 if (val != INT_MIN) 的检查,既难看又容易出错。
12345678910111213141516171819// 方法1: 使用特殊值(容易混淆)int findIndex(const std::vector<int>& v, int value) { for (size_t i = 0; i < v.size(); ++i) { if (v[i] == value) retu ...
1. std::variant 概述想象你有一个魔法盒子,这个盒子同时只能装一种东西,但这个东西可以是不同类型的,比如:今天装一个整数,明天装一个字符串,后天装一个浮点数。这个魔法盒子就是 std::variant。std::variant 是 C++17 引入的类型安全的联合体。与传统的 union 不同,它有以下特点:
多选一:在定义时必须告诉它,它只能存哪些类型(比如 int, double, string)。不能存这三种类型以外的东西。
类型安全:它永远知道自己当前存的是哪个类型。如果试图用错误的类型去取数据,编译器会在编译期帮我们检查,或者直接抛出异常,绝不会产生未定义行为。
高效:大多数情况下,std::variant 的大小就是其所有成员中最大的那个大小(加上一点点开销),它通常存储在栈上,不需要像 std::any 那样动态分配内存。
std::variant类的头文件和基础声明如下:
123#include <variant>// 定义一个 variant,它可以存 int,或者 double,或者 std::stringstd::variant< ...









































