C++ 23 实用工具(一)

C++ 23 实用工具(一)

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

常用函数

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

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

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

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

int main()
{
	std::cout << std::min(2011, 2022) << std::endl; // 2011
	std::cout << std::min({3, 1, 2011, 2022, -5}) << std::endl; // -5
	std::cout << std::min(-10, -5, [](int a, int b) { return std::abs(a) < std::abs(b); }) << std::endl; // -5

	auto 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,2022
	std::cout << pairSeq.first << "," << pairSeq.second << std::endl; // -5,2022
	std::cout << pairAbs.first << "," << pairAbs.second << std::endl; // 1,2022

	return 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 函数需要头文件

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

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

#include 
#include 
#include 

int main()
{
	// midpoint
	std::cout << std::midpoint(10, 20) << std::endl; // 15
	std::cout << std::midpoint(10.5, 20.5) << std::endl; // 15.5

	int arr[] = {1, 2, 3, 4, 5};
	int* p1 = &arr[0];
	int* p2 = &arr[4];
	std::cout << *std::midpoint(p1, p2) << std::endl; // 3

	// lerp
	std::cout << std::lerp(10, 20, 0.5) << std::endl; // 15
	std::cout << std::lerp(10.5, 20.5, 0.5) << std::endl; // 15.5

	return 0;
}

image-20230411154217108

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

头文件 中定义的函数 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

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

#include 
// ...
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 
using std::initialiser_list;

struct MyData{
    MyData(int, double, char){};
};

template 
T createT(Args&&... args){
    return T(std::forward(args)... );
}

int a = createT();
int b = createT(1);
std::string s = createT("Only for testing.");
MyData myData2 = createT(1, 3.19, 'a');
typedef std::vector IntVec;
IntVec intVec = createT(initialiser_list({1, 2, 3}));

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

#include 
#include 
#include 
#include 

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::type>(enum),使用了类型特征函数 std::underlying_type

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

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

std::swap

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

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

#include 

template 
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 来实现移动语义,从而提高了效率

你可能感兴趣的:(C++,C++23,c++,开发语言)