目錄
- 前言:工欲善其事,必先利其器
- 兩種資料
- 參考資料及其使用說明
- 官方對于左值和右值的定義
- 實際使用時的疑問
- 左值的涵蓋范圍
- 重要概念: 左值轉化(lvalue conversion)
- 左值與指標
- 概念上的區別
- 左值與指標值的互相轉化
- 指標值的構成
- 補充知識:存盤單元的地址編排
- 指標值的構成
- 陣列名與陣列下標運算
- 運算子歸納表格及實體說明
- 各種運算子運算結果左右值型別總結表
- 實體分析
前言:工欲善其事,必先利其器
兩種資料
學習編程語言, 有兩類資料可以讓人"高潮".
? 一類是針對初學者而設計的入門類書籍, 這種書總是適時地結合生動的生活實體, 來讓啥都不懂的萌新理解一些基本的和關鍵的東西, 達到撥云見日的效果. 為將來的進一步學習培養出良好的興趣和打下堅實的基礎. 最具代表性的就是 headfirst 系列叢書.
? 而另一類資料, 便是標準文獻了. 它就像博學的導師或者修仙小說里的隨身老爺爺, 能夠完美地解答你的任何疑惑(就算有解答不了的問題, 那也是暫時的, 因為標準文獻本身也是不斷改進和迭代的).
? 這邊作者假設讀者都有一定的C基礎,不是啥都不懂的萌新, 但是對于左值和右值的概念仍存有疑惑的朋友, 另外作者水平有限, 如有錯誤和瑕疵, 歡迎各位朋友指正.
參考資料及其使用說明
參考資料
? 本文的參考資料是C11標準文獻草案(N1570), 是免費且幾乎等同于C11標準文獻的版本.
-
外網版C11標準文獻資料(需翻墻)
html版
pdf版
-
筆者提供的國內版(筆者自建站)
html版
-
筆者所提供的本地下載(7z壓縮包, 內含pdf與html版)
本地下載
本文的鏈接及資料使用說明
-
本文鏈接說明
本文的鏈接部分,均是國內html版的鏈接
-
本地下載的資料說明
-
c11標準文獻不僅每一個章節都有編號, 且每一個自然段都有編號,方便定位
-
c11標準的html版: 可以用錨點直接定位到對應章節, 自然段 以及 注解
-
錨點: 形如
#6.3.1.2p3的東西, 出現在網址欄的最后, 用于定位到網頁中的位置(滾輪會自動滾到對應內容處) -
c11標準html版的錨點構成說明:
示例1:
#6.3.1.2p3- 6.3.1.2是具體的章節編號: 第6章第3部分1小節第2節
- p3是對應的自然段編號: p3代表第3自然段
示例2:
#note99- note99是對應的注解編號: note99代表第99個注解
-
應用說明:
- 查看國內版c11標準的第3章第2部分7小節第4自然段,可以直接輸入以下網址: peterzhang.cool:3000/pdfs/c11.html#3.2.7p4,然后回車
- 查看本地下載的c11的html版本也可以打開c11.html之后,在網址后面加上#3.2.7p4,然后按回車即可
-
-
官方對于左值和右值的定義
? 可見, 左值右值的概念來自賦值運算式, =號左邊的為左值(可修改的左值), 它代表(定位)了一個可用于存放資料的存盤空間; 而右值通常被理解為 "運算式的值"(value of an expression).
實際使用時的疑問
? 那么到底哪些是左值, 哪些又屬于右值? 什么情況下屬于左值, 什么情況下屬于右值呢?
左值的涵蓋范圍
-
變數名
-
指標變數
-
一些運算子的運算結果:
- * -- 取內容運算子
- [] -- 陣列下標運算子
- (type-name){initialize-list} -- 復合字面量
- . (只有左運算元為左值時,結果才為左值)
- ->(無論左運算元為左值還是右值,結果均為左值)
舉例說明:
- a是陣列名,絕大部分情況下屬于指標值(見后續部分),是右值
- a[1]屬于運算子[]的結果, 屬于左值, 可以放在等號左邊進行賦值操作.
重要概念: 左值轉化(lvalue conversion)
#6.3.2.1p2: 滿足以下條件的左值會被轉化成對應的存盤空間(資料物件)中所存盤的值,并且不再是一個左值, 這一程序被稱為 左值轉化
不是 sizeof, _Alignof, &, ++, -- 運算子的運算元
不是 . 或 賦值運算子的左運算元
該左值不是陣列型別(陣列型別的左值按其他規定進行轉化)
一維陣列: 不是陣列名,但可以是陣列元素
多維陣列: 不是任意N維(N>1)的陣列名或陣列元素,但可以是一維的陣列元素
(也就是說: 二維陣列arr[][]中, arr[1]仍舊代表一個陣列, 等同于一個陣列名,不滿足左值不是陣列型別的條件)
左值與指標
概念上的區別
- 左值: 可以放在賦值號的左邊, 與一個存盤單元(資料物件)對應, 代表了可直接獲取和設定該單元內容的途徑. (左值就像是一個已經撥通且未掛斷的電話)
- 指標值: 某一資料的存盤位置的資訊. (指標值就像是一個電話號碼)
通過左值, 你可以通過它直接獲取和設定存盤單元(資料物件)中的內容, 就像你可以直接問已撥通電話的另一頭問題或告訴另一頭一些資訊; 而指標值, 就像一個電話號碼, 想要像左值那樣獲取或設定內容, 必須先要 "按照號碼撥打電話", 這一步驟通常由取內容運算子 * 完成. 如果我們用另一個變數保存這個 "電話號碼", 這個變數就成了 "指標變數".
注意: 指標變數是一個變數, 它是左值, 而指標值并不是左值.
舉例: (我們把其他人當作是一個存盤空間,而你扮演主程式)
你正在跟小張通電話 -- 左值 <==> int a;
你手里有小張的電話號碼 -- 指標值 <==> &a;
你通過給小劉打電話,獲取了小張的電話號碼,然后再給小張打電話告訴他一些事 -- 利用指標變數 <==> int *p = &a; *(p) = 314;
左值與指標值的互相轉化
我們宣告的變數名是一類天然的左值, 它就像是我們和朋友直接面對面說話(或者一通已打通的電話); 而有時候,我們需要交談的物件并不在我們身邊, 這時候就需要我們自己去撥打電話.
- 將指標值轉化為對應的左值: 取內容運算子*
- 獲取某一左值的指標值: 取地址運算子&
指標值的構成
補充知識:存盤單元的地址編排
-
地址編號是基于位元組的: 一個位元組對應一個地址編號, 地址值(指標值)只能指向單個位元組
-
除了char外,C中的資料型別是多位元組
-
讀取多位元組資料的策略:
-
地址值(指標值)指向存盤單元的第一個位元組
-
定義一個取值范圍, 說明取得資料的長度

-
指標值的構成
- 指標值/地址值: 指向存盤空間的起始位元組
- 指標值的存盤型別是無符號的多位元組數值
- 指標指向的型別(
int *p;中的int)并不影響指標值的sizeof大小
- 指標指向的型別: 規定 利用指標進行一次內容讀取/內容設定 所影響的位元組范圍
- 一次讀取或設定: 同時操作包含起始位元組在內的N個位元組(N由指標指向的型別確定)
- 指標變數增加或減少1: 地址值/指標值增加或減少N
圖示:

測驗代碼: test.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
short int test = 314;
int *pInt = &test;
float *pFloat;
double *pDouble;
long double *pLongDouble;
printf("The sizeof short int is %d\n",sizeof(short int)); //2
//指標(地址)是一個獨立的資料存盤型別,類似于int,double等,占用的記憶體大小相同
printf("The sizeof pInt is %d\n",sizeof(pInt)); //4
printf("The sizeof pFloat is %d\n",sizeof(pFloat)); //4
printf("The sizeof pDouble is %d\n",sizeof(pDouble)); //4
printf("The sizeof pLongDouble is %d\n",sizeof(pLongDouble)); //4
//指標指向的型別確定讀取的位元組范圍
printf("The address of test is %p\n",pInt);
printf("Input the address above and use it without a type bounded:\n");
unsigned long long p;
scanf("%x",&p); //手動輸入上面列印的地址值
printf("The value of p is %lx\n",p);
printf("The value of *(short int *)p is %d\n",*(short int *)p); //314(10)
printf("The value of *(char *)p is %d\n",*(char *)p); //只讀取后8位,所以是58(10)
//指標變數+1,指標值/地址值的變化?
short int *pTest = &test;
printf("The address of test is %p\n",pTest);
pTest++;
printf("The address of test now is %p\n",pTest);
getchar();
return 0;
}
控制臺輸出:

陣列名與陣列下標運算
#6.3.2.1p3: 滿足下列條件的陣列型別值(通常是陣列名)會被轉換為一個指向該陣列首個元素的首個位元組的指標值(注意,不是指標變數而是指標值):
- 陣列名不是sizeof或&的運算元
- 不是用來初始化一個陣列的陣列字面量
因此:
陣列名本身是屬于左值的, 但是這并沒有什么卵用
因為絕大多數情況下(包括位于賦值號左邊的時候),陣列名會被轉換為指標值(不再是左值)
陣列名經過下標[]運算或*運算子卻會變成左值,代表陣列內某一元素,可以用于賦值
運算子歸納表格及實體說明
各種運算子運算結果左右值型別總結表

實體分析
-
復合字面量(compound literial)
#include <stdio.h> #include <stdlib.h> int main(void) { int p = ((int){314})++; //works just fine printf("p is %d\n",p); //314 //int *p = ((int [2]){314,110})++; //error: lvalue required as increment operand getchar(); return 0; }分析:
-
int p = ((int){314})++;復合字面量
(int){314}生成一個未命名的左值(其值為314)對該左值應用后綴形式的++運算子,生成一個右值(314)
將該右值賦值給變數p
-
int *p = ((int [2]){314,110})++;//報錯陳述句復合字面量
(int [2]){314,110}生成一個未命名的陣列左值陣列左值經過轉化,變成指向該陣列第一個元素的指標值(右值)
對該指標值應用后綴++運算子報錯(++運算子的運算元必須是左值)
-
-
結構體相關運算子(*與->)
結構體運算子 . :
#include <stdio.h> #include <stdlib.h> //宣告結構體s struct s { double i; }; //宣告聯合體g union { struct { int f1; struct s f2; } u1; struct { struct s f3; int f4; } u2; } g; struct s f(void){ //回傳結構體s的函式 return g.u1.f2; //回傳g.u1.f2 } int main(void) { //測驗: 結構體變數 struct s varible = {3.1415}; varible.i++; printf("varible.i.i is %f\n",varible.i); //4.1415 //測驗: 結構體回傳值函式 struct s f(void); //f().i = 20.0; //error: lvalue required as left operand of assignment getchar(); return 0; }分析:
varible.i++;陳述句作業正常: 說明其執行結果為左值f().i = 20.0;陳述句報錯: 說明f().i不是左值- 函式呼叫的回傳值是右值(盡管它回傳的是檔案域的聯合體變數的成員的內容)
右值.i,根據C11標準的規定,其執行結果也是右值,因此報錯
結構體指標運算子->:
#include <stdio.h> #include <stdlib.h> struct s { double i; }; union { struct { int f1; struct s f2; } u1; struct { struct s f3; int f4; } u2; } g; struct s * f(void){ //回傳結構體指標的函式 return &(g.u1.f2); } int main(void) { //測驗: 結構體指標回傳值函式 struct s * f(void); f()->i = 20.0;//結構體指標指向的成員是左值 printf("return value is: %f\n", f()->i); struct s newS = {3.14}; *(f()) = newS; //函式回傳的結構體指標也是右值,用*之后才變為左值 printf("Now,value is: %f\n", f()->i); getchar(); return 0; }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/3632.html
標籤:C
上一篇:C連載18-轉換說明,轉換說明修飾符、sizeof回傳型別可移植
下一篇:C 實戰練習題目77
