對于連續(xù)兩個右尖括號>, 那么它們之間需要一個空格來進行分隔葱椭,以避免發(fā)生編譯時的錯誤捂寿。
#include<iostream>
using namespace std;
template<int i>class X{};
template<class T>class Y{};
Y<X<1>>x1;
Y<X<2>>x2;
int main(){
return 0;
}
C++98編譯器會把>>優(yōu)先解析為右移符號。除了嵌套的模板標識挫以,在使用形如static_cast者蠕、dynamic_cast、reinterpret_cast, 或者const_cast表達式進行轉換的時候掐松,我們也常會遇到相同的情況踱侣。
const vector<int> v=static_cast<vector<int>>(v)
C++11標準要求編譯器智能地去判斷在哪些情況下>>不是右移符號。
auto類型推導
在C/C++程序員的眼中大磺,每個變量使用前必須定義幾乎是天經(jīng)地義的事抡句,這樣通常被視為編程語言中的"靜態(tài)類型"的體現(xiàn)。而對于如Python杠愧、Perl待榔、JavaScript等語言中變量不需要聲明,而幾乎"拿來就用"的變量使用方式,則被視為是編程語言中"動態(tài)類型"的體現(xiàn)锐锣。靜態(tài)類型和動態(tài)類型的主要區(qū)別在于對變量進行類型檢查的時間點腌闯。對于所謂的靜態(tài)類型,類型檢查主要發(fā)生在編譯階段雕憔;而對于動態(tài)類型姿骏,類型檢查主要發(fā)生在運行階段。形如Python等語言中變量"拿來就用"的特性斤彼,則需要歸功于一個技術分瘦,即類型推導。
C++11中類型推導的實現(xiàn)的方式之一就是重定義了auto關鍵字琉苇。另外一個現(xiàn)實是decltype嘲玫。
#include<iostream>
using namespace std;
int main(){
auto name="world.\n";
cout<<"hello,"<<name;
}
這里我們使用了auto關鍵字來要求編譯器對變量name的類型進行自動推導。這里編譯器根據(jù)它的初始化表達式的類型并扇,推導出name的類型為char*去团。
auto聲明的變量必須被初始化,以使編譯器能夠從其初始化表達式中推導出其類型拜马。從這個意義上來講渗勘,auto并非一種"類型"聲明,而是一個類型聲明時的"占位符"俩莽,編譯器在編譯時間會將auto替代為變量實際的類型旺坠。
auto的優(yōu)勢
直觀地,auto推導的一個最大優(yōu)勢就是在擁有初始化表達式的復雜類型變量聲明時簡化代碼扮超。由于C++的發(fā)展取刃,聲明變量類型也變得越來越復雜,很多時候出刷,名字空間璧疗,模板成為了類型的一部分,導致程序員在使用庫的時候如履薄冰馁龟。
#include<string>
#include<vector>
voidvoid loopover(std:: vector<std:: string>&vs){
std:: vector<std:: string>:: iterator i =vs.begin(); //想要使用iterator,往往需要書寫大量代碼
for(; i<vs.end();i++){
//一些代碼
}
}
在不使用using namespace std的情況下崩侠,想對一個vector數(shù)組進行循環(huán)】篱荩可以看到却音,當想定義個迭代器i的時候,我們必須寫出std:: vector<std:: string>:: iterator 這樣長的類型聲明矢炼。而使用auto的話系瓢,代碼會的可讀性可以成倍增長。
#include<string>
#include<vector>
voidvoid loopover(std:: vector<std:: string>&vs){
//std:: vector<std:: string>:: iterator i =vs.begin(); //想要使用iterator,往往需要書寫大量代碼
for(auto i=vs.begin(); i<vs.end() ; i++){
//一些代碼
}
}
使用了auto,程序員甚至可以將i的聲明放入for循環(huán)中句灌,i的類型將由表達式vs.begin() 推導出夷陋。事實上,在C++11中,由于auto的存在骗绕,使得STL將會變得更加容易藐窄,寫出的代碼也會更加清晰可讀。
auto的第二個優(yōu)勢則在于可以免除程序員在一些類型聲明時的麻煩爹谭,或者避免一些在類型聲明時的錯誤枷邪。事實上,在C/C++中诺凡,存在著很多隱式或者用戶自定義的類型轉換規(guī)則(比如整型與字符型進行加法運算后,表達式返回的是整型践惑,這是一條隱式規(guī)則)腹泌。這個時候,auto就有用武之地了尔觉。
#include<string>
#include<vector>
using namespace std;
class PI{
public:
double operator*(float v){
return(double) val*v;//這里精度被擴展了
}
const float val=3.1415927f;
};
int main(){
float radius=1.7e10;
PI pi;
auto circumference=2*(pi*radius);
}
定義了float型的變量radius(半徑) 以及一個自定義類型PI變量pi(π值), 在計算圓周長的時候凉袱,使用了auto類型來定義變量circumference。這里侦铜,PI在于float類型數(shù)據(jù)相乘時专甩,其返回值為double。而PI的定義可能是在其他的地方(頭文件里)钉稍,main函數(shù)的程序員可能不知道PI的作者為了避免數(shù)據(jù)上溢或者精度降低而返回了double類型的浮點數(shù)涤躲。因此main函數(shù)程序員如果使用float類型聲明circumference, 就可能享受不了PI作者細心設計帶來的好處。反之贡未,將circumference聲明為auto种樱,就沒有問題。因為編譯器已經(jīng)自動地做出了最好的選擇俊卤。
但是嫩挤,auto并不能解決所有的精度問題:(這跟一些動態(tài)類型語言中數(shù)據(jù)會自動進行擴展的特性還是不一樣的)
#include <iostream>
using namespace std;
int main(){
unsigned int a=4294967295;//最大的unsigned Int值
unsigned int b=1;
auto c=a+b; //c的類型依然是unsigned int
cout<<"a="<<a<<endl;//a=4294967295
cout<<"b="<<b<<endl;//b=1
cout<<"a+b="<<c<<endl;//a+b=0
return 0;
}
auto的第三個優(yōu)點就是其"自適應"性能夠在一定程度上支持泛型的編程。
在PI那個代碼中消恍,如果將operator*返回值變成了long double岂昭,此時,main函數(shù)并不需要修改狠怨,因為auto會"自適應"新的類型约啊。對于不同的平臺上的代碼維護,auto也會帶來一些"泛型"的好處取董。以strlen函數(shù)為例棍苹,在32位的編譯環(huán)境下,strlen返回的為一個4字節(jié)的整型茵汰,而在64位的編譯環(huán)境下枢里,strlen會返回一個8字節(jié)的整型。雖然系統(tǒng)庫<cstring>為其提供了size_t類型來支持多平臺間的代碼共享支持,但是使用auto關鍵字我們同樣可以達到代碼跨平臺的效果栏豺。
auto var=strlen("hello world!").
由于size_t的適用范圍往往局限于<cstring>中定義的函數(shù)彬碱,auto的適用范圍明顯更加廣泛。
當auto應用于模板的定義中奥洼,其"自適應"行會得到更加充分的體現(xiàn)巷疼。
#include <iostream>
using namespace std;
template<typename T1,typename T2>
double Sum(T1&t1, T2&t2){
auto s=t1+t2;//s的類型會在模板實例化時被推導出來
return s;
}
int main(){
int a=3;
long b=5;
float c=1.0f,d=2.3f;
auto e=Sum<int,long>(a,b);//s的類型被推導為long
auto f=Sum<float,float>(c,d);//s的類型被推導為float
}
Sum模板函數(shù)接受兩個參數(shù)。由于類型T1灵奖、T2要在模板實例化時才能確定嚼沿,所以在Sum中將變量s的類型聲明為auto的。在函數(shù)main中我們將模板實例化時瓷患,Sum<int,long>中的s變量會被推導為long類型骡尽,而Sum<float,float>中的s變量則會被推導為float∩帽啵可以看到攀细,auto與模板一起使用時,其"自適應"特性能夠加強C++中"泛型"的能力爱态。不過在這個例子中谭贪,由于總是返回double類型的數(shù)據(jù),所以Sum模板函數(shù)的適用范圍還是受到了一定的限制锦担。另外俭识,應用auto還會在一些情況下取得意想不到的好效果。
#include <iostream>
using namespace std;
#define Max1(a,b)((a)>(b))?(a):(b)
#define Max2(a,b)({\
auto _a=(a);\
auto _b=(b);\
(_a>_b)? _a:_b;})
int main(){
int m1=Max1(1*2*3*4,5+6+7+8);
int m2=Max2(1*2*3*4,5+6+7+8);
}
(#define 里的"" 意味著就是可以把多行一起處理吆豹。)
我們定義了兩種類型的宏Max1和Max2鱼的。兩者作用相同,都是求a和b中較大者并返回痘煤。前者采用傳統(tǒng)的三元運算符表達式凑阶,這可能會帶來一定的性能問題。因為a或者b在三元運算符中都出現(xiàn)了兩次衷快,那么無論是取a還是取b宙橱,其中之一都會被運算兩次。而在Max2中蘸拔,我們將a和b都先算出來师郑,再使用三元運算符進行比較。
在傳統(tǒng)的C++98標準中调窍,由于a和b的類型無法獲得宝冕,所以我們無法定義Max2這樣的高性能宏。而新的標準中的auto則提供了這種可行性邓萨。
auto的使用細則
auto類型指示符與指針和引用之間的關系地梨。在C++11中菊卷,auto可以與指針和引用結合起來使用,使用的效果基本上會符合C/C++程序員的想象宝剖。
#include <iostream>
using namespace std;
int x;
int *y=&x; //把x的地址給y
double foo();
int &bar();
auto *a=&x;//int*
auto &b=x;//int&
auto c=y;//int*
auto *d=y;//int*
auto *e=&foo();//編譯失敗洁闰,指針不能指向臨時變量
auto &f=foo();//編譯失敗,nonconst的左值引用不能和一個臨時變量綁定
auto g=bar(); //int
auto &h=bar(); //int&
(問題:指針和引用的區(qū)別)
指針:指針是一個變量万细,只不過這個變量存儲的是一個地址扑眉,指向內(nèi)存的一個存儲單元;而引用跟原來的變量實質上是同一個東西赖钞,只不過是原變量的一個別名而已腰素。
int a=1; int *p=&a;
int a=1; int &b=a;
上面定義了一個整型變量和一個指針變量p, 該指針變量指向a的存儲單元,即p的值是a存儲單元的地址仁烹。而下面兩句定義了一個整型變量a和這個整型a的引用b,事實上耸弄,a和b是同一個東西,在內(nèi)存占有同一個存儲單元卓缰。
- 可以有const指針,但是沒有const引用砰诵;
- 指針可以有多級征唬,但是引用只能是一級(int **p, 合法而 int &&a是不合法的)
- 指針的值可以為空,但是引用的值不能為NULL, 并且引用在定義的時候必須初始化茁彭;
- 指針的值在初始化后可以改變总寒,即指向其它的存儲單元,而引用在進行初始化后就不會再改變了理肺。
- "sizeof引用"得到的是所指向的變量(對象)的大小摄闸,而"sizeof指針"得到的是指針本身的大小妹萨;
- 指針和引用的自增(++)運算意義不一樣年枕;
變量a、c乎完、d的類型指針類型熏兄,且都指向變量x。實際上對于a树姨、c摩桶、d三個變量而言,聲明其為auto* 或 auto 并沒有區(qū)別帽揪。而如果要使得auto聲明的變量是另一個變量的引用硝清,則必須使用auto&,如圖本例中的變量b和h一樣转晰。
其次芦拿,auto與volatile和const之間也存在著一些項目的聯(lián)系士飒。volatile和const代表了變量的兩種不同的屬性:易失的和常量的。在C++標準中防嗡,它們常常被一起叫做cv限制符变汪。鑒于cv限制符的特殊性,C++11標準規(guī)定auto可以與cv限制符一起使用蚁趁,不過聲明為auto的變量并不能從其初始化表達式中"帶走" cv限制符裙盾。
#include <iostream>
using namespace std;
int main(){
double foo();
float *bar();
const auto a=foo();//a:const double
const auto &b=foo(); //b:const double&
volatile auto*c=bar(); //c:volatile float*
auto d=a;//d:double
auto &e = a;//e:const double&
auto f=c;//f:float*
volatile auto&g=c;//g:volatile float*&
return 0;
}
我們可以通過非cv限制的類型初始化一個cv限制的類型,如變量a他嫡、b番官、c所示。不過通過auto聲明的變量d钢属、f卻無法帶走a和f的常量性或者易失性徘熔。而引用,比如淆党,聲明為引用的變量e酷师、g都保持了其引用的對象相同的屬性(事實上,指針也是一樣的)染乌。
此外山孔,跟其他的變量指示符一樣,同一個賦值語句中荷憋,auto可以用來聲明多個變量的類型台颠,不過這些變量的類型必須相同。如果這些變量的類型不相同勒庄,編譯器則會報錯串前。事實上,用auto來聲明多個變量類型時实蔽,只有第一個變量用于auto的類型推導荡碾,然后推導出來的數(shù)據(jù)類型被作用于其他的變量。所以盐须,不允許這些變量的類型不相同玩荠。
auto x=1,y=2;//x和y的類型均為int
//m是一個指向const int類型變量的指針筒饰,n是一個int類型的變量
const auto*m=&x,n=1;
auto i=1,j=3.14f;//編譯失敗
auto o=1,&p=o,*q=&p;//從左向右推導
對于變量m和n钉汗,這里似乎是auto被替換成了int,所以m是一個int*指針類型葫慎,而n只是一個int類型塑径。同樣的情況也發(fā)生在變量o女坑、p、q上统舀,這里o是一個類型為int的變量匆骗,p是o的引用劳景,而q是p的指針。auto的類型推導按照從左往右碉就,且類似于字面替換的方式進行盟广。事實上,標準里稱auto是一個將要推導出的類型的"占位符"瓮钥。這樣的規(guī)則無疑是直觀而讓人略感意外的筋量。包括C++11新引入的初始化列表,以及new碉熄,都可以使用auto關鍵字桨武。
#include<initializer_list>
auto x=1;
auto x1(1);
auto y{1};//使用初始化列表的auto
auto z=new auto(1);//可以用于new
不過auto也不是萬能的,受限于語法的二義性锈津,或者是實現(xiàn)的困難性呀酸,auto往往也會有上的限制。例外都寫在了下面代碼中:
#include<vector>
using namespace std;
void fun(auto x=1){} //1:auto函數(shù)參數(shù)琼梆,無法通過編譯
struct str{
auto var=10;//2:auto非靜態(tài)成員變量性誉,無法通過編譯
};
int main(){
char x[3];
auto y=x;
auto z[3]=x;//3:auto數(shù)組茎杂,無法通過編譯
//4:auto模板參數(shù)(實例化時)艾栋,無法通過編譯
vector<auto> v={1};
}
(1) 對于函數(shù)fun來說,auto不能是其形參類型蛉顽。可能感覺對于fun來說先较,由于其有默認參數(shù)携冤,所以應該推導fun形參x的類型為int型。但事實卻無法符合大家的想象闲勺。因為auto是不能做形參的類型的曾棕。如果程序員需要泛型的參數(shù),還是需要求助于模板菜循。
(2) 對于結構體來說翘地,非靜態(tài)成員變量的類型不能是auto的。同樣的癌幕,由于var定義了初始值衙耕,讀者可能認為auto可以推導str成員var的類型為int的。但編譯器阻止了auto對結構體中的非靜態(tài)成員進行推導勺远,即使成員擁有初始值橙喘。
(3) 聲明auto數(shù)組。我們可以看到胶逢,main中的x是一個數(shù)組厅瞎,y的類型是可以推導的饰潜,而聲明auto z[3]這樣的數(shù)組同樣會被編譯器禁止。
(4) 在實例化模板的時候使用auto作為模板參數(shù)和簸,如果main中我們聲明的vector<auto> v彭雾。雖然讀者可能認為這里一眼而知是int類型,但編譯器卻阻止了編譯锁保。
為了避免和C++98中auto的含義發(fā)生混淆薯酝,C++11只保留auto作為類型指示符的用法,以下的語句在C++98和C語言中都是合法的身诺。但是在C++11中蜜托,編譯器會報錯。
auto int i=1;
auto只是C++11中類型推導體現(xiàn)的一部分霉赡。其余的橄务,則會在decltype中得到體現(xiàn)。
decltype
typeid 與decltype
C++98對動態(tài)類型支持就是C++中的運行時類型識別(RTTI)
RTTI的機制是為每個類型產(chǎn)生一個type_info類型的數(shù)據(jù)穴亏,程序員可以在程序中使用typeid隨時查詢一個變量的類型蜂挪,typeid就會返回變量相應的type_info數(shù)據(jù)。而type_info的name成員函數(shù)可以返回類型的名字嗓化。而在C++11中棠涮,又增加了hash_code這個成員函數(shù),返回該類型唯一的哈希值刺覆,以供程序員對變量的類型隨時進行比較严肪。
#include<iostream>
#include<typeinfo>
using namespace std;
class White{};
class Black{};
int main(){
White a;
Black b;
cout<<typeid(a).name()<<endl;//5White
cout<<typeid(b).name()<<endl;//5Black
White c;
bool a_b_sametype=(typeid(a).hash_code()==typeid(b).hash_code());
bool a_c_sametype=(typeid(a).hash_code()==typeid(c).hash_code());
cout<<"Same type?" <<endl;
cout<<"A and B?"<<(int)a_b_sametype<<endl;
cout<<"A and C?"<<(int)a_c_sametype<<endl;
}
這里我們定義了兩個不同的類型White和Black,以及其類型的變量a和b。此外我們使用typeid返回類型的type_info,并分別引用name打印類型的名字(5這樣的前綴是g++這類編譯器輸出的名字谦屑,其他編譯器可能會打印出其他的名字驳糯,這個標準并沒有明確規(guī)定),應用hash_code進行類型的比較氢橙。在RTTI的支持下酝枢,程序員可以在一定程度上了解程序中類型的信息(相比于is_same模板函數(shù)的成員類型value在編譯時得到信息,hash_code是運行時得到的信息)悍手。
除了typeid外帘睦,RTTI還包括C++中的dynamic_cast等特性。但是坦康,RTTI會帶來一些運行時的開銷竣付,所以編譯器會讓用戶選擇性地關閉該特性(比如XL C/C++編譯器得-qnortti, GCC的選項-fno-rttion, 或者微軟編譯器選項/GR-)。而且很多時候涝焙,運行時才確定出類型對于程序員來說為時過晚卑笨,程序員更多需要的是在編譯時期確定出類型()標準庫中非常常見。而通常程序員是要使用這樣的類型而不是識別該類型仑撞,因此RTTI無法滿足需求赤兴。
在C++的發(fā)展中妖滔,類型推導是隨著模板和泛型編程的廣泛使用而引入的。因為在泛型編程中桶良,類型成了未知數(shù)座舍。例如:
#include <iostream>
using namespace std;
template<typename T1,typename T2>
double Sum(T1&t1, T2&t2){
auto s=t1+t2;//s的類型會在模板實例化時被推導出來
return s;
}
其中,模板函數(shù)Sum的參數(shù)的t1和t2類型都是不確定的陨帆,因此t1+t2這個表達式將返回的類型也就不可由Sum的編寫者確定曲秉。無疑,這樣的狀況會限制模板的使用范圍和編寫方式疲牵。 最好的解決辦法就是讓編譯器輔助進行類型推導承二。
與auto類似地,decltype也能進行類型推導纲爸,不過兩者的使用方式卻又一定的區(qū)別亥鸠。
#include<iostream>
#include<typeinfo>
using namespace std;
int main(){
int i;
decltype(i)j=0;
cout<<typeid(j).name() <<endl;//打印出"i",g++表示int
float a;
double b;
decltype(a+b)c;
cout<<typeid(c).name() <<endl;//打印出"d",g++表示double
}
變量j的類型由decltype(i)進行聲明,表示j的類型跟i相同(或者準確地說识啦,跟i這個表達式返回得類型相同)负蚊。而c的類型則跟(a+b)這個表達式返回的類型相同。而由于a+b加法表達式返回的類型為double(a會被擴展為double類型與b相加),所以c的類型被decltype推導為double颓哮。
decltype的類型推導并不是像auto一樣是從 變量聲明的初始化表達式 獲得變量的類型家妆,decltype總是以一個普通的表達式為參數(shù),返回該表達式的類型冕茅。而與auto相同的是伤极,作為
一個類型指示符,decltype可以將獲得的類型來定義另外一個變量姨伤。與auto相同的是塑荒,作為一個類型指示符,decltype可以將獲得的類型來定義另外一個變量姜挺。與auto相同,decltype類型推導也是在編譯時進行的彼硫。
使用decltype推導類型是非常常見的事情炊豪。比較典型的就是decltype與typdef/using的合用。在C++11的頭文件中拧篮,我們常常能看以下這樣的代碼:
using size_t=decltype(sizeof(0));
using ptrdiff_t=decltype((int*)0-(int*)0);
using nullptr_t=decltype(nullptr);
這里的size_t以及ptrdiff_t還有nullptr_t都是由decltype推導出的類型词渤。在一些常量、基本類型串绩、運算符缺虐、操作符都已經(jīng)被定義好的情況下,類型可以按照規(guī)則被推導出來礁凡。而使用using, 都可以為這些類型取名高氮。這就顛覆了之前類型拓展需要將擴展類型"映射"到基本類型的常規(guī)做法慧妄。
此外,decltype在某些場景下剪芍,可以極大地增加代碼的可讀性塞淹。
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> vec;
typedef decltype(vec.begin()) vectype;
for(vectype i=vec.begin();i<vec.end();i++){
//做一些事情
}
for(decltype(vec)::iterator i=vec.begin();i<vec.end();i++){
//做一些事情
}
}
//需要默寫
(C++的 vector容器 和typedef
1、vector容器基本操作
(1) 頭文件#include<vector>,
(2) 創(chuàng)建vector對象, vector<int> vec;
(3) 尾部插入數(shù)字:vec.push_back(a);
(4) 使用下標訪問元素罪裹,cout<<vec[0]<<endl;
下標是從0開始的饱普。
(5) 使用迭代器訪問元素.
vector<int>:: iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<endl;
(6) 插入元素: vec.insert(vec.begin()+i,a);在第i+1個元素前面插入a; (7) 刪除元素:
vec.erase(vec.begin()+2);刪除第3個元素
vec.erase(vec.begin()+i,vec.end()+j);刪除區(qū)間[i,j-1];區(qū)間從0開始
(8) 向量大凶垂病:vec.size();
(9) 清空:vec.clear();
2套耕、typedef用法小結
A :定義一種類型的別名,而不只是簡單的宏替換峡继》肱郏可以用作同時聲明指針型的多個對象。比如:
char* pa,pb
這多數(shù)不符合我們的意圖鬓椭,它只聲明了一個指向字符變量的指針颠猴,和一個字符變量。
但用如下方法可以:
typedef char* PCHAR; //一般用大寫
PCHAR pa,pb小染;//可行翘瓮,同時聲明了兩個指向字符變量的指針。
//雖然:char *pa,*pb;也可行裤翩,但相對來說沒有用typedef的形式直觀资盅,尤其在需要大量指針的地方,typedef的方式更省事踊赠。
B :用typedef來定義與平臺無關的類型呵扛。
比如定義一個叫 REAL 的浮點類型,在目標平臺一上筐带,讓它表示最高精度的類型為:
typedef long double REAL;
在不支持long double 的平臺二上今穿,改為:
typedef double REAL;
在連double都不支持的平臺三上,改為:typedef float REAL;
也就是說伦籍,當跨平臺時蓝晒,只要改下typedef本身就行,不用對其他源碼做任何修改帖鸦。
另外芝薇,因為typedef是定義了一種類型的新別名,不是簡單的字符串替換作儿,所以它3比宏來得更加穩(wěn)铰宥(雖然用宏有時可以完成以上的用途)。
我們定義了vector的iterator的類型。這個類型還可以再main函數(shù)中重用晾嘶。當我們遇到一些具有復雜類型的變量或表達式時妓雾,就可以利用decltype和typedef/using的組合來將其轉化為一個簡單的表達式,這樣在以后的代碼寫作中可以提高可讀性和可維護性变擒。此外我們可以看到decltype(vec)::iterator這樣的靈活用法君珠,這看起來和auto非常類似,也類似于是一種"占位符"式的替代娇斑。在C++11中策添,我們有時會遇到匿名的類型,而擁有了decltype這個利器之后毫缆,重用匿名類型也并非難事唯竹。
#include<iostream>
using namespace std;
enum class{K1,K2,K3} anon_e;//匿名的強類型枚舉
union{
decltype(anon_e) key;
char*name;
}anon_u; // 匿名的Union聯(lián)合體
struct{
int d;
decltype(anon_u) id;
}anon_s[100];//匿名的struct數(shù)組
int main() {
decltype(anon_s) as;
as[0].id.key=decltype(anon_e)::K1;//引用匿名強類型枚舉中的值
}
(匿名枚舉的功能等價于靜態(tài)常成員變量)
(問題:什么是匿名的強類型枚舉)
匿名的強類型枚舉anon_e、匿名的聯(lián)合體anon_u,以及匿名的結構體數(shù)組anon_s苦丁〗牵可以看到,只要通過匿名類型的變量名anon_e旺拉、anon_u产上,以及anon_s, decltype可以推導其類型并且進行重用。
有了decltype蛾狗,我們可以適當擴大模板泛型的能力晋涣。如果稍微改變下函數(shù)模板的接口,就可以將該目標適用于更大的范圍沉桌。
#include<iostream>
using namespace std;
template<typename T1, typename T2>
void Sum(T1& t1, T2&t2, decltype(t1+t2)&s){
s=t1+t2;
}
int main(){
int a=3;
long b=5;
float c=1.0f, d=2.3f;
long e;
float f;
Sum(a,b,e); //s的類型被推導為long
Sum(c,d,f); //s的類型被推導為float
}
代碼Sum函數(shù)模板增加了類型為decltype(t1+t2)的s作為參數(shù)谢鹊,而函數(shù)本身不返回任何值。這樣一來留凭,Sum的適用性增加佃扼,返回的類型是根據(jù)t1+t2推導而來的類型。
這里最大的問題蔼夜,在于返回值得類型必須一開始就被指定兼耀,程序員必須清楚Sum運算的結果使用什么樣的類型來存儲是合適的(這里指的是long e 和float f)對于一些泛型編程中依然不能滿足需求。解決的方法是結合decltype和auto關鍵字求冷,使用追蹤返回類型的函數(shù)定義來使得編譯器對函數(shù)返回值進行推導翠订。
某些情況下,模板庫的使用人員可能認為一些自然而簡單的數(shù)據(jù)結構遵倦,比如數(shù)組,也是可以被模板類所包含的官撼。不過很明顯梧躺,如果t1+t2是兩個數(shù)組,t1+t2不會是合法的表達式。為了避免不必要的誤解掠哥,模板庫的開發(fā)人員應該為這些特殊情況考慮其他的版本巩踏。
#include<iostream>
using namespace std;
template<typename T1, typename T2>
void Sum(T1& t1, T2&t2, decltype(t1+t2)&s){
s=t1+t2;
}
void Sum(int a[], int b[], int c[]){
//數(shù)組版本
}
int main(){
int a[5], b[5], c[5];
Sum(a,b,c);//選擇數(shù)組版本
int d,e,f;
Sum(d,e,f);//選擇模板的實例化版本
}
//默寫
由于聲明了數(shù)組版本Sum,編譯器在編譯Sum(a,b,c)的時候,會優(yōu)先選擇數(shù)組版本续搀,而編譯Sum(d,e,f)的時候塞琼,依然會對應到模板的實例化版本。這就能夠保證Sum模板函數(shù)最大的可能性禁舷。
#include<map>
using namespace std;
int hash(char*);
map<char*, decltype(hash)>dict_key;//無法通過編譯
map<char*, decltype(hash(nullptr))> dict_key1;
標準庫中的map模板彪杉。因為該map是為了存儲字符串以及與其對應的哈希值的,因此我們可以decltype(hash(nullptr)) 來確定哈希值得類型牵咙。這樣的定義非常直觀派近。但是需要注意的是,decltype只能接受表達式做參數(shù)洁桌,像函數(shù)名做參數(shù)的表達式decltype(hash)是無法通過編譯的渴丸。
(問題:標準庫的map函數(shù)介紹)
事實上,decltype在C++11的標準庫中也有一些應用另凌,一些標準庫的實現(xiàn)也會依賴于decltype的類型推導谱轨。一個典型的例子是基于decltype的模板類result_of,其作用是推導函數(shù)的返回類型吠谢。我們可以看一下應用的實例土童。
#include<type_traits>
using namespace std;
typedef double(*func)(); //double型的函數(shù)指針。
int main(){
result_of<func()>:: type f;//由func()推導其結果類型
}
(typedef 用途四:為復雜的聲明定義一個新的簡單的別名囊卜。方法是:在原來的聲明里逐步用別名替換一部分復雜聲明娜扇,如此循環(huán),把帶變量名的部分留到最后替換栅组,得到的就是原聲明的最簡化版)
從變量名看起雀瓢,先往右,再往左玉掸,碰到一個圓括號就調轉閱讀的方向刃麸;括號內(nèi)分析完就跳出括號,還是按先右后左的順序司浪,如此循環(huán)泊业,直到整個聲明分析完。舉例:
int (func)(int p);
首先找到變量名func啊易,外面有一對圓括號吁伺,而且左邊是一個號,這說明func是一個指針租谈;然后跳出這個圓括號篮奄,先看右邊捆愁,又遇到圓括號,這說明(func)是一個函數(shù)窟却,所以func是一個指向這類函數(shù)的指針昼丑,即函數(shù)指針,這類函數(shù)具有int類型的形參夸赫,返回值類型是int菩帝。
int (func[5])(int );
func右邊是一個[]運算符,說明func是具有5個元素的數(shù)組茬腿;func的左邊有一個呼奢,說明func的元素是指針(注意這里的不是修飾func,而是修飾func[5]的滓彰,原因是[]運算符優(yōu)先級比高控妻,func先跟[]結合)。跳出這個括號揭绑,看右邊弓候,又遇到圓括號,說明func數(shù)組的元素是函數(shù)類型的指針他匪,它指向的函數(shù)具有int*類型的形參菇存,返回值類型為int。
也可以記住2個模式:
type ()(....)函數(shù)指針
type ()[]數(shù)組指針
這里的f 的類型最終被推導為double邦蜜,而result_of并沒有真正調用func() 這個函數(shù)依鸥,這一切都是因為底層的實現(xiàn)使用了decltype。result_of的一個可能的實現(xiàn)方式如下:
#include<iostream>
using namespace std;
template<class>
struct result_of;
template<class F,class... ArgTypes>
struct result_of<F(ArgTypes...)>
{
typedef decltype(
std:: declval<F>()(std:: declval<ArgTypes>()...)
)type;
};
這里標準庫將decltype作用于函數(shù)調用上(使用了變長函數(shù)模板),并將函數(shù)調用表達式返回的類型typedef為一個名為type的類型悼沈。這樣一來贱迟,result_of<func()>:: type就會被decltype推導為double.
decltype推導四規(guī)則
作為auto的伙伴,decltype在C++11中非常重要絮供,不過和auto一樣衣吠,由于應用廣泛,所以使用decltype也有很多的細則條款需要注意壤靶。很多時候缚俏,用戶會發(fā)現(xiàn)decltype的行為不如預期,那么下面的這些規(guī)則將解釋為什么這些行為不如預期贮乳。
int i;
decltype(i) a;//a:int
decltype((i)) b;//b:int& ,無法編譯通過
我們在編譯的時候忧换,發(fā)現(xiàn)decltype((i)) b; 這樣的語句編譯不過。編譯器會提示b是一個引用向拆,但沒有被賦初值亚茬。
當程序員用decltype(e)來獲取類型時,編譯器將依序判斷以下四規(guī)則:
1浓恳、如果e是一個沒有帶括號的標記符表達式或者類成員訪問表達式刹缝,那么decltype(e)就是e所命名的實體的類型葡兑。此外,如果e是一個被重載的函數(shù)赞草,則會導致編譯時錯誤。
2吆鹤、否則厨疙,假設e的類型是T,如果e是一個將亡值疑务,那么decltype(e)為T&&沾凄。
3、否則知允,假設e的類型是T撒蟀,如果a是一個左值,則decltype(e)為T&温鸽。
4保屯、否則,假設e的類型是T涤垫,則decltype(e)為T姑尺。
標記符表達式:基本上,所有除去關鍵字蝠猬、字面量等編譯器需要使用的標記之外的程序員自定義的標記都可以是標記符切蟋。而單個標記符對應的表達式就是標記符表達式。
int arr[4];
那么arr是一個標記符表達式榆芦,而arr[3]+0, arr[3]等柄粹,則都不是標記符表達式。
decltype(i)a匆绣,使用了推導規(guī)則1--因為i是一個標記符表達式驻右,所以類型被推導為int。而decltype((i)) b;中犬绒,由于(i)不是一個標記符表達式旺入,但卻是一個左值表達式(可以有具體的地址),因此凯力,按照decltype推導規(guī)則3茵瘾,其類型應該是一個int的引用。
{回顧下:左咐鹤、右值以及左值引用和右值引用:}
不過C++中拗秘,有一個被廣泛認同的說法,那就是可以取值的祈惶、有名字的就是左值雕旨,反之扮匠,不能取地址的、沒有名字的就是右值凡涩。
【在C++11中棒搜,右值引用就是對一個右值進行引用的類型。事實上活箕,由于右值通常不具有名字力麸,我們也只能通過引用的方式找到它的存在。
右值引用和左值引用都是屬于引用類型育韩。無論是聲明一個左值引用還是右值引用克蚂,都必須立即進行初始化。原因可以理解為是引用類型本身自己并不擁有所綁定對象的內(nèi)存筋讨,只是該對象的一個別名埃叭。左值引用是具名變量值的別名,而右值引用則是不具名(匿名)變量的別名悉罕。(也就是說需要找一個寄主)】
#include<vector>
#include<iostream>
using namespace std;
int i=4;
int arr[5]={0};
int *ptr=arr;
struct S{
double d;
}s;
void Overloaded(int);
void Overloaded(char);//重載的函數(shù)
int&& RvalRef();
const bool Func(int);
//規(guī)則1:單個標記符表達式以及訪問類成員赤屋,推導為本類型
decltype(arr) var1;//int[5], 標記符表達式
decltype(ptr) var2;//int*, 標記符表達式
decltype(s.d) var4;//double, 成員訪問表達式
//decltype(Overloaded) var5;//無法通過編譯,是個重載的函數(shù)
//規(guī)則2:將亡值蛮粮,推導為類型的右值引用
decltype(RvalRef()) var6=1; //int&& 而對于右值引用而言益缎,一定要賦初始化。
//規(guī)則3:左值然想,推導為類型的引用
decltype(true?i:i) var7=i;//int&,三元運算符莺奔,這里返回一個i的左值
decltype((i)) var8=i;//int&,帶圓括號的左值
decltype(++i) var9=i;//int&,++i返回i的左值
decltype(arr[3]) var10=i;//int&[] 操作返回左值
decltype(*ptr) var11=i;//int&* 操作返回左值
decltype("lval") var12="lval";//const char(&)[9], 字符串字面常量為左值
//規(guī)則4:以上都不是,推導為本類型
decltype(1) var13;//int,除字符串外字面常量為右值
decltype(i++) var14;//int,i++返回右值
decltype((Func(1))) var15;//const bool变泄,圓括號可以忽略令哟。
int main(){
return 0;
}
通過decltype(++i)和decltype(i++)可以看出編譯器所識別的不同的類型。
可以看到妨蛹,規(guī)則1不但適用于基本數(shù)據(jù)類型屏富,還適用于指針、數(shù)組蛙卤、結構體狠半,甚至函數(shù)類型的推導,事實上颤难,規(guī)則1在decltype類型推導中運用的最為廣泛神年。
規(guī)則3其實是一個左值規(guī)則。decltype的參數(shù)不是標志表達式或者類成員訪問表達式行嗤,且參數(shù)都為左值已日,推導出的類型均為左值引用。規(guī)則4則是適用于以上都不適用者栅屏。
引起麻煩的只能是規(guī)則3帶來的左值引用的推導飘千。一個簡單的能夠讓編譯器提示的方法是:如果使用decltype定義變量堂鲜,那么先聲明這個變量,再在其他語句里對其進行初始化护奈。這樣一來缔莲,由于左值引用總是需要初始化的,編譯器會報錯提示霉旗。另外一些時候酌予,C++11標準庫中添加的模板類is_lvalue_reference 或者 is_rvalue_reference,可以幫助程序員進行一些推導結果的識別音同。
#include<type_traits>
#include<iostream>
using namespace std;
int i=4;
int arr[5]={0};
int *ptr=arr;
int *ptr=arr;
int&&RvalRef();
int main(){
cout<<is_rvalue_reference<decltype(RvalRef())>:: value<<endl;//1
cout<<is_lvalue_reference<decltype(true?i:i)>:: value<<endl;//1
cout<<is_lvalue_reference<decltype((i))>:: value<<endl;//1
cout<<is_lvalue_reference<decltype(++i)>:: value<<endl;//1
cout<<is_lvalue_reference<decltype(arr[3])>:: value<<endl;//1
cout<<is_lvalue_reference<decltype(*ptr)>:: value<<endl;//1
cout<<is_lvalue_reference<decltype("lval")>:: value<<endl;//1
cout<<is_lvalue_reference<decltype(i++)>:: value<<endl;//0
cout<<is_rvalue_reference<decltype(i++)>:: value<<endl;//0
}
如果程序員在書寫中不是非常確定decltype是否將類型推導為左值引用昏滴,也可以通過這樣的小實驗來輔助確定滥沫。
cv限制符的繼承與冗余的符合
與auto類型推導時不能"帶走" cv限制符不同,decltype是能夠"帶走"表達式的cv限制符的简僧。不過,如果對象的定義中有const或volatile限制符雕欺,使用decltype進行推導時岛马,其成員不會繼承const或volatile限制符。
#include <type_traits>
#include <iostream>
using namespace std;
const int ic=0;
volatile int iv;
struct S{int i;};
const S a={0};//結構體變量a
volatile S b;//結構體變量b
volatile S *p=&b;//結構體指針p
int main(){
cout<< is_const<decltype(ic)>:: value<<endl;//1
cout<< is_volatile<decltype(iv)>:: value<<endl;//1
cout<< is_const<decltype(a)>:: value<<endl;//1
cout<< is_volatile<decltype(b)>:: value<<endl;//1
cout<< is_const<decltype(a.i)>:: value<<endl;//0,成員不是const
cout<< is_volatile<decltype(p->i)>:: value<<endl;//0,成員不是volatile
}
運用C++庫<type_traits>函數(shù)里的is_const和is_volatile來查看類型是否是常量或者易失的屠列±材妫可以看到,結構體變量a笛洛、b和結構體指針p和cv限制符并沒有出現(xiàn)在其成員的decltype類型推導結果中夏志。
而與auto相同的,decltype從表達式推導出類型后苛让,進行類型定義時沟蔑,也會允許一些冗余的符號。比如cv限制符以及引用符號&狱杰,通常情況下瘦材,如果推導出的類型以及有了這些屬性,冗余的符號則會被忽略:
#include <type_traits>
#include <iostream>
using namespace std;
int i=1;
int &j=i;
int *p=&i;
const int k=1;
int main(){
decltype(i) &var1=i;
decltype(j) &var2=i;//冗余的&仿畸,被忽略食棕。 這里依然是 int& 左值引用
cout<<is_lvalue_reference<decltype(var1)>:: value<<endl;//1,是左值引用
cout<<is_rvalue_reference<decltype(var2)>:: value<<endl;//0,不是右值引用
cout<<is_lvalue_reference<decltype(var2)>:: value<<endl;//1,是左值引用
decltype(p) *var3=&i;//無法通過編譯
decltype(p) *var3=&p;//var3的類型是Int**
auto *v3=p;//v3的類型是Int*
v3=&i;
const decltype (k) var4=1;//冗余的const,被忽略
}
我們定義了類型為decltype(i)& 的變量var1, 以及類型為decltype(j)&的變量var2。由于i的類型為int,所以這里的引用符號保證var1成為一個int&引用類型错沽。而由于j本身就是一個int&的引用類型簿晓,所以decltype之后的&成為了冗余符號,會被編譯器忽略甥捺,因此j的類型依然是int&.
這里特別需要注意的是decltype(p)*的情況抢蚀。可以看到镰禾,在定義var3變量的時候皿曲,由于p的類型是int*,因此var3被定義為了int**類型唱逢,這跟auto聲明中,*也可以是冗余的不同屋休。在decltype后的*號坞古,并不會被編譯器忽略。
var4中const可以被冗余的聲明劫樟,但會被編譯器忽略痪枫,同樣的情況也會發(fā)生在volatile限制符上。下面的追蹤返回類型的函數(shù)定義叠艳,則將融合auto奶陈、decltype,將C++11中的泛型能力提升到更高的水平附较。
使用追蹤放回類型的函數(shù)
追蹤返回類型的函數(shù)和普通函數(shù)的聲明最大的區(qū)別在于返回類型的后置吃粒。在一般情況下,普通函數(shù)的聲明方式會明顯簡單于最終返回類型拒课。比如:
int func(char* a,int b);
//比 auto func(char* a,int b) ->int; 好很多
有的時候徐勃,追蹤返回類型聲明的函數(shù)也會帶來大家一些意外:
class OuterType{
struct InnerType{int i;};
InnerType GetInner(); //返回值是結構體:InnerType。
InnerType it; //變量是it結構體早像。
};
//可以不寫OuterType::InnerType
auto OuterType::GetInner() ->InnerType{
return it;
}
// 這里不太明白僻肖,為什么這里需要這樣寫;
InnerType 不必寫明其作用域卢鹦。
因為返回類型后置臀脏,使模板中的一些類型推導就成為了可能。
#include <type_traits>
#include <iostream>
using namespace std;
template<typename T1,typename T2>
auto Sum(const T1&t1,const T2&t2)->decltype(t1+t2){
return t1+t2;
}
template<typename T1,typename T2>
auto Mul(const T1&t1,const T2&t2)->decltype(t1*t2){
return t1*t2;
}
int main(){
auto a=3;
auto b=4L;
auto pi=3.14;
auto c=Mul(Sum(a,b),pi);
cout<<c<<endl;//21.98
}
我們定義了兩個模板函數(shù)Sum和Mul冀自,它們的參數(shù)的類型和返回值都是實例化時決定谁榜。而由于main函數(shù)中還使用了auto,整個例子中沒有看到一個"具體"的類型聲明凡纳。這一切都要歸功于類型推導幫助下的泛型編程窃植。程序員在編寫時無需關心任何時段的類型選擇,編譯器會合理地進行推導荐糜,而簡單程序的書寫也由此得到了極大的簡化巷怜。
追蹤返回類型的另一個優(yōu)勢是簡化函數(shù)的定義,提高代碼的可讀性暴氏。
#include <type_traits>
#include <iostream>
using namespace std;
//有的時候延塑,你會發(fā)現(xiàn)這是面試題
int (*(*pf())())(){
return nullptr;
}
//auto(*)() ->int(*)() 一個返回函數(shù)指針的函數(shù)(假設為a函數(shù))
//auto pf1() ->auto(*)() ->int(*)() 一個返回a函數(shù)的指針的函數(shù)
auto pf1() ->auto(*)() ->int(*)(){
return nullptr;
}
int main(){
cout<<is_same<decltype(pf),decltype(pf1)>:: value<<endl;//1
}
我們定義了兩個類型完全一樣的函數(shù)pf和pf1。其返回的都是一個函數(shù)指針答渔。而該函數(shù)指針又指向一個返回函數(shù)指針的函數(shù)关带。這一點通過is_same的成員value已經(jīng)能夠確定了。而仔細看一看函數(shù)類型的聲明,可以發(fā)現(xiàn)老式的聲明法可讀性非常差宋雏。而追蹤返回類型只需要依照從右向左的方式芜飘,就可以將嵌套的聲明解析出來。大大提高了嵌套函數(shù)這類代碼的可讀性磨总。
除此之外嗦明,追蹤返回類型也被廣泛地應用在轉發(fā)函數(shù)中:
#include <type_traits>
#include <iostream>
using namespace std;
double foo(int a){
return(double)a+0.1;
}
int foo(double b){
return(int)b;
}
template<class T>
auto Forward(T t)->decltype(foo(t)){
return foo(t);
}
int main(){
cout<<Forward(2)<<endl;//2.1
cout<<Forward(0.5)<<endl;//0
}
由于使用了追蹤返回類型,可以實現(xiàn)參數(shù)和返回類型不同時的轉發(fā)蚪燕。
追蹤返回類型還可以用于函數(shù)指針中娶牌,聲明方式與追蹤返回類型的函數(shù)比起來是一樣的。
auto(*fp)()->int; 和 int(*fp)();
函數(shù)引用也是一樣適用的:
auto(&fr)()->int; 和 int(&fr)(); 也是等價的
另外馆纳,沒有返回值得函數(shù)也可以被聲明為追蹤返回類型诗良,程序員只需要將返回類型聲明為void即可。
基于范圍的for循環(huán)鲁驶。
在C++98標準中累榜,如果要遍歷一個數(shù)組,通常會需要如下代碼:
#include<iostream>
using namespace std;
int main(){
int arr[5]={1,2,3,4,5};
int *p;
for(p=arr;p<arr+sizeof(arr)/sizeof(arr[0]); ++p){
*p*=2;
}
for(p=arr;p<arr+sizeof(arr)/sizeof(arr[0]); ++p){
cout<<*p<<'\t';
}
}
用指針p來遍歷數(shù)組arr中的內(nèi)容灵嫌,兩個循環(huán)分別完成了每個元素自乘以2和打印工作。而C++的標準模板庫中葛作,我們還可以找到形如for_each的模板函數(shù)寿羞。如果我們使用for_each來完成代碼的工作,代碼將會是:
#include<algorithm>
#include<iostream>
using namespace std;
int action1(int&e){
e*=2;
}
int action2(int&e){
cout<<e<<'\t';
}
int main(){
int arr[5]={1,2,3,4,5};
for_each(arr,arr+sizeof(arr)/sizeof(arr[0]),action1);
for_each(arr,arr+sizeof(arr)/sizeof(arr[0]),action2);
}
for_each使用了迭代器的概念赂蠢,其迭代器就是指針绪穆。由于迭代器內(nèi)含了自增操作的概念,所以虱岂,++p操作則可以不寫在for_each循環(huán)中了玖院。不過都需要告訴循環(huán)體其界限的范圍,即arr到arr+sizeof(arr)/sizeof(arr[0])之間第岖,才能按元素執(zhí)行操作难菌。
很多時候,對于一個有范圍的集合而言蔑滓,由程序員來說明循環(huán)的范圍是多余的郊酒,也是容易犯錯誤的。而C++11也引入了基于范圍的for循環(huán)键袱,就很好地解決了這個問題燎窘。
#include<algorithm>
#include<iostream>
using namespace std;
int main(){
int arr[5]={1,2,3,4,5};
for(int&e:arr) e*=2;
for(int&e:arr) cout<<e<<'\t';
}
這是個基于范圍的for循環(huán)的實例。for循環(huán)后的括號由冒號":"分為兩部分蹄咖,第一部分是范圍內(nèi)用于迭代的變量褐健,第二部分則表示將被迭代的范圍。在上面的例子中澜汤,表示的是在數(shù)組arr中用迭代器e進行遍歷蚜迅。這樣一來舵匾,遍歷數(shù)組和STL容器就非常容易了。
基于范圍的for循環(huán)中迭代的變量采用了引用的形式慢叨,如果迭代變量的值在循環(huán)中不會被改變纽匙,那我們完全可以不用引用的方式來做迭代變量。
for(int e:arr)
cout<<e<<'\t';
或者用auto類型指示符拍谐,循環(huán)會更加簡練烛缔。
for(auto e:arr)
cout<<e<<'\t';
基于范圍的for循環(huán)跟普通循環(huán)是一樣的,可以用continue語句跳出循環(huán)的本次迭代轩拨,而用break語句來跳出整個循環(huán)践瓷。
能否使用基于范圍的for循環(huán),必須依賴于一些條件亡蓉。首先晕翠,就是for循環(huán)迭代的范圍是可確定的。對于類來說砍濒,如果該類有begin和end函數(shù)淋肾,那么begin和end之間就是for循環(huán)迭代的范圍。對于數(shù)組而言爸邢,就是數(shù)組的第一個和最后一個元素間的范圍樊卓。其次,基于范圍的for循環(huán)還要求迭代的對象實現(xiàn)++和==等操作符杠河。對于標準庫中的容器碌尔,如string,array,vector,deque,list,queue,map,set等,不會有問題券敌,因為標準庫總是保證其容器定義了相關的操作唾戚。
如:
#include<iostream>
using namespace std;
int func(int a[]){
for(auto e:a)
cout<<e;
}
int main(){
int arr[] = {1,2,3,4,5};
func(arr);
}
上述代碼會報錯,因為作為參數(shù)傳遞而來的數(shù)組a的范圍不能確定待诅,因此也就不能使用基于范圍循環(huán)for循環(huán)對其進行迭代的操作叹坦。
如果使用auto來聲明迭代的對象的話,那么這個對象不會是迭代器對象卑雁。
#include<vector>
#include<iostream>
using namespace std;
int main(){
vector<int> v={1,2,3,4,5};
for(auto i=v.begin();i!=v.end();++i)
cout<<*i<<endl;//i是迭代器對象
for(auto e:v)
cout<<e<<endl;//e是解引用后的對象
}
(補充內(nèi)容)C/C++中volatile關鍵字詳解
用volatile的原因:
C/C++中的volatile關鍵字和const對應立由,用來修飾變量,通常用于建立語言級別的 memory barrier序厉。
volatile 關鍵字是一種類型修飾符锐膜,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)弛房、硬件或者其它線程等道盏。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問荷逞。聲明時語法:int volatile vInt; 當要求使用 volatile 聲明的變量的值的時候媒咳,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)种远。而且讀取的數(shù)據(jù)立刻被保存涩澡。
volatile 指出 i 是隨時可能發(fā)生變化的,每次使用它的時候必須從i的地址中讀取坠敷,因而編譯器生成的匯編代碼會重新從i的地址讀取數(shù)據(jù)放在 b 中妙同。編譯器對訪問該變量的代碼就不再進行優(yōu)化,而優(yōu)化做法是膝迎,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒有對 i 進行過操作粥帚,它會自動把上次讀的數(shù)據(jù)放在 b 中。而不是重新從 i 里面讀限次。這樣以來芒涡,如果 i是一個寄存器變量或者表示一個端口數(shù)據(jù)就容易出錯,所以說 volatile 可以保證對特殊地址的穩(wěn)定訪問卖漫。
其實不只是“內(nèi)嵌匯編操縱椃丫。”這種方式屬于編譯無法識別的變量改變,另外更多的可能是多線程并發(fā)訪問共享變量時羊始,一個線程改變了變量的值旱幼,怎樣讓改變后的值對其它線程 visible。一般說來店枣,volatile用在如下的幾個地方:
- 中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
- 多任務環(huán)境下各任務間共享的標志應該加volatile叹誉;
- 存儲器映射的硬件寄存器通常也要加volatile說明鸯两,因為每次對它的讀寫都可能由不同意義;
多線程下的volatile
有些變量是用volatile關鍵字聲明的长豁。當兩個線程都要用到某一個變量且該變量的值會被改變時钧唐,應該用volatile聲明,該關鍵字的作用是防止優(yōu)化編譯器把變量從內(nèi)存裝入CPU寄存器中匠襟。如果變量被裝入寄存器钝侠,那么兩個線程有可能一個使用內(nèi)存中的變量,一個使用寄存器中的變量酸舍,這會造成程序的錯誤執(zhí)行帅韧。volatile的意思是讓編譯器每次操作該變量時一定要從內(nèi)存中真正取出,而不是使用已經(jīng)存在寄存器中的值啃勉,如下:
volatile BOOL bStop = FALSE;
(1) 在一個線程中:
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) 在另外一個線程中忽舟,要終止上面的線程循環(huán):
bStop = TRUE;
while( bStop ); //等待上面的線程終止,如果bStop不使用volatile申明,那么這個循環(huán)將是一個死循環(huán)叮阅,因為bStop已經(jīng)讀取到了寄存器中刁品,寄存器中bStop的值永遠不會變成FALSE,加上volatile浩姥,程序在執(zhí)行時挑随,每次均從內(nèi)存中讀出bStop的值,就不會死循環(huán)了勒叠。
這個關鍵字是用來設定某個對象的存儲位置在內(nèi)存中兜挨,而不是寄存器中。因為一般的對象編譯器可能會將其的拷貝放在寄存器中用以加快指令的執(zhí)行速度缴饭,例如下段代碼中:
...
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
...
}
...
在此段代碼中暑劝,nMyCounter的拷貝可能存放到某個寄存器中(循環(huán)中,對nMyCounter的測試及操作總是對此寄存器中的值進行)颗搂,但是另外又有段代碼執(zhí)行了這樣的操作:nMyCounter -= 1;這個操作中担猛,對nMyCounter的改變是對內(nèi)存中的nMyCounter進行操作,于是出現(xiàn)了這樣一個現(xiàn)象:nMyCounter的改變不同步丢氢。