深入浅出之C++11新特性
1. auto類型賦予新含義
1.1 auto類型定義
在之前的 C++ 版本中,auto 關鍵字用來指明變量的存儲類型,它和 static 關鍵字是相對的。auto 表示變量是自動存儲的,這也是編譯器的默認規則,所以寫不寫都一樣,一般我們也不寫,這使得 auto 關鍵字的存在變得非常雞肋。
C++11 賦予 auto 關鍵字新的含義,使用它來做自動類型推導。也就是說,使用了 auto 關鍵字以后,編譯器會在編譯期間自動推導出變量的類型,這樣我們就不用手動指明變量的數據類型了。
auto 關鍵字基本的使用語法如下:
auto name = value;
name 是變量的名字,value 是變量的初始值。
注意:auto 僅僅是一個占位符,在編譯器期間它會被真正的類型所替代。或者說,C++ 中的變量必須是有明確類型的,只是這個類型是由編譯器自己推導出來的。
auto 除了可以獨立使用,還可以和某些具體類型混合使用,這樣 auto 表示的就是“半個”類型,而不是完整的類型。
int x = 0; auto *p1 = &x; //p1 為 int *,auto 推導為 int auto p2 = &x; //p2 為 int*,auto 推導為 int* auto &r1 = x; //r1 為 int&,auto 推導為 int auto r2 = r1; //r2 為 int,auto 推導為 intauto 與 const 結合的用法:
int x = 0; const auto n = x; //n 為 const int ,auto 被推導為 int auto f = n; //f 為 const int,auto 被推導為 int(const 屬性被拋棄) const auto &r1 = x; //r1 為 const int& 類型,auto 被推導為 int auto &r2 = r1; //r1 為 const int& 類型,auto 被推導為 const int 類型結論:
- 當類型不為引用時,auto 的推導結果將不保留表達式的 const 屬性;
- 當類型為引用時,auto 的推導結果將保留表達式的 const 屬性。
1.2 auto 的限制
- 使用 auto 的時候必須對變量進行初始化
- 當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。?
- auto不能作為函數的參數。 列如:
- auto不能直接用來聲明數組。 列如:
- ?auto不能定義類的非靜態成員變量
- 實例化模板時不能使用auto作為模板參數
?
1.3? for循環的auto用法
- 拷貝range的元素時,使用for(auto x : range).
for(auto a:b)中b為一個容器,效果是利用a遍歷并獲得b容器中的每一個值,但是a無法影響到b容器中的元素。
int array[] = { 1, 2, 3, 4, 5 };for (auto e : array)e *= 2;for (auto e : array)cout << e << " ";輸出結果:1 2 3 4 5- 修改range的元素時,使用for(auto && x : range).
for(auto &a:b)中加了引用符號,可以對容器中的內容進行賦值,即可通過對a賦值來做到容器b的內容填充。
int array[] = { 1, 2, 3, 4, 5 };for (auto& e : array)e *= 2;for (auto e : array)cout << e << " ";輸出結果:2 4 6 8 10- 只讀range的元素時,使用for(const auto & x : range).
?1.4?auto 的應用?
- 使用 auto 定義迭代器
auto 的一個典型應用場景是用來定義 stl 的迭代器。
我們在使用 stl 容器的時候,需要使用迭代器來遍歷容器里面的元素;不同容器的迭代器有不同的類型,在定義迭代器時必須指明。而迭代器的類型有時候比較復雜,書寫起來很麻煩,請看下面的例子:
?可以看出來,定義迭代器 i 的時候,類型書寫比較冗長,容易出錯。然而有了 auto 類型推導,我們大可不必這樣,只寫一個 auto 即可。
修改上面的代碼,使之變得更加簡潔:
auto 可以根據表達式 v.begin() 的類型(begin() 函數的返回值類型)來推導出變量 i 的類型。
- auto 用于泛型編程
?auto 的另一個應用就是當我們不知道變量是什么類型,或者不希望指明具體類型的時候,比如泛型編程中。我們接著看例子:
#include <iostream> using namespace std;class A{ public:static int get(void){return 100;} };class B{ public:static const char* get(void){return "http://c.biancheng.net/cplus/";} };template <typename T> void func(void){auto val = T::get();cout << val << endl; }int main(void){func<A>();func<B>();return 0; }2.??decltype類型
2.1 auto與decltype區別
auto 和 decltype 關鍵字都可以自動推導出變量的類型,但它們的用法是有區別的:
auto varname = value; decltype(exp) varname = value; 其中,varname 表示變量名,value 表示賦給變量的值,exp 表示一個表達式。auto 根據=右邊的初始值 value 推導出變量的類型,而 decltype 根據 exp 表達式推導出變量的類型,跟=右邊的 value 沒有關系。?
?auto 要求變量必須初始化,而 decltype 不要求。這很容易理解,auto 是根據變量的初始值來推導出變量類型的,如果不初始化,變量的類型也就無法推導了。decltype 可以寫成下面的形式:
decltype(exp) varname;
int a = 0; decltype(a) b = 1; //b 被推導成了 int decltype(10.8) x = 5.5; //x 被推導成了 double decltype(x + 100) y; //y 被推導成了 double2.2 decltype 推導規則
?三條規則:
3.?列表初始化
?C++98中,標準允許使用花括號{}對數組元素和結構體進行統一的列表初始值設定。
int i_arr[3] = { 1, 2, 3 }; long l_arr[] = { 1, 3, 2, 4 }; struct A {int x;int y; } a = { 1, 2 };但是這種初始化方式的適用性非常狹窄,只有上面提到的這兩種數據類型可以使用初始化列表。
對對于一些自定義類型,卻不行.
vector<int> v{1,2,3,4,5};在 C++11 中,初始化列表的適用性被大大增加了。
// 內置類型 int x1 = {10}; int x2{10} // 數組 int arr1[5] {1,2,3,4,5} int arr2[]{1,2,3,4,5}; // 標準容器 vector<int> v{1,2,3} map<int,int> m{{1,1},{2,2}} // 自定義類型 class Point { int x; int y; } Power p{1,2};4. 智能指針?
4.1 shared_ptr
4.1.1 shared_ptr基本用法
shared_ptr采用引用計數的方式管理所指向的對象。當有一個新的shared_ptr指向同一個對象時(復制shared_ptr等),引用計數加1。當shared_ptr離開作用域時,引用計數減1。當引用計數為0時,釋放所管理的內存。
這樣做的好處在于解放了程序員手動釋放內存的壓力。之前,為了處理程序中的異常情況,往往需要將指針手動封裝到類中,通過析構函數來釋放動態分配的內存;現在這一過程就可以交給shared_ptr去做了。
一般我們使用make_shared來獲得shared_ptr。
cout<<"test shared_ptr base usage:"<<endl; shared_ptr<string> p1 = make_shared<string>(""); if(p1 && p1->empty())*p1 = "hello"; auto p2 = make_shared<string>("world"); cout<<*p1<<' '<<*p2<<endl; cout<<"test shared_ptr use_count:"<<endl; cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<endl; auto p3 = p2; cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<"\tp3 cnt:"<<p3.use_count()<<endl; p2 = p1; cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<"\tp3 cnt:"<<p3.use_count()<<endl;4.1.2 shared_ptr和new
shared_ptr可以使用一個new表達式返回的指針進行初始化。
cout<<"test shared_ptr and new:"<<endl; shared_ptr<int> p4(new int(1024)); //shared_ptr<int> p5 = new int(1024); // wrong, no implicit constructor cout<<*p4<<endl;但是,不能將一個new表達式返回的指針賦值給shared_ptr。
另外,特別需要注意的是,不要混用new和shared_ptr!
void process(shared_ptr<int> ptr) {cout<<"in process use_count:"<<ptr.use_count()<<endl; } cout<<"don't mix shared_ptr and normal pointer:"<<endl; shared_ptr<int> p5(new int(1024)); process(p5); int v5 = *p5; cout<<"v5: "<<v5<<endl; int *p6 = new int(1024); process(shared_ptr<int>(p6)); int v6 = *p6; cout<<"v6: "<<v6<<endl;上面的程序片段會輸出:
in process use_count:2 v5: 1024 in process use_count:1 v6: 0可以看到,第二次process p6時,shared_ptr的引用計數為1,當離開process的作用域時,會釋放對應的內存,此時p6成為了懸掛指針。
所以,一旦將一個new表達式返回的指針交由shared_ptr管理之后,就不要再通過普通指針訪問這塊內存!
4.1.3 shared_ptr.reset
shared_ptr可以通過reset方法重置指向另一個對象,此時原對象的引用計數減一。
cout<<"test shared_ptr reset:"<<endl; cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<"\tp3 nt:"<<p3.use_count()<<endl; p1.reset(new string("cpp11")); cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<"\tp3 cnt:"<<p3.use_count()<<endl;4.1.4 shared_ptr deleter
可以定制一個deleter函數,用于在shared_ptr釋放對象時調用。
void print_at_delete(int *p) {cout<<"deleting..."<<p<<'\t'<<*p<<endl;delete p; } cout<<"test shared_ptr deleter:"<<endl; int *p7 = new int(1024); shared_ptr<int> p8(p7, print_at_delete); p8 = make_shared<int>(1025);4.2 unique_ptr
4.2.1 unique_ptr基本用法
unique_ptr對于所指向的對象,正如其名字所示,是獨占的。所以,不可以對unique_ptr進行拷貝、賦值等操作,但是可以通過release函數在unique_ptr之間轉移控制權。
cout<<"test unique_ptr base usage:"<<endl; unique_ptr<int> up1(new int(1024)); cout<<"up1: "<<*up1<<endl; unique_ptr<int> up2(up1.release()); cout<<"up2: "<<*up2<<endl; //unique_ptr<int> up3(up1); // wrong, unique_ptr can not copy //up2 = up1; // wrong, unique_ptr can not copy unique_ptr<int> up4(new int(1025)); up4.reset(up2.release()); cout<<"up4: "<<*up4<<endl;4.2.2 unique_ptr作為參數和返回值
上述對于拷貝的限制,有兩個特殊情況,即unique_ptr可以作為函數的返回值和參數使用,這時雖然也有隱含的拷貝存在,但是并非不可行的。
unique_ptr<int> clone(int p) {return unique_ptr<int>(new int(p)); } void process_unique_ptr(unique_ptr<int> up) {cout<<"process unique ptr: "<<*up<<endl; } cout<<"test unique_ptr parameter and return value:"<<endl; auto up5 = clone(1024); cout<<"up5: "<<*up5<<endl; process_unique_ptr(move(up5)); //cout<<"up5 after process: "<<*up5<<endl; // would cause segmentfault這里的std::move函數,以后再單獨具體細說^_^
4.2.3 unique_ptr deleter
unique_ptr同樣可以設置deleter,和shared_ptr不同的是,它需要在模板參數中指定deleter的類型。好在我們有decltype這個利器,不然寫起來好麻煩。
cout<<"test unique_ptr deleter:"<<endl; int *p9 = new int(1024); unique_ptr<int, decltype(print_at_delete) *> up6(p9, print_at_delete); unique_ptr<int> up7(new int(1025)); up6.reset(up7.release());4.3 weak_ptr
weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的對象,但是卻不增加對象的引用計數。這樣就有可能出現weak_ptr所指向的對象實際上已經被釋放了的情況。因此,weak_ptr有一個lock函數,嘗試取回一個指向對象的shared_ptr。
cout<<"test weak_ptr basic usage:"<<endl; auto p10 = make_shared<int>(1024); weak_ptr<int> wp1(p10); cout<<"p10 use_count: "<<p10.use_count()<<endl; //p10.reset(new int(1025)); // this will cause wp1.lock() return a false obj shared_ptr<int> p11 = wp1.lock(); if(p11) cout<<"wp1: "<<*p11<<" use count: "<<p11.use_count()<<endl;4.4 總結
- shared_ptr采用引用計數的方式管理所指向的對象。
- shared_ptr可以使用一個new表達式返回的指針進行初始化;但是,不能將一個new表達式返回的指針賦值給shared_ptr。
- 一旦將一個new表達式返回的指針交由shared_ptr管理之后,就不要再通過普通指針訪問這塊內存。
- shared_ptr可以通過reset方法重置指向另一個對象,此時原對象的引用計數減一。
- 可以定制一個deleter函數,用于在shared_ptr釋放對象時調用。
- unique_ptr對于所指向的對象,是獨占的。
- 不可以對unique_ptr進行拷貝、賦值等操作,但是可以通過release函數在unique_ptr之間轉移控制權。
- unique_ptr可以作為函數的返回值和參數使用。
- unique_ptr同樣可以設置deleter,需要在模板參數中指定deleter的類型。
- weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的對象,但是卻不增加對象的引用計數。
- weak_ptr有一個lock函數,嘗試取回一個指向對象的shared_ptr。
參考:
總結
以上是生活随笔為你收集整理的深入浅出之C++11新特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 磁盘写保护怎么删除 删除磁盘写保护问题解
- 下一篇: excel文件在u盘损坏怎么办啊 U盘中