变量之--列表初始化和结构化绑定
變量之(變長)列表初始化
關鍵詞
std::initializer_list
解釋
在 C++98/03 中的對象初始化方法有很多種,這無疑增大了學習難度。這中情況在C++11中終于得到解決。
先看看沒有C++11的時候
//初始化列表 int i_arr[3] = { 1, 2, 3 }; //普通數組 struct A { int x; struct B { int i; int j; } b; } a = { 1, { 2, 3 } }; //POD類型 //拷貝初始化(copy-initialization) int i = 0; class Foo { public: Foo(int) {} } foo = 123; //需要拷貝構造函數 //直接初始化(direct-initialization) int j(0); Foo bar(123); int data = 0; //賦值初始化 int data = {0}; //花括號初始化 int data(0); //構造初始化 int data{0}; //花括號初始化這些不同的初始化方法,都有各自的適用范圍和作用。最關鍵的是,這些種類繁多的初始化方法,沒有一種可以通用所有情況。
為了統一初始化方式,并且讓初始化行為具有確定的效果,C++11 中提出了列表初始化(List-initialization)的概念。
?
統一的初始化
舉個小例子看看C++11變長初始化列表是怎么用的
#include <initializer_list>class MagicFoo{public:std::vector<int> vec;MagicFoo(std::initializer_list<int> list){for (std::initializer_list<int>::iterator it = list.begin(); it!= list.end(); ++it)vec.push_back(*it);}};int main(){ // after C++11MagicFoo magicFoo = {1, 2, 3, 4, 5};for (auto it : magicFoo.vec) std::cout << it << std::endl;}這里需要注意的是,雖然使用了等于號,但它仍然是列表初始化,因此,私有的拷貝構造并不會影響到它。
總結
C++11統一了所有初始化方式,都變成了下面的形式:
Foo a3 = { 123 }; Foo a4 { 123 };除了上面所述的內容之外,列表初始化還可以直接使用在函數的返回值上:
struct Foo { Foo(int, double) {} }; Foo func(void) { return { 123, 321.0 }; }這里的 return 語句就如同返回了一個 Foo(123, 321.0)。
?
結構化綁定
簡介
C++17語言上(語言特性,而不是標準庫新特性)引入了一種結構化綁定的新特性,使用該特性可以利用auto同時聲明多個不同類型的變量并即時從一個tuple-like對象得到賦值/初始化。
Structured binding不但可以使C++的代碼更加簡潔,而且似乎從語法上更貼近Python這種腳本語言了。另外,auto變量會在編譯時推導出變量的類型,所以無需擔心會有運行時效率的下降。而且,好像也并不會影響到編譯效率,這一點尚未看到有實測。
在C++11的時候,如果要接收從函數返回的std::tuple對象,我們可以使用std::tie。
舉個例子:
#include <iostream>#include <tuple>std::tuple<int, double, std::string> f(){ return std::make_tuple(1, 2.3, "456");}int main(){ // C++17 auto [x, y, z] = f(); std::cout << x << ", " << y << ", " << z << std::endl; //C++11 int a; double b, std::string c; std::tie(a, b, c) = f();std::cout << a << ", " << b << ", " << c << std::endl; return 0;}結構化綁定聲明?(C++17 起)
下面部分可以忽略。。。只是為了解釋C++17的結構化綁定。
綁定指定名稱到初始化器的子對象或元素。
類似引用,結構化綁定是既存對象的別名。不同于引用的是,結構化綁定的類型不必為引用類型。
定義
attr(可選) cv-autoref-運算符(可選) [ 標識符列表 ] = 表達式 ; (1)
attr(可選) cv-autoref-運算符(可選) [ 標識符列表 ] { 表達式 } ; (2)
attr(可選) cv-autoref-運算符(可選) [ 標識符列表 ] ( 表達式 ) ; (3)
attr -?? 任意數量的屬性的序列
cv-auto -?? 可有 cv 限定的 auto 類型說明符,亦可包含存儲類說明符 static 或 thread_local ;在 cv 限定符中包含 volatile 是被棄用的 (C++20 起)
ref-運算符 ?? - ?? &或 && 之一
標識符列表 ? -?? 此聲明所引入的各標識符的逗號分隔的列表
表達式 ? -?? 頂層沒有逗號運算符的表達式(文法上為賦值表達式),且具有數組或非聯合類之一的類型。若表達式 涉及任何來自 標識符列表 的名字,則聲明非良構。
情況 1:綁定數組
標識符列表?中的每個標識符均成為指代數組的對應元素的左值。標識符的數量必須等于數組的元素數量。
每個標識符的被引用類型都是數組的元素類型。注意若數組類型?E?為 cv 限定的,則其元素亦然。
int a[2] = {1,2}; auto [x,y] = a; // 創建 e[2],復制 a 到 e,然后 x 指代 e[0],y 指代 e[1]auto& [xr, yr] = a; // xr 指代 a[0],yr 指代 a[1]情況 2:綁定元組式類型
表達式?std::tuple_size<E>::value?必須是良構的整數常量表達式,且標識符的數量必須等于?std::tuple_size<E>::value。
對于每個標識符,引入一個類型為“std::tuple_element<i, E>::type?的引用”的變量:若其對應初始化器是左值,則為左值引用,否則為右值引用。第 i 個變量的初始化器
-
若在?E?的作用域中對標識符?get?按類成員訪問進行的查找中,至少找到一個聲明是首個模板形參為非類型形參的函數模板,則為?e.get<i>()
-
否則為?get<i>(e),其中?get?只進行實參依賴查找,忽略非 ADL 的查找。
這些初始化器表達式中,若實體?e?的類型為左值引用(這僅在?ref-運算符?為?&,或為&&?且初始化器為左值時才發生),則?e?為左值,否則為亡值(這實際上進行了一種完美轉發),i?是?std::size_t?的純右值,而且始終將?<i>?解釋為模板形參列表。
變量擁有與?e?相同的存儲期。
然后該標識符變成指代與上述變量綁定的對象的左值。
第 i 個標識符的被引用類型為?std::tuple_element<i, E>::type。
float x{};char y{};int z{};std::tuple<float&,char&&,int> tpl(x,std::move(y),z);const auto& [a,b,c] = tpl;// a 指名指代 x 的結構化綁定;decltype(a) 為 float&// b 指名指代 y 的結構化綁定;decltype(b) 為 char&&// c 指名指代 tpl 的第 3 元素的結構化綁定;decltype(c) 為 const int情況 3:綁定到數據成員
E?的所有非靜態數據成員必須都是?E?或?E?的同一基類的直接成員,必須在指名為e.name?時于結構化綁定的語境中是良構的。E?不能有匿名聯合體成員。標識符的數量必須等于非靜態數據成員的數量。
標識符列表?中的各個標識符,按聲明順序依次成為指代?e?的各個成員的左值的名字(支持位域);左值的類型是?cv?T_i,其中?cv?是?E?的 cv 限定符且?T_i?是第 i 個成員的聲明類型。
第 i 個標識符的被引用類型是?cv?T_i。
struct S { int x1 : 2; volatile double y1;};S f(); const auto [x, y] = f(); // x 是標識 2 位位域的 const int 左值 // y 是 const volatile double 左值 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的变量之--列表初始化和结构化绑定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nullptr/nullptr_t
- 下一篇: 变长参数模板 和 外部模板