python基礎(演算法)
什麼是左值?什麼是右值?
這裡的左右不代表位置,也就是說左值不是左邊的值,右值也不是右邊的值;
這裡的左右僅僅是一種叫法。
那什麼是左值呢? 左值通常來說就是一個變數;例如: int x=10; 則x就是一個左值!
那麼什麼是右值呢?右值通常來說是一種不可改變的值,例如:剛才的常量10,臨時物件(表示式計算後的結果;函式返回的值);而右值又可以分為純右值和將亡值。純右值是指基本型別的常量或者臨時物件,將亡值是指自定義型別的臨時物件。
int x=10; int y=2; int z=x+y; 這裡的10和2就是純右值,是基本型別的常量,x+y計算的結果會有一個臨時物件儲存,這個臨時物件也是純右值。
右值引用和左值引用的本質,都是起別名;區別:左值引用是給左值起別名,右值引用是給右值起別名
左值引用
int x=10;
int& y=x;//左值引用
int& y=10;//會編譯出錯,但是不能對右值進行左值引用嗎?
可以對右值進行左值引用 但要加上const 關鍵字 eg: const int& y=10 //不會編譯報錯
右值引用
int&& x=10;
int y=1;
int z=2;
int&& aa=y+z;
應用:
右值引用最主要莫過於 右值引用的移動語義:右值引用的移動拷貝和移動賦值可以減少拷貝次數,從而提高效率。
我們可以從下面string類的現代寫法中去體會
-
class mystring{
-
public:
-
mystring(
char*
str =
"")
-
{
-
if(
str==nullptr)
-
str=
"";
-
int size = strlen(
str);
-
int capacity = size +
1;
-
_
str = new
char[capacity];
-
strcpy(_
str,
str);
-
}
-
-
~mystring()
-
{
-
if (this != nullptr)
-
{
-
delete[] _
str;
-
}
-
_
str = nullptr;
-
}
-
-
//s1(s2)
-
mystring(
const mystring&
str)
-
:_
str(nullptr)
-
{
-
mystring tmp(
str._
str);
-
std::swap(_
str, tmp._
str);
-
}
-
-
mystring(mystring&&
str)
-
:_
str(
str._
str)
-
{
-
str._
str = nullptr;
-
}
-
//賦值運算子過載
-
//s3=s4
-
mystring& operator=(
const mystring&
str)
-
{
-
if (this != &
str)
-
{
-
mystring tmp(
str._
str);
-
std::swap(_
str, tmp._
str);
-
}
-
-
return *this;
-
}
-
mystring& operator=(mystring&&
str)
-
{
-
_
str =
str._
str;
-
str._
str = nullptr;
-
return *this;
-
}
-
-
private:
-
char* _
str;
-
};
拷貝賦值和賦值過載函式原本需要進行深拷貝,如果傳入的右值(將亡值)的話,我們直接可以掠奪將亡值的資源,進行移動賦值和移動拷貝,只需要進行淺拷貝。效率自然而言的也就得到了提升。
賦值返回
我們只可以從右值引用的移動語義解決了那些問題的角度去理解右值引用的作用。
-
mystring
operator+(
const mystring& str)
-
{
-
char* tmp =
new
char[
strlen(str._str) +
strlen(_str) +
1];
-
strcpy(tmp, _str);
-
strcpy(tmp +
strlen(_str), str._str);
-
mystring ret(tmp);
-
return ret;
-
}
-
mystring str(
"Hello ");
-
mystring str
1(
"World");
-
mystring str
2 = str + str
1;
如果沒有右值引用,傳值返回的時候,我們需要用ret構造一個臨時物件,臨時物件構造完畢後ret會被銷燬,在用臨時物件構造str2;也就是需要一次拷貝構造,而程式的執行結果(如下圖)也佐證了我們的想法。而有了右值引用之後,我們就可以用效率高的移動構造代替拷貝構造。
通過上面,我們可以理解右值引用的移動語義,就是實現資源的轉移,將深拷貝轉換為代價小的淺拷貝
右值引用引用左值
之前我們說過,右值引用只能引用右值,但是使用左值被move之後可以進行右值引用
eg:
int x=10;
int&& y=move(x);
但是在使用move的時候一定要小心
- mystring str( "Hello ") ;
- mystring str1( "World") ;
- mystring str2( std: :move( str)) ;
在執行完mystring str2(std::move(str));語句後,str是一個null
完美轉發
完美轉發是指在函式模板中,完全按照模板的引數的型別,將引數按照模板型別,傳遞給另一個函式(所謂的完美就是,左值轉發後還是左值,右值轉發後還是右值)
上面的左圖實現了完美轉發,而右圖中傳遞的引數屬性發生變化,即右值變成了左值
即為了實現轉發的時候,引數屬性不變,C++11中我們需要使用forward函式(完美轉發)
總結:
引用就是起別名,無論是左值引用還是右值引用本質都是起別名;
左值引用做引數 void Fun(A& a)還是做返回值 mystring& operator=(const mystring& str) 都是通過減少拷貝的次數,從而提高效率;但是左值引用在做返回值的時候,存在一個巨大的限制(不能出對應的作用域,一旦出了作用域,就會出現程式錯誤,而右值引用就可以用來解決這個問題)
右值引用在做引數的,做返回值的時候都可以通過移動語句減少拷貝的次數,並且不會出現左值引用做返回值的問題。
右值引用最主要的用途就是移動語義(移動拷貝和移動賦值)