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 ...
1. 非类型模板参数简单来说,非类型模板参数就是模板中可以传入的不是类型的参数。可以用生活中的例子来解释,想象一下现在有一个饼干模具:
类型模板参数:决定了模具的形状(圆形、方形、星形)
非类型模板参数:决定了模具的大小(直径5cm、直径10cm)
在代码中,非类型模板参数允许我们在编译时传入具体的值,而不是类型。
12345678910111213141516template<typename T, int Size> // T是类型参数,Size是非类型参数class FixedArray {private: T data[Size]; // 使用Size来指定数组大小 public: int getSize() const { return Size; }};// 使用示例FixedArray<int, 10> arr10; // 创建一个包含10个int的数组FixedArray<double, 100> arr100; // 创建一 ...
1. 枚举初始化在 C++17 之前,给作用域枚举(强类型枚举)变量赋值有时会很麻烦,甚至不得不写强转。C++17 修复了这个痛点,让枚举的使用变得更像普通的整数变量一样自然。
如果我们定义了一个作用域枚举(C++11),想要给它初始化一个值,必须小心翼翼。假设我们有一个代表颜色的枚举:
123456enum class Color : unsigned int { Red = 1, Green = 2, Blue = 3};
如果我们想在代码中临时定义一个灰度值(比如 0),这样做是不行的:
12// 错误!C++17 之前不允许用整数直接初始化 enum classColor c = 0; // 编译报错!类型不匹配
以前必须显式地告诉编译器:“我知道我在干什么,请把这个整数强转成 Color。”
12// 麻烦的旧做法:必须写 static_castColor c = static_cast<Color>(0); // 即使是0,也得写这么长一串
这种写法非常繁琐,尤其是在处理底层硬件寄存器、网络协议或者位掩码时,枚举可能代 ...
1. C++17 中的聚合类聚合类就是一种可以直接用初始化列表 {} 进行初始化的类(像 struct 或数组一样)。它通常用来表现“数据集合”,没有太复杂的封装逻辑。
在 C++17 之前,一个类要成为聚合类,必须满足以下严苛条件:
没有用户声明的构造函数。
没有私有或保护的非静态数据成员(必须是 public 的)。
没有虚函数。
没有虚基类。
在 C++17 之前,如果想让一个类继承自另一个类,同时又想保持聚合初始化的特性(即直接用 {} 赋值),是做不到的。一旦继承了基类,这个类就不再是聚合类了,必须写构造函数来转发参数给基类。
C++17 放宽了限制,允许聚合类拥有基类(前提是基类本身也是非虚的聚合类)。这意味着我们可以更方便地扩展数据结构。现在的规则是:
只要基类也是非虚的、没有私有/保护成员的,就可以继承它。
派生类依然可以不写构造函数,直接使用 {} 初始化。
初始化顺序: 先初始化基类部分,再初始化派生类成员。
综上所述,在C++17中对聚合类的要求如下:
必须是公有继承(private ...
1. C++17 的主要改进Lambda 表达式是 C++11 引入的一种匿名函数(没有名字的函数)。它可以让我们在需要函数的地方快速定义一个小型函数,特别适合用于算法、回调函数等场景。
C++17 对 Lambda 表达式进行了改进,让我们用起来更方便!
2. constexpr Lambda在 C++17 之前,Lambda 表达式默认是不能在编译期常量表达式(如 constexpr 函数或模板参数)中使用的。虽然编译器经常在内部做一些优化,但标准层面并不允许显式地将 Lambda 用于编译期计算。
C++17 规定,Lambda 表达式默认就是隐式的 constexpr(前提是它的函数体满足 constexpr 的要求)。这意味着我们可以在编译期调用 Lambda,比如在 constexpr 函数里使用 Lambda、把 Lambda 当作模板参数传递。这极大地增强了 C++ 的元编程能力。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950#in ...
1. 什么是属性在 C++ 中,属性 是一种给编译器下指令的机制。它允许程序员告诉编译器某些额外的信息,比如“不要警告我”、“这段代码可能会失败”或者“把这个函数放到某个特定的段”。
属性的标准写法是用 两个方括号 [[ ... ]] 括起来:
1[[标签]] 代码
在 C++17 之前,大家最常使用的属性是 [[deprecated]],用来标记某个函数或变量已经被弃用,如果别人还在用,编译器就会发出警告。
假设你写了一个旧版本的函数,后来发现更好的实现方法,但为了兼容性又不能立刻删除旧函数。
123456789101112131415161718192021222324#include <iostream>// 告诉编译器:这个函数过时了, 不带文字描述[[deprecated]] void oldFunc() {}// 旧的实现方式(被标记为过时)[[deprecated("请使用新的 FastCalculate() 函数")]]int CalculateOldWay() { return 100;} ...
1. 什么是命名空间在 C++ 中,为了避免变量名、函数名冲突(比如你也写了个 max 函数,库里也有个 max),我们通常使用“命名空间”来把它们圈在不同的地盘里。
传统的“嵌套”写法是这样的:
12345678910111213namespace A { namespace B { namespace C { void func() { // 我是函数 } } }}
调用的代码是这样的:****使用 ::(两个冒号)来连接各个命名空间层级。
1A::B::C::func();
看上面的定义代码,是不是像俄罗斯套娃一样一层套一层?不仅代码显得很长,而且每一层都要写一对花括号 {},缩进层级太多,代码很难看,还浪费屏幕空间。
2. C++17 带来的改变C++17 允许我们用一种更直观、更扁平的方式来写嵌套命名空间。新写法: ...







































