1. 程式人生 > >左值、右值與常引用

左值、右值與常引用

程式碼編譯執行環境:VS2017+Win32+Debug

1.左值的定義

左值(Lvalue)是C++中的一個基本概念,指可定址的非只讀表示式。通俗來講,凡是可以出現在賦值運算子左邊的表示式都是左值。與左值相對的就是右值(Rvalue),只能出現在賦值運算右邊的表示式都是右值,所以,左值一定可以作為右值,右值一定不能作為左值。

理解左值的概念,需要注意一下幾點:
(1)左值一定是可以定址的表示式,不能定址的表示式不能作為左值。例如,表示式3+5是一個符號常量表達式,它不能被定址,因此就不能作為左值。
(2)常變數雖然可以定址,但是由於只讀的限制,也不能作為左值。
(3)如果表示式的運算結果是一個由文字常量生成的臨時無名物件,則表示式不能作為左值,如下面的例子。

#include <iostream>
using namespace std;

int func()
{
    return 0;
}

int main(int argc,char* argv[])
{
    int i;
    i+1=5;    //statement1
    func()=5; //statement2
}

以上程式中statement1和statement2均是非法語句,原因是i+1的運算結果是一個文字常量構成的臨時無名物件,函式func()的返回值也是一個文字常量構成的臨時無名物件,所以他們都不能作為左值。注意,這裡的臨時無名物件指的是沒有任何識別符號與之關聯的文字常量,包括數值常量、字元常量與符號常量,不包括類物件。

(4)如果表示式的運算結果是一個引用,則此表示式可以作為左值,如下面的例子。

#include <iostream>
using namespace std;

int& func()
{
    int a=0;
    return a;
}

int main(int argc,char* argv[])
{
    int i=1;
    (i+=1)=5;          //statement1
    cout<<" "<<i<<endl; 
    cout<<(func()=6);  //statement2
}

程式的輸出結果是:5 6。在statement1中,由於表示式i+=1的運算結果是對i的引用,所以它也可以作為左值。而在statement2中,函式呼叫func()的返回結果是對區域性變數a的引用,所以該表示式也可以作為左值。

由此可以,並不是只有單個變數才能作為左值,也不能僅由表示式的外在形式判斷它是否為左值,需要根據一個表示式的運算結果的性質進行判斷。

2.建立引用的條件

由於引用變數中實際上存放的是被引用物件的地址,所以,左值一定可以建立非常引用。對非左值建立常引用,首先要考慮該表示式結果是否能定址,其次還要考慮表示式結果的資料型別與引用資料型別是否一致,只有在滿足了這兩個條件的基礎上,才能將表示式結果的地址送入引用變數。否則,只能另外建立一個無名變數,該變數中存放非左值表示式的運算結果,然後再建立對該無名變數的常引用。

在C++語言中,經常把函式的引數宣告為引用,這樣在發生函式呼叫時可以減少執行時的開銷。但要特別注意的是,將函式的引數宣告為一般的引用還是宣告為常引用,是有講究的。考察如下函式:

#include <iostream>
using namespace std;

int Max(int& a, int& b)
{
    return (a>b)?a:b;
}

int main(int argc,char* argv[])
{
    int i=2;
    cout<<Max(i,5)<<endl; //編譯出錯
}

這個程式無法通過編譯,函式呼叫Max(i,5)中,由於5不是左值,不能為它建立引用,所以出現編譯錯誤。在這種情況下,必須修改函式Max()的定義,也就是把它的引數宣告為常引用:int Max(const int& a, const int&b),這樣就解決問題了。可見,將函式的引數宣告為常引用,不完全是因為引數的值在函式體內不能修改,還考慮了接受非左值作為函式實參的情況。

3.常引用的特殊性質

對某個變數(或表示式)建立常引用時,允許發生型別轉換,而一般的引用則不允許,見下面的程式。

#include <time.h>
#include <iostream>
using namespace std;

int Max(const int& a, const int& b)
{
    return (a>b)?a:b;
}

int main(int argc,char* argv[])
{
    char c='a';
    const int &rc=c;
    cout<<(void*)&c<<endl;
    cout<<(void*)&rc<<endl;

    int i=7;
    const int &ri=i;
    cout<<(void*)&i<<endl;
    cout<<(void*)&ri<<endl;
    cout<<Max(rc,5.5)<<endl;
}

程式輸出結果如下:

002BF9E3
002BF9C8
002BF9BC
002BF9BC
97

在這個程式中,如果將語句const int &rc=c;中的const去掉,將發生編譯錯。原因是一般的引用只能建立在相同資料型別的變數上。同樣,之所以允許Max(i,5.5)這樣的函式呼叫,也是因為函式Max()的第二個引數是常引用,因此可以將實參5.5先轉換為int型別的無名變數,然後再建立對該無名變數的常引用。

所以,對一個表示式建立常引用時,如果該表示式的結果可以定址,並且表示式的資料型別與引用型別相同,那麼可以直接將該表示式結果的地址送入引用變數。此例中,&i和&ri的值相等就說明了這一點。否則,若表示式的資料型別與引用型別不相同,或是表示式結果不可定址,那麼只能另外建立一個無名臨時變數存放表示式的結果(或其轉換後的值),然後將引用於無名臨時變數繫結,此例中&c與&rc的值不同正好說明了這一點。

以上說明了無名臨時變數具有常量性,只能建立常引用。需要注意,無名臨時變數具有常量性與能否作為左值沒有必然聯絡,並不是所有型別的無名臨時變數都不能作為左值,非文字常量構建的臨時變數是可以作為左值被賦值的,比如類的臨時物件。具體參見:臨時變數的常量性

參考文獻

[1]C++高階進階教程.陳剛.武漢大學出版社.1.9左值的概念
[2]C專家程式設計(中文版).第4章