98中的引用
- 概念
- 特性
- 引用的使用場景
- 三種傳參方式效率的比較
- 探索:引用的底層實現方式----->指針
- T&------>T* const
- const T&---->const T*const
- 引用和指針的區別
引用的總結
11中的右值引用
為什么要有右值引用
為了提高程序運行效率,C++11中引入了右值引用,右值引用也是別名,但其只能對右值引用
int main()
{//右值引用---->引用形式----->只能引用右值int a = 10;int b = 20;//a可以為左值,能放到=號左側的一定是左值//能放到=號右側的不一定是右值a = b;//ra對10的右值引用,ra稱為對10的別名int&& ra = 10;system("pause");return 0;
}
左值與右值
一般認為認為:可以放在=左邊的,或者能夠取地址的稱為左值,只能放在=右邊的,或者不能取地址的稱為右值,但肯定有特殊情況,不一定上述說法就完全正確
int main()
{const int a = 10;//a = 100; a不能出現在左側//a也不是右值//a能夠取地址cout << &a << endl;//int && ra = a;system("pause");return 0;
}
左值與右值總結
左值與右值不是很好區分,一般認為:
- 普通類型的變量,因為有名字,可以取地址,都認為是左值
- const修飾的常量,不可修改,只讀類型的,理論上應該按照右值對待,但因為其可以取地址(如果只是const類型常量的定義,編譯器不給其開辟空間,如果對該常量取地址時,編譯器才為其開辟空間),C++11認為其是左值
- 如果表達式的運行結果是一個臨時變量或者對象,認為是右值
- 如果表達式運行結果或單個變量是一個引用則認為是左值
int b = 10;//為右值
int&& rb = b + 10;
int g_a = 10;int& GetG_A()
{return g_a;
}int main()
{GetG_A() = 10;//下面這行代碼編譯報錯//int && rc = GetG_A();return 0;
}
不能簡單的通過能否放在=左側右側或者取地址來判斷左值或者右值,要根據表達式結果或變量的性質判斷,比如上述:c常量
C++11中的右值
右值引用,顧名思義就是對右值的引用。C++11中,右值由兩個概念組成:純右值和將亡值。
- 純右值
純右值是C++98中右值的概念,用于識別臨時變量和一些不跟對象關聯的值。比如:常量、一些運算表達式(1+3)等 - 將亡值
聲明周期將要結束的對象。比如:在值返回時的臨時對象。
//將亡值
//值得方式返回
//使用ret--->創建一個臨時變量---->函數運行結束棧空間被回收
int Add(int a, int b)
{int ret = a + b;return ret;
}int main()
{int&& Ret = Add(10,20);return 0;
}
引用與右值引用的比較
//C++98引用
//1. 普通類型得引用
//2. cosnt類型的引用
int main()
{//98中的普通類型的引用不能引用右值int a = 10;int&ra = a;//int&rra = 10; 不行//98中的const引用既可以引用左值也可以引用右值const int&cral = a;const int& cra2 = 10;return 0;
}
C++11中右值引用:只能引用右值,一般情況不能直接引用左值
int main()
{//10純右值,本來只是一個符號,沒有具體空間//右值引用變量r1在定義過程中,編譯器產生了一個臨時變量,r1實際引用的是臨時變量int&& r1=10;r1=100;int a=10;int&& r2=a; //編譯失敗:右值引用不能引用左值return 0;
}
std::move()
C++11中,std::move()函數位于 頭文件中,這個函數名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值引用,通過右值引用使用該值,實現移動語義。 注意:被轉化的左值,其聲明周期并沒有隨著左右值的轉化而改變,即std::move轉化的左值變量不會被銷毀。
int main()
{int a = 10;int&& ra = move(a);return 0;
}
右值引用的場景
class String
{
public:String(char* str = ""){if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}String(const String& s): _str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}String& operator=(const String& s){if (this != &s){char* pTemp = new char[strlen(s._str) + 1];strcpy(pTemp, s._str);delete[] _str;_str = pTemp;}return *this;}String operator+(const String&s){char* pTemp = new char[strlen(_str) + strlen(s._str)];strcpy(pTemp, _str);strcpy(pTemp + strlen(_str), s._str);String strRet(pTemp);delete pTemp;return strRet;}~String(){if (_str) delete[] _str;}
private:char* _str;
};
int main()
{String s1("hello");String s2("world");String s3(s1 + s2);return 0;
}
假設編譯器在返回值位置不優化
上述代碼在加號運算符的重載時,是創建了臨時變量,存儲拼接起來的字符串,返回的時候是按值返回是通過strRet拷貝了一個臨時的對象。函數體內的strRet在return后,已經被銷毀。所以最后s3拷貝構造的時候是通過臨時對象構造的,所以s3也要再創建一個臨時變量,一但拷貝構造好之后,臨時對象就釋放掉了
一個剛釋放一個又申請,有點多此一舉。能不能不讓s3再創建一個臨時對象
我們可以讓s3直接使用返回值的臨時對象。
移動語義
C++11提出移動語義概念:將一個對象中資源移動到另外一個對象的方式
- 可以有效的緩解內存的壓力
- 提高程序的運行效率,因為不需要開辟空間和釋放空間
//移動構造
//移動構造參數一定不能加cosnt,資源可以轉移出去,但是資源不能清空了
String(String&& s):_str(s._str)
{s._str = nullptr;
}
- 移動構造函數參數不能加const類型的右值引用,因為資源無法轉移而導致語義失效
- 在C++11中,編譯器會為類默認生成一個移動構造,該移動構造為淺拷貝,因此類中涉及到資源管理時,用戶必須顯示定義自己的移動構造
在C++11中,拷貝構造/移動構造/賦值/移動賦值函數必須同時提供,或者同時不提供,程序才能保證類同時具有拷貝和移動語義
auto_ptr中的資源轉移
auto_ptr<int> sp1(new int);auto_ptr<int> sp2(sp1);//sp1資源給了sp2,但是sp1生命周期還沒有到頭,所以不能被使用
右值引用引用左值
通過move函數把左值轉化為右值。
class Person
{
public:Person(char* name, char* sex, int age): _name(name), _sex(sex), _age(age){}Person(const Person& p): _name(p._name), _sex(p._sex), _age(p._age){}
#if 0Person(Person&& p): _name(p._name), _sex(p._sex), _age(p._age){}
#elsePerson(Person&& p): _name(move(p._name)), _sex(move(p._sex)), _age(p._age){}//移動賦值Person& operator=(Person&&p){return *this;}
#endif
private:String _name;String _sex;int _age;
};
Person GetTempPerson()
{Person pp("prety", "male", 18);return pp; //pp確實是右值,但是里面的成員變量是當作左值,所以用move把左值轉化為右值
}
int main()
{Person p(GetTempPerson());system("pause");return 0;
}
用move的時機,確定當前變量是將亡值,例如函數返回值。
完美轉發
完美轉發是指在函數模板中,完全依照模板的參數的類型,將參數傳遞給函數模板中調用的另外一個函數
void Func(int x)
{// ......
}
template<typename T>
void PerfectForward(T t)
{Fun(t);
}
PerfectForward為轉發的模板函數,Func為實際目標函數,但是上述轉發還不算完美**,完美轉發是目標函數總希望將參數按照傳遞給轉發函數的實際類型轉給目標函數,而不產生額外的開銷**,上述代碼中的Fun(t)只是外部t的一份拷貝,就好像轉發者不存在一樣。
所謂完美:
函數模板在向其他函數傳遞自身形參時,如果相應實參是左值,它就應該被轉發為左值;如果相應實參是右值,它就應該被轉發為右值。這樣做是為了保留在其他函數針對轉發而來的參數的左右值屬性進行不同處理(比如參數為左值時實施拷貝語義;參數為右值時實施移動語義)。
C++11 通過forward函數來實現完美轉發:
void Fun(int &x) //普通類型引用
{ cout << "lvalue ref" << endl;
}
void Fun(int &&x) //普通類型右值引用
{ cout << "rvalue ref" << endl;
}
void Fun(const int &x) //const類型普通引用
{cout << "const lvalue ref" << endl;
}
void Fun(const int &&x) //const類型的右值引用
{ cout << "const rvalue ref" << endl;
}//通過函數模板作為轉發函數
template<typename T>
//完美轉發:t是左值---->傳給Fun函數,t應該也是左值
// t如果是右值--->傳給Fun函數,t應該是右值
void PerfectForward(T &&t)
{ Fun(std::forward<T>(t)); //forward把t轉化為右值。//Fun(t);
}int main()
{PerfectForward(10); // 10為右值int a;PerfectForward(a); // lvalue refPerfectForward(std::move(a)); // rvalue refconst int b = 8;PerfectForward(b); // const lvalue refPerfectForward(std::move(b)); // const rvalue refreturn 0;
}
foward和move
- move實現移動語義
- forward實現完美轉發
右值引用作用
- 實現移動語義
- 給中間臨時變量取別名
- 實現完美轉發
- unordered_map 中的構造和插入都有用到右值引用。vectorC++11中的
emplace
插入(尾插)也用到了右值引用。
pash_back;必須要把對象構造好
emplace:構造加尾插