2.3 C語言運算符
什么是運算符呢?當然是能進行相關運算的一些符號啦!就像小學數學里所學到的“+、-、×、÷”四則運算符。剛才已經學到賦值運算符了,當然C語言中還有大量的運算符,這些運算符若從所需要的操作數個數上看,可分為一目、二目和三目運算符。例如賦值運算符,它需要左右兩個操作數,所以它就是二目運算符;對于用作說明一個數是正數還是負數的正號運算符“+”和負號運算符“–”,由于它只需要一個操作數,所以它就是一目運算符!至于三目運算符,就是同時需要三個操作數了。其實C語言中只有一個三目運算符,物以稀為貴,好東西我們放到后面再講,不過先提醒一句,C語言中的所有運算符都需要使用英文字符,千萬不要使用中文的標點符號了(初學者常犯的錯誤)。
2.3.1 算術運算符
算術運算符應該是我們最為熟悉的運算符,加、減、乘、除,從小學就認識它,不過C語言里還多了個取模運算符,用于求兩個整數相除后所得的余數,因此也被叫作“求余運算符”。這5個算術運算符都是二目運算符,使用案例見表2.9。
表2.9 算術運算符

從表里可以看出,與我們從小所熟知的運算相比,有兩點不同之處:①乘法運算符是一個星號“*”,而不是傳統的“×”;除法運算符是一個斜杠“/”,而不是傳統的“÷”。②“7 / 4”的結果是整數值“1”,而不是小數值“1.75”。這是因為C語言規定,進行算術運算時,在左右兩個操作數中,將以較大的那個數據類型為標準進行運算,也就是會先把較小的那個數據類型轉換成較大的數據類型,然后再進行運算。這種把小類型自動轉為大類型的過程,我們把它稱為“隱式類型轉換”。C語言的基本數據類型從小到大排列如下(在本書中long與int具有同等大小,所以沒有列出long):
char < unsigned char < short < unsigned short < int < unsigned < float < long long < double < long double
在本例中,由于兩個操作數都是整型的,不需要進行隱式類型轉換,所以結果仍為整型。若是把其中的一個操作數變成double類型的,那么就會發生隱式類型轉換,先把int類型轉換為double類型,然后再進行運算。假若現在的式子為“7.0 / 4”,就需要先把整型的“4”轉換成double類型的“4.0”,然后再運算,得出一個double類型的結果“1.75”;同理,若式子為“7 / 4.0”,就會把整型“7”轉換為double類型的“7.0”,再運算并得出結果“1.75”。
下面再講一下在使用算術運算符時的一些注意點。
小學數學老師告訴過我們:“先乘除、后加減”,這在C語言中仍然有效,也就是算術運算符中的乘法、除法以及取模運算符的優先級要比加法、減法的優先級高。
在除法運算中,除數不能為0,否則就會得到一個錯誤的結果(例如得到一個表示無窮大或無窮小的值),并且容易使程序出現異常。
取模運算時兩邊的操作數都應是整型,并且只有左邊操作數才會影響到結果的正負關系,即左操作數若為正值,則取模結果也為正值。反之,若左操作數為負值,取模結果也為負值。例如:“7 % –4”的結果仍為3,但“–7 % 4”的結果為–3。
2.3.2 關系運算符
關系運算符和算術運算符一樣,也都是二目運算符,算術運算符的作用是為了求值,而關系運算符的作用是用于比較左右兩個操作數的大小關系。因此,筆者更喜歡把“關系運算符”稱為“比較運算符”。當然比較的結果無非就是“是”與“否”兩個,但C語言把“是”用“真(1)”來表示,把“否”用“假(0)”來表示。也就是通過關系運算符運算的結果非“1”即“0”。C語言中關系運算符見表2.10。
表2.10 關系運算符

對于每一種關系運算,我們可以把它想象成是老師在向我們提問。例如老師問:“5和8是否相等?”同學們回答:“否”,否的話就表示假,那么結果就是0。反之,如果老師問:“5和8是否不相等?”同學們回答“是”,是的話就表示真,結果就是1。
不過不得不提的是,能夠表示真的值不僅僅只是1,而是任何的非零值。也就是說在C語言中,只有值為0表示假,其他的都表示真,只不過通常都用1來表示真罷了。
最后還有一點,對于由兩個字符組成的運算符,書寫的時候,字符的順序不可顛倒,除非構成運算符的兩個字符是相同的。例如“!=”不可寫成“=!”,“>=”不可寫成“=>”。
2.3.3 邏輯運算符
C語言中有3個邏輯運算符,分別是邏輯非(!)、邏輯與(&&)、邏輯或(||),通過邏輯運算的結果和關系運算符一樣,都是真(1)或假(0),所以也常把這種值稱為邏輯值。邏輯運算符是把操作數當成邏輯值來看待,并進行相關運算。
具體的邏輯運算符使用方式如表2.11所示。
表2.11 邏輯運算符

邏輯非運算符的作用是得到一個反轉操作數的邏輯值,即操作數若為真(非零值),則得到的結果為假,反之,若操作數為假(零值),得到的結果就為真(值為1)。
使用邏輯與運算符和邏輯或運算符的時候也要注意,這兩個運算符有“短路”效果,不過不要害怕機器會爆炸,它不是電路的短路,而是運算的短路。當使用邏輯與運算符時,若左操作數的結果為假,則直接返回結果為假,而不會去檢查右操作數;同樣地,使用邏輯或運算符時,若左操作數的結果為真,則直接返回結果為真,也不會再去檢查右操作數了。這種只要通過左操作數就能得知結果,而不用去檢查右操作數的行為就稱為邏輯運算的“短路”現象。
2.3.4 位運算符
前面講過,二進制碼中最小的單位是位(bit),8位構成1字節。但我們前面所講的算術運算符、關系運算符和邏輯運算符都不能對位直接進行操作,如果想要對位進行相應的操作就需要用到位運算符,如表2.12所示。
表2.12 位運算符

為了更好地理解位運算符,下面舉例說明整數23,其8位的二進制碼為“0001 0111”。
23 << 1:進行按位左移1位的操作,則會把這8位都向左移動1位,原來的最高位0被移出拋棄,最低位補0,最終得到“0010 1110”這樣一個8位的二進制碼,對應的整數值為46,正好是23的2倍。
23>>1:進行按位右移1位的操作,則會把這8位都向右移動1位,原來的最低位1被移出拋棄,最高位補符號位0,最終得到“0000 1011”這樣一個8位的二進制碼,對應的整數值為11,正好是23被2整除的結果。
~23:對8位二進制碼進行按位取反,得到結果“1110 1000”,最高位由0變成了1,所以成了一個負數,對應的整數值為–24。
現在再來一個整數50,其8位二進制碼為“00110010”。
23 & 50:將兩個整數的二進制碼的每一位進行按位與的操作,即對應的兩位都為1時,結果為1,否則為0,得到結果碼為“0001 0010”,對應的整數值為18。
23 | 50:將兩個整數的二進制碼的每一位進行按位或的操作,即對應的兩位都為0時,結果為0,否則為1,得到結果碼為“0011 0111”,對應的整數值為55。
23 ^ 50:將兩個整數的二進制碼的每一位進行按位異或的操作,即對應的兩位不同(一個為1,一個為0)時,結果為1,兩位相同(同為1或同為0)時為0,得到結果碼為“0010 0101”,對應的整數值為37。
這里的按位與運算符與邏輯與運算符、按位或運算符與邏輯或運算符、按位取反運算符與邏輯非運算符看起來是有些類似,但有著本質的不同:邏輯運算符都是對操作數進行運算的,而位運算符是對操作數的二進制位進行運算的,這一點要謹記。
2.3.5 復合賦值運算符
把前面所學的賦值運算符與算術運算符或部分位運算符結合就會構成復合賦值運算符,使用復合賦值運算符可以起到簡化代碼、提高編譯效果的作用。不過這些運算符只能對可修改的變量使用,不可用于常量。假設有整型變量a,可通過復合賦值運算符對它進行操作,具體如表2.13所示。
表2.13 復合賦值運算符

2.3.6 帶副作用的運算符
算術運算符、關系運算符、邏輯運算符和位運算符,不管是單目還是雙目,都有一個共同之處:這些運算符不會修改操作數,只會通過運算產生一個新值作為結果返回。例如“!0”,表示對操作數0(假)進行邏輯非運算,這會產生一個新的值1(真)作為結果返回,而不是把0(假)修改成1(真);再例如“23 << 1”,表示將左操作數23按位左移1(右操作數)位后,產生一個新值46作為結果返回,它并不會修改左操作數23和右操作數1的值。
那么有沒有可以修改操作數的運算符呢?答案是肯定的,例如之前學過的賦值運算符和復合賦值運算符,這些運算符都會把產生的結果賦值給左操作數,也就是它修改了左操作數的值。我們通常把這些能夠改變操作數的行為稱為“副作用”,把擁有這類行為的運算符稱為“帶副作用的運算符”。賦值運算符和復合賦值運算符就是屬于這種帶副作用的運算符。這時,肯定有讀者會好奇,想刨根問底,C語言中還有其他帶副作用的運算符嗎?哈哈,你猜!
2.3.7 自增、自減運算符
這兩個運算符的名字挺有趣,不過先在這兒告訴大家,這兩個運算符是最簡單的,同時也是最讓人頭疼的兩個運算符,很容易讓人疑惑。
先說它簡單的原因吧,它們都是一目運算符,作用就是對操作數進行加1或減1的操作,自增運算符就是讓操作數加1,自減運算符就是讓操作數減1,是不是很簡單?當然看到這也該知道它們都是帶副作用的運算符了吧。
那又為什么會說它們是容易讓人疑惑的運算符呢?因為它們會“變身”。不可思議吧,它們“搖身一變”就會各自多出個孿生的兄弟出來,讓人不小心就被迷惑,分不清誰是誰了。也就是說自增、自減運算符不是兩個,而是四個運算符。為了分清它們,把它們中的一個稱為“前綴的”,另一個稱為“后綴的”,所以就有了兩個前綴的自增、自減運算符和兩個后綴的自增、自減運算符。
還是通過一個例子讓它們露出“廬山真面目”吧,例如現在有一個整型變量a,它的初始值為1,現通過自增、自減運算符來對它進行操作,看看是何結果,具體見表2.14。
表2.14 自增、自減運算符

先來看自增運算符,所謂前綴就是運算符在操作數的前面,后綴就是運算符在操作數的后面,不管是使用前綴或是后綴,通過運算都會讓操作數加1,也就是變量a的值都會被修改(運算符的副作用產生的效果)為2。也許有讀者注意到表里的后綴自增所對應的“結果”欄里明顯是1!注意!“結果”欄里顯示的不是變量a的最終值,而是通過這個自增運算符運算后產生的新值。如果使用前綴自增運算符,新值就是操作數加1之后的值,如果使用的是后綴自增運算符,則新值就是操作數加1之前的值。
重點就在這里:如果我們只是單純地希望操作數加1,而不會去使用這個新值,則不管使用前綴的或后綴的自增運算符都可以;反之,如果需要使用這個新值,則前綴的與后綴的就有區別了,下面再用代碼片段來說明一下:

最終4個變量中,由于經過自增運算,變量a和b的值都被修改為11,變量m得到的新值為變量a修改(加1)之后的值,所以也是11,而變量n得到的新值為變量b修改之前的值,所以是10。
自增運算符如果搞懂了,那么自減運算符也就自然懂了,此處不再贅述。
2.3.8 其他運算符
一下學了這么多的運算符,是不是C語言的運算符都學完了呢?沒有!不過剩下的也不太多了,而且部分運算符會留到后面的章節中再講。下面再來講幾個比較常用的運算符。
1.類型轉換運算符“( )”
在講算術運算符的時候說過,如果左右兩個操作數類型不同,那么相對較小的數據類型會自動地轉換成較大的數據類型,然后再進行運算,這種自動將小類型轉換為大類型的行為就屬于隱式類型轉換。那么現在要講的這個類型轉換運算符就屬于顯式的類型轉換,它不但可以像隱式類型轉換一樣將一個小類型轉換為大類型,而且也可以將一個大類型轉換為一個小類型,這是隱式類型轉換做不了的,所以它的功能更強大。類型轉換運算符的使用方式如下:
( 數據類型 ) 操作數
其中“( )”為類型轉換運算符,小括號內的數據類型表示要轉換的目的數據類型,即在操作數的基礎上,得到一個目的數據類型的值作為結果返回。例如:
double d = 3.14; //定義一個雙精度浮點數類型變量d,其初始值為3.14 int a = (int)d; //對變量d進行類型轉換,得到整型值3作為整型變量a的初始值
這個例子中,變量d是double類型的,其值為3.14,通過類型轉換運算符將其進行轉換,得到一個整型新值3(拋棄了小數部分),并把它賦給整型變量a。需要注意的是,類型轉換運算符不是帶副作用的運算符,所以它的操作數(變量d)并不會被修改,它仍然是double類型的,值也依然是3.14。
此外,在C語言中小括號“( )”并非都是作為類型轉換運算符來使用的,例如下面的逗號運算符例子中,會將小括號用于一個賦值表達式中,從而起到提升優先級的作用。
2.逗號運算符
逗號也是個運算符?沒錯!但不是C語言中所有的逗號都是運算符,例如前面在定義變量的時候可以使用逗號:
int a, b, c;
這里定義了三個整型變量,每個變量名之間用逗號隔開。這兒的逗號就不是運算符,它只是個分隔符。其實不只是這里,在很多時候逗號也不算為運算符。例如在函數的參數列表中也會用逗號來分隔各個參數;在為數組初始化的時候,也會在初始值列表里用逗號來分隔各個值。關于函數與數組我們會在后面講到。
那什么情況下逗號才是運算符呢?我們先來看看逗號運算符的使用方式:
操作數1, 操作數2, 操作數3, …
像這樣位于多個操作數間的逗號,就是逗號運算符,是不是很簡單?那逗號運算符有什么作用呢?其實也很簡單,既然是運算符,就會有運算的結果,逗號運算符的運算結果就是最后一個操作數的值。例如:
int a; a = (3, 4, 5);
上面的小括號內共有3個操作數,操作數之間的逗號就是逗號運算符。由于最后一個操作數的值是5,所以逗號運算符的運算結果就是5,也就是最終會把5賦值給整型變量a。需要注意的是,這兒的小括號不是類型轉換的意思,而是為了提升小括號內逗號表達式的優先級,因為逗號運算符的優先級比賦值運算符的優先級低(逗號運算符是C語言所有運算符中優先級最低的),所以若沒有小括號,就會把“a = 3”作為第一個操作數來使用了,而逗號運算符的運算結果5被拋棄,最終變量a的值為3。
3.條件運算符
C語言中唯一的三目運算符出場啦!它就是條件運算符,符號為“?:”,一個英文的問號和一個英文的冒號。它的使用方式如下:
操作數1 ? 操作數2 : 操作數3
三個操作數被問號和冒號所分隔,那這個條件運算符的運算結果是什么呢?有兩種情況:①若操作數1為真(非零值),則將操作數2的值作為運算結果;②若操作數1為假(零值),則將操作數3的值作為運算結果。也就是根據操作數1是真是假這個條件,來決定結果是操作數2還是操作數3,二者中必選其一。例如:
int a, b; a = 1 ? 10 : 100; //條件運算符的結果為操作數2的值 b = 0 ? 10 : 100; //條件運算符的結果為操作數3的值
由于1為真,所以變量a的值被賦為操作數2的值10;而0表示假,所以變量b的值為操作數3的值100。
4.sizeof運算符
前面所學的運算符都是由符號構成的,而sizeof運算符是C語言中唯一一個由字母構成的運算符。它的作用是獲取操作數的大小。這個操作數可以是一種數據類型,也可以是某種數據類型的常量或變量。它的使用方式是:
sizeof (操作數);
在小括號中放入一個操作數,sizeof運算符就可以返回它的大小,以字節為單位。通過sizeof運算符就可以很方便地獲知某種數據類型在內存中所占用的空間大小。例如:

sizeof運算符是不是很厲害啊?另外,還有一個小竅門:若操作數是一種數據類型,那么必須使用小括號,如果操作數并非是數據類型的話,就可以省略小括號,像下面這樣來使用:

- Deploying Node.js
- Python量化投資指南:基礎、數據與實戰
- Visual FoxPro程序設計教程(第3版)
- OpenCV實例精解
- 程序員面試筆試寶典
- Django開發從入門到實踐
- Reactive Programming with Swift
- Hands-On Natural Language Processing with Python
- Swift語言實戰精講
- Asynchronous Android Programming(Second Edition)
- SQL Server實用教程(SQL Server 2008版)
- Java Fundamentals
- 計算機應用基礎教程(Windows 7+Office 2010)
- Mastering Adobe Captivate 7
- Node.js 6.x Blueprints