一文带你深入浅出C语言运算符、表达式和语句
目錄
🔋前言
🔋1.關(guān)于操作符(運算符)
🔋1.1操作符分類
🔋1.2算術(shù)操作符
🔋?1.2.1 加減乘除取模
🔋1.2.2指數(shù)運算?
🔋1.3移位操作符
🔋1.3.1?左移操作符
🔋1.3.2 右移操作符
🔋1.4位操作符
🔋1.4.1位操作符分類
🔋1.4.2實例+分析+總結(jié)
🔋(1)不能創(chuàng)建臨時變量(第三個變量),實現(xiàn)兩個數(shù)的交換
🔋(2)求一個整數(shù)存儲在內(nèi)存中的二進制中1的個數(shù)
🔋(3)兩個int(32位)整數(shù)m和n的二進制表達中,有多少個位(bit)不同
🔋(4)獲取一個整數(shù)二進制序列中所有的偶數(shù)位和奇數(shù)位
🔋1.5 賦值操作符
🔋1.5.1 單獨賦值?
🔋1.5.2復(fù)合賦值
🔋1.5.3 左值、右值與數(shù)據(jù)對象
🔋1.6 單目操作符
🔋1.6.1 sizeof()
🔋1.6.2 自增++或自減--
🔋1.7 關(guān)系操作符
🔋1.8 邏輯操作符
🔋1.8.1 邏輯與和按位與
🔋1.8.2 邏輯或和按位或
🔋1.8.3 典題剖析
🔋1.9 條件操作符
🔋1.10 逗號操作符
🔋1.11 [ ] 下標(biāo)引用操作符
🔋1.12 ( ) 函數(shù)調(diào)用操作符
🔋1.13 訪問一個結(jié)構(gòu)的成員的操作符
🔋2. 操作符的屬性
🔋2.1 求值因素
🔋2.2 例題分析?
🔋2.2.1 例1
🔋2.2.2 例2
🔋2.2.3 例3
🔋2.2.4 例4
🔋2.2.5 例5
🔋2.2.6 總結(jié)
🔋3.深入理解——副作用與序列點(了解一下)
🔋4.?自增運算符計算路徑問題
🔋表達式匹配——貪心法
🔋5. 表達式和語句的那些事兒
🔋5.1 表達式?
🔋5.2 語句
🔋5.3 關(guān)系
🔋敬請期待更好的作品吧~
🔋前言
? ? ? ? 本文內(nèi)容還挺豐富的,干貨滿滿了屬于是😉,這不寫著寫著就近萬字了嘛,主要分享一波C語言運算符、表達式和語句,重點在于運算符,這篇幅不可謂不“浩蕩”。
? ? ? ? 有一點C語言基礎(chǔ)就可以閱讀此文,希望我的分享心得能幫助到大家,同時由于作者水平實在有限,難免存在紕漏,大伙各取所需即可。
給你點贊,加油加油!
🔋1.關(guān)于操作符(運算符)
🔋1.1操作符分類
算術(shù)操作符
移位操作符
位操作符
賦值操作符
單目操作符
關(guān)系操作符
邏輯操作符
條件操作符
逗號表達式
下標(biāo)引用、函數(shù)調(diào)用和結(jié)構(gòu)成員
????????操作符(運算符)對象是操作數(shù)(運算對象),不只是單單的一個數(shù),實際上對象也可以是表達式的值。
🔋1.2算術(shù)操作符
🔋?1.2.1 加減乘除取模
1. 除了 % 操作符之外,其他的幾個操作符可以作用于整數(shù)和浮點數(shù)。
2. 對于 / 操作符如果兩個操作數(shù)都為整數(shù),執(zhí)行整數(shù)除法。而只要有浮點數(shù)執(zhí)行的就是浮點數(shù)除法。
????????浮點數(shù)除法的結(jié)果是浮點數(shù),而整數(shù)除法的結(jié)果是整數(shù)。
????????在C語言中,整數(shù)除法結(jié)果的小數(shù)部分被直接舍棄而非四舍五入,這一過程被稱為截斷。
3. % 操作符的兩個操作數(shù)必須為整數(shù)。返回的是整除之后的余數(shù)。
🔋1.2.2指數(shù)運算?
????????C沒有指數(shù)運算符,不過標(biāo)準(zhǔn)庫里提供了pow()函數(shù)用于指數(shù)運算:
函數(shù)原型:
double pow( double x, double y );
所屬頭文件:
<math.h>
功能:求取x的y次方的值,注意參數(shù)和返回值都是雙精度浮點型。
比如pow(2.0, 3.0)得到的值是8.0。
要注意的取值:
🔋1.3移位操作符
????????移位操作符的操作數(shù)只能是整數(shù),并且移位針對補碼操作。
🔋1.3.1?左移操作符
移位規(guī)則:
左邊拋棄、右邊補0,每移動一位相當(dāng)于*2。
🔋1.3.2 右移操作符
移位規(guī)則:
右移運算分兩種:
1. 邏輯移位
左邊用0填充,右邊丟棄
2. 算術(shù)移位
左邊用原該值的符號位填充,右邊丟棄
到底使用哪一種取決于編譯器。
VS使用算術(shù)右移。
警告? : 對于移位運算符,不要移動負數(shù)位(如a << -1),這個是標(biāo)準(zhǔn)未定義的。
🔋1.4位操作符
🔋1.4.1位操作符分類
&? ? ? ? ? //按位與,兩個操作數(shù)二進制位都為1才為1,否則為0
?|? ? ? ? ? //按位或,兩個操作數(shù)二進制位有1就為1,否則為0
?^? ? ? ? ?//按位異或 ,兩個操作數(shù)二進制位相同為0,相異為1
注:他們的操作數(shù)必須是整數(shù),而且操作對象是補碼!
?示例:
#include <stdio.h> int main() {int num1 = 1;int num2 = 2;num1 & num2;num1 | num2;num1 ^ num2;return 0; }分析:
🔋1.4.2實例+分析+總結(jié)
🔋(1)不能創(chuàng)建臨時變量(第三個變量),實現(xiàn)兩個數(shù)的交換
方法一:
int a = 3; int b = 5; a = a + b; b = a - b;//得到原來的a a = a - b;//得到原來的b方法二:
關(guān)于異或的小總結(jié):3^3 = 0, 3^0 = 3
????????也就是說一個數(shù)異或它本身結(jié)果為0,一個數(shù)異或0結(jié)果為它本身。
3^5^3 = 5,說明異或運算具有交換律,即3^5^3 = 3^3^5
所以可以這樣:
int a = 4; int b = 7; a = a ^ b; b = a ^ b;//也就是a^b^b=b^b^a=0^a=a得到原來的a a = a ^ b;//也就是a^b^a=a^a^b=0^b=b得到原來的b🔋(2)求一個整數(shù)存儲在內(nèi)存中的二進制中1的個數(shù)
方法一:
????????循環(huán)進行以下操作,直到n被縮減為0。
????????用該數(shù)據(jù)模2,檢測其是否能夠被2整除,若可以則該數(shù)據(jù)對應(yīng)二進制比特位的最低位一定是0,否則是1。如果是1給計數(shù)加1,如果n不等于0,繼續(xù)模2除2。(類比一下十進制)
????????十進制數(shù)除10去掉一個十進制位,二進制數(shù)除2去掉一個二進制位。
int count_one_bit(int n) {int count = 0;while(n){if(n%2==1)count++;n = n/2;}return count;}????????這樣測不了負數(shù),需要小小改動一下,把函數(shù)形參改為無符號整型unsigned int,當(dāng)傳入負數(shù)時,會自動轉(zhuǎn)換類型,看成一個很大的正數(shù)。
缺陷:進行了大量的取模以及除法運算,取模和除法運算的效率本來就比較低。
方法二:
????????一個數(shù)&1,若該數(shù)二進制第0位為1則結(jié)果為1,為0則結(jié)果為0
????????那我們可以這樣來設(shè)計,循環(huán)地把目標(biāo)整數(shù)的二進制位右移,讓每一個位上的數(shù)都&1,以此來判斷每一位是不是1,結(jié)果為1則計數(shù)+1。正數(shù)負數(shù)都可以。
????????實際上二進制位移位&1相當(dāng)于取出每一位來判斷。
int count = 0; int a = 65; for(int n = 0; n < sizeof(int); n++) {if((a >> n)&1)count++; }方法三:
或者這樣(更巧妙高效):
int tmp = 0; scanf("%d", &tmp); int count = 0; while(tmp) {tmp = tmp&(tmp-1);count++; }????????這樣做每次與運算都會讓目標(biāo)數(shù)的二進制位減少最低位的一個1。
????????為什么呢?
????????每次-1,即n-1,最靠后的1變?yōu)?,它后面的0全變?yōu)?,這時候從這一位開始往后的所有位都與n上對應(yīng)位相反,比如
?????????這時候n&(n-1)的話,dist前面的不變,后面的全變?yōu)?,得到的結(jié)果相較于n,最靠后的1就被消掉了。
?????????此種方式,數(shù)據(jù)的二進制比特位中有幾個1,循環(huán)就循環(huán)幾次,而且中間采用了位運算,處理起來比較高效。
拓展:判斷一個數(shù)是不是2的n次方
2 ^ 0 = 1 -> 1
2 ^ 1 = 2 -> 10
2 ^ 2 = 4? -> 100
……
????????我們發(fā)現(xiàn)2的n次方的二進制位上只有一個1,那么只要一個數(shù)的二進制位上去掉一個1后為0就說明它的二進制位上只有一個1,也就是2的n次方。
????????if(n & (n - 1) == 0)條件成立說明是2的n次方。
🔋(3)兩個int(32位)整數(shù)m和n的二進制表達中,有多少個位(bit)不同
思路一:
????????把兩個數(shù)各個二進制位都比較一下,用到了前面講過的移位&1法
int count_diff_bit(int m, int n) {int count = 0;int i = 0;for (i = 0; i < 32; i++){if (((m >> i) & 1) != ((n >> i) & 1)){count++;}}return count; }思路二:
????????直接用異或比較每一個二進制位,相異為1,再統(tǒng)計結(jié)果的二進制位有幾個1,用n&(n-1)法
int count_diff_bit(int m, int n) {int count = 0;//^ 異或操作符//相同為0,相異為1int ret = m ^ n;//統(tǒng)計一下ret中二進制位有幾個1while (ret){ret = ret & (ret - 1);count++;}return count; }🔋(4)獲取一個整數(shù)二進制序列中所有的偶數(shù)位和奇數(shù)位
????????其實是取出每一位的變式題,取出每一位就一位一位地右移,那取出奇數(shù)位或偶數(shù)位就兩位兩位地移,注意一下起點終點即可。這里從高位開始取出,如圖
int main() {int i = 0;int num = 0;scanf("%d", &num);//獲取奇數(shù)位的數(shù)字for (i = 30; i >= 0; i -= 2){printf("%d ", (num >> i) & 1);}printf("\n");//獲取偶數(shù)位的數(shù)字for (i = 31; i >= 1; i -= 2){printf("%d ", (num >> i) & 1);}return 0; }🔋1.5 賦值操作符
🔋1.5.1 單獨賦值?
????????賦值操作符就是將右邊操作對象的值賦給左邊的。
int a, x, y = 3; a = 8; x = a; a = x = y+1;//連續(xù)賦值 //不推薦,因為可讀性較差且不易調(diào)試。🔋1.5.2復(fù)合賦值
int x = 10; x = x + 10; x += 10;//復(fù)合賦值,將兩個表達式復(fù)合在一起 //其他運算符一樣的道理,這樣寫更加簡潔🔋1.5.3 左值、右值與數(shù)據(jù)對象
????????2002 = val;
????????這樣的語句在C語言中式無意義的,因為2002被稱為右值,只是字面常量,而常量不可以被賦值,常量本身就是它的值。
????????實際上,賦值運算符的左側(cè)必須引用一個存儲位置,最簡單的方法就是使用變量名(變量名標(biāo)識特定內(nèi)存空間),還可以使用指針(指向一個存儲位置)??偟膩碚f,C語言使用可修改的左值標(biāo)記那些可賦值的實體。
🔋1.6 單目操作符
!?????????? 邏輯反操作?????? 對一個值邏輯取反,根據(jù)C語言非零為真零為假,比如!8=0
-?????????? 負值???????????????? 對一個數(shù)取負數(shù)
+?????????? 正值??????? 實際上對一個數(shù)沒什么影響,比如a = -10; b = +a;算出來b的值還是-10
&?????????? 取地址???????? 取出一個變量的地址
sizeof????? 操作數(shù)的類型長度(以字節(jié)為單位)
~?????????? 對一個數(shù)的二進制按位取反? 比如~65,65補碼(省略前面的24位0):01000001,按位取反得到10111110,再轉(zhuǎn)成原碼得到11000010即-66
--????????? 前置、后置--
++????????? 前置、后置++
*?????????? 間接訪問操作符(解引用操作符)
(類型)?????? 強制類型轉(zhuǎn)換
🔋1.6.1 sizeof()
????????sizeof運算符以字節(jié)為單位返回運算對象的大小,返回值時無符號整數(shù)類型,運算對象可以是具體的數(shù)據(jù)對象(如變量名),也可以是類型(如int),如果是類型就必須用圓括號()括起來。
()里的操作數(shù)可以直接是一個變量,如:
int a = 10; printf("%d\n", sizeof(a));也可以是一個常數(shù),如:
?printf("%d\n", sizeof(10));會根據(jù)該常數(shù)對應(yīng)類型來計算內(nèi)存大小。
還可以是類型關(guān)鍵字,如:
?printf("%d\n", sizeof(int));????????但要注意的是,這里計算的肯定不可能是int的內(nèi)存,int只是規(guī)定的關(guān)鍵字,不是變量,壓根沒有存在內(nèi)存中,那這里是什么意思呢?就是計算int類型的變量在內(nèi)存所占用空間大小。
????????等會,不是說計算變量的內(nèi)存大小嗎,那為什么常數(shù)的也可以求?實際上是根據(jù)操作數(shù)的類型來計算該類型的變量所占內(nèi)存空間大小,你放入一個常數(shù)比如10到sizeof()里,它會根據(jù)10對應(yīng)數(shù)據(jù)類型int來計算int類型的變量大小。
?printf("%d\n", sizeof a);//這樣寫行不行?可以,結(jié)果不影響。
printf("%d\n", sizeof int);//這樣寫行不行?不行,會出問題。
看看sizeof()和數(shù)組以及函數(shù)傳參
#include <stdio.h> void test1(int arr[]) {printf("%d\n", sizeof(arr));//(2) }void test2(char ch[]) {printf("%d\n", sizeof(ch));//(4) }int main() {int arr[10] = {0};char ch[10] = {0};printf("%d\n", sizeof(arr));//(1)printf("%d\n", sizeof(ch));//(3)test1(arr);test2(ch);return 0; }問:
(1)、(2)兩個地方分別輸出多少?
(3)、(4)兩個地方分別輸出多少?
????????一個int四個字節(jié),數(shù)組arr帶有10個int就是40個字節(jié),一個char一個字節(jié),ch數(shù)組大小就是10個字節(jié)。
????????函數(shù)傳參傳數(shù)組名實際上傳的是數(shù)組首元素地址,而函數(shù)形參看起來是數(shù)組,但編譯器會把它看成對應(yīng)類型的指針,這時候再使用sizeof計算的就是指針大小,32位系統(tǒng)下指針大小4個字節(jié),64位系統(tǒng)下指針大小8個字節(jié)。
🔋1.6.2 自增++或自減--
關(guān)于++和--,要分前置和后置的情況
后置的話先使用后自增
前置的話先自增后使用
這里講的使用是作為表達式去使用
在數(shù)組中,比如arr[r++] = 10;,就是arr[r] = 10;r++;,而arr[++r] = 10;就是r++;arr[r] = 10;。
在函數(shù)調(diào)用中,比如add(r++);就是add(r);r++;,而add(++r);就是r++;add(r);。
🔋1.7 關(guān)系操作符
>
>=
<
<=
!=?? 用于測試“不相等”
==????? 用于測試“相等”
關(guān)系表達式的值為1或0,當(dāng)關(guān)系滿足時,表達式值為真即1,關(guān)系不滿足時,表達式值為假即0。
🔋1.8 邏輯操作符
&&???? 邏輯與? ?兩邊操作數(shù)(運算對象)都為真表達式值才為真
||????????? 邏輯或? ?兩邊操作數(shù)(運算對象)有一個為真表達式值即為真
注意區(qū)分:
🔋1.8.1 邏輯與和按位與
邏輯與:左右操作數(shù)都為真(非0),表達式的值才為真(為1),否則為假(為0)
按位與:左右操作數(shù)的二進制位補碼一一進行“全為1則為1,否則為0”的操作
🔋1.8.2 邏輯或和按位或
邏輯或:左右操作數(shù)有一個為真(非0),表達式的值就為真(為1),否則為假(為0)
按位或:左右操作數(shù)的二進制位補碼一一進行“有一個為1則為1,否則為0”的操作
????????其實感覺比較相像,可以這么看,邏輯與和邏輯或是對于數(shù)值本身進行邏輯真假的判斷,而按位與和按位或是對于數(shù)值的二進制位進行邏輯真假的判斷,邏輯真假即非0與0。
🔋1.8.3 典題剖析
?程序輸出的結(jié)果是什么?
#include <stdio.h>int main() {int i = 0;int a=0, b=2, c =3, d=4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;}其實上面故意少提了一點:
????????對于邏輯與和邏輯或,都是左結(jié)合性,邏輯與表達式要是第一個表達式值為0則整個表達式的值就為0,后面的表達式可以不用計算,相似地,邏輯或表達式要是第一個表達式值為1則整個表達式的值就為1,后面的表達式可以不用計算。
????????再回到上面的例題,注意a++是后置++,所以(a++)&&…相當(dāng)于a&&…再a++,而a的值為0,為假,則不管++b是否為真,a++&&++b都為假,則無論d++是否為真,整個邏輯與表達式的值都為假,后面的++b和d++表達式就不會再計算了,所以只有a自增了1,程序輸出結(jié)果就為1 2 3 4。
如果稍微修改一下這段代碼結(jié)果會怎樣呢?看看:
程序輸出的結(jié)果是什么?
#include <stdio.h>int main() {int i = 0;int a=1, b=2, c =3, d=4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0; }答案是2 3 3 5
????????因為這次a為非0,后面的++b和d++都被執(zhí)行了。
那我們再來看看邏輯或表達式:
程序輸出的結(jié)果是什么?
#include <stdio.h>int main() {int i = 0;int a=1, b=2, c =3, d=4;i = a++ || ++b || d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0; }????????注意a為非0,不管++b是否為真,a++||++b都為真,則無論d++是否為真,整個邏輯或表達式都為真,所以后面的表達式不會再計算,只有a自增了1,輸出結(jié)果就是2 2 3 5。
我們再改改看看:
程序輸出的結(jié)果是什么?
#include <stdio.h>int main() {int i = 0;int a=0, b=2, c =3, d=4;i = a++||++b || d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0; }????????注意a為0,而++b非0,則a++||++b為真,所以無論d++是否為真,整個邏輯或表達式都為真,d++也就不會計算了,故輸出結(jié)果為1 3 3 4。
🔋1.9 條件操作符
exp1 ? exp2 : exp3????????如果exp1為真,則整個表達式的值為exp2的值,而exp3不執(zhí)行;
????????如果exp1為假,則整個表達式的值為exp3的值,而exp2不執(zhí)行。
????????要注意的就是不宜把表達式搞得太復(fù)雜,可以換成用if…else語句。
🔋1.10 逗號操作符
exp1, exp2, exp3, …expN????????逗號表達式,從左向右依次執(zhí)行。整個表達式的結(jié)果是最后一個表達式的結(jié)果。
????????不過由于逗號的運算優(yōu)先級是最低的,要使用逗號表達式要使用圓括號。
//代碼1 int a = 1; int b = 2; int c = (a>b, a=b+10, a, b=a+1);//代碼2 if (a =b + 1, c=a / 2, d > 0)//判斷條件也可以是逗號表達式????????不用圓括號括起來的話,很容易出錯!
比如:
????????int d = ++a, b++, c++, a++;實際上是先執(zhí)行賦值表達式(d = ++a),而不是將后面的逗號表達式都執(zhí)行完取最后一個的值,那是有圓括號的情況,因為逗號運算符優(yōu)先級最低啊。
🔋1.11 [ ] 下標(biāo)引用操作符
操作數(shù):一個數(shù)組名 + 一個索引值
比如:
????????arr[9]的操作數(shù)是arr和9
騷操作:9[arr] 等價于 arr[9]
我直呼好家伙 Σ(っ °Д °;)っ
為什么可以這樣呢?
????????究其本質(zhì),arr[9]等價于*(arr + 9),實際上[ ]就是*( )操作數(shù)放在圓括號里相加,所以9[arr] 等價于 *(9+arr),而加法具有交換律,這兩種表示方式的意義都一樣。
🔋1.12 ( ) 函數(shù)調(diào)用操作符
????????接受一個或者多個操作數(shù):第一個操作數(shù)是函數(shù)名,剩余的操作數(shù)就是傳遞給函數(shù)的參數(shù)。
#include <stdio.h>void test1(){printf("hehe\n");}void test2(const char *str){printf("%s\n", str);}int main(){test1();???????????test2("hello bit.");return 0;}????????所以說函數(shù)調(diào)用其實也是表達式語句,操作符為()。
🔋1.13 訪問一個結(jié)構(gòu)的成員的操作符
. 結(jié)構(gòu)體.成員名
-> 結(jié)構(gòu)體指針->成員名
#include <stdio.h>struct Stu {char name[10];int age;char sex[5];double score; };int main() {struct Stu stu;struct Stu* pStu = &stu;stu.age = 20;//直接訪問結(jié)構(gòu)成員pStu->age = 20;//間接訪問結(jié)構(gòu)成員return 0;}🔋2. 操作符的屬性
🔋2.1 求值因素
復(fù)雜表達式的求值有三個影響的因素。
1. 操作符的優(yōu)先級
2. 操作符的結(jié)合性
3. 是否控制求值順序。
????????兩個相鄰的操作符先執(zhí)行哪個?取決于他們的優(yōu)先級。當(dāng)運算符共享一個運算對象時,優(yōu)先級決定了求值順序。
????????如果兩者的優(yōu)先級相同,則取決于他們的結(jié)合性。
????????但是大部分運算符都沒有規(guī)定同優(yōu)先級下的求值順序。
????????實際上只有邏輯與,邏輯或,條件操作符和逗號操作符控制求值順序,其他操作符沒有控制求值順序,這就有可能產(chǎn)生一些問題代碼。
🔋2.2 例題分析?
🔋2.2.1 例1
a*b + c*d + e*f其實計算路徑不唯一,有兩種順序:
????????代碼1在計算的時候,由于*比+的優(yōu)先級高,只能保證,*的計算是比+早,但是優(yōu)先級并不能決定第三個*比第一個+早執(zhí)行,因為算術(shù)操作符并沒有控制求值順序,所以就可能會有多條計算路徑。
????????結(jié)合性只適用于共享同一運算對象的運算符,例如,在表達式12/3*2中,/和*優(yōu)先級相同,共享運算對象3,因此從左往右的結(jié)合性起作用,表達式簡化為4*2即8,但是如果從右往左計算的話會得到12/6即2,這種情況下計算的先后順序會影響最終結(jié)果。
????????對于如y = 6 * 12 + 5 * 20;這樣的語句,兩個*運算符并沒有共享同一個運算對象,因此左結(jié)合性不起作用,*優(yōu)先級比+高,肯定先算乘法運算對吧,那到底先進行哪一個乘法呢?C標(biāo)準(zhǔn)未定義,實際上得根據(jù)不同硬件或編譯環(huán)境來確定,只不過該例中先算哪個對結(jié)果沒有影響而已。那萬一有影響呢?
🔋2.2.2 例2
int c = 2; int d = c + --c;????????自增自減與算術(shù)操作符復(fù)合的時候很容易產(chǎn)生歧義,因為求值順序不是唯一的,實際上這類是C標(biāo)準(zhǔn)未定義行為,結(jié)果有多種可能值,與編譯器有關(guān)(相當(dāng)于甩鍋給編譯器)。
編譯器:我太難了……?_?
????????+左邊c的值是什么時候確定的呢?是--c后嗎?還是在--c之前呢?因為沒有統(tǒng)一的標(biāo)準(zhǔn),所以都有可能。
????????若是--c后確定左邊c的值,則1+1=2,若是--c前確定左邊c的值,則2+1=3。
????????操作符的優(yōu)先級只能決定自減--的運算在+的運算的前面,但是我們并沒有辦法得知,+操作符的左操作數(shù)的獲取在右操作數(shù)之前還是之后求值,所以結(jié)果是不可預(yù)測的,是有歧義的。
🔋2.2.3 例3
int main() {int i = 10;i = i-- - --i * ( i = -3 ) * i++ + ++i;printf("i = %d\n", i);return 0; }????????這個就純屬惡心人的,估計能夠到世界亂碼大賽的門檻了(笑🤣)。
????????有前輩測試過,發(fā)現(xiàn)在各個編譯器上的值都大不相同。
?????????這段代碼堪稱究極反面教材,提醒我們不要自作聰明玩一些花里胡哨的東西(惱),沒有什么實際意義,本身就存在歧義,可讀性就更別提了,無論是學(xué)習(xí)還是工作都要極力避免寫出過于復(fù)雜的表達式。
🔋2.2.4 例4
int fun() {static int count = 1;return ++count; }int main() {int answer;answer = fun() - fun() * fun();printf( "%d\n", answer);//輸出多少?return 0; }????????在VS下是先一個一個地進入fun()后再進行算術(shù)運算的,所以變成了2 - 3 * 4 = -12 -10。
????????還可以這樣,先分別調(diào)用后面兩個fun()進行乘法運算后再調(diào)用前面的fun()再進行減法運算,就變成了4 - 2 * 3=-6 -2。
????????我們只能通過操作符的優(yōu)先級得知:先算乘法,再算減法。
????????然而函數(shù)的調(diào)用先后順序無法通過操作符的優(yōu)先級確定
🔋2.2.5 例5
int main() {int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret);printf("%d\n", i);return 0; }????????沒錯又是自增的鍋😅。
????????可以先執(zhí)行前兩個++i再執(zhí)行(++i)+(++i)得到2+3,再執(zhí)行最后一個++i后執(zhí)行2+3+(++i)得到9
????????還可以先把三個自增表達式執(zhí)行完后再執(zhí)行加法運算,即4+4+4=12。
????????這段代碼中的第一個 + 在執(zhí)行的時候,第三個++是否執(zhí)行,這個是不確定的,因為依靠操作符的優(yōu)先級和結(jié)合性是無法決定第一個 + 和第 三個前置 ++ 的先后順序。
🔋2.2.6 總結(jié)
????????我們寫出的表達式如果不能通過操作符的屬性確定唯一的計算路徑,那這個表達式就是存在問題的。
????????為什么會有問題,因為計算順序?qū)Ρ磉_式的值有影響,一般都和++或--脫不了干系,因為自增自減會改變變量的值,導(dǎo)致不清楚什么時候確認變量的值。
????????所以我們盡量不要寫太復(fù)雜的表達式,尤其是帶有自增或自減操作符的。
????????遵循以下規(guī)則,不要濫用自增或自減運算符
? ? ? ? 1.如果一個變量出現(xiàn)在一個函數(shù)的多個參數(shù)中,不要對其使用自增或自減運算符。
? ? ? ? 2.如果一個變量多次出現(xiàn)在一個表達式中,不要對其使用自增或自減運算符。
🔋3.深入理解——副作用與序列點(了解一下)
🔋4.?自增運算符計算路徑問題
????????為什么計算路徑不唯一?編譯器識別表達式,是同時加載至寄存器還是分批加載完全不確定而導(dǎo)致的。
????????為什么不確定呢?因為對此沒有明確的規(guī)定,這也涉及到序列點的問題。
????????比如對于int b = (++i)+(++i)+(++i);,(++i)并不是一個完整表達式,而是作為整個表達式的子表達式,整個賦值表達式語句才是一個完整表達式,分號標(biāo)記了序列點,也就是在執(zhí)行下一條語句之前肯定會完成表達式中的三次自增,只是以怎樣的順序進行并沒有被明確規(guī)定,到底是先把三次自增都進行完(同時加載至寄存器)再進行加法運算,還是先把前兩個自增完(分批加載)后相加,再進行后面的自增,然后幾個數(shù)相加,或者其他順序都有可能。
????????具體實現(xiàn)細節(jié)因為標(biāo)準(zhǔn)未定義,相當(dāng)于一股腦甩給了編譯器,讓它自己決定(編譯器:我**&%¥#@),不同編譯器下的過程可謂五花八門了。
🔋表達式匹配——貪心法
?????????比如a+++10,在讀到a+時,由于要讀盡可能多的字符,所以會繼續(xù)往后讀也就是a++,此時若再往后讀就無法組成有意義的符號了,所以a++是一個整體,原式相當(dāng)于a++ + 10。
????????這個規(guī)則不保證表達式正確,只是“貪多”。
🔋5. 表達式和語句的那些事兒
🔋5.1 表達式?
????????表達式由運算符和運算對象組成。最簡單的表達式是一個單獨的運算對象。
下面是一些表達式:
4
-6
a*(b + c / d) / 20
q > 3
p = 5 * 2
????????運算對象可以是常量、變量或二者的結(jié)合。
????????每一個表達式都有對應(yīng)值
????????賦值表達式的值與運算符左邊的值相同,也就是賦給的是什么表達式的值就是什么,如p = 6*7的值就是42。
????????關(guān)系表達式(如q>3)的值不是0就是1,關(guān)系成立就是1,不成立就是0。
🔋5.2 語句
????????語句是C程序的基本構(gòu)建塊,一條語句相當(dāng)于一條完整的計算機指令。在C語言中,大部分語句都以分號結(jié)尾。
????????比如legs = 4沒有分號就只是個表達式。
????????最簡單的語句是空語句:
?????????;(只有一個分號)
????????C把末尾加上一個分號的表達式都看作是一條語句(表達式語句)。
????????有些語句實際上不起作用(如3+4;),算不上真正的語句,語句應(yīng)該是可以改變值或調(diào)用函數(shù)的。
????????不是所有的指令都是語句,如:
????????x = 6 + (y = 5);
????????y = 5是一條完整的指令,但它只是語句的一部分。
????????復(fù)合語句使用花括號括起來的一條或多條語句,也被稱為塊。
🔋5.3 關(guān)系
????????C的基本程序步驟由語句組成,而大多數(shù)語句都由表達式構(gòu)成。
🔋敬請期待更好的作品吧~
感謝觀看,你的支持就是對我最大的鼓勵,閣下何不成人之美,點贊收藏關(guān)注走一波~
總結(jié)
以上是生活随笔為你收集整理的一文带你深入浅出C语言运算符、表达式和语句的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 而立之年——那些从一线城市退到二三线的程
- 下一篇: 3D打印成型技术:看得见摸得着的真实