1. 程式人生 > >C語言中什麼叫做左值?右值?

C語言中什麼叫做左值?右值?

左值就是在賦值中可以放在賦值操作符兩邊的值,比如:

int a = 1;
double b = 2.0

a = b;
b = a;

這裡a和b都是左值,一切變數都是左值,但const變數是例外。 

*p是一個左值,和變數一樣,只要在*p的右邊加上賦值運算子,就可改變*p的值。

如果p是一個指向常量的指標,*p就是一個不能修改的左值,即它不能被放到賦值運算子的左邊。

i和 -i 都是表示式
但一個是左值(i),一個是右值(-i)。
++,--這兩種操作符要求作用於左值,所以i++合法,(-i)++不合法。

不嚴謹的講,左值右值的區分在於位於等號的那一側,左側的是左值,通常是一個變數,右側的是右值,可以是一個變數,或者是一個表示式。

先看什麼是表示式:
表示式由一個或多個運算元通過操作符組合而成。最簡單的表示式僅包含一個字面值常量或變數。較複雜的表示式則由操作符以及一個或多個運算元構成。
再看什麼是左值:
C++ 中存在兩種表示式:左值可以出現在賦值語句的左邊或右邊。右值只能出現在賦值的右邊,不能出現在賦值語句的左邊。
另外說明一下,i不僅是一個表示式,它還是一個變數,但是-i卻不是一個變數,這是他們一個可以自增一個不能自增的根本原因
對於i++來說,i是一個變數,所以是一個左值,執行i=i+1
但是對於(-i)++來說,-i是一個表示式,而不是一個變數,一個表示式是不可以作為左值的,因為沒有辦法執行這條語句:-i=-i+1(-i+1的值不能附給-i,因為沒有-i這個變數儲存空間)

一個賦值表示式:
X = Y;
在這個表示式裡,符號X的含義是X所代表的地址,這被稱為左值,左值在編譯時可知,左值表示儲存結果的地方;
在這個表示式裡,符號Y的含義是Y所代表的地址的內容,這被稱為右值,右值在執行時才可知,如無特別說明,右值表示“Y的內容”。 在《C專家程式設計(中文版)》中第4章,對左值和右值的基本描述。
而右值則是隻可以放在賦值操作符右邊的值,比如:
int a = 0;
char *b = "hello";

3 = a; // ERROR
"howdy" = b // ERROR

這裡3和"howdy"都是右值,所以不能放在賦值操作符左邊,一切常數、字元和字串都是右值。

l-value 與 r-value 區別
左值是引用某個物件的表示式,就是可以放在賦值左邊的東西,如:*(p+1)=7, 沒有名字的變數(*(p+1)表示式一定是一個型別的物件)被賦值了,但左值並不一定能被賦值,因為左值可以引用某個常量。 所有的引用都是左值。
右值是表示式的值(不是引用),可以放在賦值右面。
所有的左值都可以是右值,反之不成立
int i, j, *p;
i = 7; // Correct. A variable name, i, is an l-value.
7 = i; // ERROR. A constant, 7, is an r-value.
j * 4 = 7; // ERROR. The expression j * 4 yields an r-value.
*p = i; // A dereferenced pointer is an l-value.
const int ci = 7; // Declare a const variable.
ci = 9; // ERROR. ci is a nonmodifiable l-value


普通引用和const引用的初始化
當引用的初始式是一個左值(是一個物件,你可以取得他的地址)時,其初始化就是非常簡單的事情。普通T&的初始式必須是一個T型別的。而cosnt T&則不必是一個左值,甚至可以不是T型別的。在這樣的情況下,經過以下幾個步驟。
(1)首先,如果需要的話,將應用到型別T的隱式型別轉換。
(2)而後,將結果存入一個型別T的臨時變數。
(3)最後,將此臨時變數用作初始化的值。
例如
double& d=1; //錯誤,d是左值,不能用右值1 來初始化
const double& cd=1; //ok

對後一個初始化的解釋是:
double temp=double(1); //首先建立一個具有正確資料型別的臨時變數
const double& cd=temp; //而後用這個臨時變數作為cd的初始式,temp是個右值

由於左值的一大特點是可以對其賦值,而const正好將這個特點給閹割了,使該表示式成為了一個右值,所以可以用右值來初始化。

===7.3.1引用引數===
函式的引用引數
把引數宣告成引用,實際上改變了預設的按值傳遞引數的傳遞機制。在按值傳遞時,函式操縱的是實參的本地拷貝。當引數是引用時,函式接收的是實參的左值而不是值的拷貝。
這意味著函式知道實參在記憶體中的位置,因而能夠改變它的值或取它的地址。
int obj;
void frd( double & );
int main()
{
frd( obj ); // 錯誤: 引數必須是 const double &
return 0;
}
對frd()的呼叫是錯誤的.實參型別是int 必須被轉換成double 以匹配引用引數的型別,
該轉換的結果是個臨時值,因為這種引用不是const 型的,所以臨時值(不是左值)不能被用來初始化該引用


什麼時候將一個引數指定為引用比較合:
1、允許改變實參的值
2、向主調函式返回額外的結果
3、向函式傳遞大型類物件

使用指標還是引用作為引數的一個重要區分:
引用必須被初始化為指向一個物件,一旦初始化了,它就不能再指向其他物件,而指標可以指向一系列不同的物件也可以什麼都不指向
所以,如果一個引數可能在函式中指向不同的物件,或者這個引數可能不指向任何物件,則必須使用指標引數

===7.3.3 陣列引數===
在C++中陣列永遠不會按值傳遞它是傳遞第一個元素(準確地說是第0 個)的指標
如下宣告
void putValues( int[ 10 ] ); //當編譯器對實參型別進行引數型別檢查時,並不檢查陣列的長度,編澤器忽略10
被編譯器視為
void putValues( int* );
陣列的長度與引數宣告無關,因此下列三個宣告是等價的
// 三個等價的putValues()宣告
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );

傳遞陣列長度的三種機制
1、提供一個含有陣列長度的額外引數:void putValues( int[], int size );
2、將引數宣告為陣列的引用:void putValues( int (&arr)[10] );
當引數是一個數組型別的引用時,陣列長度成為引數和實參型別的一部分,編譯器檢查陣列實參的長度與在函式引數型別中指定的長度是否匹配
// 引數為10 個int 的陣列
// parameter is a reference to an array of 10 ints
void putValues( int (&arr)[10] );//只接受10 個int的陣列
int main() {
int i, j[ 2 ];
putValues( i ); // 錯誤: 實參不是 10 個 int 的陣列
putValues( j ); // 錯誤: 實參不是 10 個 int 的陣列
return 0;
}

3、使用抽象容器
void putValues(const vector<int> &vec );