C++11新特性之列表初始化的具體使用
在我們實(shí)際編程中,我們經(jīng)常會(huì)碰到變量初始化的問(wèn)題,對(duì)于不同的變量初始化的手段多種多樣,比如說(shuō)對(duì)于一個(gè)數(shù)組我們可以使用 int arr[] = {1,2,3}的方式初始化,又比如對(duì)于一個(gè)簡(jiǎn)單的結(jié)構(gòu)體:
struct A { int x; int y; }a={1,2};
這些不同的初始化方法都有各自的適用范圍和作用,且對(duì)于類來(lái)說(shuō)不能用這種初始化的方法,最主要的是沒(méi)有一種可以通用的初始化方法適用所有的場(chǎng)景,因此C++11中為了統(tǒng)一初始化方式,提出了列表初始化(list-initialization)的概念。
統(tǒng)一的初始化方法
在C++98/03中我們只能對(duì)普通數(shù)組和POD(plain old data,簡(jiǎn)單來(lái)說(shuō)就是可以用memcpy復(fù)制的對(duì)象)類型可以使用列表初始化,如下:
數(shù)組的初始化列表: int arr[3] = {1,2,3}
POD類型的初始化列表:
struct A { int x; int y; }a = {1,2};
在C++11中初始化列表被適用性被放大,可以作用于任何類型對(duì)象的初始化。如下:
class Foo { public: Foo(int) {} private: Foo(const Foo &); }; int _tmain(int argc, _TCHAR* argv[]) { Foo a1(123); //調(diào)用Foo(int)構(gòu)造函數(shù)初始化 Foo a2 = 123; //error Foo的拷貝構(gòu)造函數(shù)聲明為私有的,該處的初始化方式是隱式調(diào)用Foo(int)構(gòu)造函數(shù)生成一個(gè)臨時(shí)的匿名對(duì)象,再調(diào)用拷貝構(gòu)造函數(shù)完成初始化 Foo a3 = { 123 }; //列表初始化 Foo a4 { 123 }; //列表初始化 int a5 = { 3 }; int a6 { 3 }; return 0; }
由上面的示例代碼可以看出,在C++11中,列表初始化不僅能完成對(duì)普通類型的初始化,還能完成對(duì)類的列表初始化,需要注意的是a3 a4都是列表初始化,私有的拷貝并不影響它,僅調(diào)用類的構(gòu)造函數(shù)而不需要拷貝構(gòu)造函數(shù),a4,a6的寫法是C++98/03所不具備的,是C++11新增的寫法。
同時(shí)列表初始化方法也適用于用new操作等圓括號(hào)進(jìn)行初始化的地方,如下:
int* a = new int { 3 }; double b = double{ 12.12 }; int * arr = new int[] {1, 2, 3};
讓人驚奇的是在C++11中可以使用列表初始化方法對(duì)堆中分配的內(nèi)存的數(shù)組進(jìn)行初始化,而在C++98/03中是不能這樣做的。
列表初始化的一些使用細(xì)節(jié)
雖然列表初始化提供了統(tǒng)一的初始化方法,但是同時(shí)也會(huì)帶來(lái)一些使用上的疑惑需要各位苦逼碼農(nóng)需要注意,比如對(duì)下面的自定義類型的例子:
struct A { int x; int y; }a = {123, 321}; //a.x = 123 a.y = 321 struct B { int x; int y; B(int, int) :x(0), y(0){} }b = {123,321}; //b.x = 0 b.y = 0
對(duì)于自定義的結(jié)構(gòu)體A來(lái)說(shuō)模式普通的POD類型,使用列表初始化并不會(huì)引起問(wèn)題,x,y都被正確的初始化了,但看下結(jié)構(gòu)體B和結(jié)構(gòu)體A的區(qū)別在于結(jié)構(gòu)體B定義了一個(gè)構(gòu)造函數(shù),并使用了成員初始化列表來(lái)初始化B的兩個(gè)變量,,因此列表初始化在這里就不起作用了,b采用的是構(gòu)造函數(shù)的方式來(lái)完成變量的初始化工作。
那么如何區(qū)分一個(gè)類(class struct union)是否可以使用列表初始化來(lái)完成初始化工作呢?關(guān)鍵問(wèn)題看這個(gè)類是否是一個(gè)聚合體(aggregate),首先看下C++中關(guān)于類是否是一個(gè)聚合體的定義:
(1)無(wú)用戶自定義構(gòu)造函數(shù)。
(2)無(wú)私有或者受保護(hù)的非靜態(tài)數(shù)據(jù)成員
(3)無(wú)基類
(4)無(wú)虛函數(shù)
(5)無(wú){}和=直接初始化的非靜態(tài)數(shù)據(jù)成員。下面我們逐個(gè)對(duì)上述進(jìn)行分析。
1、首先存在用戶自定義的構(gòu)造函數(shù)的情況,示例如下:
struct Foo { int x; int y; Foo(int, int){ cout << "Foo construction"; } }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo{ 123, 321 }; cout << foo.x << " " << foo.y; return 0; }
輸出結(jié)果為:Foo construction -858993460 -858993460
可以看出對(duì)于有用戶自定義構(gòu)造函數(shù)的類使用初始化列表其成員初始化后變量值是一個(gè)隨機(jī)值,因此用戶必須以用戶自定義構(gòu)造函數(shù)來(lái)構(gòu)造對(duì)象。
2、類包含有私有的或者受保護(hù)的非靜態(tài)數(shù)據(jù)成員的情況
struct Foo { int x; int y; //Foo(int, int, double){} protected: double z; }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo{ 123,456,789.0 }; cout << foo.x << " " << foo.y; return 0; }
實(shí)例中z是一個(gè)受保護(hù)的成員變量,該程序直接在VS2013下編譯出錯(cuò),error C2440: 'initializing' : cannot convert from 'initializer-list' to 'Foo',而如果將z變量聲明為static則,可以用列表初始化來(lái),示例:
struct Foo { int x; int y; //Foo(int, int, double){} protected: static double z; }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo{ 123,456}; cout << foo.x << " " << foo.y; return 0; }
程序輸出:123 456,因此可知靜態(tài)數(shù)據(jù)成員的初始化是不能通過(guò)初始化列表來(lái)完成初始化的,它的初始化還是遵循以往的靜態(tài)成員的額初始化方式。
3、類含有基類或者虛函數(shù)
struct Foo { int x; int y; virtual void func(){}; }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo {123,456}; cout << foo.x << " " << foo.y; return 0; }
上例中類Foo中包含了一個(gè)虛函數(shù),該程序也是非法的,編譯不過(guò)的,錯(cuò)誤信息和上述一樣cannot convert from 'initializer-list' to 'Foo'。
struct base{}; struct Foo:base { int x; int y; }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo {123,456}; cout << foo.x << " " << foo.y; return 0; }
上例中則是有基類的情況,類Foo從base中繼承,然后對(duì)Foo使用列表初始化,該程序也一樣無(wú)法通過(guò)編譯,錯(cuò)誤信息仍然為cannot convert from 'initializer-list' to 'Foo',
4、類中不能有{}或者=直接初始化的費(fèi)靜態(tài)數(shù)據(jù)成員
struct Foo { int x; int y= 5; }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo {123,456}; cout << foo.x << " " << foo.y; return 0; }
在結(jié)構(gòu)體Foo中變量y直接用=進(jìn)行初始化了,因此上述例子也不能使用列表初始化方法,需要注意的是在C++98/03中,類似于變量y這種直接用=進(jìn)行初始化的方法是不允許的,但是在C++11中放寬了,是可以直接進(jìn)行初始化的,對(duì)于一個(gè)類來(lái)說(shuō)如果它的非靜態(tài)數(shù)據(jù)成員使用了=或者{}在聲明同時(shí)進(jìn)行了初始化,那么它就不再是聚合類型了,不適合使用列表初始化方法了。
在上述4種不再適合使用列表初始化的例子中,需要注意的是一個(gè)類聲明了自己的構(gòu)造函數(shù)的情形,在這種情況下使用初始化列表是編譯器是不會(huì)給你報(bào)錯(cuò)的,操作系統(tǒng)會(huì)給變量一個(gè)隨機(jī)的值,這種問(wèn)題在代碼出BUG后是很難查找到的,因此這種情況不適合使用列表初始化需要特別注意,而其他不適合使用的情況編譯器會(huì)直接報(bào)錯(cuò),提醒你這些場(chǎng)景下使用列表初始化時(shí)不合法的。
那么是否有一種方法可以使得在類不是聚合類型的時(shí)候可以使用列表初始化方法呢?相信你肯定猜到了,作為一種很強(qiáng)大的語(yǔ)言不應(yīng)該也不會(huì)存在使用上的限制。自定義構(gòu)造函數(shù)+成員初始化列表的方式解決了上述類是非聚合類型使用列表初始化的限制。看下面的例子:
struct Foo { int x; int y= 5; virtual void func(){} private: int z; public: Foo(int i, int j, int k) :x(i), y(j), z(k){ cout << z << endl; } }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo {123,456,789}; cout << foo.x << " " << foo.y; return 0; }
輸出結(jié)果為 789 123 456 ,可見(jiàn),盡管Foo中包含了私有的非靜態(tài)數(shù)據(jù)以及虛函數(shù),用戶自定義構(gòu)造函數(shù),并且使用成員列表初始化方法可以使得非聚合類型的類也可以使用列表初始化方法,因此在這里給各位看官提個(gè)建議,在對(duì)類的數(shù)據(jù)成員進(jìn)行初始化的時(shí)候盡量在類的構(gòu)造函數(shù)中用成員初始化列表的方式來(lái)對(duì)數(shù)據(jù)成員進(jìn)行初始化,這樣可以防止一些意外的錯(cuò)誤。
初始化列表
在上面的使得一個(gè)類成為非聚合類的例子2、3、4中,這些非法的用法編譯器都報(bào)出的錯(cuò)誤是cannot convert from 'initializer-list' to 'Foo',那么這個(gè)initializer-list是什么呢?為什么使用列表初始化方法是將initializer-list轉(zhuǎn)換成對(duì)應(yīng)的類類型呢?下面我們就來(lái)看看這個(gè)神秘的東西
1、任何長(zhǎng)度的初始化列表
在C++11中,對(duì)于任意的STL容易都與和為顯示指定長(zhǎng)度的數(shù)組一樣的初始化能力,如:
int arr[] = { 1, 2, 3, 4, 5 }; std::map < int, int > map_t { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }; std::list<std::string> list_str{ "hello", "world", "china" }; std::vector<double> vec_d { 0.0,0.1,0.2,0.3,0.4,0.5};
STL容易跟數(shù)組一樣可以填入任何需要的任何長(zhǎng)度的同類型的數(shù)據(jù),而我們自定義的Foo類型卻不具備這種能力,只能按照構(gòu)造函數(shù)的初始化列表順序進(jìn)行依次賦值。實(shí)際上之所以STL容易擁有這種可以用任意長(zhǎng)度的同類型數(shù)據(jù)進(jìn)行初始化能力是因?yàn)镾TL中的容器使用了std::initialzer-list這個(gè)輕量級(jí)的類模板,std::initialzer-list可以接受任意長(zhǎng)度的同類型的數(shù)據(jù)也就是接受可變長(zhǎng)參數(shù){...},那么我們是否可以利用這個(gè)來(lái)改寫我們的Foo類,是的Foo類也具有這種能力呢?看下面例子:
struct Foo { int x; int y; int z; Foo(std::initializer_list<int> list) { auto it= list.begin(); x = *it++; y = *it++; z = *it++; } }; int _tmain(int argc, _TCHAR* argv[]) { Foo foo1 {123,456,789}; Foo foo2 { 123, 456}; Foo foo3{ 123}; Foo foo4{ 123, 456, 789,258 }; cout << foo1.x << " " << foo1.y << " " << foo1.z<<endl; cout << foo2.x << " " << foo2.y << " " << foo2.z << endl; cout << foo3.x << " " << foo3.y << " " << foo3.z << endl; cout << foo4.x << " " << foo4.y << " " << foo4.z << endl; return 0; }
程序的輸出結(jié)果為:
123 456 789
123 456 -858993460
123 -858993460 -858993460
123 456 789
在上面的例子中我們用std::initialzer-list將類改寫,是的類Foo也具有了接受可變長(zhǎng)參數(shù)的能力,在Foo類中定義了三個(gè)變量,分別在main函數(shù)中使用1個(gè)2個(gè)3個(gè)4個(gè)參數(shù)的列表初始化方法來(lái)初始化foo變量,可見(jiàn),由程序的輸出結(jié)果可知,對(duì)于這種擁有固定數(shù)目的數(shù)據(jù)成員來(lái)說(shuō)使用std::initialzer-list來(lái)改寫后,如果列表初始化的參數(shù)剛好是3個(gè)則數(shù)據(jù)成員完全初始化,如果列表初始化的個(gè)數(shù)小于3個(gè)則未給予的值是一個(gè)隨機(jī)值,而大于3個(gè)的話超出的列表初始化參數(shù)將被拋棄。雖然std::initialzer-list可以改寫我們自定義的類,但是對(duì)于用于固定的數(shù)據(jù)成員的類來(lái)說(shuō)這種改寫意義不大,若列表初始化個(gè)數(shù)少于數(shù)據(jù)成員個(gè)數(shù)則會(huì)導(dǎo)致某些數(shù)據(jù)成員未被初始化,容易引起程序出BUG,BUT如果我們自定義的類也是一個(gè)容器類情況呢?
class FooVec { public: std::vector<int> m_vec; FooVec(std::initializer_list<int> list) { for (auto it = list.begin(); it != list.end(); it++) m_vec.push_back(*it); } }; int _tmain(int argc, _TCHAR* argv[]) { FooVec foo1 { 1, 2, 3, 4, 5, 6 }; FooVec foo2 { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; return 0; }
可見(jiàn),用std::initialzer-list改寫我們自定義的容器類可以使得我們自定義的容器類和STL中的容器類一樣擁有接受可變長(zhǎng)相同數(shù)據(jù)類型的數(shù)據(jù)的能力,注意數(shù)據(jù)類型必須相同。
std::initialzer-list不僅可以用于自定義類型的列表初始化方法,也可以用于傳遞相同類型數(shù)據(jù)的集合:
void func(std::initializer_list<int> list) { for (auto it = list.begin(); it != list.end(); it++) { cout << *it << endl; } } int _tmain(int argc, _TCHAR* argv[]) { func({});//傳遞一個(gè)空集 func({ 1, 2, 3 });//傳遞int類型的數(shù)據(jù)集 return 0; }
因此在以后碰到需要使用可變長(zhǎng)度的同類型的數(shù)據(jù)時(shí),可以考慮使用std::initialzer-list。
2、std::initialzer-list的使用細(xì)節(jié)
簡(jiǎn)單了解了initialzer-list后,看看它擁有哪些特點(diǎn)呢?
1、它是一個(gè)輕量級(jí)的容器類型,內(nèi)部定義了迭代器iterator等容器必須的一些概念。
2、對(duì)于initialzer-list<T>來(lái)說(shuō),它可以接受任意長(zhǎng)度的初始化列表,但是元素必須是要相同的或者可以轉(zhuǎn)換為T類型的。
3、它只有三個(gè)成員接口,begin(),end(),size(),其中size()返回initialzer-list的長(zhǎng)度。
4、它只能被整體的初始化和賦值,遍歷只能通過(guò)begin和end迭代器來(lái),遍歷取得的數(shù)據(jù)是可讀的,是不能對(duì)單個(gè)進(jìn)行修改的。
類似下面的操作時(shí)不合法的
std::initializer_list<int> list_t ={ 1, 2, 3, 4 }; int _tmain(int argc, _TCHAR* argv[]) { for (auto it = list_t.begin(); it != list_t.end; it++) (*it) = 1; return 0; }
此外initialzer-list<T>保存的是T類型的引用,并不對(duì)T類型的數(shù)據(jù)進(jìn)行拷貝,因此需要注意變量的生存期。比如我們不能這樣使用:
std::initializer_list<int> func(void) { auto a = 2, b = 3; return{ a, b }; }
雖然看起來(lái)沒(méi)有任何問(wèn)題,且能正常編譯通過(guò),但是a,b是在func內(nèi)定義的局部變量,但程序離開(kāi)func時(shí)變量a,b就銷毀了,initialzer-list卻保存的是變量的引用,因此返回的將是非法未知的內(nèi)容。
列表初始化防止類型收窄
C++11的列表初始化還有一個(gè)額外的功能就是可以防止類型收窄,也就是C++98/03中的隱式類型轉(zhuǎn)換,將范圍大的轉(zhuǎn)換為范圍小的表示,在C++98/03中類型收窄并不會(huì)編譯出錯(cuò),而在C++11中,使用列表初始化的類型收窄編譯將會(huì)報(bào)錯(cuò):
int a = 1.1; //OK int b{ 1.1 }; //error float f1 = 1e40; //OK float f2{ 1e40 }; //error const int x = 1024, y = 1; char c = x; //OK char d{ x };//error char e = y;//error char f{ y };//error
上面例子看出,用C++98/03的方式類型收窄并不會(huì)編譯報(bào)錯(cuò),但是將會(huì)導(dǎo)致一些隱藏的錯(cuò)誤,導(dǎo)致出錯(cuò)的時(shí)候很難定位,而利用C++11的列表初始化方法定義變量從源頭了遏制了類型收窄,使得不恰當(dāng)?shù)挠梅ň筒粫?huì)用在程序中,避免了某些位置類型的錯(cuò)誤,因此建議以后再實(shí)際編程中盡可能的使用列表初始化方法定義變量。
到此這篇關(guān)于C++11新特性之列表初始化的具體使用的文章就介紹到這了,更多相關(guān)C++11 列表初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCODE+cmake配置C++開(kāi)發(fā)環(huán)境的實(shí)現(xiàn)步驟
這篇文章主要介紹了VSCODE+cmake配置C++開(kāi)發(fā)環(huán)境的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C++ TensorflowLite模型驗(yàn)證的過(guò)程詳解
這篇文章給大家介紹了C++ TensorflowLite模型驗(yàn)證的過(guò)程,測(cè)試代碼,主要是RunInference()和read_file(),詳細(xì)操作過(guò)程跟隨小編一起看看吧2021-08-08關(guān)于C++使用std::chrono獲取當(dāng)前秒級(jí)/毫秒級(jí)/微秒級(jí)/納秒級(jí)時(shí)間戳問(wèn)題
這篇文章主要介紹了C++使用std::chrono獲取當(dāng)前秒級(jí)/毫秒級(jí)/微秒級(jí)/納秒級(jí)時(shí)間戳,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07MATLAB Delaunay算法提取離散點(diǎn)邊界的方法
這篇文章主要為大家詳細(xì)介紹了MATLAB Delaunay算法提取離散點(diǎn)邊界的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12用C語(yǔ)言簡(jiǎn)單實(shí)現(xiàn)掃雷小游戲
這篇文章主要為大家詳細(xì)介紹了用C語(yǔ)言簡(jiǎn)單實(shí)現(xiàn)掃雷小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08C語(yǔ)言結(jié)構(gòu)體簡(jiǎn)單入門講解
這篇文章主要介紹了C語(yǔ)言結(jié)構(gòu)體簡(jiǎn)單入門講解,本文講述了結(jié)構(gòu)體的基本定義和操作,講解了幾個(gè)比較實(shí)用的函數(shù)和案例,希望對(duì)你有所幫助2021-06-06C++?JSON庫(kù)?nlohmann::basic_json::array?的用法示例詳解
nlohmann::json是一個(gè)C++的JSON庫(kù),它提供了一種容易和直觀的方法來(lái)處理JSON數(shù)據(jù),nlohmann::json::array()是用來(lái)創(chuàng)建一個(gè)JSON數(shù)組的方法,這篇文章主要介紹了C++ JSON庫(kù)nlohmann::basic_json::array的用法,需要的朋友可以參考下2023-06-06Clion-MinGW編譯后的exe文件添加ico圖標(biāo)的操作方法
這篇文章主要介紹了Clion-MinGW編譯后的exe文件添加ico圖標(biāo)的操作方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07