域名停域app免費(fèi)下載上海還能推seo嗎
? ? ? ? C++11相比于C++98增加以許多新特性,讓C++語言更加靈活好用,但是貌似也增加了許多學(xué)習(xí)的難度,現(xiàn)在先看第一部分。
? ? ? ? 一、右值引用和移動語義
? ? ? ? 1.右值引用和左值引用
? ? ? ? 在C++中,值可以大致分為右值和左值,左值大概是哪些已經(jīng)被定義的變量或者對象,它一般具有持久性,它可以出現(xiàn)在賦值符號的左邊,也可以出現(xiàn)在賦值符號的右邊;右值一般指的是臨時變量、字面常量等等。一般來說,區(qū)分左右值的方法可以用是否可以取地址來判斷:左值可以取地址,右值不可以被取地址。
? ? ? ? 下面讓我們簡單看一段代碼吧:
void Func(int& a)
{cout << "左值引用" << endl;
}void Func(int&& a)
{cout << "右值引用" << endl;
}int main()
{int a = 1;Func(1);Func(a);return 0;
}
? ? ? ? 其中,a是一個普通變量,為左值,而1是一個字面常量,為右值,那么最后的輸出結(jié)果因該是“右值引用”、“左值引用”。讓我們看一看結(jié)果:
? ? ? ?2.右值引用引用左值和左值引用引用右值
? ? ? ? 右值可以被左值引用引用,因?yàn)橛抑狄话憔哂谐P?#xff0c;所以可以通過在左值引用前加上“const”來使得左值引用可以引用右值;而右值引用引用左值則需要使用“move”函數(shù)來改變左值的屬性。
int main()
{int a = 2;const int& L = 1;int&& R = move(a);cout << L << endl << R << endl;return 0;
}
3.右值引用存在的意義
a.延長臨時對象的生命周期
? ? ? ? 右值引用可以延長臨時對象的生命周期比如:
int main()
{string("123456789");return 0;
}
? ? ? ? 此代碼中,string的生命周期僅限于這一行,倘若使用右值引用來引用,那么就可以延長它的生命周期:
int main()
{string&& S = string("123456789");cout << S << endl;return 0;
}
? ? ? ? 同樣的,表達(dá)式相加的臨時對象,函數(shù)返回時的臨時對象,都可以使用右值引用來延長它的生命周期。
b.移動語義
? ? ? ? 當(dāng)然了,右值引用存在的意義可不是為了簡單的延長生命周期,而是為了轉(zhuǎn)移臨時對象的數(shù)據(jù),這使得數(shù)據(jù)的轉(zhuǎn)移更加高效和安全,這也正是移動語義的的機(jī)制。在此之前還需要了解的是雖然右值是不可以被改變,但是右值引用是具有左值屬性的,也就是說,被右值引用引用的右值是可以被修改的。
? ? ? ? 拿拷貝構(gòu)造來說,我們會將它的它的參數(shù)寫為const類型的,在保證不修改實(shí)參的情況下,還能夠接收右值。但是無論是左值還是右值,在有資源的情況下需要進(jìn)行大量的復(fù)制行為,特別是在右值的情況下(因?yàn)橛抑档纳芷诩磳⒔Y(jié)束,還得進(jìn)行一次復(fù)制,這樣會造成效率的低下)。為了解決這個問題,這個時候就需要介紹一下移動構(gòu)造和移動賦值了。
? ? ? ? 移動構(gòu)造和移動賦值旨在將臨時對象(右值)的資源轉(zhuǎn)移到我們的類中,由于右值的生命周期即將結(jié)束,秉承著趁你病要你命的原則,我是可以在虛弱的時候掠奪你的資源。這個時候我就可以直接把我的沒用的東西和你的資源進(jìn)行交換,這樣做的效率可不是一般的高,因?yàn)楸苊饬舜罅康臄?shù)據(jù)拷貝。
? ? ? ? 移動構(gòu)造和移動賦值同普通的構(gòu)造和賦值函數(shù)一樣,只是參數(shù)變?yōu)榱擞抑?#xff0c;并且函數(shù)內(nèi)部進(jìn)行資源的互換。
????????就用我們的自己寫的string類為例。(此處在linux環(huán)境下測試,因?yàn)閂S的編譯器會進(jìn)行優(yōu)化)
#pragma once
#include<iostream>using namespace std;namespace Mynamespace
{class string{friend ostream& operator<<(ostream& _cout, const Mynamespace::string& s);friend istream& operator>>(istream& _cin, Mynamespace::string& s);public:typedef char* iterator;public:string(const char* str = ""){_str = new char[strlen(str)+1];char* der = _str;const char* sour = str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = strlen(str);_capacity = _size;}string(const string& s){_str = new char[s._capacity];char* der = _str;const char* sour = s._str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = s._size;_capacity = s._capacity;}string(string&& s){cout << "string(string&& s) 移動構(gòu)造" << endl;swap(s);}~string(){delete[] _str;_capacity = 0;_size = 0;}string& operator=(const string& s){delete[] _str;_str = new char[strlen(s._str) + 1];char* der = _str;const char* sour = s._str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = s._size;_capacity = s._capacity;return *this;}// 移動賦值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移動賦值" << endl;swap(s);return *this;}//// iteratortypedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}/// modifyvoid push_back(char c);string& operator+=(char c);void append(const char* str);string& operator+=(const char* str);void clear();void swap(string& s);const char* c_str()const;/// capacitysize_t size()const;size_t capacity()const;bool empty()const;void resize(size_t n, char c = '\0');void reserve(size_t n);/// accesschar& operator[](size_t index);const char& operator[](size_t index)const;///relational operatorsbool operator<(const string& s){return strcmp(_str, s._str) < 0;}bool operator<=(const string& s){return *this < s || *this == s;}bool operator>(const string& s){return !(*this <= s);}bool operator>=(const string& s){return !(*this < s);}bool operator==(const string& s){return strcmp(_str, s._str) == 0;}bool operator!=(const string& s){return !(*this == s);}// 返回c在string中第一次出現(xiàn)的位置size_t find(char c, size_t pos = 0) const;// 返回子串s在string中第一次出現(xiàn)的位置size_t find(const char* s, size_t pos = 0) const;// 在pos位置上插入字符c/字符串str,并返回該字符的位置string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);// 刪除pos位置上的元素,并返回該元素的下一個位置string& erase(size_t pos, size_t len);private:char* _str;size_t _capacity;size_t _size;static const size_t npos = -1;};
}
int main()
{Mynamespace::string mystring1 = Mynamespace::string("123456789");return 0;
}
? ? ? ? 可能是編譯器的版本的問題,在我的linux環(huán)境中,以上代碼即使關(guān)閉了優(yōu)化,也仍然進(jìn)行直接構(gòu)造而不是構(gòu)造+移動構(gòu)造:
cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.outstring(const char* str )
? ? ? ? 有條件的小伙伴可以自己試一試,其中“-fno-elide-constructors”或者“-O0”可以關(guān)閉優(yōu)化。
所以這里先測試一下移動賦值:
int main()
{Mynamespace::string mystring1;mystring1 = Mynamespace::string("123456789");return 0;
}
cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.outstring(const char* str )string(const char* str )
string& operator=(string&& s)
? ? ? ? ? 可以看到最后調(diào)用了一次移動賦值。
二、引用折疊
? ? ? ? 在C++11中,新增加了引用折疊的特性,引用折疊針對的是引用的引用,但是不可以顯式的寫出來引用的引用,比如:
int main()
{int a;int&& & b = a;return 0;
}
? ? ? ? 但是可以隱式的引用(typedef后的引用):
int main()
{typedef int& L;typedef int&& R;int a;L& b = a;R& c = a;R&& d = 1;L&& e = a;return 0;
}
? ? ? ? 引用折疊理解起來有點(diǎn)像與門,在這里,左值引用代表著0,右值引用代表著1,當(dāng)左值引用右值引用同時存在的時候就是左值引用,只有當(dāng)兩個引用都為右值的時候才為右值引用。
? ? ? ? 就拿以上代碼為例?? ?L& b = a; 里,b的類型為int&,?? ?R& c = a; 里,c的類型為int&,R&& d = 1;? 里,d 的類型為int&&,? ? L&& e = a; 里 e為左值。
? ? ? ? 1.萬能引用
? ? ? ? 引用折疊的用途在哪里呢?實(shí)際上,它可以用來在函數(shù)模板中實(shí)現(xiàn)萬能引用。請看以下函數(shù)模板:
template <class T>
void func(T&& n)
{cout << "void func(int&& n)" << endl;
}
int main()
{int a = 1;func(1);func(a);return 0;
}
????????已知字面常量1是一個右值,變量a是一個左值,由于引用折疊,第一次調(diào)用的是右值引用版本的func,第二次是左值引用的func。最后的運(yùn)行結(jié)果如下:
? ? ? ? 這樣可以用一個模板實(shí)現(xiàn)左值引用和右值引用的兩個版本。
? ? ? ? 2.完美轉(zhuǎn)發(fā)
? ? ? ? 先看以下代碼:
void F(int& a)
{cout << "左值引用" << endl;}
void F(int&& a)
{cout << "右值引用" << endl;}
void F(const int& a)
{cout << "左值引用" << endl;
}template <class T>
void func(T&& n)
{F(n);
}int main()
{int a = 1;func(a);func(2);return 0;
}
? ? ? ? 我們想要的輸出結(jié)果是“左值引用”、“右值引用”,那么先看一下運(yùn)行結(jié)果:
? ? ? ? 哎。為什么是兩個左值引用呢?
????????由于右值引用的屬性為左值,當(dāng)我們想連續(xù)傳遞一個右值的時候,在第一次傳遞后,這個值就已經(jīng)變?yōu)樽笾祵傩粤?#xff0c;為了解決這個問題,C++11引入了完美轉(zhuǎn)發(fā),它的目得是為了在右值連續(xù)傳遞的過程中不改變右值的屬性。
? ? ? ? 完美轉(zhuǎn)發(fā)的本質(zhì)是一個函數(shù)模板forward,它的底層是一個強(qiáng)轉(zhuǎn)(左值引用和右值引用的本質(zhì)還是指針,只是在語義上不同)。它的用法是在傳遞參數(shù)的地方加上模板的類型,后邊緊接著參數(shù)是傳遞的值:
void F(int& a)
{cout << "左值引用" << endl;}
void F(int&& a)
{cout << "右值引用" << endl;}
void F(const int& a)
{cout << "左值引用" << endl;
}template <class T>
void func(T&& n)
{F(forward<T>(n));
}int main()
{int a = 1;func(a);func(2);return 0;
}
? ? ? ? 運(yùn)行結(jié)果:
? ? ? ? 我們可以分析一下:
? ? ? ? a.傳遞的參數(shù)為a的時候,a為左值,func發(fā)生了引用折疊,編譯器推導(dǎo)出T為int&,那么forward強(qiáng)轉(zhuǎn)n為左值并返回。
? ? ? ? b.傳遞的參數(shù)為字面常量1的時候,1為右值,func沒有發(fā)生引用折疊,編譯器推導(dǎo)出T為int&&,那么forward強(qiáng)轉(zhuǎn)n為右值并返回。
? ? ? ? 總的來說,完美轉(zhuǎn)發(fā)就是根據(jù)T的類型來推導(dǎo)最后返回的類型,如果T是右值,那么最后返回的就是右值屬性的對象,如果T為左值,那么最后返回的就是左值屬性的對象。