前言
C++中的元组(tuple)是一个类似于 pair 的标准库模板类(template class),它是一个固定大小且可以包含不同类型的值的容器。
元组可以用于组合和处理多个值,类似于结构体(struct),但不需要定义新的类型。元组的长度和每个元素的类型都是在编译时确定的。由于已经在编译期间已经确定元组的大小,所以不能在后续扩充 tuple 的大小了。
C++中的元组(tuple)是在C++11标准中引入的。在此之前,C++标准库中没有元组的实现,但是一些第三方库和框架提供了自己的元组实现。
C++11标准引入了std::tuple模板类,它定义了一个固定大小且可以包含不同类型的值的容器。std::tuple是一个非常有用的工具,可以用于组合和处理多个值,以及在函数返回多个值、遍历多个容器和参数包展开等场景中使用。
除了std::tuple之外,C++11标准还引入了std::make_tuple和std::tie函数,它们可以方便地创建和解包元组。C++11标准库中还引入了一些其他的元组相关功能,如std::tuple_size和std::tuple_element等,用于获取元组的大小和元素类型。
当我们需要将一些数据组合成单一的对象,但又不想定义一个新的数据结构来表示这些数据时, tuple 便适用于此场景。
接下来的示例我都是参考chatgpt。
一、tuple的使用
1.1 基本使用
(1)
#include <tuple>
#include <string>
#include <iostream>
int main() {
//初始化 tuple :myTuple
//std::tuple<int, std::string, double> myTuple(42, "hello", 3.14);
std::tuple<int, std::string, double> myTuple;
//使用 make_tuple 初始化 tuple
myTuple = std::make_tuple(42, "hello", 3.14);
//tuple 成员都是未命名的,使用 std::get()函数访问 tuple 成员:get<i>(tuple)
std::cout << std::get<0>(myTuple) << std::endl; // 42
std::cout << std::get<1>(myTuple) << std::endl; // "hello"
std::cout << std::get<2>(myTuple) << std::endl; // 3.14
//使用 tuple_size 获取 tuple 中元素的个数
std::cout << std::tuple_size<decltype(myTuple)>::value << std::endl; //3
return 0;
}
上面的代码创建了一个包含三个元素的元组,并使用get函数分别访问每个元素的值。元组的元素可以使用索引或者std::get函数来访问。
(2)
元组还可以使用std::tie函数将元素解包到不同的变量中:
#include <tuple>
#include <string>
#include <iostream>
int main() {
std::tuple<int, std::string, double> myTuple(42, "hello", 3.14);
std::cout << std::get<0>(myTuple) << std::endl; // 42
std::cout << std::get<1>(myTuple) << std::endl; // "hello"
std::cout << std::get<2>(myTuple) << std::endl; // 3.14
int myInt;
std::string myString;
double myDouble;
//使用 tie 将 myTuple这将元组的每个元素分别赋值给myInt、myString和myDouble变量。
std::tie(myInt, myString, myDouble) = myTuple;
std::cout << myInt << std::endl; // 42
std::cout << myString << std::endl; // "hello"
std::cout << myDouble << std::endl; // 3.14
return 0;
}
1.2 tuple_element
std::tuple_element是一个类型别名模板,用于获取std::tuple中指定位置的元素类型。
#include <iostream>
#include <tuple>
#include <type_traits>
int main() {
// 创建一个std::tuple对象
std::tuple<int, double, std::string> myTuple(42, 3.14, "hello");
// 获取元组的第一个元素
int x = std::get<0>(myTuple);
std::cout << "x = " << x << std::endl;
// 获取元组的第二个元素
double y = std::get<1>(myTuple);
std::cout << "y = " << y << std::endl;
// 获取元组的第三个元素
std::string z = std::get<2>(myTuple);
std::cout << "z = " << z << std::endl;
// 检查元素类型
// std::tuple_element<0, decltype(myTuple)>::type表示获取myTuple的第一个元素的类型,即int类型
std::cout << "type of 1st element: " << typeid(std::tuple_element<0, decltype(myTuple)>::type).name() << std::endl;
std::cout << "type of 2nd element: " << typeid(std::tuple_element<1, decltype(myTuple)>::type).name() << std::endl;
std::cout << "type of 3rd element: " << typeid(std::tuple_element<2, decltype(myTuple)>::type).name() << std::endl;
// 使用std::tuple_element_t类型别名获取元素类型
static_assert(std::is_same_v<std::tuple_element_t<0, decltype(myTuple)>, int>);
static_assert(std::is_same_v<std::tuple_element_t<1, decltype(myTuple)>, double>);
static_assert(std::is_same_v<std::tuple_element_t<2, decltype(myTuple)>, std::string>);
return 0;
}
其中:
typeid(std::tuple_element<0, decltype(myTuple)>::type).name()
typeid是一个C++运算符,用于获取一个表达式的类型信息。当应用于一个类型时,typeid返回一个std::type_info对象,表示该类型的类型信息。而std::type_info::name()函数用于获取该类型的名称。
在这段代码中,typeid(std::tuple_element<0, decltype(myTuple)>::type)用于获取myTuple的第一个元素的类型,即int类型的std::type_info对象。而std::type_info::name()函数用于获取int类型的名称,即字符串"int"。
类似地,typeid(std::tuple_element<1, decltype(myTuple)>::type).name()用于获取myTuple的第二个元素的类型名称,即字符串"double";而typeid(std::tuple_element<2, decltype(myTuple)>::type).name()用于获取myTuple的第三个元素的类型名称,即字符串"std::string"。
总之,typeid运算符和std::type_info类是C++标准库中用于获取类型信息的工具,它们可以帮助我们在运行时查询和操作类型信息。在程序中使用typeid运算符和std::type_info类可以帮助我们进行一些类型安全检查和调试任务。
其中:
static_assert(std::is_same_v<std::tuple_element_t<0, decltype(myTuple)>, int>);
使用了C++11中的static_assert断言,用于在编译时检查一个表达式是否为true,如果不是,则会在编译时产生一个编译错误。
在这段代码中,static_assert用于检查std::tuple_element_t<0, decltype(myTuple)>是否等于int类型。其中,std::tuple_element_t<0, decltype(myTuple)>表示获取myTuple的第一个元素的类型,即int类型别名。而std::is_same_v是一个类型比较模板,用于检查两个类型是否相同,如果相同则返回true,否则返回false。
因此,如果std::tuple_element_t<0, decltype(myTuple)>等于int类型,则static_assert不会产生编译错误,程序正常编译和运行;否则,static_assert会产生编译错误,提示用户类型不匹配。
这种在编译期间进行静态检查的技术可以帮助我们在编译时发现代码中的错误,从而提高代码的健壮性和可维护性。
比如我把上述改为:
static_assert(std::is_same_v<std::tuple_element_t<0, decltype(myTuple)>, long>);
$ g++ tuple.cpp
tuple.cpp: In function ‘int main()’:
tuple.cpp:28:24: error: static assertion failed
28 | static_assert(std::is_same_v<std::tuple_element_t<0, decltype(myTuple)>, long>);
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
编译期间报错。
1.3 tuple引用的使用
tuple可以通过引用(reference)来访问和修改元组中的元素。
#include <iostream>
#include <tuple>
#include <functional>
int main() {
int x = 42;
double y = 3.14;
// 创建一个std::tuple对象,存储x和y的引用
std::tuple<std::reference_wrapper<int>, std::reference_wrapper<double>> myTuple(std::ref(x), std::ref(y));
// 修改元组中的元素
std::get<0>(myTuple).get() = 100;
std::get<1>(myTuple).get() = 2.718;
// 输出原始变量的值
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
//修改x,y的值
x = 10;
y = 1.1;
//tuple元素中的值也发生了相应的变换
std::cout << std::get<0>(myTuple) << std::endl; // 10
std::cout << std::get<1>(myTuple) << std::endl; // 1.1
return 0;
}
std::ref 是一个函数模板,它接受一个对象的引用,并返回一个包装(wrapper)该引用的 std::reference_wrapper 对象。这样,我们就可以将这个 std::reference_wrapper 对象作为元素插入到 std::tuple 中。
首先创建了两个变量x和y,然后创建了一个std::tuple对象myTuple,存储了x和y的引用。在创建myTuple时,我们使用了std::ref函数将x和y转换为了std::reference_wrapper类型,即引用类型的包装器。然后,我们使用std::get函数和std::reference_wrapper::get函数分别访问和修改了myTuple中的元素。最后,我们输出了原始变量x和y的值,可以看到它们已经被修改为了std::tuple中的值。
需要注意的是,当我们使用std::ref函数创建std::tuple时,需要使用std::reference_wrapper类型来存储引用,因为std::tuple通常不直接存储引用类型。在访问std::tuple中的元素时,需要使用std::reference_wrapper::get函数来获取原始的引用变量。
std::tuple可以存储引用类型,但需要注意引用的生命周期问题。
当我们将一个引用类型的变量作为std::tuple的元素时,实际上是将该引用的值存储在std::tuple中。如果该引用的原始变量在std::tuple被访问之前被销毁了,那么在访问std::tuple时就会出现未定义行为。在访问std::tuple中的引用元素时,我们需要确保引用的原始变量的生命周期足够长,以避免未定义行为。通常情况下,我们应该避免在std::tuple中存储引用类型,并使用std::reference_wrapper类型代替引用类型来存储元素的引用。
总之,使用std::ref函数可以方便地将变量转换为std::tuple的元素引用,从而方便地访问和修改元素。同时,使用std::reference_wrapper类型可以避免在std::tuple中存储引用类型的问题。
二、c++17结构化绑定
1.结构化绑定简介
C++17引入了结构化绑定(Structured Bindings)特性,它允许我们使用一种更简洁的语法来分解(解包)一个复杂类型的成员变量。结构化绑定通过将一个复杂类型的成员变量分解为多个单独的变量,使得我们可以更方便地访问和处理这些变量。
原则上,结构化绑定可以用于公有成员,原始C-style数组,以及“似若tuple”的对象(比如:std::pair,std::tuple和std::array):
下面是一个简单的示例:
#include <iostream>
#include <tuple>
//x,y是结构体的公有成员
struct MyStruct {
int x;
double y;
};
int main() {
MyStruct myObj {42, 3.14};
// 使用结构化绑定分解myObj成为单独的变量x和y
auto [x, y] = myObj;
// 输出分解后的变量
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
return 0;
}
结构化绑定的好处是可以直接通过名字访问值,并且由于名字可以传递语义信息,使得代码可读性也大大提高。
在上面的示例中,我们首先定义了一个结构体MyStruct,其中包含两个成员变量x和y。然后,我们创建了一个MyStruct对象myObj,包含了一些随机的值。接着,我们使用结构化绑定将myObj分解为两个单独的变量x和y。最后,我们输出了分解后的变量x和y的值。
需要注意的是,结构化绑定只能用于支持std::tuple-like接口的类型(例如std::pair和std::tuple),或者可以使用std::get函数进行访问的类型。如果我们想使用结构化绑定来分解自定义的类型,需要在该类型中实现std::tuple-like接口或者提供std::get函数的实现。
总之,结构化绑定是C++17引入的一项非常方便的特性,它可以帮助我们更方便地访问和处理复杂类型的成员变量。
2.tuple 使用结构化绑定
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello, world!");
// 使用结构化绑定分解myTuple成为单独的变量x, y, z
auto [x, y, z] = myTuple;
// 输出分解后的变量
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
std::cout << "z = " << z << std::endl;
return 0;
}
在上面的示例中,我们首先创建了一个std::tuple对象myTuple,包含了三个元素。然后,我们使用结构化绑定将myTuple分解为三个单独的变量x、y和z。最后,我们输出了分解后的变量的值。
三、tuple返回多个值
使用std::tuple来返回多个值可以使得代码更加简洁和易于理解。
例子:
#include <iostream>
#include <tuple>
std::tuple<int, double, std::string> myFunction() {
int x = 42;
double y = 3.14;
std::string z = "Hello, world!";
return std::make_tuple(x, y, z);
}
int main() {
// 调用myFunction函数,获取返回值
// 使用结构化绑定特性
auto [x, y, z] = myFunction();
// 输出返回值
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
std::cout << "z = " << z << std::endl;
return 0;
}
在上面的示例中,我们定义了一个函数myFunction,该函数返回一个std::tuple对象,包含了三个不同类型的值。然后,我们在主函数中调用myFunction函数,并使用结构化绑定将返回值分解为三个单独的变量x、y和z。最后,我们输出了分解后的变量的值。
需要注意的是,使用std::tuple返回多个值可能会影响代码的可读性。因此,我们应该在需要时选择合适的返回类型,以使代码更加清晰易懂。
总结
元组(tuple)的使用有其优点和缺点。下面是一些主要的优点和缺点:
优点:
元组可以容纳任意数量和类型的元素,因此它们非常灵活。这意味着您可以使用它们来存储和处理多个值,而不必定义新的类型或容器。
元组可以通过std::get函数或使用类似于结构体的点表示法来访问其元素。这使得元组使用起来非常方便。
元组可以在函数中作为返回值,以便返回多个值。这使得函数的返回值更加清晰和易读。
缺点:
元组的元素可以通过std::get函数和索引访问,但不能像结构体一样使用成员变量名。这使得代码可读性稍有降低。
元组的长度和每个元素的类型都是在编译时确定的。这意味着您无法在运行时动态添加或删除元素。
元组的元素可以包含不同的类型,这意味着您需要小心处理类型转换和类型匹配的问题。文章来源:https://www.toymoban.com/news/detail-427795.html
参考资料
Chatgpt
https://github.com/CnTransGroup/Cpp17TheCompleteGuideChinese/blob/master/src/part1/cp1.md
https://www.zhihu.com/question/298981020文章来源地址https://www.toymoban.com/news/detail-427795.html
到了这里,关于C++11 tuple的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!