- C教程
- 鄭阿奇 丁有和編著
- 5743字
- 2018-12-30 07:30:52
2.5 算術運算
數值的算術運算是程序中最常見的,也是C語言最主要的應用之一。數學中,算術運算包括加、減、乘、除、乘方及開方等。但在C語言中,算術運算符只能實現四則運算的最基本功能,對于乘方和開方卻沒有專門的運算符,它們一般是通過頭文件math.h中定義的pow(求冪)、sqrt(求平方根)等庫函數來實現(見附錄C)。
2.5.1 算術運算符
由操作數和算術運算符構成的算術表達式常用于數值運算,與數學中的代數表達式相對應。在C語言中,最常用的算術運算符是加、減、乘、除和求余運算符,它們都是雙目運算符,所謂雙目運算符,或稱二元運算符,是指這樣的運算符需要兩個操作數。例如,6-8,6和8都是“-”運算符的操作數,由于是兩個操作數,因而“-”運算符是雙目運算符。
除此之外,算術運算符還有單目的正、負運算符,所謂單目運算符,或稱一元運算符,是指這樣的運算符僅需要1個操作數。例如,-4中的負運算符“-”就是一個單目運算符, 4是它的操作數。類似地,在C語言中,算術運算符有:
+ 正號運算符,如+4,+1.23等 – 負號運算符,如–4,–1.23等 ? 乘法運算符,如6?8,1.4?3.56等 / 除法運算符,如6/8,1.4/3.56等 % 模運算符或求余運算符,如40%11等 + 加法運算符,如6+8,1.4+3.56等 – 減法運算符,如6–8,1.4–3.56等
那么,C語言的算術運算和數學中的算術運算有什么區別呢?下面就正號、除法、求余這幾個運算符來討論。
1.正號運算符(+)
由于正號運算符并不改變操作數的符號,因而正號運算符是沒有什么實際意義的,僅為語法而設定。例如,+ -8就是-8;+a就是a,但+a是一個表達式,而a卻是一個變量。
2.除法運算符(/)
兩個整數相除,結果為整數,如7/5的結果為1,它會將小數部分去掉,而不是四舍五入;若除數和被除數中有一個是實數,則進行實數除法,結果是實型。如7/5.0,7.0/5, 7.0/5.0的結果都是1.4。之所以是這樣的結果是因為C語言有類型自動轉換機制(后面會討論)。
3.求余運算符(%)
求余運算(%)是指求左操作數(運算符左邊的操作數)被右操作數(運算符右邊的操作數)整除后的余數,或指求被除數整除后的余數。例如,7%4是求7被4整除后的余數,結果是3;40%5是求40被5整除后的余數,結果是0。
需要說明:
(1)求余結果的符號與被除數(左操作數)的符號相同,而不論除數(右操作數)是正還是負。例如,-7%4、-7%-4的結果都是-3;7%-4的結果是3;特殊地,-7%7或-7%-7或7/-7的結果都是0(因為0沒有正、負之分,因此-0和+0的補碼都是一樣的)。
(2)在算術運算符中,只有求余運算的兩個操作數要求都是整型值,含有實數的求余運算在C語言中是無效的。
(3)合理利用“/”和“%”運算符可以得到一個數的位數值。例如,下面的程序是將一個正的十進制的3位數變成逆序的3位數,例如將123變成321。
【例Ex_Rev.c】 正3位數的逆序轉換
#include <stdio.h> #include <conio.h> int main() { unsigned short a,b1,b2,b3,b; scanf( "%d", &a ); b1=a/100; /* 求百位上的數值 */ b2=(a % 100)/10; /* 求十位上的數值 */ b3=a % 10; /* 求個位上的數值 */ b =100*b3+10*b2+b1; printf( "the reverse of %d is %d\n", a, b); return 0; }
代碼中,b1是取百位上的數值,例如,若a = 123,則123/100 = 1;b2是取十位上的數值,若a = 123,則a%100的結果便是23,23/10=2,即得到十位上的數為2;同樣,通過a%10可以得到個位上的數。程序的運行結果如下:
123? the reverse of 123 is 321
事實上,除了上述運算外,C語言的其他算術運算符和數學運算的概念及運算方法也都是一致的,但需要注意算術運算中的數值類型、運算次序和表達式的值和類型等一些問題。
2.5.2 數值類型轉換
在由雙目運算符構成或由多個運算符構造的混合表達式中,由于存在兩個或兩個以上的操作數,當這些操作數的數據類型不一致時,就會出現這樣的問題:表達式最終是什么樣的結果?其結果值究竟是什么類型?例如,對于除法運算符,若有7/5,因7和5都是整數(整型),因而最后運算的結果應為整數1;類似地,若有5/7,則結果應為整數0。若有7.0/5.0,因7.0和5.0都是double型實數,因而最后運算的結果也應為double型實數1.4;但若有7.0/5或7/5.0,即其中一個操作數是實數,另一個是整數,則表達式結果的類型是什么呢?
為了保證數值運算結果的準確性,當運算符的多個操作數的數據類型不一致時,C語言編譯器會自動將低類型的操作數向高類型進行轉換,稱為類型的自動轉換。這里,類型的高與低,是指類型所能表示的最大數值的大小程度。例如,char型允許數據的最大值為127,而short型允許數據的最大值為32767。這樣,對于char和short來說,short就是高類型,而char就是低類型。
一般來說,由低類型向高類型轉換是不會丟失有效的數據位的,可見這種類型轉換是安全的轉換。如圖2.8所示箭頭方向表示轉換方向。

圖2.8 類型轉換的順序
顯然,按圖2.8中的自動轉換順序,則7.0/5或7/5.0相當于7.0/5.0,其結果是double實數1.4。需要強調:
(1)同類型的有符號自動轉換為無符號。由于這種轉換的結果與實際運算結果不一致,所以有的編譯器(如Visual C++ 6.0)就會出現相應的警告錯誤。警告錯誤雖不影響編譯的順利通過,但對于編程者來說,仍應引起足夠重視。特別要注意,當負數自動轉換為無符號時,由于負數補碼的最高位(符號位)是1,符號位被視為數據位,從而使負數變成一個很大的正數。例如,-6+1u的結果卻是65531(ANSI C結果),這是因為-6的補碼(16位)是(1111 1111 1111 1010)2,轉換成無符號時,-6就變成了65 530,從而導致最后的結果是65 531。
(2)當字符型自動轉換成其他類型時,實質上是將其編碼值進行轉換。換一句話說,當字符參與算術運算時,實質上就是其編碼值在參與運算。例如,‘a’+ 20,則其結果為117,即用于運算的是字符‘a’的編碼值97。
可見,自動轉換是C語言編譯器對多個類型不一致的操作數進行的默認處理。這種默認處理方式有時并不是程序所指定的結果,這時就指定在程序中對操作數(或表達式的值)進行強制轉換,即在操作數或表達式的左邊加上要轉換成的合法的類型名,且類型名兩邊加上圓括號“()”,格式如下:
(<類型名>)<操作數> (<類型名>)(<表達式>)
例如,在8/(int)3.1中,(int)3.1是將double型實數3.1強制轉換成整數3,這樣原來的表達式就變成了8/3,結果為整數2。
再比如,若有8 / (int)( 3.0 + 2.1 ),則(int)( 3.0 + 2.1 )是將表達式3.0 + 2.1的結果(double型實數5.1)強制轉換成整型,即為整數5,這樣,原來的表達式就變成了8/5,結果為整數1。
注意:
(1)使用類型的強制轉換時,類型名兩邊必須加上左括號和右括號,當被強制轉換的操作數是表達式時,表達式的兩邊也要加上圓括號,如(int)( 3.0 + 2.1 )。
(2)若強制轉換的類型比操作數或表達式的類型要高,則這種強制轉換是安全的,否則是不安全的,因為會丟失數據。如(int)3.8,結果為3,丟失0.8。
(3)在程序中要合理地使用強制轉換。例如,7/5,兩個操作數的類型都是整數,結果為1。若要想得到的結果為1.4,那么,(double)7 / 5、7 /(double)5和(double)7 /(double)5都是可以的,但(double)(7/5)是不行的,因為它對7/5這個表達式的值(為1)進行(double)類型的強制轉換,結果是1.0。
2.5.3 優先級和結合性
在由多個運算符構造的混合表達式中,表達式的運算次序顯得格外重要,因為這將影響其最后的運算結果。例如,2+3*4,究竟是先運算2+3,還是先運算3*4呢?
為了解決這個問題,C語言首先規定了各個運算符的優先等級,并用相對的數值來反映優先等級的高低,數值越小,優先級越高,數值相同,優先級相同,如附錄A所示。這里只列出算術運算符的優先級,如表2.3所示。
表2.3 算術運算符的優先級和結合性

從表中可以看出,在算術運算符中,單目運算符的優先級最高,其次是乘、除和求余,最后是加、減。這就是說,對于2+3*4來說,由于“*”的優先級比“+”高,故先運算3*4,結果為12,再運算2+12,結果為14。所以,在一個包含多種算術運算的混合運算中,先乘除后加減的運算規則是由運算符的優先級來保證的。
但當有:4%3*2,由于運算符“%”和“*”的優先等級數值都是3,即優先級相同。那么此時究竟是先運算4%3,還是先運算3*2呢?
C語言規定,優先級相同的運算符按它們的結合性來進行處理。所謂運算符的結合性是指運算符和操作數的結合方式,它有從左至右和從右至左兩種。從左至右的結合,簡稱左結合,是指操作數先與其左邊的運算符相結合,再與右邊的運算符結合;而自右至左的結合,簡稱右結合,次序剛好相反,它將操作數先與其右側的運算符相結合。
需要說明:
(1)上述左結合和右結合的定義僅僅是從其字面的含義而給出的。實際上,運算符的結合性的使用必須滿足這樣的條件:兩個相同優先等級的運算符共用一個操作數。
例如,在算術運算符中,除單目運算符外,其余算術運算符的結合性都是從左至右(參見表2.3)。這樣,由于4%3*2表達式中,運算符“%”和“*”共用一個操作數3,因它們的結合性是左結合,因此3先與左邊的運算符“%”相結合,亦即先運算4%3,結果為1,然后再與右邊的運算符“*”相結合,即1*2,結果為2。
(2)若不滿足結合性的使用條件,則具體的運算次序由編譯器來決定。例如, 2*3+4*5,究竟是先運算2*3還是先運算4*5,由編譯器來決定,即不同的編譯器有不同的處理方式,但運算的結果通常都是相同的。
(3)若表達式由優先等級相同的運算符構成,例如,2 * 3 * 4 * 5 * 6,則運算的次序由編譯器來決定。
在程序設計中,不同編譯器對表達式處理方式的不一致是一種常見現象,一般情況下,這種不一致的現象不會改變它們的最終結果。若最終結果會改變,則這種情況必須在程序中通過修改代碼來回避。
2.5.4 算術表達式的值和類型
由不同運算符構成的表達式的值和類型是不同的。對于算術表達式(由算術運算符構成的表達式)來說,最后表達式的結果總是表現為是優先級最低的那個運算符的表達式,其值的類型是該運算符可使用的操作數中最高的類型。
例如,10 + 'a' + 2*1.25 - 5.0/4L,則根據優先級和結合性,表達式的運算次序應為
(1)進行2*1.25的運算,因1.25是double型,故將2轉換成double型,結果為double型的2.5。這時表達式變為:10 + 'a' + 2.5 - 5.0/4L。
(2)進行5.0/4L的運算,因5.0是double型,故將長整型4L轉換成double型,結果值為1.25。這時表達式變為:10 + 'a' + 2.5 - 1.25。
(3)進行10 + 'a' 的運算,因10是int型,故將'a'轉換成int整數97,運算結果為107。這時表達式變為:107 + 2.5 - 1.25。
(4)整數107和2.5相加,因2.5是double型,故將整數107轉換成double型,結果為double型,值為109.5。這時表達式變為:109.5 - 1.25。
(5)進行109.5 - 1.25的運算,結果為double型的108.25。
可見,上述算術表達式的最終結果表現為由優先級最低的“-”運算符構成的表達式“109.5 - 1.25”,最后結果是它們的最高數據類型——double型,值為108.25。實際上,由于不同的編譯器對表達式的優化有所不同,因此上述運算次序可能不一樣,但結果一般應是相同的。
下面來看一個程序,試分析printf函數中的實參表達式“x + a % 3 * (int)(x+y) % 2 / 4”的值和類型。
【例Ex_Express.c】 分析表達式的值和類型
#include <stdio.h> #include <conio.h> int main() { int a=7; float x=2.5,y=4.7; printf( "%f\n", x + a % 3 * (int)(x+y) % 2 / 4 ); return 0; }
分析:
(1)上述代碼中,變量x和y的初值分別設定為double型的2.5和4.7,但由于變量x和y定義時指定的類型是float,因此x和y的實際初值分別為2.5f和4.7f。這種將高類型的數據用于低數據類型的變量初始化或賦值時,由于在轉換過程中存在數據丟失的危險,因而較好的編譯器在編譯時都會給出相應的警告錯誤。
(2)有了a,x和y的初值后,表達式x + a % 3 * (int)(x+y) % 2/4就變成2.5f + 7 % 3 *(int)( 2.5f +4.7f) % 2/4,由于圓括號的運算優先等級最高,故先運算( 2.5f +4.7f),結果為float型數值7.2f,然后運算(int) 7.2f,因為強制類型轉換運算符的優先等級僅次于圓括號,結果為int型數值7,這樣表達式就變成2.5f + 7 % 3 * 7 % 2/4。
()在表達式 * 中,由于運算符“”的優先等級是最低的,所以應先運算7 % 3 * 7 % 2/4,而這個式子中,運算符“*”、“/”和“%”的優先等級都是一樣的,因而應按其“從左至右”的結合性來運算。但要注意,編譯對程序代碼中的語義、句義和詞義的驗證和識別一般總是按“自上而下,從左至右”的順序來進行的。這就是說,表達式7 % 3 * 7 % 2/4首先被提取的應是“7 % 3 * 7”,由于結合性是“從左至右”的,故先運算7 % 3,結果為1,表達式變成1* 7 % 2 / 4,然后再提取、再運算,結果為0。這樣表達式2.5f + 7 % 3 * 7 % 2 / 4就成為2.5f + 0。
程序的運行結果如下:
2.500000
2.5.5 代數式和表達式
數值計算是所有高級語言的最典型的應用之一。為了能讓C程序進行數值計算,還必須將代數式寫成C語言合法的表達式。例如,若有代數式:

則相應的C語言的表達式應為
1.0 / 2.0 * ( a *x + ( a + x )/( 4.0 * a ) )
需要強調:
(1)注意書寫規范。在使用運算符進行數值運算時,在雙目運算符的兩邊與操作數之間一定要添加空格。若缺少空格,有時編譯器會得出與我們的設想不同的結果。例如:
–5*–6––7 /* 不合法的表達式 */
和

結果是不一樣的,前者發生編譯錯誤,而后者的結果是37。但對于單目運算符來說,雖然也可以使其與操作數之間存在一個或多個空格,但最好與操作數寫在一起。
(2)注意加上圓括號。在書寫C語言的表達式時,應盡可能地、有意識地加上一些圓括號。這不僅能增強程序的可讀性,而且,當對優先關系不確定時,加上圓括號是保證得到正確結果的最好方法,可見括號運算符“( )”的優先級幾乎是最高的。例如,若有代數式:

在代數式中,3ae和bc隱含了乘運算,這是代數式的一種約定。但在C語言中,這種約定是不允許的,相應的C語言的表達式應寫成:
(3.0 * a * e )/( b * c)
要注意圓括號“( )”中的左括號“(”和右括號“)”是成對出現的。
(3)注意數據類型。盡管在混合數據類型的運算中,C編譯器會將數據類型向表達式最高類型自動轉換,但這種轉換是有條件的。例如:
1/2 * ( 3.0 + 4 )
其結果為0.0。這是因為編譯器首先運算圓括號里的3.0 + 4,由于3.0是double型實型,故結果也為double型,值為3.4。此時表達式變為1/2*3.4,按結合性應先運算1/2,由于“/”運算符兩側的操作數都是整型,類型不會自動轉換成double,故其計算結果是整數0,最后運算0*3.4,由于3.4是double型,所以整個表達式的結果是double實數0.0。
可見,只有當雙目運算符兩側的操作數的類型不一致時,編譯器才會對其進行類型的自動轉換。為了保證計算結果的正確性,應盡可能地將操作數的類型寫成表達式中的最高類型。即上述表達式應寫成:
1.0/2.0 * ( 3.0 + 4.0 )
(4)注意符號^。數學中的符號^表示冪運算,而在C語言中,該符號表示位運算的異或操作,要注意它們的區別。例如,若有代數式:
x^2 – e^5
則相應的C語言的表達式可寫成:
x * x – pow(2.718281828, 5.0)
或寫成:
x * x – exp( 5.0)
pow和exp都是在頭文件math.h中定義的庫函數。其中,庫函數pow有兩個參數x, y,用來求其冪,即等于數學式x^y。exp函數用來進行以e為底的冪運算ex,x是該庫函數要指定的參數。
同時,作為技巧,對于數學式x^2或x^3等,應盡量使用連乘的形式(如x*x或x*x*x),而不要調用庫函數pow,因為每次函數調用都要額外占用內存,且計算速度也比較慢。
- Practical Data Analysis Cookbook
- Redis入門指南(第3版)
- JavaScript修煉之道
- iOS開發實戰:從零基礎到App Store上架
- 精通Scrapy網絡爬蟲
- MySQL數據庫管理與開發實踐教程 (清華電腦學堂)
- Hands-On GPU:Accelerated Computer Vision with OpenCV and CUDA
- 用戶體驗增長:數字化·智能化·綠色化
- 深入分布式緩存:從原理到實踐
- SQL Server數據庫管理與開發兵書
- Learning Material Design
- Deep Learning with R Cookbook
- App Inventor 2 Essentials
- Python Machine Learning Blueprints:Intuitive data projects you can relate to
- 進入IT企業必讀的324個Java面試題