using(别名)和range based for
using(別名)替代typedef
關(guān)鍵字
using
語(yǔ)法
別名聲明是具有下列語(yǔ)法的聲明:
using?標(biāo)識(shí)符?attr(可選)?=?類(lèi)型標(biāo)識(shí)?;?(1)
template?<?模板形參列表?>using?標(biāo)識(shí)符?attr(可選)?=?類(lèi)型標(biāo)識(shí)?;(2)?
attr(C++11)?-???可選的任意數(shù)量屬性的序列
標(biāo)識(shí)符?-???此聲明引入的名字,它成為一個(gè)類(lèi)型名?(1)?或一個(gè)模板名?(2)
模板形參列表??-???模板形參列表,同模板聲明
類(lèi)型標(biāo)識(shí)????-???抽象聲明符或其他任何合法的?類(lèi)型標(biāo)識(shí)(可以引入新類(lèi)型,如?類(lèi)型標(biāo)識(shí)?中所注明)。類(lèi)型標(biāo)識(shí)?不能直接或間接涉指?標(biāo)識(shí)符。注意,標(biāo)識(shí)符的聲明點(diǎn)處于跟在?類(lèi)型標(biāo)識(shí)?之后的分號(hào)處。
解釋
大家都知道,在 C++ 中可以通過(guò) typedef 重定義一個(gè)類(lèi)型:
typedef unsigned int uint_t;被重定義的類(lèi)型并不是一個(gè)新的類(lèi)型,僅僅只是原有的類(lèi)型取了一個(gè)新的名字。因此,下面這樣將不是合法的函數(shù)重載:
void func(unsigned int);void func(uint_t); // error: redefinition使用 typedef 重定義類(lèi)型是很方便的,但它也有一些限制,比如,無(wú)法重定義一個(gè)模板。
想象下面這個(gè)場(chǎng)景:
我們需要的其實(shí)是一個(gè)固定以 std::string 為 key 的 map,它可以映射到 int 或另一個(gè) std::string。然而這個(gè)簡(jiǎn)單的需求僅通過(guò) typedef 卻很難辦到。
因此,在 C++98/03 中往往不得不這樣寫(xiě):
一個(gè)雖然簡(jiǎn)單但卻略顯煩瑣的 str_map 外敷類(lèi)是必要的。這明顯讓我們?cè)趶?fù)用某些泛型代碼時(shí)非常難受。
現(xiàn)在,在 C++11 中終于出現(xiàn)了可以重定義一個(gè)模板的語(yǔ)法。請(qǐng)看下面的示例:
這里使用新的 using 別名語(yǔ)法定義了 std::map 的模板別名 str_map_t。比起前面使用外敷模板加 typedef 構(gòu)建的 str_map,它完全就像是一個(gè)新的 map 類(lèi)模板,因此,簡(jiǎn)潔了很多。
實(shí)際上,using 的別名語(yǔ)法覆蓋了 typedef 的全部功能。先來(lái)看看對(duì)普通類(lèi)型的重定義示例,將這兩種語(yǔ)法對(duì)比一下:
可以看到,在重定義普通類(lèi)型上,兩種使用方法的效果是等價(jià)的,唯一不同的是定義語(yǔ)法。
typedef 的定義方法和變量的聲明類(lèi)似:像聲明一個(gè)變量一樣,聲明一個(gè)重定義類(lèi)型,之后在聲明之前加上 typedef 即可。這種寫(xiě)法凸顯了 C/C++ 中的語(yǔ)法一致性,但有時(shí)卻會(huì)增加代碼的閱讀難度。比如重定義一個(gè)函數(shù)指針時(shí):
與之相比,using 后面總是立即跟隨新標(biāo)識(shí)符(Identifier),之后使用類(lèi)似賦值的語(yǔ)法,把現(xiàn)有的類(lèi)型(type-id)賦給新類(lèi)型:
using func_t = void (*)(int, int);從上面的對(duì)比中可以發(fā)現(xiàn),C++11 的 using 別名語(yǔ)法比 typedef 更加清晰。因?yàn)?typedef 的別名語(yǔ)法本質(zhì)上類(lèi)似一種解方程的思路。而 using 語(yǔ)法通過(guò)賦值來(lái)定義別名,和我們平時(shí)的思考方式一致。
下面再通過(guò)一個(gè)對(duì)比示例,看看新的 using 語(yǔ)法是如何定義模板別名的。
從示例中可以看出,通過(guò) using 定義模板別名的語(yǔ)法,只是在普通類(lèi)型別名語(yǔ)法的基礎(chǔ)上增加 template 的參數(shù)列表。使用 using 可以輕松地創(chuàng)建一個(gè)新的模板別名,而不需要像 C++98/03 那樣使用煩瑣的外敷模板。
需要注意的是,using 語(yǔ)法和 typedef 一樣,并不會(huì)創(chuàng)造新的類(lèi)型。也就是說(shuō),上面示例中 C++11 的 using 寫(xiě)法只是 typedef 的等價(jià)物。雖然 using 重定義的 func_t 是一個(gè)模板,但 func_t<int> 定義的 xx_2 并不是一個(gè)由類(lèi)模板實(shí)例化后的類(lèi),而是 void(*)(int, int) 的別名。
因此,下面這樣寫(xiě):
同樣是無(wú)法實(shí)現(xiàn)重載的,func_t<int> 只是 void(*)(int, int) 類(lèi)型的等價(jià)物。
細(xì)心的讀者可以發(fā)現(xiàn),using 重定義的 func_t 是一個(gè)模板,但它既不是類(lèi)模板也不是函數(shù)模板(函數(shù)模板實(shí)例化后是一個(gè)函數(shù)),而是一種新的模板形式:模板別名(alias template)。
其實(shí),通過(guò) using 可以輕松定義任意類(lèi)型的模板表達(dá)方式。比如下面這樣:
type_t 實(shí)例化后的類(lèi)型和它的模板參數(shù)類(lèi)型等價(jià)。這里,type_t<int> 將等價(jià)于 int。
?
range based for
熟悉C#或者python的人都知道在C#和python中存在一種for的使用方法不需要明確給出容器的開(kāi)始和結(jié)束條件,就可以遍歷整個(gè)容器。C++11吸取了他們的優(yōu)點(diǎn),引入了這種方法也就是基于范圍的For(Range-Based-For)。
值得一說(shuō)的是,如果沒(méi)有range-based-for,我們還是可以遍歷容器的。它是對(duì)for的擴(kuò)展。
比如C98時(shí)候我們可以這樣:
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };for (int i = 0; i < 10; i++) cout << arr[i];或者遍歷容器,這樣:
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};for?(std::vector<int>::iterator?itr?=?vec.begin();?itr?!=?vec.end();?itr++) std::cout << *itr;不過(guò)有了C++11, 我們就可以這樣:
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};for?(auto?n?:vec) std::cout << n; int?arr[10]?=?{?1,?2,?3,?4,?5,?6,?7,?8,?9,?10?};for?(auto?n?:?arr)????std::cout?<<?n;乍一看,是不是顯得代碼非常簡(jiǎn)潔?確實(shí),這可能也是為啥引入這個(gè)概念。不過(guò)我覺(jué)得range-based-for最主要的作用可能就是“一統(tǒng)江湖”。有了它,不管你是數(shù)組,map,vector還是其他容器, 都可以用它來(lái)遍歷。
下面看看它的語(yǔ)法以及解釋:
//語(yǔ)法屬性(可選) for ( 范圍聲明 : 范圍表達(dá)式 ) 循環(huán)語(yǔ)句//解釋{ auto && __range = 范圍表達(dá)式 ; for (auto __begin = 首表達(dá)式, __end = 尾表達(dá)式 ; __begin != __end; ++__begin) { 范圍聲明 = *__begin; 循環(huán)語(yǔ)句 }}range-based-for看起來(lái)那么美好,其實(shí)在應(yīng)用的時(shí)候還是要有一些需要注意的事情:
range-base-for 默認(rèn)是只讀遍歷,也就是說(shuō)下邊的例子
這也許并不是我們想要的,如果要修改就要將遍歷的變量聲明為引用型。
也就是將代碼改成這樣:
for (auto& n :vec) std::cout << n++;2. 在遍歷容器的時(shí)候,auto自動(dòng)推導(dǎo)的類(lèi)型是容器的value_type類(lèi)型,而不是迭代器。
之前提過(guò)range-based-for的實(shí)現(xiàn)方式,其中有范圍表達(dá)式是自動(dòng)推導(dǎo)的
auto && __range = 范圍表達(dá)式 ;舉個(gè)例子,如果你遍歷的容器是map,那么value_type是std::pair,也就是說(shuō)val的類(lèi)型是std::pair類(lèi)型的,因此需要使用val.first,val.second來(lái)訪問(wèn)數(shù)據(jù)。
std::map<string, int> map = { { "a", 1 }, { "b", 2 }, { "c", 3 } }; for (auto &val : map) cout << val.first << "->" << val.second << endl;此外,使用基于范圍的for循環(huán)還要注意一些容器類(lèi)本身的約束,比如set的容器內(nèi)的元素本身有容器的特性就決定了其元素是只讀的,哪怕的使用了引用類(lèi)型來(lái)遍歷set元素,也是不能修改器元素的,看下面例子:
set<int> ss = { 1, 2, 3, 4, 5, 6 }; for (auto& n : ss) cout << n++ << endl;3. 不能迭代的時(shí)候修改容器, 下面的代碼運(yùn)行時(shí)候可能崩潰。
vector<int> vec = { 1, 2, 3, 4, 5, 6 }; int main(){ for (auto &n : vec) { cout << n << endl; vec.push_back(7); }}由于在遍歷容器的時(shí)候,在容器中插入一個(gè)元素導(dǎo)致迭代器失效了,因此,基于范圍的for循環(huán)和普通的for循環(huán)一樣,在遍歷的過(guò)程中如果修改容器,會(huì)造成迭代器失效。
4. 循環(huán)陷阱
說(shuō)這個(gè)問(wèn)題之前,還是要把它的實(shí)現(xiàn)再次粘貼過(guò)來(lái)(C++17以前):
{ auto && __range = 范圍表達(dá)式 ; for (auto __begin = 首表達(dá)式, __end = 尾表達(dá)式 ; __begin != __end; ++__begin) { 范圍聲明 = *__begin; 循環(huán)語(yǔ)句 }}舉個(gè)例子:
#include <iostream>#include <string> using namespace std; struct MyClass{ string text = "MyClass"; string& getText() { return text; }}; int main(){ for (auto ch : MyClass().text) { cout << ch; } cout << endl;}輸出結(jié)果就是
MyClass但是我們把代碼改成下邊的樣子, 結(jié)果什么都不會(huì)輸出,程序直接退出
for (auto ch : MyClass().getText()) { cout << ch; }為什么會(huì)出現(xiàn)這樣的現(xiàn)象呢?答案就在于range-based-for實(shí)現(xiàn)的時(shí)候有這樣一句話(huà):
auto && __range = 范圍表達(dá)式 ;如果“范圍表達(dá)式”返回臨時(shí)量,則其生存期被延續(xù)到循環(huán)結(jié)尾,如綁定到轉(zhuǎn)發(fā)引用?__range?所示,要注意的是“范圍表達(dá)式”中任何臨時(shí)量生存期都不被延長(zhǎng)。
for (auto& x : foo().items()) { /* .. */ } // 若 foo() 返回右值則為未定義行為原始的例子中,range_expression是 "MyClass().text",MyClass()是臨時(shí)對(duì)象,同時(shí) "MyClass()" 這個(gè)表達(dá)式是右值。所以,"MyClass().text" 這個(gè)表達(dá)式也是右值,"MyClass().text" 這個(gè)對(duì)象是臨時(shí)對(duì)象中的一部分。所以,在 "auto && __range = range_expression;" 這個(gè)語(yǔ)句中,auto會(huì)被推導(dǎo)為 "std::string"。初始化右值引用為臨時(shí)對(duì)象的一部分時(shí),可以延長(zhǎng)整個(gè)臨時(shí)對(duì)象的生存期,在引用被銷(xiāo)毀時(shí)臨時(shí)對(duì)象才會(huì)被銷(xiāo)毀。所以for循環(huán)可以正常執(zhí)行。
但是在修改過(guò)后,range_expression是 "MyClass().getText()"。同樣地,MyClass()是臨時(shí)對(duì)象,"MyClass()" 這個(gè)表達(dá)式是右值。但是 "getText()" 的返回類(lèi)型為 "string&",所以,"MyClass().getText()" 這個(gè)表達(dá)式是左值。所以,在 "auto && __range = range_expression;" 這個(gè)語(yǔ)句中,auto會(huì)被推導(dǎo)為 "string &",語(yǔ)句等價(jià)于 "string & __range = range_expression;" 。雖然"MyClass().getText()" 這個(gè)對(duì)象是臨時(shí)對(duì)象中的一部分,但是在初始化非const的左值引用時(shí),不會(huì)延長(zhǎng)臨時(shí)對(duì)象的生存期,所以在這個(gè)初始化語(yǔ)句結(jié)束的同時(shí)MyClass()這個(gè)臨時(shí)對(duì)象就被銷(xiāo)毀了,__range成為了野引用,所以后面的循環(huán)語(yǔ)句可能會(huì)出現(xiàn)內(nèi)存錯(cuò)誤。
要解決這個(gè)問(wèn)題,可以利用C++20 “初始化語(yǔ)句”變通解決:
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK總結(jié)
以上是生活随笔為你收集整理的using(别名)和range based for的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 变长参数模板 和 外部模板
- 下一篇: 左值、右值、左值引用、右值引用