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< ...
1. std::any 概述在学习 C++ 过程中,你可能遇到过这样的烦恼:C++ 是强类型语言,每个变量都必须有明确的类型(int, double, std::string 等)。如果你想写一个函数,既能存 int,又能存 string,甚至存你自己定义的类对象,通常只能用:
模板:但模板必须在编译期确定类型。
空指针 void*:这是 C 语言的做法,不安全,且无法记住类型信息。
联合体 union:C++ 的 union 有很多限制(比如不能有 std::string 这种构造函数复杂的对象)。
为了解决这个问题,C++17 引入了 std::any,它是一种类型安全、可以存储任何可拷贝构造类型的容器。
大家可以把它想象成一个贴了标签的万能快递箱:
使用者往里面扔什么东西都可以(int, double, std::vector, 自定义类…)。
它会自动记住我们扔进去的是什么类型(这就是标签)。
当把东西取出来的时候,如果猜的类型不对,它会报错(这就是类型安全),而不是给我们一堆乱码让程序崩溃。
std::any 持有的是值的拷贝(除非存储的是引用包装器),对于小对象,s ...
1. 节点句柄在 C++17 之前,当我们尝试从一个 std::map 或 std::unordered_map 中提取元素并进行修改时,往往不得不面对代码冗余、效率低下甚至查找两次的尴尬局面。C++17 针对关联容器(包括 map, set, unordered_map, unordered_set)引入了一组至关重要且细节满满的改进。这些改进的核心宗旨是:拒绝冗余查找,拒绝不必要的拷贝,让接口更加统一和易用。
C++17 引入了节点句柄的概念,它是一种轻量级对象,允许在容器间安全转移单个元素的所有权,而无需复制或移动元素本身。这类似于直接操作底层数据结构的节点。
map 的节点句柄类型是 node_type。
set 的节点句柄类型是 node_type。
对于 map,句柄拥有键和值的完整所有权;对于 set,它拥有键的所有权。
节点句柄的关键特性有以下几点:
独占所有权:当一个节点被 extract 出来后,它在原容器中就不复存在了。
内存保留:句柄持有该节点的内存分配器。这意味着当你把句柄插入到另一个容器时,不需要重新分配内存,只是修改指针指向。这是零分配的操作。
修改 ...
1. std::not_fn 概述在 C++17 之前,标准库提供了 std::not1 和 std::not2,用于对谓词(返回 bool 的函数对象)进行取反。如果你用过它们,你一定知道它们有多难用:
要求繁琐:必须给函数对象定义 argument_type 和 result_type 等嵌套类型(或者继承 std::unary_function,后者在 C++11 就被废弃了)。
仅限一元和二元:如果你有一个接受 3 个参数的谓词想取反,std::not1 和 std::not2 爱莫能助。
无法处理 Lambda:Lambda 没有那些嵌套类型,所以不能直接传给 std::not1,必须用 std::function 包裹,这会有性能损耗。
C++17 彻底解决了这个问题,推出了 std::not_fn。
它是谁:一个函数适配器。
它能干什么:接受任何可调用对象,返回一个新的可调用对象。这个新对象的逻辑是:调用原对象,并对返回值进行逻辑非(!)操作。
通用性:无论是一元、二元还是 N 元谓词;无论是函数指针、Lambda 还是成员函数,统统兼容
核心语法与头文件:
头 ...
1. std::apply 概述std::apply 是 C++17 引入的一个实用工具函数,其主要目的是将元组(或类元组对象)解包为函数调用的参数。这解决了在 C++14 及之前版本中,需要手动解包元组参数调用函数的繁琐问题。
函数原型与语法:
1234567#include <tuple>namespace std { template <class F, class Tuple> constexpr decltype(auto) apply(F&& f, Tuple&& t);}
模板参数:
F:可调用对象类型(函数、函数指针、函数对象、lambda 等)
Tuple:类元组类型(std::tuple、std::pair、std::array 或任何支持 std::get 和 std::tuple_size 的类型)
返回值:
返回 f 的调用结果
使用 decltype(auto) 自动推导返回类型,完美转发返回值
C++23 之前只有元组版本,C++23 增加了类似 std:: ...
1. 什么是 std::invoke在 C++17 之前,如果想调用一个函数,直接写 func(args);如果想调用一个对象的成员函数,需要写 obj.func(args) 或者 ptr->func(args)。
但在写一些通用的代码(比如库代码、模板)时,编译器并不知道我们传进来的是一个普通函数、一个成员函数,还是一个像函数一样的对象(比如 lambda)。这时候,写法就非常麻烦了。
123456789// 普通函数func(args...);// 成员函数obj.func_ptr(args...);obj->func_ptr(args...);// 函数对象func_obj(args...);
std::invoke 是 C++17 引入的一个函数模板,用于统一调用各种可调用对象。简单来说,std::invoke 是一个万能调用器,它提供了一种标准化的方式来调用函数、成员函数、函数对象等,无论它们是什么类型,这样可以让代码更加一致和可读。
1234567#include <functional> // std::invoke 头文件template&l ...
1. 什么是类模板参数推导在 C++17 之前,我们在实例化一个类模板时,必须显式地把模板参数写在尖括号 < > 里,即使这些参数很明显可以从构造函数推导出来。举个例子,std::pair 是一个模板类:
1234// C++17 之前,必须手动指定类型std::pair<int, std::string> p1(42, "hello"); // 好啰嗦!std::vector<int> v{1, 2, 3, 4, 5};std::vector<std::string> vs{"hello", "world"};
我们会发现,std::pair(42, "hello") 明明能看出第一个是 int,第二个是 const char*(或者 std::string),为什么还要写那么长呢?
C++17 允许编译器根据我们在构造函数中传入的参数,自动推断出模板参数 T 到底是什么类型,这就叫做类模板参数推导(Class T ...









































