C++ 23 实用工具(一)

news/2024/12/23 0:34:30/

C++ 23 实用工具(一)

工具函数是非常有价值的工具。它们不仅可以用于特定的领域,还可以应用于任意值和函数,甚至可以创建新的函数并将它们绑定到变量上。

常用函数

你可以使用各种变体的 minmaxminmax 函数来对值和初始化列表进行操作。这些函数需要头文件 <algorithm>。相反,std::movestd::forwardstd::to_underlyingstd::swap 函数则定义在头文件 <utility> 中,你可以将它们应用于任意值中。

以上是本章节中提供的一些实用工具函数和库,可以在各个领域和场景中灵活使用。希望这些函数和库能够帮助你更加高效地开发和管理项目。

std::min、std::max和std::minmax

在 C++ 的 <algorithm> 头文件中,有三个非常有用的函数:std::minstd::maxstd::minmax。它们可以作用于值和初始化列表,并将所请求的值作为结果返回。对于 std::minmax 函数,你会得到一个 std::pair,其中第一个元素是最小值,第二个元素是最大值。默认情况下使用小于运算符(<),但你可以应用自己的比较运算符。这个函数需要两个参数并返回一个布尔值。这种返回 truefalse 的函数称为谓词。

int main()
{std::cout << std::min(2011, 2022) << std::endl; // 2011std::cout << std::min({3, 1, 2011, 2022, -5}) << std::endl; // -5std::cout << std::min(-10, -5, [](int a, int b) { return std::abs(a) < std::abs(b); }) << std::endl; // -5auto pairInt = std::minmax(2011, 2022);auto pairSeq = std::minmax({3, 1, 2011, 2022, -5});auto pairAbs = std::minmax({3, 1, 2011, 2022, -5}, [](int a, int b) { return std::abs(a) < std::abs(b); });std::cout << pairInt.first << "," << pairInt.second << std::endl; // 2011,2022std::cout << pairSeq.first << "," << pairSeq.second << std::endl; // -5,2022std::cout << pairAbs.first << "," << pairAbs.second << std::endl; // 1,2022return 0;
}

image-20230411153725929

函数描述
min(a, b)返回 ab 中的较小值。
min(a, b, comp)根据谓词 comp 返回 ab 中的较小值。
min(initializer list)返回初始化列表中的最小值。
min(initializer list, comp)根据谓词 comp 返回初始化列表中的最小值。
max(a, b)返回 ab 中的较大值。
max(a, b, comp)根据谓词 comp 返回 ab 中的较大值。
max(initializer list)返回初始化列表中的最大值。
max(initializer list, comp)根据谓词 comp 返回初始化列表中的最大值。
minmax(a, b)返回 ab 中的较小值和较大值。
minmax(a, b, comp)根据谓词 comp 返回 ab 中的较小值和较大值。
minmax(initializer list)返回初始化列表中的最小值和最大值。
minmax(initializer list, comp)根据谓词 comp 返回初始化列表中的最小值和最大值。

std::midpoint 和 std::lerp

std::midpoint(a, b) 函数计算 ab 的中点。ab 可以是整数、浮点数或指针。如果 ab 是指针,则必须指向同一数组对象。std::midpoint 函数需要头文件 <numeric>

std::lerp(a, b, t) 函数计算两个数的线性插值。它需要头文件 <cmath>。返回值为 a + t(b - a)

线性插值是一种常见的数值分析技术,用于在两个已知数据点之间估算未知点的值。它可以用于许多应用程序,例如图像处理、计算机图形学和动画。其中一个常见的用途是在两个颜色之间进行插值,以创建渐变效果。

#include <iostream>
#include <numeric>
#include <cmath>int main()
{// midpointstd::cout << std::midpoint(10, 20) << std::endl; // 15std::cout << std::midpoint(10.5, 20.5) << std::endl; // 15.5int arr[] = {1, 2, 3, 4, 5};int* p1 = &arr[0];int* p2 = &arr[4];std::cout << *std::midpoint(p1, p2) << std::endl; // 3// lerpstd::cout << std::lerp(10, 20, 0.5) << std::endl; // 15std::cout << std::lerp(10.5, 20.5, 0.5) << std::endl; // 15.5return 0;
}

image-20230411154217108

std::cmp_equal, std::cmp_not_equal, std::cmp_less, std::cmp_greater, std::cmp_less_equal 和 std::cmp_greater_equal

头文件 <utility> 中定义的函数 std::cmp_equalstd::cmp_not_equalstd::cmp_lessstd::cmp_greaterstd::cmp_less_equalstd::cmp_greater_equal 提供整数的安全比较。安全比较意味着负有符号整数与无符号整数进行比较时,负有符号整数的比较小于无符号整数,并且除有符号或无符号整数之外的其他值的比较会在编译时出错。

以下是一个代码片段,演示了有符号/无符号比较的问题。

-1 < 0u; // true
std::cmp_greater(-1, 0u); // false

将 -1 视为有符号整数时,它被提升为无符号类型,这导致了一个令人惊讶的结果。

std::move

头文件 <utility> 中定义的 std::move 函数授权编译器移动资源。在所谓的移动语义中,源对象的值被移动到新对象中。之后,源对象处于一个定义良好但不确定的状态。大多数情况下,这是源对象的默认状态。使用 std::move,编译器将源参数转换为右值引用:static_cast<std::remove_reference<decltype(arg)>::type&&>(arg)。如果编译器无法应用移动语义,则回退到复制语义:

#include <utility>
// ...
std::vector<int> myBigVec(10000000, 2011);
std::vector<int> myVec;
myVec = myBigVec; // 复制语义
myVec = std::move(myBigVec); // 移动语义

移动语义可以提高程序的性能,因为它避免了不必要的内存分配和复制操作。但是请注意,移动语义只适用于具有可移动的资源的对象,例如指针、文件句柄和大型数组。对于简单类型,移动语义通常不会提供任何性能优势。

std::forward

std::forward是定义在头文件中的一个函数,它可以让你编写函数模板,以完全相同的方式转发它们的参数。使用std::forward的典型用例是工厂函数或构造函数。工厂函数创建一个对象,因此应该传递它们的参数而不作任何修改。构造函数通常使用它们的参数来使用相同的参数初始化它们的基类。因此,std::forward是通用库作者的完美工具。

createT是一个函数模板,它必须将它们的参数作为通用引用(Args&&… args)接收。通用引用或转发引用是一个类型推断上下文中的右值引用。std::forward与可变模板一起使用,可以定义完全通用的函数模板。您的函数模板可以接受任意数量的参数并将它们不变地转发。以下是createT的示例用法:

#include <utility>
using std::initialiser_list;struct MyData{MyData(int, double, char){};
};template <typename T, typename... Args>
T createT(Args&&... args){return T(std::forward<Args>(args)... );
}int a = createT<int>();
int b = createT<int>(1);
std::string s = createT<std::string>("Only for testing.");
MyData myData2 = createT<MyData>(1, 3.19, 'a');
typedef std::vector<int> IntVec;
IntVec intVec = createT<IntVec>(initialiser_list<int>({1, 2, 3}));

以上是使用std::forward和createT示例的代码。这段代码定义了一个MyData结构体以及一个createT函数模板,用于创建各种类型的对象。createT函数模板使用std::forward将其参数转发到T类型的构造函数中,以创建T类型的对象。这使得createT函数模板可以用于创建任何类型的对象,从基本类型到自定义类型。

#include <utility>
#include <vector>
#include <string>
#include <initializer_list>using std::initializer_list;struct MyData
{MyData(int, double, char){};
};template <typename T, typename... Args>
T createT(Args&&... args)
{return T(std::forward<Args>(args)...);
}int main()
{int a = createT<int>();int b = createT<int>(1);std::string s = createT<std::string>("Only for testing.");MyData myData2 = createT<MyData>(1, 3.19, 'a');typedef std::vector<int> IntVec;IntVec intVec = createT<IntVec>(initializer_list<int>({1, 2, 3}));return 0;
}

使用std::forward实现完全通用的函数模板

在 C++ 中,使用模板可以实现通用的函数,但是当函数需要接受任意类型的参数时,需要使用可变模板参数(variadic templates)和通用引用(universal reference)。

通用引用是指在类型推导上下文中的右值引用,其语法为 Args&&… args。它可以接受任意类型(左值或右值)的参数,并保持完整性。在函数模板中使用通用引用时,需要使用 std::forward 函数来实现参数的完美转发。

std::forward 是一个 C++11 的模板函数,它可以将一个参数以右值或左值的形式进行转发。通常用于在一个函数中将参数转发到另一个函数,以实现参数的完美转发。

使用 std::forward 和可变模板参数,我们可以定义完全通用的函数模板。函数模板可以接受任意数量和类型的参数,并将它们完美转发到另一个函数中。这样可以大大提高代码的复用性和灵活性。

std::to_underlying

在 C++23 中,函数 std::to_underlying 将枚举类型 enum 转换为其基础类型。这个函数是一个便利函数,其表达式为 static_cast<std::underlying_type<Enum>::type>(enum),使用了类型特征函数 std::underlying_type

enum class Color { RED, GREEN, BLUE };
Color c = Color::RED;
std::underlying_type_t<Color> cu = std::to_underlying(c);

上面的代码将枚举类型 Color 的值 Color::RED 转换为其基础类型 std::underlying_type_t<Color>

std::swap

使用头文件 <utility> 中定义的函数 std::swap,您可以轻松交换两个对象。C++ 标准库中的通用实现内部使用函数 std::move

下面是带有移动语义的 std::swap 的示例实现代码:

#include <utility>template <typename T>
inline void swap(T& a, T& b) noexcept {T tmp(std::move(a));a = std::move(b);b = std::move(tmp);
}

上面的代码定义了一个模板函数 swap,可以交换两个类型为 T 的对象。该函数内部使用了 std::move 来实现移动语义,从而提高了效率


http://www.ppmy.cn/news/44956.html

相关文章

测试注意事项

一、禅道编写测试用例注意事项 1、基于一个测试用例只测试一个功能点&#xff0c;可以在分组中标记测试点&#xff0c;在下方的每一条中进行具体的测试步骤描述 2、测试用例要具有通用性&#xff0c;不能侧重于第一次进入&#xff0c;更侧重功能 3、编写后&#xff0c;阅读时…

【Mysql】日志

【Mysql】日志 文章目录【Mysql】日志1. 错误日志2. 二进制日志2.1 格式2.2 查看2.3 删除3. 查询日志4. 慢查询日志1. 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过 程中发生任何严重错误时的相关…

笔记:C++

一、C以及C基本函数 1、面向对象&#xff1a;将能够实现某一事物的万事万物都封装在一起&#xff0c;称之为类&#xff0c;在类中提供公共的接口&#xff0c;用户可以通过公共的接口对类中的相关属性进行控制。 2、C兼容C&#xff0c;但是C的编译器比C语言的编译器更加严格。…

大数据能力提升项目|学生成果展系列之四

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

Vue学习——【第二弹】

前言 上一篇文章 Vue学习——【第一弹】 中我们学习了Vue的相关特点及语法&#xff0c;这篇文章接着通过浏览器中的Vue开发者工具扩展来进一步了解Vue的相关工作机制。 Vue的扩展 我们打开Vue的官方文档&#xff0c;点击导航栏中的生态系统&#xff0c;点击Devtools 接着我…

第02章_变量与运算符

第02章_变量与运算符 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 关键字&#xff08;keyword&#xff09; 定义&#xff1a;被Java语言赋予了特殊含义&#xff0c;用做专门…

四十六、docker-compose部署

一个项目肯定包含多个容器&#xff0c;每个容器都手动单独部署肯定费时费力。docker-compose可以通过脚本来批量构建镜像和启动容器&#xff0c;快速的部署项目。 使用docker-compose部署主要是编写docker-compose.yml脚本。 一、项目结构 不论是Dockerfile还是docker-compo…

【java】eclipse

eclipse重置窗口布局https://jingyan.baidu.com/article/0bc808fc55a2bc5ad585b923.html 快速查找文件 CtrlShiftR 自定义ctrel P 查找函数 Ctrl O eclipse 如何自定义快捷键&#xff1f;eclipse快捷键大全https://www.xitongbuluo.com/jiaocheng/74408.html F5键与F6键均…