程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++中的左值與右值

c++中的左值與右值

編輯:C++入門知識

左值(lvalue),右值(rvalue)是一個比較晦澀的概念,有些人可能甚至沒有聽過,但這個概念到了c++11後,卻變得十分重要,它們是理解move(),forward()等新語義的基礎。   那什麼是左值右值呢?   左值與右值這兩概念是從c中傳承而來的,在c中,左值指的是能夠出現在等號左邊及右邊的變量(表達式), 右值指的是只能出現在等號右邊的變量(表達式).   復制代碼 int a; int b;   a = 3; b = 4; a = b; b = a;   //不合法。   3 = a; a+b = 4; 復制代碼 通常來說,有名字的變量就是左值(如上面例子中的a, b),而由運算(加減乘除,函數調用返回值等)產生的中間結果(沒有名字)就是右值,如上的3+4, a + b等。   我們暫且可以認為:左值就是在程序中能夠尋值的東西,右值就是沒法取到它的地址的東西(不完全准確)。   如上概念到了c++中,就變得稍有不同。   在c++中,每一個表達式都會產生一個左值,或者右值,相應的,該表達式也就被稱作“左值表達式”,“右值表達式”。對於內置的數據類型來說(build-in types),左值右值的概念和c的沒有太多不同,不同的地方在於自定義的類型。   而且這種不同比較容易讓人混淆:   1)對於內置的類型,右值是不可被修改的(non-modifiable),也不可被const, volatile所修飾(cv-qualitification ignored)   2)對於自定義的類型(user-defined types), 右值卻允許通過它的成員函數進行修改。   對於1),這是和c是一致的,2)卻是c++中所獨有, 因此,如果你看到c++中如下的寫法,也許有會些驚訝:   復制代碼 class cs {     public:           cs(int i): i_(i) { cout << "cs(" << i <<") constructor!" << endl; }         ~cs() { cout << "cs destructor,i(" << i_ << ")" << endl; }           cs& operator=(const cs& other)         {             i_ = other.i_;             cout << "cs operator=()" << endl;             return *this;         }           int get_i() const { return i_; }         void change(int i) { i_ = i; }       private:         int i_; };     cs get_cs() {     static int i = 0;     return cs(i++); }     int main() {     // 合法     (get_cs() = cs(2)).change(323);     get_cs() = cs(2);// operator=()     get_cs().change(32);       return 0; } 復制代碼 這個特性多少有些奇怪,通常來說,c++中的自定義類型是應該設計地盡量和內置類型一樣才對的,但這個特性卻偏偏違背了這個原則。   對於這個特性,我們其實可以這樣想,也許會好理解點:自定義類型允許有成員函數,而通過右值調用成員函數是被允許的,但成員函數有可能不是const類型,因此通過調用右值的成員函數,也就可能會修改了該右值,done!       關於右值,還有一個需要注意的地方是:右值能被const類型的引用所指向   const cs& ref = get_cs(); 而且只能被const 類型的reference所指向:   //error    cs& ref = get_cs(); 當一個右值被const reference指向時,它的生命周期就被延長了,這個用法我在前面一篇博客裡講到過它的相關應用,點這。   這裡暗藏邏輯其實就是:右值不能直接轉化成左值(但左值可以轉化為右值).       上面提到的這兩個特性:    1)允許調用成員函數。    2)只能被const reference指向。   導致了一些比較有意思的結果,比如:   復制代碼 void func(cs& c) {    cout << "c:" << c.get_i() << endl; }   //error func(get_cs());   //正確 func(get_cs() = get_cs()); 復制代碼 其中:func(get_cs() = get_cs());能夠正確的原因就在於,cs的成員函數operator=() 返回的是cs&!       不允許非const reference 引用rvalue並不是完美的,它事實上也引起了一些問題,比如說拷貝構造函數的接口不一致了,這是什麼意思呢?   復制代碼 class cs {     public:                cs& operator=(const cs& c); };   // 另一種寫法 class cs2 {     public:                cs2& operator=(cs2& c); }; 復制代碼 上面兩種寫法的不同之處就在於參數,一個是const reference,一個是非const.   通常來說,如果不需要修改傳進來的參數,我們往往就按const reference的寫法,但對於copy constructor來說,經常是需要修改參數的值,比如auto_ptr。   復制代碼 // 類似auto_ptr class auto_ptr {    public:          auto_ptr(auto_tr& p)         {              ptr_ = p.ptr_;              p.ptr_ = NULL;         }         private:            void*  ptr_; }; 復制代碼     所以,對於auto_ptr來說,它的copy constructor傳的參數是non const reference。   這個種寫法本來應該被鼓勵的,non const reference比const reference更能靈活應對各種情況,從而保持一致的接口類型。   但如果拷貝構造函數寫成這樣子,卻又對rvalue的使用帶來了極大的不變,如前面所講的例子,rvalue不能被non const reference所引用,所以像auto_ptr的這樣的copy constructor就不能傳入rvalue.   //錯誤 auto_ptr p(get_ptr());   // operator=() 同理,錯誤。 auto_ptr p = get_ptr(); 這也是auto_ptr很不好用的其中一個原因。   為了解決這個問題,c++11中引入了一種新的引用類型,該種引用類型是專門用來指向rvalue的,有了這種新類型,在c++11中,lvalue 和rvalue的引用類型從此區分了開來,而在之前,它們是一樣的。   因為有了這種新的類型,接著就引出了c++11中新的語義,move(), forward()等,這兒先賣個關子,我們下次再講。    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved