C语言再学习 -- 运算符与表达式
分三部分來講
一、左值與右值
參看:左值與右值
首先我們需要理解左值和右值的定義:
左值指的是如果一個表達式可以引用到某一個對象,并且這個對象是一塊內存空間且可以被檢查和存儲,那么這個表達式就可以做為一個左值。??????
右值指的是引用了一個存儲在某個內存地址里的數據。
從上面的兩個定義可以看出,左值其實要引用一個對象,而一個對象在我們的程序中又肯定有一個名字或者可以通過一個名字訪問到,所以左值又可以歸納為:左值表示程序中必須有一個特定的名字引用到這個值。而右值引用的是地址里的內容,所以相反右值又可以歸納為:右值表示程序中沒有一個特定的名字引用到這個值除了用地址。
好這些都是從定義上理解左值右值,那么我們再用這些定義作為我們的理論基礎來總結一下哪些是左值,哪些是右值:
左值:
| Expression???????????? ?? | Lvalue??????????????????????????????????? |
| x = 4 | ?x |
| *ptr = newvalue | *ptr |
| ++a | ++a |
| b[0] = 100 | b[0] |
| const int m = 10 | m |
| int & f() | The function call to f() |
右值:
| Expression??? | Rvalue |
| 100 | 100 |
| a * b | The expression of a?* b |
| a++ | a++ |
| int f() | The function call to f() that does notreturn reference |
以上這些內容都可以用定義來解釋為什么這些為左值,而那些為右值。但我要特殊解釋一下為什么函數的調用只能作為右值除了這個函數返回的是引用。其實這個也非常好解釋,因為如果一個函數返回的值是內建類型,那么這個返回值是沒有辦法通過一個名字或者表達式引用到的,同理如果一個函數返回的是一個對象,那么這個對象是一個臨時的,也不可能用一個名字訪問到。所以函數的調用通常只能作為右值,但如果一個函數返回引用,那么它的返回值就有意義了,因為它是另一個名字的別名,有名字了,所以它就變成了左值。
注意:左值能轉化為右值,但反之不行。
好了,講了這么多我覺得已經足夠,但還要多講一點,這點就是哪些操作符必需左值.
| Operator ? ? ? ? ? ? ? ? ? ? ? | Requirement ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| & | Operand must be an lvalue |
| ++ -- | Operand must be an lvalue.This applies? to?both?prefix and posfix forms |
| =?? +=?? -=?? *=?? %=??? <<=?? >>= &= ^= |= | Left operand munst be an lvalue |
附:《C primer plus》中左值、右值的定義
“數據對象”是泛指數據存儲區的術語,數據存儲區能用于保存值。
“左值”指用于標識一個特定的數據對象的名字或表達式。即對象指實際的數據存儲,而左值是用于識別或定位那個存儲的標識符(或表達式)。
“右值”指可能賦給可修改左值的量。
二、運算符優先級
參看:C語言運算符優先級列表(超詳細)
| 優先級 | 運算符 | 名稱或含義 | 使用形式 | 結合方向 | 說明 |
| 1 | [] | 數組下標 | 數組名[常量表達式] | 左到右 | -- |
| () | 圓括號 | (表達式)/函數名(形參表) | -- | ||
| . | 成員選擇(對象) | 對象.成員名 | -- | ||
| -> | 成員選擇(指針) | 對象指針->成員名 | -- | ||
| 2 | - | 負號運算符 | -表達式 | 右到左 | 單目運算符 |
| ~ | 按位取反運算符 | ~表達式 | |||
| ++ | 自增運算符 | ++變量名/變量名++ | |||
| -- | 自減運算符 | --變量名/變量名-- | |||
| * | 取值運算符 | *指針變量 | |||
| & | 取地址運算符 | &變量名 | |||
| ! | 邏輯非運算符 | !表達式 | |||
| (類型) | 強制類型轉換 | (數據類型)表達式 | -- | ||
| sizeof | 長度運算符 | sizeof(表達式) | -- | ||
| 3 | / | 除 | 表達式/表達式 | 左到右 | 雙目運算符 |
| * | 乘 | 表達式*表達式 | |||
| % | 余數(取模) | 整型表達式%整型表達式 | |||
| 4 | + | 加 | 表達式+表達式 | 左到右 | 雙目運算符 |
| - | 減 | 表達式-表達式 | |||
| 5 | << | 左移 | 變量<<表達式 | 左到右 | 雙目運算符 |
| >> | 右移 | 變量>>表達式 | |||
| 6 | > | 大于 | 表達式>表達式 | 左到右 | 雙目運算符 |
| >= | 大于等于 | 表達式>=表達式 | |||
| < | 小于 | 表達式<表達式 | |||
| <= | 小于等于 | 表達式<=表達式 | |||
| 7 | == | 等于 | 表達式==表達式 | 左到右 | 雙目運算符 |
| != | 不等于 | 表達式!= 表達式 | |||
| 8 | & | 按位與 | 表達式&表達式 | 左到右 | 雙目運算符 |
| 9 | ^ | 按位異或 | 表達式^表達式 | 左到右 | 雙目運算符 |
| 10 | | | 按位或 | 表達式|表達式 | 左到右 | 雙目運算符 |
| 11 | && | 邏輯與 | 表達式&&表達式 | 左到右 | 雙目運算符 |
| 12 | || | 邏輯或 | 表達式||表達式 | 左到右 | 雙目運算符 |
| 13 | ?: | 條件運算符 | 表達式1? 表達式2: 表達式3 | 右到左 | 三目運算符 |
|
| |||||
| 14 | = | 賦值運算符 | 變量=表達式 | 右到左 | -- |
| /= | 除后賦值 | 變量/=表達式 | -- | ||
| *= | 乘后賦值 | 變量*=表達式 | -- | ||
| %= | 取模后賦值 | 變量%=表達式 | -- | ||
| += | 加后賦值 | 變量+=表達式 | -- | ||
| -= | 減后賦值 | 變量-=表達式 | -- | ||
| <<= | 左移后賦值 | 變量<<=表達式 | -- | ||
| >>= | 右移后賦值 | 變量>>=表達式 | -- | ||
| &= | 按位與后賦值 | 變量&=表達式 | -- | ||
| ^= | 按位異或后賦值 | 變量^=表達式 | -- | ||
| |= | 按位或后賦值 | 變量|=表達式 | -- | ||
| 15 | , | 逗號運算符 | 表達式,表達式,… | 左到右 | -- |
說明:
同一優先級的運算符,運算次序由結合方向所決定。
簡單記就是:! > 算術運算符 > 關系運算符 > && > || > 賦值運算符
一些容易出錯的優先級問題
| 優先級問題 | 表達式 | 被誤認為的結果 | 正確的結果 |
| .優先級高于*,-> | *p.f | (*p).f | *(p.f)對p取f偏移,作為指針, 然后進行解除操作 |
| ==和!=高于位操作 | (var&mask !=0) | (var&mask)!=0 | Var & (mask !=0) |
| ==和!=高于賦值 | C=getchar() !=EOF | (c=getchar())!=EOF | C=(getchaor()!=EOF) |
| 算術運算符高于位移運算法 | Mask << 4+3 | (Mask <<4)+3 | Mask << (4+3) |
| 逗號運算符在所有運算符中 優先級最低 | i=1,2 | I=(1,2) | (i=1),2 結果為i=2 |
三、擴展總結
1、賦值運算符:=
賦值運算符的動作是從右到左,且不能將一個值賦給一個常量。賦值,從C的角度來看,主要目的是對表達式來求值。
2、除法運算符:/
7/4 = 1;
7./4 = 1.75;
7./4. = 1.75;
11 / 5 = 2; 11 / -5 = -2; -11 / -5 = 2; -11 / 5 = -2;
在C中,整數除法結果的小數部分都被丟棄,沒有把整數除法運算的結果四舍五入到最近的整數。
當對整數與浮點數進行混合運算時,結果是浮點數。
3、取余運算符:%
取余運算符用于整數運算,該運算符計算出用它右邊的整數去除它左邊的整數得到的余數。
注意:不要對浮點數使用該運算符,那將是無效的。
負數取余規則:
如果第一個操作數為負數,那么得到的余數也為負數;如果第一個操作數為正數,那么得到的余數也為正數。
11 % 5 = 1;11 % -5 = 1;-11 % 5 = -1;-11 % -5 = -1;
4、自增和自減:++和--
后綴: a++; ?a--; ?使用a的值之后改變a;
前綴: ++a; ?--a; ?使用a的值之前改變a;
++a = 10;
++a 的結果是a值的拷貝,并不是變量本身,你無法向一個值進行賦值。賦值運算的左操作數必須是左值。
后續補充幾個例題。。。
四、布爾值
通過包含stdbool.h頭文件,可以用bool代替關鍵字_BOOL表示這種類型,并用標識符true和false代替1和0.
五、邏輯運算符
與(&&)、或(||)、非(!)
或(||)和與(&&)都具有短路特征,如果前一個邏輯表達式的結果可以決定整個表達式結果則計算機會忽略后面的邏輯表達式。
短路特性具體為: ??
表達式一 && 表達式二 如果表達式一為假,就不會進行表達式二的計算
表達式一 || 表達式二 如果表達式一為真,就不會進行表達式二的計算?
六、三目表達式
三目操作符可以在兩個不同的計算機規則中選擇一個
三目操作符的格式如下
布爾值 ? 公式一: 公式二 ? ? ? ? ? ?
//布爾值需要有定義的 ?ret=(num >=0) ? num : 0-num;
如果布爾值為真則采用公式一計算結果
如果布爾值為假則采用公式二計算結果
七、== 和 =不同
符號 = 作為賦值運算,符號 ==作為比較。注意,用于測試兩個表達式是否相等的操作符是==,如果無用了=操作符,雖然它也是合法的表達式,但其結果幾乎肯定和你的本意不一樣,它將執行賦值操作而不是比較操作。
比如下例,該語句本意似乎是要檢查 x 是否等于 y
if (x = y)break; 而實際上是將 y 的值賦給了 x,然后檢查該值是否為零。
還需注意:任何非零值都是真.比如,while (-1) == while (true)
再看下面的例子:
while (c = ' ' || c == 't' || c =='\n')c = getc (f); 本例中循環語句的本意是跳過文件中的空格符、制表符和換行符。由于程序員在比較字符 ' ' 和變量 c 時,誤將比較運算符 == 寫成了賦值運算符 = 。因為賦值運算符 = 的優先級要低于邏輯運算符 || ,因此實際上是將以下表達式的值賦給了 c
' ' || c == 't' || c == '\n' 因為 ' ' 不等于零 (' ' 的ASCII 碼為 32),那么無論變量 c 此前為何值,上述表達式求值的結果是 1,因此循環將一直進行下去直到整個文件結束。
另一方面,如果把賦值運算符寫成比較運算符,同樣會造成混淆:
在本例中,如果函數open執行成功,將返回 0 或者正數;而如果函數 open 執行失敗,將返回 -1.上面這段代碼的本意是將函數 open的返回值存儲在變量 filedesc 之中,然后通過比較變量 filedesc 是否小于 0 來檢查函數 open 是否執行成功。但是,此處的 == 本應是 = 。而按照上面代碼中的寫法,實際進行的操作是比較函數 open 的返回值與變量 filedesc ,然后檢查比較的結果是否小于 0.因此比價運算符 == 的結果只可能是 0 或 1.永遠不可能小于 0,所以函數 error ( )將沒有機會被調用。如果代碼執行,似乎一些正常,除了變量 filedesc 的值不再是函數 open 的返回值。
八、在表達式中使用無符號數
庫函數 strlen 的原型如下:
size_t strlen (char const *string);
注意:strlen 返回一個類型為 size_t 的值。這個類型是在頭文件 stddef.h 中定義的,它是一個無符號整數類型。在表達式中使用無符號數可能導致不可預料的結果。例如下面的表達式:
#include <stdio.h> #include <string.h> int main (void) {char ptr1[] = "beijing";char ptr2[] = "hello world";if (strlen (ptr1) - strlen (ptr2) >= 0){printf ("1111111111\n");}printf ("2222222222\n");return 0; } strlen (ptr1) - strlen (ptr2) 為無符號類型,得不到想要的結果,應該為:
#include <stdio.h> #include <string.h> int main (void) {char ptr1[] = "beijing";char ptr2[] = "hello world";if (strlen (ptr1) >= strlen (ptr2)){printf ("1111111111\n");}printf ("2222222222\n");return 0; }
總結
以上是生活随笔為你收集整理的C语言再学习 -- 运算符与表达式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下安装ElasticSearc
- 下一篇: Linux 中/etc/profile、