欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析之C++的列表初始化語法

 更新時間:2021年05月20日 09:41:46   作者:華為云開發(fā)者社區(qū)  
有朋友在使用std::array時發(fā)現(xiàn)一個奇怪的問題:當元素類型是復合類型時,編譯通不過。按說std::array和原生數(shù)組的行為幾乎是一樣的,可為什么當元素類型不同時,初始化語法還會有差別?這篇文章會介紹這個問題的原理,以及正確的解決方式。

聚合初始化

先從std::array的內(nèi)部實現(xiàn)說起。為了讓std::array表現(xiàn)得像原生數(shù)組,C++中的std::array與其他STL容器有很大區(qū)別——std::array沒有定義任何構(gòu)造函數(shù),而且所有內(nèi)部數(shù)據(jù)成員都是public的。這使得std::array成為一個聚合(aggregate)。

對聚合的定義,在每個C++版本中有少許的區(qū)別,這里簡單總結(jié)下C++17中定義:一個class或struct類型,當它滿足以下條件時,稱為一個聚合[1]:

  • 沒有private或protected數(shù)據(jù)成員;
  • 沒有用戶提供的構(gòu)造函數(shù)(但是顯式使用=default或=delete聲明的構(gòu)造函數(shù)除外);
  • 沒有virtual、private或者protected基類;
  • 沒有虛函數(shù)

直觀的看,聚合常常對應(yīng)著只包含數(shù)據(jù)的struct類型,即常說的POD類型。另外,原生數(shù)組類型也都是聚合。

聚合初始化可以用大括號列表。一般大括號內(nèi)的元素與聚合的元素一一對應(yīng),并且大括號的嵌套也和聚合類型嵌套關(guān)系一致。在C語言中,我們常見到這樣的struct初始化語句。

解了上面的原理,就容易理解為什么std::array的初始化在多一層大括號時可以成功了——因為std::array內(nèi)部的唯一元素是一個原生數(shù)組,所以有兩層嵌套關(guān)系。下面展示一個自定義的MyArray類型,它的數(shù)據(jù)結(jié)構(gòu)和std::array幾乎一樣,初始化方法也類似:

struct S {
    int x;
    int y;
};

template<typename T, size_t N>
struct MyArray {
    T data[N];
};

int main()
{
    MyArray<int, 3> a1{{1, 2, 3}};  // 兩層大括號
    MyArray<S, 3> a2{{{1, 2}, {3, 4}, {5, 6}}};  // 三層大括號
    return 0;
}

在上面例子中,初始化列表的最外層大括號對應(yīng)著MyArray,之后一層的大括號對應(yīng)著數(shù)據(jù)成員data,再之后才是data中的元素。大括號的嵌套與類型間的嵌套完全一致。這才是std::array嚴格、完整的初始化大括號寫法。

可是,為什么當std::array元素類型是簡單類型時,省掉一層大括號也沒問題?——這就涉及聚合初始化的另一個特點:大括號省略。

大括號省略(brace elision)

C++允許在聚合的內(nèi)部成員仍然是聚合時,省掉一層或多層大括號。當有大括號被省略時,編譯器會按照內(nèi)層聚合所含的元素個數(shù)進行依次填充。

下面的代碼雖然不常見,但是是合法的。雖然二維數(shù)組初始化只用了一層大括號,但因為大括號省略特性,編譯器會依次用所有元素填充內(nèi)層數(shù)組——上一個填滿后再填下一個。

int a[3][2]{1, 2, 3, 4, 5, 6}; // 等同于{{1, 2}, {3, 4}, {5, 6}}

知道了大括號省略后,就知道std::array初始化只用一層大括號的原理了:由于std::array的內(nèi)部成員數(shù)組是一個聚合,當編譯器看到{1,2,3}這樣的列表時,會挨個把大括號內(nèi)的元素填充給內(nèi)部數(shù)組的元素。甚至,假設(shè)std::array內(nèi)部有兩個數(shù)組的話,它還會在填完上一個數(shù)組后依次填下一個。

這也解釋了為什么省掉內(nèi)層大括號,復雜類型也可以編譯成功:

std::array<S, 3> a3{1, 2, 3, 4, 5, 6};  // 內(nèi)層不加括號,編譯成功

因為S也是個聚合類型,所以這里省略了兩層大括號。編譯期按照下面的順序依次填充元素:數(shù)組0號元素的S::x、數(shù)組0號元素的S::y、數(shù)組1號元素的S::x、數(shù)組1號元素的S::y……

雖然大括號可以省略,但是一旦用戶顯式的寫出了大括號,那么必須要和這一層的元素個數(shù)嚴格對應(yīng)。因此下面的寫法會報錯:

std::array<S, 3> a1{{1, 2}, {3, 4}, {5, 6}};  // 編譯失敗!

編譯器認為{1,2}對應(yīng)std::array的內(nèi)部數(shù)組,然后{3,4}對應(yīng)std::array的下一個內(nèi)部成員??墒莝td::array只有一個數(shù)據(jù)成員,于是報錯:too many initializers for 'std::array<S, 3>'

需要注意的是,大括號省略只對聚合類型有效。如果S有個自定義的構(gòu)造函數(shù),省掉大括號就行不通了:

// 聚合
struct S1 {
    S1() = default;
    int x;
    int y;
};

std::array<S1, 3> a1{1, 2, 3, 4, 5, 6};  // OK

// 聚合
struct S2 {
    S2() = delete;
    int x;
    int y;
};

std::array<S2, 3> a2{1, 2, 3, 4, 5, 6};  // OK

// 非聚合,有用戶提供的構(gòu)造函數(shù)
struct S3 {
    S3() {};
    int x;
    int y;
};

std::array<S3, 3> a3{1, 2, 3, 4, 5, 6};  // 編譯失??!

這里可以看出=default的構(gòu)造函數(shù)與空構(gòu)造函數(shù)的微妙區(qū)別。

std::initializer_list的另一個故事

上面講的所有規(guī)則,都只對聚合初始化有效。如果我們給MyArray類型加上一個接受std::initializer_list的構(gòu)造函數(shù),情況又不一樣了:

struct S {
    int x;
    int y;
};

template<typename T, size_t N>
struct MyArray {
public:
    MyArray(std::initializer_list<T> l)
    {
        std::copy(l.begin(), l.end(), std::begin(data));
    }
    T data[N];
};

int main()
{
    MyArray<S, 3> a{{{1, 2}, {3, 4}, {5, 6}}};  // OK
    MyArray<S, 3> b{{1, 2}, {3, 4}, {5, 6}};  // 同樣OK
    return 0;
}

當使用std::initializer_list的構(gòu)造函數(shù)來初始化時,無論初始化列表外層是一層還是兩層大括號,都能初始化成功,而且a和b的內(nèi)容完全一樣。

這又是為什么?難道std::initializer_list也支持大括號省略?

這里要提一件趣事:《Effective Modern C++》這本書在講解對象初始化方法時,舉了這么一個例子[2]:

class Widget {
public:
  Widget();                                   // default ctor
  Widget(std::initializer_list<int> il);      // std::initializer_list ctor
  …                                          // no implicit conversion funcs
}; 

Widget w1;          // calls default ctor
Widget w2{};        // also calls default ctor
Widget w3();        // most vexing parse! declares a function!    

Widget w4({});      // calls std::initializer_list ctor with empty list
Widget w5{{}};      // ditto <-注意!

然而,書里這段代碼最后一行w5的注釋卻是個技術(shù)錯誤。這個w5的構(gòu)造函數(shù)調(diào)用時并非像w4那樣傳入一個空的std::initializer_list,而是傳入包含了一個元素的std::initializer_list。

即使像Scott Meyers這樣的C++大牛,都會在大括號的語義上搞錯,可見C++的相關(guān)規(guī)則充滿著陷阱!

連《Effective Modern C++》都弄錯了的規(guī)則

幸好,《Effective Modern C++》作為一本經(jīng)典圖書,讀者眾多。很快就有讀者發(fā)現(xiàn)了這個錯誤,之后Scott Meyers將這個錯誤的闡述放在了書籍的勘誤表中[3]。

Scott Meyers還邀請讀者們和他一起研究正確的規(guī)則到底是什么,最后,他們把結(jié)論寫在了一篇文章里[4]。文章通過3種具有不同構(gòu)造函數(shù)的自定義類型,來揭示std::initializer_list匹配時的微妙差異。代碼如下:

#include <iostream>
#include <initializer_list>
 
class DefCtor {
  int x;
public:
  DefCtor(){}
};
 
class DeletedDefCtor {
  int x;
public:
  DeletedDefCtor() = delete;
};
 
class NoDefCtor {
  int x;    
public:
  NoDefCtor(int){}
};
 
template<typename T>
class X {
public:
  X() { std::cout << "Def Ctor\n"; }
 
  X(std::initializer_list<T> il)
  {
    std::cout << "il.size() = " << il.size() << '\n';
  }
};
 
int main()
{
  X<DefCtor> a0({});           // il.size = 0
  X<DefCtor> b0{{}};           // il.size = 1
 
  X<DeletedDefCtor> a2({});    // il.size = 0
  // X<DeletedDefCtor> b2{{}};    // error! attempt to use deleted constructor
 
  X<NoDefCtor> a1({});         // il.size = 0
  X<NoDefCtor> b1{{}};         // il.size = 0
}

對于構(gòu)造函數(shù)已被刪除的非聚合類型,用{}初始化會觸發(fā)編譯錯誤,因此b2的表現(xiàn)是容易理解的。但是b0和b1的區(qū)別就很奇怪了:一模一樣的初始化方法,為什么一個傳入std::initializer_list的長度為1,另一個長度為0?

構(gòu)造函數(shù)的兩步嘗試

問題的原因在于:當使用大括號初始化來調(diào)用構(gòu)造函數(shù)時,編譯器會進行兩次嘗試:

1.把整個大括號列表連同最外層大括號一起,作為構(gòu)造函數(shù)的std::initializer_list參數(shù),看看能不能匹配成功;

2.如果第一步失敗了,則將大括號列表的成員作為構(gòu)造函數(shù)的入?yún)?,看看能不能匹配成功?/p>

對于b0{{}}這樣的表達式,可以直觀理解第一步嘗試是:b0({{}}),也就是把{{}}整體作為一個參數(shù)傳給構(gòu)造函數(shù)。對b0來說,這個匹配是能夠成功的。因為DefCtor可以通過{}初始化,所以b0的初始化調(diào)用了X(std::initializer_list<T>),并且傳入含有1個成員的std::initializer_list作為入?yún)ⅰ?/p>

對于b1{{}},編譯器同樣會先做第一步嘗試,但是NoDefCtor不允許用{}初始化,所以第一步嘗試會失敗。接下來編譯器做第二步嘗試,將外層大括號剝掉,調(diào)用b1({}),發(fā)現(xiàn)可以成功,這時傳入的是空的std::initializer_list。

再回頭看之前MyArray的例子,現(xiàn)在我們可以分析出兩種初始化分別是在哪一步成功的:

MyArray<S, 3> a{{{1, 2}, {3, 4}, {5, 6}}};  // 在第二步,剝掉外層大括號后匹配成功
MyArray<S, 3> b{{1, 2}, {3, 4}, {5, 6}};  // 第一步整個大括號列表匹配成功

綜合小測試

到這里,大括號初始化在各種場景下的規(guī)則就都解析完了。不知道讀者是否徹底掌握了?

不妨來試一試下面的小測試:這段代碼里有一個僅含一個元素的std::array,其元素類型是std::tuple,tuple只有一個成員,是自定義類型S,S定義有默認構(gòu)造函數(shù)和接受std::initializer_list<int>的構(gòu)造函數(shù)。對于這個類型,初始化時允許使用幾層大括號呢?下面的初始化語句有哪些可以成功?分別是為什么?

struct S {
    S() = default;
    S(std::initializer_list<int>) {}
};

int main()
{
    using MyType = std::array<std::tuple<S>, 1>;
    MyType a{};             // 1層
    MyType b{{}};           // 2層
    MyType c{{{}}};         // 3層
    MyType d{{{{}}}};       // 4層
    MyType e{{{{{}}}}};     // 5層
    MyType f{{{{{{}}}}}};   // 6層
    MyType g{{{{{{{}}}}}}}; // 7層
    return 0;
}

以上就是解析之C++的列表初始化語法的詳細內(nèi)容,更多關(guān)于C++的列表初始化語法的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言Make命令用法講解

    C語言Make命令用法講解

    本文詳細講解了C語言Make命令用法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-01-01
  • C利用語言實現(xiàn)數(shù)據(jù)結(jié)構(gòu)之隊列

    C利用語言實現(xiàn)數(shù)據(jù)結(jié)構(gòu)之隊列

    隊列 (Queue):簡稱隊,是另一種限定性的線性表,它只允許在表的一端插入元素,而在另一端刪除元素。q=(a1, a2, a3, … an),其中a1為隊頭,an為隊尾,下面文章小編將為大家詳細介紹,需要的下伙伴可以參考一下
    2021-10-10
  • c++ 有趣的動態(tài)轉(zhuǎn)換

    c++ 有趣的動態(tài)轉(zhuǎn)換

    這篇文章主要介紹了c++ 動態(tài)轉(zhuǎn)換的相關(guān)資料,幫助大家更好的理解和使用c++編程,感興趣的朋友可以了解下
    2020-09-09
  • VS2019中在源文件中如何使用自己寫的頭文件

    VS2019中在源文件中如何使用自己寫的頭文件

    通過頭文件的形式直接調(diào)用自定義的函數(shù),從而免去對函數(shù)的原型進行聲明,本文就詳細的介紹一下VS2019中在源文件中如何使用自己寫的頭文件,感興趣的可以了解一下
    2021-09-09
  • C++對象模型和this指針詳解

    C++對象模型和this指針詳解

    這篇文章主要介紹了詳解C++對象模型和this指針,是C++入門學習中的基礎(chǔ)知識,需要的朋友可以參考下,希望能夠給你帶來幫助
    2021-10-10
  • 詳解C語言初階基礎(chǔ)(2)

    詳解C語言初階基礎(chǔ)(2)

    這篇文章主要介紹了C語言中的初階基礎(chǔ),介紹了其相關(guān)概念,具有一定參考價值。需要的朋友可以了解下,希望能夠給你帶來幫助
    2021-11-11
  • 一文掌握C語言中的柔性數(shù)組

    一文掌握C語言中的柔性數(shù)組

    柔性數(shù)組在C語言的?C99?標準中,引入的新特性,結(jié)構(gòu)中的最后一個元素的大小允許是未知的數(shù)組,即為柔性數(shù)組,本文給大家介紹c語言中的柔性數(shù)組,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • C語言輾轉(zhuǎn)相除法求2個數(shù)的最小公約數(shù)

    C語言輾轉(zhuǎn)相除法求2個數(shù)的最小公約數(shù)

    輾轉(zhuǎn)相除法最大的用途就是用來求兩個數(shù)的最大公約數(shù)。下面通過本文給大家介紹C語言輾轉(zhuǎn)相除法求2個數(shù)的最小公約數(shù),非常不錯,感興趣的朋友一起看看吧
    2016-12-12
  • C++ Qt開發(fā)之PushButton按鈕組件的使用詳解

    C++ Qt開發(fā)之PushButton按鈕組件的使用詳解

    Qt 是一個跨平臺C++圖形界面開發(fā)庫,利用Qt可以快速開發(fā)跨平臺窗體應(yīng)用程序,本文將重點介紹QPushButton按鈕組件的常用方法及靈活運用,感興趣的小伙伴可以學習一下
    2023-12-12
  • C++實現(xiàn)學生信息管理系統(tǒng)

    C++實現(xiàn)學生信息管理系統(tǒng)

    這篇文章主要為大家詳細介紹了C++實現(xiàn)學生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12

最新評論