C++进阶专栏:http://t.csdnimg.cn/afM80
目录
1.“力不从心”的 std::tie
传统的 C/C++ 程序中使用结构化的数据定义,需要用 struct 或 class 定义好类型,然后才能实例化并使用。对于一些小型的轻量化数据,这么做就显得很繁琐。于是 C++ 引入了 std::pair 和 std::tuple,可以很方便地定义一些轻量化的数据类型。但是 std::pair 和 std::tuple 的使用也引入了一些问题,最主要的就是代码可读性的下降,想想当你看到一堆的 first,second,或者 get<0>,get<1> 会不会头疼?
std::tie() 似乎能解决一些问题,来看一下这个函数的使用:
std::set<int> ist{ 5, 8, 12, 9};
std::set<int>::iterator iter;
bool inserted;
std::tie(iter, inserted) = ist.insert(7);
if(inserted)
{
...
}
使用了有意义的变量名,确实提高了代码的可读性。但是 tie 之前需要定义好捆绑的数据,数据类型也必需确定,这就给使用带来了很多繁琐的步骤。仔细分析一下会发现,tie 捆绑的两个值的类型其实完全可以推导出来,要求程序员手工指定有点多此一举。
2.什么是结构化绑定
C++17引入了结构化绑定(Structured Bindings)功能,它允许以一种更加简洁和直观的方式从复合类型中提取成员变量,并将它们绑定到命名变量上。结构化绑定提供了一种更加方便和灵活的方法来处理元组、结构体、数组等复合类型数据。
结构化绑定允许我们在一行代码中声明多个变量,并将聚合类型的元素分别初始化为这些变量。这对于处理如std::pair、std::tuple、数组和结构体等类型的数据非常有用。
例如,在使用std::pair时,传统的方法是这样的:
std::pair<int, std::string> person = {11, "222222222"};
int age = person.first;
std::string name = person.second;
而使用结构化绑定,我们可以这样写:
auto [age, name] = std::make_pair(37, "44444444");
3.结构化绑定的用法
结构化绑定的语法非常直观,它使用方括号[]来列出需要绑定的变量名。这些变量将按照它们在方括号中出现的顺序与聚合类型的元素进行匹配。
3.1.对于数组
假设我们有一个整数数组arr,包含三个元素。我们可以使用结构化绑定轻松地提取出这三个元素,并为它们分别赋予变量名a、b、c:
int arr[] = {11, 22, 33};
auto [a, b, c] = arr;
3.2.对于结构体
结构化绑定同样适用于结构体。假设我们有一个表示点的结构体Point,包含x和y两个成员变量。我们可以使用结构化绑定来提取这两个成员变量:
struct Point {
int x, y;
};
Point p{11, 3442};
auto [x, y] = p;
3.3.对于std::pair
std::pair是C++标准库中用于存储两个元素的模板类。使用结构化绑定,我们可以轻松地从std::pair中提取出两个元素:
std::pair<int, std::string> p(1, "Hello");
auto [i, s] = p;
3.4.对于std::tuple
C++11中引入了元组(tuple)类型,用于存储不同类型的数据。使用结构化绑定,我们可以轻松地从元组中提取出各个元素:
std::tuple<int, std::string, double> t(1, "Hello", 2.0);
auto [i, s, d] = t;
3.5.对于std::array
std::array是C++11中引入的模板类,用于固定大小的数组。使用结构化绑定,我们可以轻松地从std::array中提取出多个元素:
std::array<int, 3> arr{1, 2, 3};
auto [a, b, c] = arr;
3.6.对于类
对于类,结构化绑定同样适用。只要类提供了对应的成员变量或成员函数(如begin、end等),我们就可以使用结构化绑定来提取数据:
class MyClass {
public:
int a, b;
};
MyClass obj{1, 2};
auto [x, y] = obj;
3.7.位域(BitFields)
位域可以理解为是一种特殊的数据结构,对位域的结构化绑定和数据结构的用法是一样的:
struct BitFields
{
unsigned int a : 16;
unsigned int : 4 ;
unsigned int b : 4;
unsigned int c : 8;
};
auto&& [i, j, k] = [] { return BitFields{ 4, 3, 2 }; }();
assert(i == 4);
3.7.结构化绑定与范围for循环
结构化绑定还可以与范围for循环结合使用,简化对容器元素的处理。假设我们有一个std::vector类型的容器v,我们可以使用结构化绑定在范围for循环中提取每个元素的值:
//[1]
std::vector<int> v{1, 2, 3};
for (auto i : v) {
std::cout << i << std::endl;
}
//[2]
// 使用结构化绑定迭代std::map
std::map<int, std::string> dataMap{{1, "one"}, {2, "two"}};
for (const auto& [key, value] : dataMap) {
// 处理每个键值对
}
3.8.与const和引用结合使用
结构化绑定也支持const和引用修饰符,这对于保护数据和避免不必要的拷贝非常有用。
const auto& [refAge, refName] = person; // refAge和refName是对person中数据的常量引用
4.结构化绑定的限制和注意事项
尽管结构化绑定非常强大,但它也有一些限制和需要注意的地方:
1)结构化绑定不能用于类类型,除非该类提供了相应的结构化绑定支持(通过特化std::get或提供tie成员函数)。
2)绑定的变量必须是可以被初始化的类型,且初始化不会引发歧义。
3)在某些情况下,结构化绑定可能导致不必要的拷贝,特别是在处理大型对象时。因此,在性能敏感的场景下要谨慎使用。
5.总结
总体而言,结构化绑定是C++17中一个非常有用的特性,它可以让我们的代码更简洁、易读,提高开发效率。通过结构化绑定,我们可以轻松地从数组、元组、结构体、类等多种数据结构中提取元素,并为它们分别赋予变量名。此外,结构化绑定还可以与范围for循环结合使用,简化对容器元素的处理。