本篇博客全站熱榜最高排名:2
一、算術(shù)操作符
因?yàn)镸arkDown的語(yǔ)法,所以用圖片的形式顯示
- 對(duì)于算術(shù)操作符而言有上面這五種,對(duì)于前面的【+】、【-】、【*】來(lái)說(shuō)操作數(shù)可以是整數(shù)或者浮點(diǎn)數(shù)
- 對(duì)于【/】來(lái)說(shuō),叫做整除,結(jié)果就是我們?cè)跀?shù)學(xué)中說(shuō)到的商。若是兩邊都是整數(shù),則執(zhí)行執(zhí)行
整數(shù)除法
;只有當(dāng)兩個(gè)操作符數(shù)中有一個(gè)數(shù)是小數(shù)的時(shí)候可以算作是浮點(diǎn)數(shù)除法
- 對(duì)于【%】來(lái)說(shuō),叫做取余,結(jié)果就是我們?cè)跀?shù)學(xué)中說(shuō)到的余數(shù)。它的兩個(gè)操作數(shù)
必須是整數(shù)
我們到VS中來(lái)驗(yàn)證一下
再來(lái)看看取余操作符
二、移位操作符
接下去我們來(lái)說(shuō)說(shuō)移位操作符,它是一個(gè)為操作符,操作的是二進(jìn)制數(shù),因?yàn)楸容^重要,所以單獨(dú)拿出來(lái)做講解
1、前言小知識(shí) —— 原碼、反碼、補(bǔ)碼
在講述左移、右移操作符之前,要給讀者先介紹一下原碼、反碼、補(bǔ)碼,以便更好地理解二進(jìn)制位的概念
- 對(duì)于一個(gè)數(shù)值來(lái)說(shuō),可以由不同的進(jìn)制來(lái)表示,在計(jì)算機(jī)內(nèi)部,有【二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制】
- 假設(shè)一個(gè)十進(jìn)制數(shù)
15
,它的八進(jìn)制表示形式即為17
,十六進(jìn)制為F
,二級(jí)制為1111
。這個(gè)要涉及到每一位上的權(quán)重概念,這里的內(nèi)容很多不好敘述,可以看看——> 進(jìn)制轉(zhuǎn)換(二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制) - 對(duì)于一個(gè)整數(shù)來(lái)說(shuō),在內(nèi)存中的二級(jí)制表示形式有【原碼】【反碼】【補(bǔ)碼】三種,它們寫成二進(jìn)制位的形式也就是32位二進(jìn)制,例如整數(shù)4,它的原碼即為
0 0000000000000000000000000000100
??梢钥吹轿覍⒆罡呶缓秃竺娴?1位做了一個(gè)分隔,因?yàn)閷?duì)于32個(gè)二進(jìn)制位來(lái)說(shuō),最高位叫做符號(hào)位
- 符號(hào)位是0,表示正整數(shù)
- 符號(hào)位是1,表示負(fù)整數(shù)
- 而對(duì)于一個(gè)正數(shù)來(lái)說(shuō),它的原碼、反碼、補(bǔ)碼都是相同的;對(duì)于一個(gè)負(fù)數(shù)來(lái)說(shuō),它的
反碼等于原碼除符號(hào)位外其余按位取反,補(bǔ)碼等于反碼 + 1
看到這里的讀者可能會(huì)很好奇,不是要講移位操作符嗎,為什么要先講這些呢?
- 在這里你需要記住這一句話,它在后面的位操作符中同樣適用
在計(jì)算機(jī)中都是使用二進(jìn)制數(shù)的補(bǔ)碼進(jìn)行運(yùn)算的,但是在計(jì)算完之后輸出的結(jié)果都要再轉(zhuǎn)化為原碼的形式
2、左移操作符【擴(kuò)大2倍】
有了上面的知識(shí)做鋪墊,接下去就讓我們真正地進(jìn)入移位操作符的學(xué)習(xí)??
??【移位規(guī)則】:左邊拋棄、右邊補(bǔ)0
- 首先來(lái)看到的是要計(jì)算一個(gè)數(shù)字4左移1位后的,將其運(yùn)算后的結(jié)果放到b里面去。那我們知道,在內(nèi)存中進(jìn)行計(jì)算都是使用補(bǔ)碼的形式,因?yàn)橐来螌懗?的原、反、補(bǔ)碼,因?yàn)樗钦麛?shù),所以均是相同的
int main(void)
{
int a = 4;
//0 0000000000000000000000000000100 - 4的原碼
//0 0000000000000000000000000000100 - 4的反碼
//0 0000000000000000000000000000100 - 4的補(bǔ)碼
int b = a << 1; //把a(bǔ)向左移動(dòng)一位
printf("a = %d, b = %d\n", a, b);
return 0;
}
- 通過(guò)圖示就可以看出,將一個(gè)二進(jìn)制數(shù)進(jìn)行左移運(yùn)算,要執(zhí)行的規(guī)則是
左邊丟棄,右邊補(bǔ)0
,其實(shí)這起到的效果就是將原來(lái)的數(shù)乘上一個(gè)2倍,因?yàn)閷?duì)于每一個(gè)相鄰的二進(jìn)制數(shù)說(shuō),均是一個(gè)2倍的關(guān)系,因?yàn)槠溥\(yùn)行出來(lái)的結(jié)果即為4 * 2 = 8
,也就是b = 8
- 可以看到,這個(gè)原來(lái)的數(shù)字a是不會(huì)發(fā)生變化的,只是對(duì)其進(jìn)行一個(gè)運(yùn)算,將其運(yùn)算之后的結(jié)果放到b中去而已
接著我們?cè)賮?lái)看看負(fù)數(shù)的情況
- 對(duì)于正數(shù)來(lái)說(shuō),其實(shí)看不出其在內(nèi)部中的數(shù)值變換,因?yàn)樵⒎?、補(bǔ)碼均是相同的;但是對(duì)于負(fù)數(shù)來(lái)說(shuō)可不一樣了,可以看到下面是對(duì)
-4
進(jìn)行一個(gè)移位的操作,寫出其補(bǔ)碼之后就開(kāi)始了移位操作
int main(void)
{
int a = -4;
//1 0000000000000000000000000000100 - -4的原碼
//1 1111111111111111111111111111011 - -4的反碼
//1 1111111111111111111111111111100 - -4的補(bǔ)碼
int b = a << 1; //把a(bǔ)向左移動(dòng)一位
//1 1111111111111111111111111111000 - -4移位后的的補(bǔ)碼
//1 1111111111111111111111111110111 - -4移位后的的反碼
//1 0000000000000000000000000001000 - -4移位后的的原碼
printf("a = %d, b = %d\n", a, b);
return 0;
}
- 但是在移位之后我們還要將二進(jìn)制數(shù)轉(zhuǎn)換為
原碼的形式
,上面說(shuō)到過(guò),雖然在計(jì)算機(jī)內(nèi)部是采用補(bǔ)碼的形式進(jìn)行計(jì)算的,但是輸出打印在屏幕是是采用原碼的形式,所以還是要按照負(fù)數(shù)原、反、補(bǔ)碼的規(guī)則進(jìn)行一個(gè)轉(zhuǎn)換,最后得出的結(jié)果再轉(zhuǎn)換為十進(jìn)制便是8
3、右移操作符【縮小2倍】
講完左移操作符,接下去我們?cè)賮?lái)講講右移操作符
>>
??【移位規(guī)則】:
① 邏輯移位
??左邊用0填充,右邊丟棄
② 算術(shù)移位
??左邊用原該值的符號(hào)位填充,右邊丟棄
- 可以看到,對(duì)于右移操作符,和左移不同的是它的運(yùn)算規(guī)則比較復(fù)雜,因?yàn)樵诓煌幾g器下對(duì)于移位后的符號(hào)位填充是有所不同的,但是在VS下采用的是第二種
算術(shù)移位
,所以我以此作為講解
- 因?yàn)樯厦嬉呀?jīng)給出過(guò)代碼了,因此這里不做展示,從運(yùn)行結(jié)果可以看出對(duì)于右移運(yùn)算來(lái)說(shuō)就是一個(gè)縮小的情況,但具體是如何計(jì)算的呢,我們通過(guò)畫圖來(lái)看看
然后我們?cè)賮?lái)看看負(fù)數(shù)的情況
- 對(duì)于負(fù)數(shù)來(lái)說(shuō)還是一樣,在計(jì)算機(jī)內(nèi)部會(huì)將其轉(zhuǎn)化為補(bǔ)碼的形式進(jìn)行運(yùn)算,然后再內(nèi)部計(jì)算完畢后還要將其轉(zhuǎn)換為原碼的形式
- 下面是它在內(nèi)存中轉(zhuǎn)換的情況
【注意?】
??對(duì)于移位運(yùn)算符,不要移動(dòng)負(fù)數(shù)位,這個(gè)是標(biāo)準(zhǔn)未定義的
int main(void)
{
int a = 5;
int b = a << -1; //error
return 0;
}
對(duì)于這個(gè)移位操作符來(lái)說(shuō),你可能會(huì)決定它不是很常用,所以也沒(méi)必要很認(rèn)真地學(xué)習(xí),其實(shí)這就錯(cuò)了,那是因?yàn)槟憬佑|得還不夠多,其實(shí)在某些特定的場(chǎng)合下,它可以起到非常之大的作用,后面有一道綜合例題我會(huì)進(jìn)行講解?
三、位操作符
好,接下去我們來(lái)講講位操作符,這也是很多同學(xué)長(zhǎng)期以來(lái)沒(méi)有搞懂的一塊
1、按位與【&】
??【規(guī)則】:全1為1,有0為0
- 一樣,對(duì)于位運(yùn)算來(lái)說(shuō),在計(jì)算機(jī)中進(jìn)行運(yùn)算的是一個(gè)數(shù)的補(bǔ)碼形式,然后打印在屏幕上是原碼的形式
//按位與 - 全1為1,有0為0
int main(void)
{
int a = 3;
int b = -5;
int c = a & b;
printf("c = %d\n", c);
//00000000000000000000000000000011 - 3的原碼、反碼、補(bǔ)碼
//10000000000000000000000000000101 - -5的原碼
//11111111111111111111111111111010 - -5的反碼
//11111111111111111111111111111011 - -5的補(bǔ)碼
//00000000000000000000000000000011
//11111111111111111111111111111011
//00000000000000000000000000000011 - 3【補(bǔ)碼即為原碼】
return 0;
}
- 根據(jù)
按位與
的運(yùn)算規(guī)則,我們就可以得出最后的結(jié)果為3
2、按位或【|】
??【規(guī)則】:有1為1,全0為0
//按位或 - 有1為1,全0為0
int main(void)
{
int a = 3;
int b = -5;
int c = a | b;
printf("c = %d\n", c);
//00000000000000000000000000000011 - 3的原碼、反碼、補(bǔ)碼
//10000000000000000000000000000101 - -5的原碼
//11111111111111111111111111111010 - -5的反碼
//11111111111111111111111111111011 - -5的補(bǔ)碼
//00000000000000000000000000000011
//11111111111111111111111111111011
// --------------------------------------
//11111111111111111111111111111011 |
//11111111111111111111111111111010 |
//10000000000000000000000000000101 | - 5
return 0;
}
- 根據(jù)
按位或
的運(yùn)算規(guī)則,我們就可以得出最后的結(jié)果為-5
3、按位異或【^】
??【規(guī)則】:相同為0,相異為1
//按位異或 - 相同為0,相異為1
int main(void)
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("c = %d\n", c);
//00000000000000000000000000000011 - 3的原碼、反碼、補(bǔ)碼
//10000000000000000000000000000101 - -5的原碼
//11111111111111111111111111111010 - -5的反碼
//11111111111111111111111111111011 - -5的補(bǔ)碼
//00000000000000000000000000000011
//11111111111111111111111111111011
// --------------------------------------
//11111111111111111111111111111000
//11111111111111111111111111110111
//10000000000000000000000000001000 -> -8
return 0;
}
- 根據(jù)
按位異或
的運(yùn)算規(guī)則,我們就可以得出最后的結(jié)果為-8
- 對(duì)于異或有兩個(gè)很重要的結(jié)論要記,在使用異或操作符的時(shí)候基本都是用到它們
??兩個(gè)相同的數(shù)異或?yàn)?【a ^ a = 0
】
??任何數(shù)和0異或均為那個(gè)數(shù)本身【a ^ 0 = a
】
4、按位取反【~】
??【規(guī)則】:1變0, 0變1
- 對(duì)于按位取反來(lái)說(shuō)不考慮符號(hào)位,也就是將所有的1變成0,所有的0變成1即可。但是不要忘了那句口訣,對(duì)于負(fù)數(shù)的補(bǔ)碼來(lái)說(shuō)是需要再進(jìn)行一個(gè)轉(zhuǎn)換的
int main(void)
{
int a = 0;
int b = ~a;
printf("b = %d\n", b);
//00000000000000000000000000000000
//11111111111111111111111111111111 按位取反【補(bǔ)碼】
//11111111111111111111111111111110 【反碼】
//10000000000000000000000000000001 -> -1【原碼】
return 0;
}
- 那么0按位取反之后就變成了
-1
??兩道很變態(tài)的面試題??
介紹完了所有的位操作符符,接下去我們馬上來(lái)檢驗(yàn)一下學(xué)習(xí)的成果, 下面是兩道歷年面試中的考題,比較復(fù)雜而且也很多的位運(yùn)算在里面,因?yàn)槟贸隼沧鲋v解
① 兩數(shù)交換
首先第一道比較容易一些,對(duì)于兩個(gè)數(shù)的交換相信大家是非常熟悉的
- 我們可以使用第三方臨時(shí)變量做一個(gè)存放;若是放到個(gè)單獨(dú)的函數(shù)中進(jìn)行交換的操作,那么就要進(jìn)行對(duì)應(yīng)的傳址操作,如果是在C++中的話還可以使用引用操作符
&
- 但是在本文中,我要使用位運(yùn)算來(lái)進(jìn)行實(shí)現(xiàn),主要是使用到
異或位運(yùn)算^
在這之前,我們通過(guò)加減的方式來(lái)試著交換一下這兩個(gè)數(shù)
int main(void)
{
int a = 3;
int b = 5;
printf("a = %d, b = %d\n", a, b);
a = a + b;
b = a - b; //a + b - b
a = a - b; //a + b - a
printf("a = %d, b = %d\n", a, b);
return 0;
}
- 可以看到,變量中的兩個(gè)數(shù)發(fā)生了交換,我們來(lái)分析一下它們是如何發(fā)生交換的
- 首先執(zhí)行的是
a = a + b
,也就是把a(bǔ) + b的值放到a里面去,接著第二句是b = a - b
,因?yàn)榻?jīng)過(guò)上面一句的運(yùn)算,a里面存放的已經(jīng)是a + b的和了,那么再減去b的話也就是a,因?yàn)槲覀円粨Q兩個(gè)值,所以就是要將a的值放到變量b里面去,所以拿b來(lái)接受 - 那此時(shí)b里面存放的就是a的值了,但是a里面存放的還是a + b的值,所以第三句
a = a - b
計(jì)算出來(lái)的結(jié)果就是b的值,將其存入變量a中 - 那么此時(shí)打印出來(lái)a與b值便是交換之后的值
- 但其實(shí)對(duì)于上面這種方法是有缺陷的,若是a與b是兩個(gè)很大的數(shù)時(shí),就會(huì)出現(xiàn)數(shù)據(jù)溢出的情況,所以在下面我要使用
異或^
的方法來(lái)運(yùn)算 - 可以看到使用異或來(lái)進(jìn)行運(yùn)算的時(shí)候總體的思路還是不變的,將加減運(yùn)算符改成異或運(yùn)算符之后,就需要使用到我們上面講到過(guò)的異或拓展規(guī)則??
- 首先第一句
a = a ^ b
將a和b異或后的結(jié)果暫存到a里面去,然后再去異或b的話就相當(dāng)于是a ^ b ^ b
,根據(jù)規(guī)則便可以得出結(jié)果為a,將其放入b中 - 然后第三句
a = a ^ b
,就相當(dāng)于是a ^ b ^ a
,那么結(jié)果就是b,將其放入a中
int main(void)
{
int a = 3;
int b = 5;
printf("a = %d, b = %d\n", a, b);
a = a ^ b;
b = a ^ b; //a ^ b ^ b = a ^ 0 = a
a = a ^ b; //a ^ b ^ a = b ^ 0 = b
printf("a = %d, b = %d\n", a, b);
return 0;
}
- 最后打印出來(lái)就是交換之后的結(jié)果
通過(guò)這道題,相信你對(duì)異或的運(yùn)算一定可以掌握得很好,平常在進(jìn)行OJ刷題的時(shí)候,其實(shí)也是可以使用到的,但這前提是你要會(huì)靈活使用
② 進(jìn)制定位
接下去第二道是比較困難的,因?yàn)榻Y(jié)合了我們上面所學(xué)習(xí)的所有位操作符
【需求1】:將變量a的第n位置為1
- 看到這個(gè)需求你可能有點(diǎn)懵,我來(lái)解釋一下,這個(gè)第n位值的不是十進(jìn)制位,而是二進(jìn)制位,也就是將一個(gè)變量在內(nèi)存中的32位中的某一位置為1,但是又不能改變其他位置的數(shù)字,那有同學(xué)瞬間感覺(jué)有些燒腦了??
不過(guò)沒(méi)關(guān)系,我們慢慢來(lái)分析一下??
① 思路分析
- 首先不去考慮第n位置,先去考慮某個(gè)特定的位置,假設(shè)我現(xiàn)在有個(gè)變量a為10,那么它的二進(jìn)制位即為
00000000000000000000000000001010
,那么此時(shí)我們先將其第三位置為1,要怎么去實(shí)現(xiàn)呢? - 首先就是要使用到我們上面學(xué)習(xí)過(guò)的
按位或 |
運(yùn)算,將第三位按位或上一個(gè)1,那么這一位就變成了1,但是呢又不想讓其他位置發(fā)生變化,那此時(shí)就讓其他位按位或上一個(gè)0
即可,若是那個(gè)位上為0,那么就是0,若是那個(gè)位上為1,那也為1,那也就是00000000000000000000000000000100
,但是要如何去獲取到這個(gè)二進(jìn)制數(shù)呢,此時(shí)就又需要使用到我們上面講到過(guò)的一個(gè)操作符叫做左移<<
那也就是將一個(gè)數(shù)擴(kuò)大兩倍,這里我們對(duì)1進(jìn)行操作,擴(kuò)大2倍就是2,再擴(kuò)大兩倍就是我們想要的4,即1 << 2
- 具體的表達(dá)式應(yīng)該為:
[a = a | 1 << 2]
- 來(lái)看看運(yùn)行結(jié)果
- 此時(shí)我們已經(jīng)完成了第一步,若是你想要置哪個(gè)位上的數(shù)為1的話,那就修改表達(dá)式的最后一個(gè)數(shù)字即可,但是呢這樣的話并沒(méi)有很大的通用性,需要每次運(yùn)行前做一個(gè)修改,此時(shí)就來(lái)實(shí)現(xiàn)題目中的需求
- 可以再來(lái)修改一個(gè)位上的數(shù),若是我們要將第5位置為1的話,左移4位即可那也就是
1 << 4
,最后的結(jié)果就是26
- 再來(lái)看看運(yùn)行結(jié)果
② 規(guī)律總結(jié)
- 從上述的兩個(gè)例子就可以找出規(guī)律
-
若是要置【第3位】的數(shù)為1的話,使用數(shù)字1左移2位
1 << 2
; -
若是要置【第5位】的數(shù)為1的話,使用數(shù)字1左移4位
1 << 4
; -
若是要置【第n位】的數(shù)為1的話,使用數(shù)字1左移(n - 1)位
1 << (n - 1)
; - 那么此時(shí)的話就可以將這個(gè)n作為我們自己輸入的一個(gè)變量,每次想要修改哪一個(gè)直接輸入即可
int main(void)
{
int a = 10;
int n = 0;
scanf("%d", &n);
a = a | 1 << (n - 1);
//把變量a的第n為置1
//000000000000000000001010
//000000000000000000010000
//--------------------------------
//000000000000000000001110
printf("a = %d\n", a);
return 0;
}
【需求2】:將變量a的第n位置為0
實(shí)現(xiàn)了上面這個(gè)需求之后,便要去另一個(gè)需求,可以將一個(gè)二進(jìn)制數(shù)的某一位置為1,那能不能置為0呢? 我們來(lái)研究研究??
- 要將一個(gè)二進(jìn)制位置為0的話就又需要使用到我們上面所學(xué)習(xí)過(guò)的
按位與&
,也就是將需要置0的那一位按位與上一個(gè)0即可,因?yàn)槿魏螖?shù)和0進(jìn)行與都為0,但是呢又不能使得其他二進(jìn)制位發(fā)生改變,那就要使其他二進(jìn)制位按位與上一個(gè)1即可,若是那個(gè)位上為0,那么就是0,若是那個(gè)位上為1,那也為1,那此時(shí)我們?cè)賹?duì)剛才的第三位進(jìn)行一個(gè)按位與即11111111111111111111111111111011
- 可是要怎么產(chǎn)生這些個(gè)二進(jìn)制位呢,還記得剛才使用的1 << 2嗎,即
00000000000000000000000000000100
,那其實(shí)仔細(xì)觀察就可以看出這兩個(gè)二進(jìn)制位其實(shí)每個(gè)位呈現(xiàn)的都是一個(gè)相反的趨勢(shì),那么我們?cè)谏厦媸褂玫降奈徊僮鞣心膫€(gè)具有取反的功能呢?其實(shí)說(shuō)得很明顯了,就是按位取反
,那其實(shí)只需要將剛才求出的那個(gè)表達(dá)式外層再加上一個(gè)按位取反符就可以了 - 具體的表達(dá)式應(yīng)該為:
[a = a & ~(1 << (n - 1))]
- 再來(lái)看看運(yùn)行結(jié)果
給出整體代碼?
int main(void)
{
int a = 10;
int n = 0;
scanf("%d", &n);
a = a | 1 << (n - 1);
//把變量a的第n為置1
//00000000000000000000000000001010
//00000000000000000000000000000100
//--------------------------------
//00000000000000000000000000011010
printf("置1:a = %d\n", a);
a = a & ~(1 << (n - 1));
//把變量a的第n為置0
//00000000000000000000000000001110
//11111111111111111111111111111011
//--------------------------------
//00000000000000000000000000001010
printf("置0:a = %d\n", a);
return 0;
}
從上述這個(gè)案例來(lái)看,真的可以說(shuō)是淋漓盡致地展現(xiàn)了位運(yùn)算的奇妙之處??
四、賦值操作符
- 賦值操作符是一個(gè)很棒的操作符,他可以讓你得到一個(gè)你之前不滿意的值。也就是你可以給自己重新賦值
int weight = 120; //體重
weight = 89; //不滿意就賦值
double salary = 10000.0;
salary = 20000.0; //使用賦值操作符賦值
- 不僅如此,賦值操作符還可以進(jìn)行連續(xù)使用
int b = a += 10;
- 但是對(duì)于上面這樣的代碼其實(shí)是不太好的,將所有的運(yùn)算都堆積堆積在一起,就無(wú)法調(diào)試了,一個(gè)F10就進(jìn)入了下一行代碼
- 但如果寫成像下面這樣的話就顯得非常清爽而且易于調(diào)試
a += 10;
b = a;
復(fù)合賦值符
- 對(duì)于賦值操作符來(lái)說(shuō),還可以和其他操作符進(jìn)行一個(gè)復(fù)合的操作
- 例如:【+=】、【-=】、【*=】、【/=】、【%=】、【>>=】、【<<=】、【^=】等等。下面舉出兩個(gè)例子??
五、單目操作符
1、單目操作符介紹
首先來(lái)瀏覽一下所有的單目操作符??
2、【!】邏輯反操作
- 對(duì)于邏輯取反操作符來(lái)說(shuō),就是
[真變假,假變真]
int main(void)
{
int flag = 0;
if (!flag)
{
printf("haha\n");
}
return 0;
}
- 具體的用法就是像上面這樣,若是某個(gè)變量為假的時(shí)候,就做一些特殊的操作,當(dāng)然你也可以寫成
if(flag == 0)
,不過(guò)這樣看起來(lái)就不像是一個(gè)真假的判斷 - 對(duì)于這個(gè)操作符其實(shí)我們?cè)诤竺鏀?shù)據(jù)結(jié)構(gòu)的二叉樹(shù)中也蠻常用的,因?yàn)樵诙鏄?shù)進(jìn)行向下遞歸的時(shí)候需要有遞歸出口,那就是當(dāng)前傳進(jìn)來(lái)的根節(jié)點(diǎn)是否為NULL,就可以寫成
if(!root)
或者是if(root == NULL)
,當(dāng)然如果你聽(tīng)不懂的話可以看看我的二叉樹(shù)文章,后續(xù)對(duì)應(yīng)地去學(xué)習(xí)一下即可,這里想到里我就順便說(shuō)一下
3、【&】和【*】
- 對(duì)于【&】來(lái)說(shuō)叫做取地址操作符,可以獲取一個(gè)變量在內(nèi)存中的地址。就如下面這樣去取到這兩個(gè)整形變量和字符型變量的地址
- 當(dāng)然我現(xiàn)在覺(jué)得這樣去取地址打印查看太費(fèi)時(shí)間了,那我們就可以將這塊地址存放到一個(gè)指針中去,在初始C語(yǔ)言的時(shí)候我有說(shuō)到過(guò)一個(gè)對(duì)于地址而言其實(shí)就是一個(gè)指針,所以我們將其給到一個(gè)指針變量是完全沒(méi)有問(wèn)題的,只是我們廣義上說(shuō)的
指針接收地址
- 再帶大家稍微回顧一下指針的有關(guān)知識(shí),對(duì)于pa前面的
[*]
而言,指的就是此為一個(gè)指針變量,而[*]
前面的[int]
表示這個(gè)指針變量所指向的是一個(gè)整型的地址。那當(dāng)前這個(gè)指針的名字是什么呢,就是pa
,而不是*pa
- 那這個(gè)時(shí)候我要獲取到這個(gè)指針?biāo)赶虻牡刂分械膶?duì)象要怎么做呢,那就是要用到【*】這個(gè)解引用操作符
- 若是可以獲取到這個(gè)地址中的對(duì)象,那我們就可以對(duì)其進(jìn)行一個(gè)修改了,即
*pa = 20
- 但若是你沒(méi)有使用
[*]
解引用操作符獲取到這個(gè)對(duì)象就為他賦值,那其實(shí)編譯器會(huì)會(huì)報(bào)出一個(gè)Warning說(shuō)是等號(hào)兩邊的類型級(jí)別不同,這其實(shí)就是講一個(gè)整型的變量強(qiáng)制給到一個(gè)指針變量,才會(huì)出現(xiàn)的類型異常問(wèn)題
- 那其實(shí)這一步操作就是在使這個(gè)指針變量重新指向一塊新的地址,即
00000014
,轉(zhuǎn)換為十進(jìn)制也就是【20】
- 再來(lái)拓展一塊有關(guān)
野指針
的內(nèi)容,因?yàn)檫@一個(gè)只有相關(guān)很危險(xiǎn)?的東西,也是很多初學(xué)者容易犯的
*(int*)0x0012f40 = 100
- 對(duì)于上面這個(gè)語(yǔ)句,首先我
捏造了
一個(gè)十六進(jìn)制的整數(shù),然后將其強(qiáng)制化為一個(gè)整型地址,在前面加上一個(gè)[*]
解引用操作符,此時(shí)我就可以去修改這個(gè)地址中存放著的對(duì)象了,將其對(duì)象的值修改為100,然后可以看到去編譯的時(shí)候是沒(méi)有問(wèn)題的
- 但是一運(yùn)行起來(lái)可以看到發(fā)生了寫入異常,這其實(shí)就是
野指針
,因?yàn)檫@個(gè)十六進(jìn)制的數(shù)值,這個(gè)地址是我隨手捏造出來(lái)的,操作系統(tǒng)并沒(méi)有為其分配內(nèi)存,因此這是一塊隨機(jī)的地址值,是不確定的,若是隨便去訪問(wèn)這塊地址的話就會(huì)造成問(wèn)題 - 這其實(shí)和我們?cè)卺尫诺粢粔K申請(qǐng)的地址時(shí)然后沒(méi)有將其置為NULL是一個(gè)道理,若是你有一個(gè)指針去訪問(wèn)這塊隨機(jī)的地址,那么你的這個(gè)指針就叫做
[野指針]
聽(tīng)完這些,相信你一定回憶起了一些有關(guān)指針和地址的知識(shí)點(diǎn)
4、【-】和【+】
對(duì)于【-】和【+】這個(gè)兩個(gè)操作符并不常用,其實(shí)它們不能完全說(shuō)只是單目操作符,因?yàn)樵谔囟ǖ膱?chǎng)景下它們也可以算是一個(gè)雙目操作符
,例如:-6
的話就只一個(gè)單目操作符,8 - 3
的話就是第一個(gè)雙目操作符,【+】的話也是同理,不做贅述
5、sizeof [?]
然后我們?cè)賮?lái)說(shuō)說(shuō)一個(gè)操作符,叫做sizeof,你沒(méi)聽(tīng)錯(cuò),那就是個(gè)操作符??
① 寫在前面
- 對(duì)于
sizeof
來(lái)說(shuō),是用來(lái)計(jì)算操作數(shù)的類型長(zhǎng)度,它以字節(jié)為單位。在sizeof后跟一個(gè)小括號(hào)(),里面就是你要計(jì)算的數(shù)據(jù)類型,但是很多同學(xué)看到這個(gè)小括號(hào)()的時(shí)候很多同學(xué)就會(huì)認(rèn)為這是一個(gè)函數(shù),它確實(shí)和函數(shù)的樣子很類似。 - 對(duì)于函數(shù),它后面的這個(gè)小括號(hào)叫做
函數(shù)調(diào)用操作符
,在后面我也會(huì)說(shuō)到,可是對(duì)于sizeof后面的這個(gè)()來(lái)說(shuō)卻不一樣,這只是一種語(yǔ)法規(guī)定罷了,你只需要記住這么去用,而且不要把它當(dāng)成函數(shù)就行??
- 使用
sizeof()
可以去計(jì)算很多數(shù)據(jù)類型的長(zhǎng)度,例如一個(gè)整型變量、一個(gè)指針、一個(gè)數(shù)組元素大小、一整個(gè)數(shù)組等等。。。 - 對(duì)于
sizeof()
而言有其特定的返回值打印格式【%zu
】
int a = 10;
int* p;
int arr[10];
printf("%zu\n", sizeof(a)); //int 4
printf("%zu\n", sizeof(p)); //int 4
printf("%zu\n", sizeof(arr)); //特殊,計(jì)算的是整個(gè)數(shù)組的大小
printf("%zu\n", sizeof(arr[0])); //int [10] 40
printf("%zu\n", sizeof(arr[10])); //int 4
② sizeof后可省略()
- 如何更加地去明確sizeof是一個(gè)操作符呢,那就是它后面的()其實(shí)是可以省略的。看到下面的代碼沒(méi)有語(yǔ)法方面的報(bào)錯(cuò)。因?yàn)槲覀兤匠K?jiàn)的操作符和操作數(shù)結(jié)合的時(shí)候都不會(huì)看到(),這其實(shí)就更有力地說(shuō)明了它是一個(gè)操作符
③ sizeof()內(nèi)部表達(dá)式不參與計(jì)算
- 接著來(lái)看到下面這段代碼,可以看到我在sizeof()內(nèi)部寫了一個(gè)表達(dá)式,這其實(shí)也是合法的,大家來(lái)猜一猜輸出的結(jié)果是多少
int main(void)
{
short s = 10;
int a = 2;
printf("%d\n", sizeof(s = a + 2));
printf("%d\n", s);
return 0;
}
- 看到上面的這個(gè)結(jié)果你可能會(huì)震驚(○′?д?)?讓我猜猜看你算出來(lái)的答案是不是【4】和【7】呢,亦或是其他的答案,雖然sizeof()內(nèi)部是可以放表達(dá)式的,但是呢這個(gè)表達(dá)式是不參與運(yùn)算的
- 我們?cè)诳催@個(gè)sizeof()最后的結(jié)果時(shí)其實(shí)只需要看這個(gè)
s
即可,短整型為2個(gè)字節(jié),那如果這個(gè)表達(dá)式不計(jì)算的話s的值也就不會(huì)發(fā)生變化了,所以最后打印出來(lái)是10
- 那有同學(xué)可能會(huì)疑惑為什么這個(gè)表達(dá)式不參與運(yùn)算呢?又是語(yǔ)法規(guī)定嗎?
- 答:一方面是這樣,但是在內(nèi)存中看來(lái),在運(yùn)行階段的時(shí)候,這個(gè)表達(dá)式其實(shí)早就已經(jīng)不在了,因?yàn)樗诰幾g階段的時(shí)候就會(huì)替換成了
short
- 答:一方面是這樣,但是在內(nèi)存中看來(lái),在運(yùn)行階段的時(shí)候,這個(gè)表達(dá)式其實(shí)早就已經(jīng)不在了,因?yàn)樗诰幾g階段的時(shí)候就會(huì)替換成了
- 如果想了解為何在編譯階段會(huì)發(fā)生替換可以看看我的這篇文章 ——> C生萬(wàn)物 | 詳解程序環(huán)境和預(yù)處理【展示程序編譯+鏈接全過(guò)程】
- 不過(guò)這里還要給讀者補(bǔ)充的一點(diǎn)是有關(guān)
數(shù)據(jù)截?cái)?/code>這個(gè)小知識(shí),若是我們不將這個(gè)表達(dá)式放到sizeof()中去,而是拿到外面來(lái)計(jì)算,那么最后s的結(jié)果還是會(huì)發(fā)生改變的,不過(guò)可以看到對(duì)于
a + 5
的結(jié)果它是一個(gè)整型,占4個(gè)字節(jié),但是變量s呢它是一個(gè)短整型,占2個(gè)字節(jié),若是 強(qiáng)行將4個(gè)字節(jié)的數(shù)據(jù)放到2個(gè)字節(jié)中去,其實(shí)就會(huì)發(fā)生一個(gè)【數(shù)據(jù)截?cái)唷窟@個(gè)一個(gè)現(xiàn)象
- 下面其實(shí)就是在內(nèi)存中的一個(gè)截?cái)嗖僮鳎瑥?qiáng)行將一個(gè)整型變量給到一個(gè)短整型都會(huì)造成這樣的情況,所以我們?cè)谶M(jìn)行賦值的時(shí)候要看清楚數(shù)據(jù)類型
④ sizeof() 與數(shù)組
- 最后再來(lái)講講sizeof()和數(shù)組之間的運(yùn)算,首先看看下面這段代碼
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
- 答案是下面這4個(gè),你猜對(duì)了嗎,前面3個(gè)相信你一定沒(méi)問(wèn)題,但是對(duì)于最后一個(gè)我猜一猜是不是算成【1】了 (^ _^)
- 正好通過(guò)這一塊我們?cè)賮?lái)回顧一下有關(guān)數(shù)組名的知識(shí)
-
對(duì)于數(shù)組名,一般來(lái)說(shuō)指的都是
首元素地址
-
sizeof(數(shù)組名) —— 計(jì)算的是
整個(gè)數(shù)組的大小
【特殊情況1】 -
&數(shù)組名 —— 獲取
整個(gè)數(shù)組的地址
【特殊情況2】 - 有了這些知識(shí),再來(lái)分析一下上面的這段代碼,首先第一個(gè)【sizeof(arr)】,arr表示數(shù)組名,那么計(jì)算的就是整個(gè)數(shù)組的大小,這是一個(gè)整型數(shù)組,數(shù)組中有10個(gè)元素,每個(gè)元素4個(gè)字節(jié),那么整個(gè)數(shù)組的大小就是40個(gè)字節(jié)
- 對(duì)于【sizeof(ch)】而言也是同理,這是一個(gè)字符型數(shù)組,數(shù)組中有10個(gè)元素,每個(gè)元素1個(gè)字節(jié),那么整個(gè)數(shù)組的大小就是10個(gè)字節(jié)
- 接下去就是將數(shù)組名傳入函數(shù)中,除了兩種特殊的情況而言,數(shù)組名就相當(dāng)于是首元素地址,然后函數(shù)形參中便是指針進(jìn)行接受,那對(duì)于一個(gè)指針而言,無(wú)論它是【整型指針】、【字符型指針】【浮點(diǎn)型指針】均為4個(gè)字節(jié),當(dāng)然這是在32為系統(tǒng)下,若是在64位系統(tǒng)下就為8個(gè)字節(jié),這其實(shí)取決于計(jì)算機(jī)內(nèi)存中
地址總線
的長(zhǎng)度,有興趣可以去了解一下 - 那既然上面說(shuō)到一個(gè)指針均為4個(gè)字節(jié),就可以得出為什么最后一個(gè)
sizeof(ch)
為4了,這也只是在求一個(gè)指針的大小,不要和字符串?dāng)?shù)據(jù)所占字節(jié)數(shù)混淆了
以上便是對(duì)于sizeof()這個(gè)操作符而言要介紹的所有內(nèi)容,希望讀者能夠理解??
6、【++】和【- -】
首先來(lái)看看前置++和后置++
int a = 10;
int b = ++a;
//a = a + 1; b = a;
printf("a = %d, b = %d\n", a, b);
int a = 10;
int b = a++;
//b = a; a = a + 1;
printf("a = %d, b = %d\n", a, b);
- 可以看到,對(duì)于這里的
b = ++a
相當(dāng)于就是先讓a++,然后再把a(bǔ)的值給到b - 可以看到,對(duì)于這里的
b = a++
相當(dāng)于就是先把a(bǔ)的值給到b,然后再讓a++
接著再來(lái)看看前置- -和后置- -
int a = 10;
int b = --a;
//a = a - 1; b = a;
printf("a = %d, b = %d\n", a, b);
int a = 10;
int b = a--;
//b = a; a = a - 1;
printf("a = %d, b = %d\n", a, b);
- 可以看到,對(duì)于這里的
b = --a
相當(dāng)于就是先讓a- -,然后再把a(bǔ)的值給到b - 可以看到,對(duì)于這里的
b = a--
相當(dāng)于就是先把a(bǔ)的值給到b,然后再讓a- -
7、強(qiáng)制類型轉(zhuǎn)換
最后的話再來(lái)說(shuō)一下強(qiáng)制類型轉(zhuǎn)換
- 比方說(shuō)我這里將一個(gè)
double
類型的數(shù)據(jù)強(qiáng)制給到一個(gè)int
類型的變量,就會(huì)出現(xiàn)一種叫做【精度丟失】的現(xiàn)象,若是想要強(qiáng)制給到一個(gè)整型的變量,那就要去做一個(gè)強(qiáng)制類型轉(zhuǎn)換
- 可以看到這樣就不會(huì)出現(xiàn)問(wèn)題了,所以你想要把一個(gè)不同數(shù)據(jù)類型的值給到一個(gè)另一個(gè)數(shù)據(jù)類型的變量,就可以使用
強(qiáng)制類型轉(zhuǎn)換
int a = (int)3.14;
六、關(guān)系操作符
- 關(guān)系操作符主要有下面這些??
【>】、【>=】、【<】、【<=】、【! =】、【==】 - 對(duì)于這些操作符來(lái)說(shuō)大家平常在使用的時(shí)候也都會(huì)碰到過(guò),這里主要強(qiáng)調(diào)的一塊就是
==
,因?yàn)樗?jīng)常會(huì)和賦值操作符=
混淆,其實(shí)也不能說(shuō)是混淆,應(yīng)該說(shuō)是【遺漏】,包括是很多資深的程序員在寫代碼判斷一個(gè)數(shù)是否等于某個(gè)值的時(shí)候都會(huì)犯這樣的錯(cuò)誤- 例如將
a == 6
寫成a = 6
,若是將這個(gè)語(yǔ)句寫在if條件判斷里的話那么無(wú)論a的值為多少都會(huì)進(jìn)入這個(gè)條件分支,因?yàn)橘x值永遠(yuǎn)都是成立的,也就是為真
- 例如將
- 所以我們平常在判斷的時(shí)候一般不寫
a == 10
,一般都寫成10 == a
- 因?yàn)榍罢呷羰巧约恿艘粋€(gè)【=】的話編譯器是不會(huì)報(bào)出錯(cuò)誤的,那后面再調(diào)試的時(shí)候就會(huì)很麻煩
- 可是若后者少加了一個(gè)【=】的話編譯器就會(huì)報(bào)出錯(cuò)誤,因?yàn)橐粋€(gè)變量是不可以賦值給到一個(gè)常量的,這就屬于語(yǔ)法方面的錯(cuò)誤了
對(duì)于==
操作符還想再說(shuō)幾句的是我們有時(shí)候不僅僅會(huì)去比較某個(gè)數(shù)值,而是去比較兩個(gè)字符串的內(nèi)容是否相同或者是比較一些結(jié)構(gòu)體成員的變量,可是呢對(duì)于【==】操作符來(lái)說(shuō)是不具備那么強(qiáng)大的功能的,所以要用其他手段去實(shí)現(xiàn)
-
對(duì)于兩個(gè)字符串的內(nèi)容,我后面在將字符串內(nèi)容的時(shí)候會(huì)說(shuō)到一個(gè)庫(kù)函數(shù)叫做strcmp,它是專門用來(lái)比較字符串內(nèi)容的,若是直接用
==
去比較的話不是比較的兩個(gè)字符串的內(nèi)容,而是比較的兩個(gè)字符串的首元素地址罷了 -
對(duì)于結(jié)構(gòu)體成員的比較,也是有獨(dú)特的方法,不過(guò)對(duì)于普通的結(jié)構(gòu)體成員,整數(shù)、浮點(diǎn)數(shù)就直接用【==】比較也是可以的,字符串的話用上面的
strcmp
,而對(duì)于定義出來(lái)的整個(gè)結(jié)構(gòu)體成員對(duì)象的內(nèi)容就不好比較了,之后我們?cè)趯W(xué)習(xí)了C++之后就知道有個(gè)東西叫做[仿函數(shù)]
,可以比較自定義的數(shù)據(jù)類型
七、邏輯操作符
邏輯操作符很好記,就兩個(gè),和我們前面學(xué)過(guò)的位操作符中的按位與&
和按位或|
很像
- 要區(qū)分邏輯與和按位與
- 要區(qū)分邏輯或和按位或
- 可以看到對(duì)于邏輯與邏輯或來(lái)說(shuō)它們最終的結(jié)果只會(huì)有兩種,那就是【1】和【0】;但是對(duì)于位操作符來(lái)說(shuō)是千變?nèi)f化的,因?yàn)閮蓚€(gè)數(shù)進(jìn)行位運(yùn)算取決的是
32個(gè)二進(jìn)制位上的0和1
邏輯與和或的特點(diǎn):
?【邏輯與&&】:表達(dá)式兩邊均為真才是真,若第一個(gè)為假,那么整個(gè)表達(dá)式為假,第二個(gè)表達(dá)式不參與運(yùn)算
?【邏輯或 ||】:表達(dá)式兩邊有一邊為真即為真,若第一個(gè)為真,那么整個(gè)表達(dá)式為真,第二個(gè)表達(dá)式不參與運(yùn)算
一道【奇虎360】筆試題?
下面是一道【奇虎360】公司的校招筆試題,請(qǐng)問(wèn)程序輸出的結(jié)果是什么?
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
printf("------\n");
printf("i = %d\n", i);
return 0;
}
- 這道題考察的就是你對(duì)于
[邏輯操作符]
和[單目操作符]
的綜合運(yùn)用能力 - 下面是最終的結(jié)果,你算對(duì)了嗎?
- 來(lái)分析一下其實(shí)就可以看出 ,因?yàn)閍一開(kāi)始為0,所以前兩個(gè)邏輯與之后的結(jié)果一定為0,那么除了第一個(gè)
a++
表達(dá)式需要運(yùn)算之外后面的表達(dá)式都不會(huì)參與運(yùn)算,因此最后的結(jié)果為1 2 3 4
,【i】的結(jié)果即為0 - 這里要注意的一點(diǎn)就是邏輯與前面一個(gè)表達(dá)式已為假那么第二個(gè)表達(dá)式是不會(huì)參與運(yùn)算的
現(xiàn)在我將這個(gè)題目做幾個(gè)變形,看看讀者是否具有舉一反三的能力??
題目變形①
- 看到我將a的初始值做了一個(gè)修改,變?yōu)?,那么請(qǐng)問(wèn)結(jié)果是多少呢?
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
- 來(lái)分析一下,初始化a為1,b為2,那在
a++
和++b
之后與運(yùn)算的表達(dá)式即為1 && 3
,運(yùn)算之后的結(jié)果即為1,然后這個(gè)1再和d++
去進(jìn)行一個(gè)運(yùn)算便可以得出最后的結(jié)果為【1】,那么a b c d 最后的結(jié)果即為2 3 3 5
題目變形②
- 既然學(xué)習(xí)了邏輯與,那邏輯或也少不了,接下去來(lái)練練邏輯或的用法吧
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
- 這里要注意的一點(diǎn)就是邏輯或前面一個(gè)表達(dá)式已為真那么第二個(gè)表達(dá)式是不會(huì)參與運(yùn)算的
- 因此最后的結(jié)果即為
1 3 3 4
,【i】的值為1
題目變形③
- 將這個(gè)a改為1再來(lái)看看最后輸出的結(jié)果為多少?
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
- 同理,本次的修改也可以看出邏輯或的特性,之后
a++
參與了運(yùn)算,因?yàn)樗粸?,那么后面都不會(huì)參與運(yùn)算了,最后的a b c d結(jié)果即為2 2 3 4
通過(guò)這道奇虎360公司的筆試題以及舉一反三的練習(xí),相信你對(duì)邏輯操作符一定有了自己的理解??
八、條件操作符
接下去我們來(lái)看看條件操作符,不過(guò)一般我們都將其叫做條件表達(dá)式(三目操作符)
- 來(lái)看看下面這段代碼,一段很簡(jiǎn)答的邏輯判斷,但是呢你是否可以發(fā)現(xiàn)經(jīng)過(guò)if分支的一個(gè)判斷之后顯得非常冗余,那這個(gè)時(shí)候其實(shí)就可以使用到我們本節(jié)所要將的
條件操作符
了
int main(void)
{
int a = 5;
int b = 0;
if (5 == a)
b = 3;
else
b = -3;
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
- 將整個(gè)if的分支判斷寫成這樣的一句代碼時(shí),就顯得非常簡(jiǎn)潔,但是可能不了解這個(gè)操作符的同學(xué)可能會(huì)看懵,不夠你現(xiàn)在學(xué)習(xí)了這個(gè)操作符之后一定是完全沒(méi)問(wèn)題的
- 也就是當(dāng)條件成立的時(shí)候,執(zhí)行第一個(gè)
表達(dá)式
,當(dāng)條件不成立的時(shí)候,執(zhí)行第二個(gè)表達(dá)式
。可以看出我寫了后面也可以是一個(gè)表達(dá)式??
b = (5 == a) ? 3 : -3;
然后我們使用這個(gè)條件操作符來(lái)練習(xí)一下求解兩個(gè)數(shù)的較大值
int a = 5;
int b = 3;
int ret = (a > b) ? a : b;
printf("ret = %d\n", ret);
九、逗號(hào)表達(dá)式【生僻,需了解】
下面來(lái)說(shuō)說(shuō)有關(guān)逗號(hào)表達(dá)式的用法
【格式】:exp1, exp2, exp3, …expN
【運(yùn)算規(guī)則】:從左向右依次計(jì)算,整個(gè)表達(dá)式的結(jié)果是最后一個(gè)表達(dá)式的結(jié)果
- 首先來(lái)看一下第一段代碼,請(qǐng)你計(jì)算一個(gè)
//代碼1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗號(hào)表達(dá)式
printf("c = %d\n", c);
- 最后的結(jié)果是
13
,你算對(duì)了嗎?運(yùn)行結(jié)果就不展示了 - 來(lái)分析一下,根據(jù)逗號(hào)表達(dá)式的運(yùn)算規(guī)則可以知道它是從左向右進(jìn)行計(jì)算的,最終結(jié)果取的是最后一個(gè)表達(dá)式的結(jié)果,那么根據(jù)前面的計(jì)算可以得知
b = 12
,那么最后再計(jì)算便是13
- 再來(lái)看一句代碼,可以看到這不是一個(gè)結(jié)果的運(yùn)算,而是將逗號(hào)表達(dá)式放在一個(gè)if分支判斷中,可以看到最后一個(gè)逗號(hào)后面的表達(dá)式為
d > 0
,那此時(shí)我們就要去看看前面一些表達(dá)式的運(yùn)算會(huì)不會(huì)使得這個(gè)d變化,若不會(huì)那么這個(gè)if判斷其實(shí)就等價(jià)于if(d > 0)
//代碼2
if (a = b + 1, c = a / 2, d > 0)
- 最后再來(lái)看看下面這段代碼,就是不斷地在計(jì)算一個(gè)a的值然后求和,若是a什么時(shí)候到0了,便跳出這個(gè)while()循環(huán),但是可以看到
a = get_val()
和count_val(a)
這兩個(gè)表達(dá)式在while()循環(huán)上面調(diào)用了一次,然后再while()循環(huán)中在調(diào)用,顯得就有些冗余了,那此時(shí)我們就可以使用【逗號(hào)表達(dá)式】去進(jìn)行一個(gè)優(yōu)化
//代碼3
a = get_val();
count_val(a);
while (a > 0)
{
//業(yè)務(wù)處理
a = get_val();
count_val(a);
}
- 可以看到,通過(guò)逗號(hào)表達(dá)式的一個(gè)優(yōu)化,代碼看起來(lái)就顯得很簡(jiǎn)潔,當(dāng)while()循環(huán)一進(jìn)來(lái),就會(huì)執(zhí)行
a = get_val(), count_val(a)
這兩個(gè)表示,但是呢最后起作用的還是a > 0
,前面兩個(gè)表達(dá)式只是有可能會(huì)使a的值發(fā)生一個(gè)變化罷了
while (a = get_val(), count_val(a), a > 0)
{
//業(yè)務(wù)處理
}
十、下標(biāo)引用、函數(shù)調(diào)用和結(jié)構(gòu)成員
1、下標(biāo)引用操作符 [ ]
【操作數(shù)】:一個(gè)數(shù)組名 + 一個(gè)索引值
- 這個(gè)操作符我們?cè)谥v數(shù)組的時(shí)候也有用到過(guò),可能我們大家在使用的時(shí)候都是
arr[1]
,不過(guò)既然它一個(gè)操作符,那么對(duì)于操作數(shù)來(lái)說(shuō)其實(shí)沒(méi)有位置的一個(gè)限制,其實(shí)是可以寫成1[arr]
,這個(gè)語(yǔ)法也是支持的,訪問(wèn)的都是arr這個(gè)數(shù)組中的第一個(gè)元素 - 我們可以到VS中來(lái)演示看看
- 可以看出兩種語(yǔ)法都是可行的,這一點(diǎn)你了解了嗎??
2、函數(shù)調(diào)用操作符 ( )
??接受一個(gè)或者多個(gè)
操作數(shù):第一個(gè)操作數(shù)是函數(shù)名,剩余的操作數(shù)就是傳遞給函數(shù)的參數(shù)
- 對(duì)于函數(shù)來(lái)說(shuō)相信都不陌生,這里主要給讀者講講有關(guān)函數(shù)的操作符和操作數(shù)之間的關(guān)系,可以看到下面這段代碼,對(duì)于
test1()
來(lái)說(shuō)它的操作符為()
,只有一個(gè)操作數(shù)就是函數(shù)名test1 - 再看到
test2("hello bit.")
,對(duì)于它來(lái)說(shuō)操作符也為()
,操作數(shù)的話有兩個(gè),一個(gè)為函數(shù)名test1,另一個(gè)則為函數(shù)參數(shù)"hello bit."
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main(void)
{
test1(); //實(shí)用()作為函數(shù)調(diào)用操作符。
test2("hello bit."); //實(shí)用()作為函數(shù)調(diào)用操作符。
return 0;
}
3、結(jié)構(gòu)成員調(diào)用操作符 . ->
最后再來(lái)說(shuō)說(shuō)這個(gè)結(jié)構(gòu)成員調(diào)用操作符【.】和【->】
- 首先看到下面聲明了一個(gè)結(jié)構(gòu)體,是一本書,結(jié)構(gòu)成員有作家和價(jià)格。然后我聲明了一個(gè)結(jié)構(gòu)體成員,初始化了它的成員變量
typedef struct book {
char writer[20];
double price;
}st;
st s1 = { "羅曼·羅蘭", 50 };
- 首先我使用
.
操作符先進(jìn)行訪問(wèn),可以看到獲取了這個(gè)成員所有的成員變量
int main(void)
{
st s1 = { "羅曼·羅蘭", 50 };
printf("name = %s\n", s1.writer);
printf("price = %f\n",s1.price); //結(jié)構(gòu)體變量.結(jié)構(gòu)體成員名
return 0;
}
- 接下去我們?cè)賮?lái)嘗試一下
->
操作符進(jìn)行一個(gè)訪問(wèn),那么對(duì)于這個(gè)操作符在上面講到過(guò),那既然這樣的話我們就需要去定義一個(gè)指針去接收這個(gè)結(jié)構(gòu)體成員的地址,那么這個(gè)指針就叫做[結(jié)構(gòu)體指針]
- 那我們使用這個(gè)指針變量解引用是不是取到了這個(gè)結(jié)構(gòu)體的值,此時(shí)就可以去訪問(wèn)這些結(jié)構(gòu)體成員了,如下所示??
st* ps = &s1;
printf("name = %s\n", (*ps).writer);
printf("price = %f\n", (*ps).price);
- 那我們就用
->
操作符來(lái)試試吧
printf("name = %s\n", ps->writer); //結(jié)構(gòu)體指針->結(jié)構(gòu)體成員名
printf("price = %f\n", ps->price);
- 可以看到對(duì)于這三種形式都是可以訪問(wèn)到這個(gè)結(jié)構(gòu)體變量的成員
十一、表達(dá)式求值
1、隱式類型轉(zhuǎn)換【?整型提升?】
接下去要講的這一個(gè)隱式類型轉(zhuǎn)換,可以很好地解開(kāi)你對(duì)很多類型轉(zhuǎn)換的一些困惑
C的整型算術(shù)運(yùn)算總是至少以缺省整型類型的精度來(lái)進(jìn)行的
??為了獲得這個(gè)精度,表達(dá)式中的字符型和短整型操作數(shù)在使用之前被轉(zhuǎn)換為普通整型,這種轉(zhuǎn)換稱為[整型提升]
① 整型提升的意義
??表達(dá)式的整型運(yùn)算要在CPU的相應(yīng)運(yùn)算器件內(nèi)執(zhí)行,CPU內(nèi)整型運(yùn)算器(ALU)的操作數(shù)的字節(jié)長(zhǎng)度一般就是int的字節(jié)長(zhǎng)度,同時(shí)也是CPU的通用寄存器的長(zhǎng)度。
??因此,即使兩個(gè)char類型的相加,在CPU執(zhí)行時(shí)實(shí)際上也要先轉(zhuǎn)換為CPU內(nèi)整型操作數(shù)的標(biāo)準(zhǔn)長(zhǎng)度。
??通用CPU(general-purpose CPU)是難以直接實(shí)現(xiàn)兩個(gè)8比特字節(jié)直接相加運(yùn)算(雖然機(jī)器指令中可能有這種字節(jié)相加指令)。所以,表達(dá)式中各種長(zhǎng)度可能小于int長(zhǎng)度的整型值,都必須先轉(zhuǎn)換為int
或unsigned int
,然后才能送入CPU去執(zhí)行運(yùn)算
通過(guò)上面的陳述,相信你對(duì)整型提升有了一個(gè)初步的概念,接下去我們來(lái)看看如何去進(jìn)行一個(gè)整型提升??
② 如何進(jìn)行整型提升
- 整形提升是按照變量的數(shù)據(jù)類型的符號(hào)位來(lái)提升的
對(duì)于整型提升來(lái)說(shuō),整數(shù)和負(fù)數(shù)是不一樣的,首先我們來(lái)看看正數(shù)的整型提升
//正數(shù)的整形提升
char c1 = 1;
- 首先先來(lái)寫出
1
的32個(gè)二進(jìn)制位00000000000000000000000000000001
,然后將其轉(zhuǎn)換為原碼的形式,但是通過(guò)上面的學(xué)習(xí)我們可以知道對(duì)正數(shù)的原、反、補(bǔ)碼是相同的,因此這就是它在內(nèi)存中的形式 - 但是可以看到現(xiàn)在要將這個(gè)整數(shù)1給到一個(gè)char類型的變量c2,此時(shí),此時(shí)就會(huì)發(fā)生一個(gè)【截?cái)唷康默F(xiàn)象,因?yàn)橐粋€(gè)字符型的數(shù)據(jù)在內(nèi)存中只占1個(gè)字節(jié),也就是8個(gè)比特位,所以在截?cái)嘀缶椭皇O?code>00000001
- 接下去若是要去使用這個(gè)c1的話就會(huì)進(jìn)行一個(gè)整型提升,此時(shí)在八個(gè)比特位的首部
填充24個(gè)符號(hào)位
,以達(dá)到整型4個(gè)字節(jié)32個(gè)比特位在內(nèi)存中的要求,那么此時(shí)提升之后的結(jié)果便是00000000000000000000000000000001
可以看出又回到了原來(lái)的1,不過(guò)若是你不去使用這個(gè)c1的話是不需要進(jìn)行整型提升的
接下去再來(lái)看看負(fù)數(shù)的整型提升
//負(fù)數(shù)的整形提升
char c2 = -1;
- 那對(duì)于負(fù)數(shù)其實(shí)也是一樣,-1 在內(nèi)存中的32位二進(jìn)制補(bǔ)碼為
11111111111111111111111111111111
。同理,將其給到一個(gè)整型的變量之后就會(huì)發(fā)生截?cái)嗉礊?code>11111111 - 那這個(gè)時(shí)候再進(jìn)行一個(gè)整型提升就和正數(shù)不一樣了,因?yàn)樨?fù)數(shù)的符號(hào)位為1,而整型提升在高位是要補(bǔ)充符號(hào)位,所以會(huì)在前頭加上24個(gè)1,那其實(shí)也就變回了和之前-1的補(bǔ)碼一般的樣子,為32個(gè)1
好,說(shuō)完了該如何去進(jìn)行整型提升之后我們就可以去代碼段中看看到底是如何進(jìn)行的
③ 實(shí)戰(zhàn)演練
首先來(lái)看第一個(gè),我們要去計(jì)算兩個(gè)整數(shù)的和,但是呢卻要放到char類型的變量中去,那會(huì)發(fā)生什么化學(xué)反應(yīng)呢??
int main(void)
{
char a = 5;
char b = 126;
char c = a + b;
}
- 根據(jù)上一小節(jié)的講解,相信你已經(jīng)知道編譯器第一步會(huì)做什么了,首先的話就是分別寫出這兩個(gè)整數(shù)的32個(gè)比特位,接著轉(zhuǎn)換為補(bǔ)碼的形式,正數(shù)三者均一致。然后因?yàn)橐o到一個(gè)char類型的變量,所以會(huì)進(jìn)行一個(gè)【截?cái)唷?/li>
00000000000000000000000000000101 - 5
——> 00000101 - 5【截?cái)唷?00000000000000000000000001111110 - 126
——> 01111110 - 126【截?cái)唷?
- 接下去我們要開(kāi)始去使用到這兩個(gè)字符型的變量了,使用它們進(jìn)行一個(gè)加法運(yùn)算,那么此時(shí)就會(huì)發(fā)生一個(gè)
[整型提升]
,在高位補(bǔ)充24個(gè)符號(hào)位之后就變成了下面這樣,然后便可以對(duì)它們?nèi)ミM(jìn)行一個(gè)運(yùn)算了
//到了內(nèi)存中開(kāi)始計(jì)算 —— 整型提升(正數(shù))
00000000000000000000000000000101 - 5
00000000000000000000000001111110 - 126
- 那在運(yùn)算出來(lái)之后呢在計(jì)算機(jī)中是一個(gè)補(bǔ)碼的形式,輸出來(lái)便得是一個(gè)原碼的形式,由于正數(shù)三者是一致,所以不發(fā)生改變(一強(qiáng)調(diào)這點(diǎn)是因?yàn)橄胱屪x者搞懂每一步)其實(shí)這時(shí)可以看到運(yùn)算出來(lái)的數(shù)字是正確的,
5 + 126 = 131
- 可是呢可以看到左邊又是拿了一個(gè)char類型定義的變量在接受這個(gè)運(yùn)算后的結(jié)果,因此便又會(huì)發(fā)生一個(gè)【截?cái)唷?,就只剩?strong>1個(gè)字節(jié)8個(gè)比特位了
- 那其實(shí)在這個(gè)地方如果我用一個(gè)整型變量去接收一下然后再用
%d
做一個(gè)打印,那么此時(shí)就會(huì)輸出正確的內(nèi)容131
00000000000000000000000010000011 - 131
10000011 - 131【截?cái)唷?
可是呢,我就是不用整形去接收,就是玩??用字符型去接受,然后再用%d
去打?。ā爸饕€是為了加深知識(shí)點(diǎn)的靈活運(yùn)用”)
printf("c的整數(shù)打印形式為:%d\n", c);
- 那在若是在這個(gè)時(shí)候又要去進(jìn)行打印的話,又要放到內(nèi)存里面去運(yùn)算了,調(diào)用這個(gè)
printf()
庫(kù)函數(shù)其實(shí)也算是一個(gè)運(yùn)算,也要放到內(nèi)存里面去,然后這個(gè)變量c又不是整型,所以此時(shí)呢就又會(huì)發(fā)生一個(gè)[整型提升]
了 - 此時(shí)就需要去補(bǔ)充
10000011
前面的24個(gè)符號(hào)位了
//整型提升(負(fù)數(shù))
11111111111111111111111110000011 - 補(bǔ)碼
11111111111111111111111110000010 - 反碼
10000000000000000000000001111101 - 原碼
- 然后變要將這個(gè)32個(gè)二進(jìn)制位以十進(jìn)制的形式打印出來(lái),可是計(jì)算機(jī)中的運(yùn)算是采取補(bǔ)碼的形式,打印輸出的話就要采取補(bǔ)碼的形式了,所以此時(shí)就需要將這個(gè)補(bǔ)碼轉(zhuǎn)化為原碼了,可以看到這是一個(gè)負(fù)數(shù)的補(bǔ)碼,所以轉(zhuǎn)化為原碼的時(shí)候要小心了,需要將補(bǔ)碼-1然后再除符號(hào)位外均做取反
- 此時(shí)再去轉(zhuǎn)化為十進(jìn)制的形式輸出便是
-125
,我們來(lái)看看結(jié)果【全體起立??】
接下去再來(lái)看看第二個(gè)栗子??
- 上面呢我們只說(shuō)到了字符類型的整型提升,下面呢我們?cè)賮?lái)看看短整型,它們都是屬于整型數(shù)據(jù)類型的一種
- 可以看到定義了三個(gè)變量,分別是字符型、短整型、整型,然后初始化了一個(gè)十六進(jìn)制的數(shù)據(jù),那我們可以將其轉(zhuǎn)換為二進(jìn)制的形式便為
10110110
,那其實(shí)到這里我就已經(jīng)可以看出答案是多少了,只有最后一個(gè)if語(yǔ)句會(huì)進(jìn)去,其余的都不成立
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
- 好,來(lái)解釋一下為什么我一眼就可以看出最后的結(jié)果是多少,并不是因?yàn)槲抑澜Y(jié)果,而是我看到了這個(gè)
十六進(jìn)制的b
,因?yàn)樗亩M(jìn)制為1011
,可以看到首尾是為0,那么當(dāng)這個(gè)變量a參與運(yùn)算的時(shí)候就會(huì)發(fā)生一個(gè)[整型提升]
,在上面我說(shuō)到過(guò)對(duì)于負(fù)數(shù)的整型提升和正數(shù)不一樣,填充符號(hào)位后均為1,那么再轉(zhuǎn)化為原碼從計(jì)算機(jī)輸出之后就一定不會(huì)是原來(lái)的值了,會(huì)發(fā)生一個(gè)改變?? - 對(duì)于
char a
和short b
它們均不是一個(gè)整型int類型的數(shù)據(jù),所以都會(huì)發(fā)生一個(gè)[整型提升]
,不過(guò)int c
它就是一個(gè)整型的變量,所以是不會(huì)發(fā)生變化的
通過(guò)這個(gè)例子相信你對(duì)整型提升一定有了更加深刻的理解
最后一個(gè)小案例我們和sizeof做一個(gè)結(jié)合,順便再回顧一下前面的知識(shí)點(diǎn)??
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(c + 1));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
- 通過(guò)運(yùn)行結(jié)果可以看到,有三個(gè)結(jié)果發(fā)生了整型提升,首先對(duì)于
sizeof(c)
很明確,計(jì)算的就是char這個(gè)數(shù)據(jù)類型的字節(jié)長(zhǎng)度,也就是1,可以對(duì)于下三個(gè)結(jié)果為什么會(huì)發(fā)生整型提升呢?我們來(lái)分析一下?? -
對(duì)于
c + 1
來(lái)說(shuō)它是一個(gè)表達(dá)式,上面說(shuō)到過(guò)若是一個(gè)char類型或者是short類型定義的變量參與了運(yùn)算,那在內(nèi)存中就會(huì)發(fā)生一個(gè)整型提升,那如果這是一個(gè)表達(dá)式的話也就相當(dāng)于是參與了運(yùn)算,整型提升后變?yōu)?個(gè)字節(jié),具體細(xì)節(jié)不做展開(kāi) -
那對(duì)于
+c
和-c
來(lái)說(shuō)就是我們前面說(shuō)到過(guò)的單目操作符中的+
和-
操作符,和一個(gè)操作數(shù)結(jié)合也可以說(shuō)它是一個(gè)表達(dá)式,那么同理也會(huì)進(jìn)行一個(gè)整型提升
但是我再將它們變個(gè)形卻又不會(huì)發(fā)生【整型提升】了,一起來(lái)看看??
char c = 1;
printf("%u\n", sizeof(c + 1));
printf("%u\n", sizeof(+c));
printf("-------------------\n");
printf("%u\n", sizeof(c = c + 1));
printf("%u\n", sizeof(++c));
- 可以看到,若是將
c + 1
改換成了c = c + 1
,就不會(huì)發(fā)生整型提升了,這是為什么呢?因?yàn)閷?duì)于c + 1這個(gè)表達(dá)式來(lái)說(shuō)確實(shí)會(huì)發(fā)生整型提升,但是呢我又將這個(gè)表達(dá)式計(jì)算后的結(jié)果放到c里面去,還記得我在講述【sizeof()】的時(shí)候說(shuō)到它里面的表達(dá)式是不會(huì)運(yùn)算的嗎,所以整個(gè)表達(dá)式的結(jié)果其實(shí)就是sizeof(c)
的結(jié)果,和上面所列出的第一個(gè)是一樣的 - 再來(lái)看看這個(gè)
++c
,那又有同學(xué)會(huì)產(chǎn)生疑惑,為何+c
會(huì)發(fā)生整型提升,但是++c
卻不會(huì)呢,其實(shí)對(duì)于++c
來(lái)說(shuō)就等價(jià)于c = c + 1
,那其沒(méi)有發(fā)生整型提升的原因相信你已經(jīng)清楚了
以上就是有關(guān)【整型提升】要介紹的所有內(nèi)容,看完這些相信你對(duì)計(jì)算機(jī)內(nèi)部隱式類型轉(zhuǎn)換一定有了一個(gè)深刻的了解??
2、算術(shù)轉(zhuǎn)換
如果某個(gè)操作符的各個(gè)操作數(shù)屬于不同的類型,那么除非其中一個(gè)操作數(shù)的轉(zhuǎn)換為另一個(gè)操作數(shù)的類
型,否則操作就無(wú)法進(jìn)行。下面的層次體系稱為【尋常算術(shù)轉(zhuǎn)換】
- 其實(shí)很好理解。如果某個(gè)操作數(shù)的類型在上面這個(gè)列表中排名較低,那么首先要轉(zhuǎn)換為另外一個(gè)操作數(shù)的類型后執(zhí)行運(yùn)算?!九琶麖纳厦孀罡?,下面最低】
-
例如
int
和unsigned int
一起進(jìn)行算術(shù)運(yùn)算的時(shí)候這個(gè)前者就要轉(zhuǎn)換為后者的類型 -
例如
long int
和long double
一起進(jìn)行算術(shù)運(yùn)算的時(shí)候這個(gè)前者就要轉(zhuǎn)換為后者的類型 - 那其實(shí)可以看出,在char和short面前稱霸的int如今淪落為了小弟??
【警告】:
但是算術(shù)轉(zhuǎn)換要合理,要不然會(huì)有一些潛在的問(wèn)題
int main(void)
{
float f = 3.14;
int num = f;//隱式轉(zhuǎn)換,會(huì)有精度丟失
printf("%d\n", num);
return 0;
}
- 可以看到,在編譯的階段就出現(xiàn)了一個(gè)精度丟失的Warning?
3、操作符的屬性【附優(yōu)先級(jí)列表】
復(fù)雜表達(dá)式的求值有三個(gè)影響的因素
- 操作符的優(yōu)先級(jí)
- 操作符的結(jié)合性
- 是否控制求值順序
-
兩個(gè)相鄰的操作符先執(zhí)行哪個(gè)?取決于他們的
優(yōu)先級(jí)
。如果兩者的優(yōu)先級(jí)相同,取決于他們的結(jié)合性
下面有一張關(guān)于操作符優(yōu)先級(jí)的列表,可以保存一份日常的參考
??聊聊問(wèn)題表達(dá)式
表達(dá)式的求值部分由操作符的優(yōu)先級(jí)決定,優(yōu)先級(jí)只能決定先算誰(shuí),但是哪個(gè)表達(dá)式先調(diào)用要取決于編譯器
- 對(duì)于有些表達(dá)式而言,其實(shí)在不同的編譯器上所呈現(xiàn)的結(jié)果是不同的,我們將其稱作為【問(wèn)題表達(dá)式】
① 問(wèn)題表達(dá)式1
a*b + c*d + e*f
- 來(lái)看上面這段代碼,通過(guò)上面的優(yōu)先級(jí)列表可以看出
[*]
的優(yōu)先級(jí)一定是比[+]
要來(lái)得高,因此可以保證[*]
兩端的數(shù)字先進(jìn)行運(yùn)算,但是卻不能保證第三個(gè)*比第一個(gè)+早執(zhí)行
- 可以看到,即使是存在操作符的優(yōu)先級(jí),但是這個(gè)表達(dá)式的計(jì)算還是存在一個(gè)歧義
② 問(wèn)題表達(dá)式2
- 繼續(xù)來(lái)看下一個(gè)問(wèn)題表達(dá)式
//表達(dá)式2
c + --c;
- 雖然對(duì)于這個(gè)
[--]
操作符來(lái)說(shuō)比[+]
操作符的優(yōu)先級(jí)來(lái)得高,但是呢我們卻不知道在編譯器運(yùn)算的時(shí)候這個(gè)【c】是什么時(shí)候準(zhǔn)備好 - 這樣說(shuō)你可能不是很理解,可以看看我的這篇文章 ——> 反匯編深挖【函數(shù)棧幀】的創(chuàng)建和銷毀。通過(guò)這篇文章你一定可以看出對(duì)于遍歷的創(chuàng)建時(shí)機(jī)其實(shí)在編譯器內(nèi)部是存在一個(gè)時(shí)間的先后順序的,你并不知道它何時(shí)會(huì)壓棧
- 通過(guò)畫圖分析我們也可以看出若是前面的這個(gè)c先入棧了,先準(zhǔn)備了,那么后的
--c
就是根據(jù)這個(gè)【2】來(lái)運(yùn)算;可若是這個(gè)--c
先執(zhí)行的話,后面再去加上這個(gè)c結(jié)果就不一樣了。因此也將其成為問(wèn)題表達(dá)式
③ 問(wèn)題表達(dá)式3
- 同樣,對(duì)于下面這段代碼,也是存在很大的爭(zhēng)議,特別是對(duì)于
++
和--
混搭的這種表達(dá)式尤其嚴(yán)重,你可以去不同的編譯器上運(yùn)行看看,結(jié)果都是不一樣的【這種代碼不要寫,練練思維就行】
//代碼3-非法表達(dá)式
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
- 下面是我在不同的編譯器上運(yùn)行出來(lái)的結(jié)果,可見(jiàn)這個(gè)表達(dá)式
問(wèn)題
有多大!
值 | 編譯器 |
---|---|
- 128 | Tandy 6000 Xenix 3.2 |
- 95 | Think C 5.02(Macintosh) |
- 86 | IBM PowerPC AIX 3.2.5 |
- 85 | Sun Sparc cc(K&C編譯器) |
- 63 | gcc,HP_UX 9.0,Power C 2.0.0 |
4 | Sun Sparc acc(K&C編譯器) |
21 | Turbo C/C++ 4.5 |
22 | FreeBSD 2.1 R |
30 | Dec Alpha OSF1 2.0 |
36 | TDec VAX/VMS |
42 | Microsoft C 5.1 |
④ 問(wèn)題表達(dá)式4
- 繼續(xù)來(lái)看下面是一個(gè)有關(guān)函數(shù)調(diào)用的問(wèn)題表達(dá)式,函數(shù)內(nèi)部聲明了一個(gè)靜態(tài)的整型變量
count
,我們知道對(duì)于靜態(tài)變量是存放在內(nèi)存中的【靜態(tài)區(qū)】
,每一次運(yùn)算都是在上一次的運(yùn)算的結(jié)果后進(jìn)行一個(gè)累加 - 看到main函數(shù)中的函數(shù)調(diào)用表達(dá)式
answer = fun() - fun() * fun();
其實(shí)也是存在一個(gè)歧義的,因?yàn)槟阃耆恢谰幾g器先調(diào)用的是哪個(gè)fun()
//代碼4
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//輸出多少?
return 0;
}
- 可以看到,若是前面的fun()先執(zhí)行的話,最后的結(jié)果就是
-10
,若是后面的fun()先執(zhí)行的話,最后的結(jié)果就是-2
- 正常來(lái)說(shuō)大家應(yīng)該都認(rèn)為是第二個(gè)表達(dá)式符合我們的運(yùn)算規(guī)則,因?yàn)?code>先乘除后加減,可是呢我們最常用的VS出來(lái)的結(jié)果都不是我們想要的
我們可以到不同編譯器上面去觀察一下
- 可以看到,雖然在【VS】和【Linux】在執(zhí)行的結(jié)果是
-10
,而且在大多數(shù)的編譯器下都是這個(gè),但是呢對(duì)于函數(shù)的調(diào)用先后順序無(wú)法通過(guò)操作符的優(yōu)先級(jí)確定,因此這也是一個(gè)問(wèn)題表達(dá)式
⑤ 問(wèn)題表達(dá)式5【VS下反匯編調(diào)試觀察】
- 好,我們?cè)賮?lái)看最后一個(gè),有關(guān)
++
和+
結(jié)合的問(wèn)題表達(dá)式,這個(gè)我在之前的文章中也有提到過(guò),運(yùn)算出來(lái)的結(jié)果其實(shí)是存在歧義的
//代碼5
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
一樣,我們可以到不同的編譯器下去做一個(gè)測(cè)試
- 可以看到,這里就出現(xiàn)了兩個(gè)不同的結(jié)果,在VS里運(yùn)行出來(lái)是【12】,但是在Xshell里面運(yùn)行出來(lái)卻是【10】
下面我在VS下通過(guò)調(diào)用反匯編的指令帶大家來(lái)看一下在底層編譯器到底是如何執(zhí)行的,如果不懂可先看看我的這篇文章——> 反匯編深挖【函數(shù)棧幀】的創(chuàng)建和銷毀
- 將調(diào)試指針移動(dòng)到main函數(shù)的棧幀中代碼所要執(zhí)行的位置,便可以觀察到一些所對(duì)應(yīng)的匯編代碼
第一條指令
00631865 C7 45 F8 01 00 00 00 mov dword ptr [ebp-8],1
- 首先我們來(lái)看
int i = 1
,匯編指令為【mov】,意思是將1這個(gè)值放到main函數(shù)棧幀中ebp - 8
這個(gè)位置,也就相當(dāng)于是在這塊位置存放了變量i的地址,然后令它的值為1,那此時(shí)其實(shí)可以想到ebp - 8
和&i
的地址是一致的,我們可以通過(guò)【監(jiān)視窗口】來(lái)觀察一下
第二條指令
0063186C 8B 45 F8 mov eax,dword ptr [ebp-8]
- 接下去我們來(lái)看第二條指令,現(xiàn)在已經(jīng)進(jìn)入
(++i) + (++i) + (++i)
這個(gè)表達(dá)式。可以看到匯編指令為【mov】,通過(guò)后面的命令可以看出是將ebp - 8這塊地址的值放到寄存器eax中去,那么執(zhí)行完后eax = 1
第三條指令
0063186F 83 C0 01 add eax,1
- 接下去第三條匯編指令為【add】。很清楚,就是給eax寄存器中的值加1
第四條指令
00631872 89 45 F8 mov dword ptr [ebp-8],eax
- 第四條匯編指令為【mov】,意思是將eax所存放的值再放回
ebp - 8
這塊空間上去。通過(guò)上面一條指令我們知道此時(shí)eax里面存的值為2,并且ebp - 8
這塊地址和變量i的地址是一樣的,所以二、三、四條指令也就等價(jià)于++i
,只不過(guò)是利用寄存器eax做一個(gè)轉(zhuǎn)移
第五條指令
00631875 8B 4D F8 mov ecx,dword ptr [ebp-8]
- 第五條指令是【mov】,作用其實(shí)和第二條是一個(gè)意思,把ebp - 8這塊地址的值放到寄存器ecx中去,那么執(zhí)行完后
ecx = 2
第七條指令
00631878 83 C1 01 add ecx,1
- 接下去第三條匯編指令為【add】。和第三條是一樣的意思,就是給ecx寄存器中的值加1
第八條指令
0063187B 89 4D F8 mov dword ptr [ebp-8],ecx
- 第把條匯編指令為【mov】,和第四條是一樣的意思,將寄存器ecx中存放的值再放回
ebp - 8
這塊地址中去,也就相當(dāng)于++i
第九、十、十一條指令
0063187E 8B 55 F8 mov edx,dword ptr [ebp-8]
00631881 83 C2 01 add edx,1
00631884 89 55 F8 mov dword ptr [ebp-8],edx
- 接下去的第九、十、十一條指令和上面是一樣的,便不再贅述,給出最終結(jié)果
第十二、十三、十四、十五條指令
00631887 8B 45 F8 mov eax,dword ptr [ebp-8]
0063188A 03 45 F8 add eax,dword ptr [ebp-8]
0063188D 03 45 F8 add eax,dword ptr [ebp-8]
00631890 89 45 EC mov dword ptr [ebp-14h],eax
上面這五條指令一起說(shuō),因?yàn)楹蜕厦嫒龡l一樣是行云流水式的
-
首先將
ebp -8
里面的值存放到寄存器【eax】里面去
-
然后給【eax】的值加上一個(gè)
ebp - 8
里面存放的值,那也就是加上一個(gè)i的值,等價(jià)于(++i) + (++i)
-
然后再給【eax】的值加上一個(gè)
ebp - 8
里面存放的值,等價(jià)于(++i) + (++i) + (++i)
-
最后將上面計(jì)算出來(lái)eax里面的值存放到
ebp - 14
這塊地址中去,通過(guò)調(diào)試可以看到這塊地址和&ret
是一致的,也就是說(shuō)它們是同一塊空間,那也就是將最后的值存放到ret里面去,那么最后打印出來(lái)的ret也就是12
通過(guò)反匯編進(jìn)行觀察調(diào)試,這回應(yīng)該清楚了為什么最后的結(jié)果為12了吧
- 下面是在Linux環(huán)境下通過(guò)
objdump
進(jìn)行反匯編觀察
objdump -S a.out
- 在Linux下的反匯編調(diào)試這一塊比較復(fù)雜,就不展開(kāi)細(xì)講,這里你只需要知道對(duì)于操作符而言只有優(yōu)先級(jí)和結(jié)合性,沒(méi)法確定唯一計(jì)算路徑,所以這是一個(gè)問(wèn)題表達(dá)式
十二、總結(jié)與提煉【最后的舞臺(tái)】
好,來(lái)總結(jié)一下本文所學(xué)習(xí)的內(nèi)容?
- 本文我總共講到了46種操作符,可以說(shuō)是很全了,請(qǐng)讀者觀賞??
-
算術(shù)操作符:【+
加
】、【-減
】、【*乘
】、【/除
】、【%取余
】
-
位操作符:【&
按位與
】、【|按位或
】、【^按位異或
】、【~按位取反
】、【<<按位左移
】、【>>按位右移
】
-
賦值操作符:【=
賦值
】、【+=復(fù)合加
】、【-=復(fù)合減
】、【*=復(fù)合乘
】、【/=復(fù)合除
】、【%=復(fù)合取余
】、【<<=復(fù)合左移
】、【>>=復(fù)合右移
】、【&=復(fù)合按位與
】、【|=復(fù)合按位或
】、【^=復(fù)合按位異或
】、【~=復(fù)合按位取反
】
-
單目操作符:【!
邏輯反
】、【-負(fù)值
】、【+正值
】、【&取地址
】、【sizeof操作數(shù)的類型長(zhǎng)度
】、【- -前置、后置--
】、【++前置、后置++
】、【*間接訪問(wèn)
】、【()強(qiáng)制類型轉(zhuǎn)換
】
-
關(guān)系操作符:【>
大于
】、【>=大于等于
】、【<小于
】、【<=小于等于
】、【!=不等于
】、【==等于
】
-
邏輯操作符:【&&
邏輯與
】、【| |邏輯或
】
-
條件操作符:【?
三目運(yùn)算符
】
-
逗號(hào)表達(dá)式:【
exp1, exp2, exp3, …expN
】整個(gè)表達(dá)式的結(jié)果為最后一個(gè)逗號(hào)后面的表達(dá)式
- 下標(biāo)引用操作符:【[ ]】
- 函數(shù)調(diào)用操作符:【( )】
-
結(jié)構(gòu)成員調(diào)用操作符:【
.
】、【->
】
- 最后的話是講到了有關(guān)表達(dá)式的求值相關(guān)的概念。為讀者介紹了隱式類型轉(zhuǎn)換中的【整型提升】,知道了原來(lái)短整型和字符型的數(shù)據(jù)在內(nèi)存中是這樣變化的;然后說(shuō)到【算術(shù)轉(zhuǎn)換】,清楚了再兩個(gè)不同等級(jí)的數(shù)據(jù)類型一起操作的時(shí)候等級(jí)低的會(huì)轉(zhuǎn)化為等級(jí)高的;最后說(shuō)到了各種各樣的【問(wèn)題表達(dá)式】,也帶大家通過(guò)反匯編觀察了編譯器的執(zhí)行邏輯
以上就是本文要介紹的所有內(nèi)容,感謝您的觀看。記得給個(gè)三連哦??????文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-403911.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-403911.html
到了這里,關(guān)于C生萬(wàn)物 | 操作符匯總大全【庖丁解牛,精細(xì)講解】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!