C++11 學習筆記 列表初始化

一.列表初始化

1.在C++98/03中,只有普通數組和POD類型(plain old data類型,可以直接使用memcpy複製的對象)可以用初始化列表來進行初始化。

int i_arr[3] = { 1, 2, 3 };
long l_arr[3] = { 1, 3, 2, 4 };


struct A
{
     int x;
     int y;
} a = { 1, 2 };


2.在C++11中,初始化列表可以用於任何類型對象的初始化列表,它統一了各種對象的初始化方法,使得我們書寫更加簡單清晰。格式:變量名+初始化列表(相比之前少了“=”)。

class Foo
{
public:
     Foo(int) {}

private:
     Foo(const Foo &);
};

int main(){
     Foo a1(123);
     Foo a2 = 123; //error

     Foo a3 = { 123 };
     Foo a4 { 123 };

     int a5 = { 3 };
     int a6 { 3};

     return 0;
}
a3雖然使用了等號,但它仍然是初始化列表。

a3,a4使用了新的初始化方式來初始化對象。

a5,a6是基本數據類型的列表寢始化方法。

a4,a6的寫法在C++98/03中不存在,在C++11中,可以直接在變量名後跟上始初化列表來進行對象的初始化。

1).初始化時,{}前面的等於號是否書寫對初始化行爲沒有影響。

int a = { 123 };
int b { 123 };

2). new操作符等可以用圓括號進行初始化的地方,也可以使用初始化列表。

int* a = new int ( 123 );
int* b = new int { 123 };
3).堆上分配的動態數組也可以使用初始化 列表來進行初始化。
int* arr = new int[3] { 1, 2, 3 };

4).列表初始化還可以作用在函數的返回值上。

struct Foo
{
     Foo(int, double){}
};

Foo fun(){
     return { 123, 321.0 };
}
這裏的return語句如同返回一個Foo(123,321.0)。


二.列表初始化的使用細節

1.

struct A
{
     int x;
     int y;
} a = { 123, 321 };

struct B
{
     int x;
     int y;
     B(int, int) : x(0), y(0) {}
} b = { 123, 321 };    //b.x = 0, b.y = 0

a的初始化過程是C++98/03中就有的聚合類型的初始化。它將以拷貝的形式,用初始化列表的值來初始化struct A中的成員。

B由於定義了一個自定義的構造函數,因此,是以B的構造函數進行初始化的。

什麼情況下,C++會認爲它是一個集合體?

1).類型是一個普通數組。

int x[] = { 1, 3, 5 };

2).類型是一個類,且

  1)).無用戶自定義的構造函數。

struct Foo
{
     int x,
     double y;
     int z;
     Foo(int, int) {}
};

Foo foo{1, 2.5, 1}  //error

  2)).無私有或保護的非靜態數據成員。

struct ST
{
     int x;
     double y;
protected:
     int z;
};

ST s { 1, 2.5, 1}  //error

  3)).無基類。

struct ST
{
     int x;
     double y;
     virtual void F(){}
};

ST s { 1, 2.5 };

  4)).無虛函數。

struct Base {};

struct Foo : public Base
{
     int x;
     double y;
};

Foo foo { 1, 2.5 };

  5)).不能有{}和=直接初始化的非靜態數據成員。

struct ST
{
     int x;
     double y = 0.0;
};

ST s { 1, 2.5 };  //error
C++98/03中不能在聲明時進行初始化工作。但是在C++11放寬了這限制。但對一個類來說,如果對非靜態數據成員進行了初始化,那麼它就不再是一個聚合類型,就i 能使用初始化列表。

總之,對於聚合類型,使用初始化列對於非聚合類型,使用初始化列表相當於對其中的每個元素分別賦值,而對於非聚合類型,想要使用初始化列表的方法就是定義一個構造函數。

struct ST
{
     int x;
     double y;
     virtual void F() {}
private:
     int z;
public:
     ST(int i, double j, int k) : x(i), y(j), z(k) {}
};

ST s {1, 2.5, 2 }; 

2.

struct Foo
{
     int x;
     double y;
};

Foo foo { 1, 2.5 };

int arr[] { 1, 2, 3 };

std::map<std::string, int> mn = { {"1", 1 }, { "2", 2 ), {"3", 3 } };

std::set<int> ss = { 1, 2, 3 };

std::vector<int> arr = { 1, 2, 3, 4, 5 };

迄今爲止,我們所討論的都是按步就班的按照構造函數指定的參數列表進行初化,而像數組,std::map,std::set,std::vector可以在初始化時,使用任意長度的初始化列表。

實際上,stl中的容器是通過使用std::initializer_list這個輕量級的類模板來完成上述功能的支持。我們只需要在Foo添加一個std::initiallzer_list構造函數,它也將有這種任意長度的初始化能力。

class Foo
{
public:
     Foo(std::initializer_list<int>) {}
};

Foo foo = { 1, 2, 3, 4, 5 };  //OK
class FooVector
{
     std::vector<int> content_;

public:
     FooVector(std::initializer_list<int> list){
     	for (auto it = list.begin(); it!= list.end();++it){
          	content_.push_back(*it);
     	}
     }
};

class FooMap
{
     std::map<int, int> content_;
     using pair_t = std::map<int, int>::value_type;

public:
     FooMap(std::initializer_list<pair_t> list)
    {
         for(auto it = list.begin();it!=list.end(); ++it){
               content_.insert(*it);
          }
     }
};

FooVector foo_1 = { 1, 2, 3, 4, 5 };
FooMap foo_2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };




3.

初始化列表(std::initializer_list)的一些特點:

1).它是一個輕量級的容器類型,內部定義了iterator等容器必需的概念。

2).對於std::initializer_list<T>而言,它可以接收任意長度的初始化列表,但要求元素必須是同種類型T(或可轉化爲T)。

3).它有3個成員接口:size(),begin(),end()。

4).它只能被整體初始化或賦值。

5).std::initializer_list擁有一個無參數的構造函數,因此,可以直接定義實例,得到一個空的std::initializer_list。

6).對std::initializer_list的訪問只能通過begin()和end()進行循環遍歷,遍歷時取得的迭代器是隻讀的。要修改,只能通過列表初始化表的賦值對std::initializer_list做整體修改。

std::initializer_list<int> list;
size_t n = list.size();  // n== 0 

list = { 1, 2, 3, 4, 5 };
n = list.size();  //n == 5

list = { 3, 1, 2, 4 };;
n = list.size();  // n == 4
7).std::initializer_list在傳遞或賦值的時候跟vector容器不一樣,vector把每個元素都複製一遍,std::initializer_list則是非常高效的,它的內部並不負責保存初始化列表中元素的拷貝,僅僅存儲了列表中元素的引用而已。

std::initializer_list<int> fund(){
     int a=1;
     int b=2;
     return { a, b };    //error  a,b在返回時並沒有被拷貝
}



三.防止類型收窄

類型收窄包括以下幾種情況:

1).從一個浮點數隱式轉換爲一個整型數。

2).從高精度浮點數隱式轉換爲低精度浮點數。

3).從一個整型數隱式轉換爲一個長度較短的整型數,並且超出了長度較短的整型數的表示範圍。

4).從一個整型數隱式轉換爲一個浮點數,,並且超出了浮點數的表示範圍。

上述情況,編譯器並不會報錯,而是給出一些警告。


使用列表初始化來檢查及防止類型收窄。

int a =1.1                                //OK
int b = { 1.1 };                          //error

float fa = 1e40;                          //OK
float fb = { 1e40 };                      //error

float fc = (unsigned long long)-1;        //OK
float fd = {(unsigned long long)-1 };     //OK
float fe = (unsigned long long)1;         //OK
float ff = { (unsigned long long)1 };     //OK

const int x = 1024, y = 1;                
char c = x;                               //OK
char d { x };                             //error
char e = y;                               //OK
char f = { y };                           //OK
唯一要注意的是,x,y被定義爲const int,如果去掉const限定符,那麼最後一個變量f也會因爲類型收窄而報錯。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章