C++學習之算術運算符使用詳解
1. 前言
編寫程序時,數據確定後,就需要為數據提供相應的處理邏輯(方案或算法)。所謂邏輯有2種存在形態:
- 抽象形態:存在於意識形態,強調思考過程,與具體的編程語言無關。
- 具體形態:通過代碼來實現。需要使用表達式描述完整的計算過程。
表達式由2個部分組成:
- 數據。也可稱為操作數。
- 運算符。
運算符是計算機語言提供的能對數據進行基本運算操作的功能體。開發者在實現自己的邏輯運算時,需要組合這些運算符來描述自己的邏輯運算過程。
Tip: 可以把C++
的運算符看成一種特殊語法格式的函數,或把C++
中的函數當成一種特殊的運算符。
在使用運算符時,需要遵守下面的2個基本原則:
- 運算符對操作的數據有內置的類型要求。如數學運算符要求操作數是數字類型。
- 如果運算符需要多個操作數時,則要求數據類型必須相同。如果出現類型不一致時,編譯器會試著把不同類型的數據轉換成同類型的數據後再進行運算。開發者也可以顯示進行強制類型轉換。
2. 運算符種類
C++
中的運算符非常多,如下是幾類常用的運算符:
- 算術運算符。
- 邏輯、關系運算符。
- 賦值運算符。
- 遞增、遞減運算符。
- 成員訪問運算符。
- 條件運算符。
- 位運算符。
sizeof
運算符。- 逗號運算符。
使用運算符前,需要理解如下幾個概念:
- 運算符的優先級: 不同類別中的運算符的優先級是不相同的。當在一個表達式中出現多個運算符時,則需要根據運算符的優先級進行先後運算。
- 運算符的操作數: 作用於一個操作數的運算符為一元運算符,作用於兩個操作數的運算符為二元運算符。
C++
中還有一個可作用於三個操作數的條件運算符。 - 結合性: 當復雜表達式中的多個運算符的優先級相同時,則要根據運算符的結合性進行運算。如
100/4*8
這個表達式,/
和*
的優先級是相同,因乘、除都是具有從左到右的結合性。所以先計算100/4=25
再計算25*8
。
Tip: 隻有當兩個運算符作用於同一個操作數時,優先級和結合性才有意義。
C++
中的基礎運算符較多,且因C++
是弱類型語言,每一種運算符在使用過程中都存在很多細節問題。算術運算符又是運算符中的基礎運算符。
本文試圖通過講解清楚算術運算符,讓閱讀者瞭解使用C++
運算符時應該註意的事項。
3. 算術運算符
3.1 功能描述
算術運算符用來對數字型數據進行數學語義上的加、減、乘、除。此類中有 5個運算符:
+
:對2個數字類型的數據進行數學語義上的加法運算。-
:對2個數字類型的數據進行數學語義上的減法運算。*
:對2個數字類型的數據進行數學語義上的乘法運算。/
:對2個數字類型的數據進行數學語義上的除法運算。%
:取餘或取模操作運算符。運算結果是兩個操作數相除後的餘數部分,不能用於浮點數據類型。
算術運算符是二元運算符。使用時,需要提供2個操作數。
3.2 運算符重載問題
C++
可以重載運算符,所謂重載運算符,指同一個運算符可以根據使用時的上下文信息,表現出不同的運算能力。如-
運算符, 當作為二元運算符時,用來對操作數進行相減操作。
int num1=30; int num2=20; //此處的 - 運算符表現出減法運算能力 int res=num1-num2; cout<<res<<endl; //輸出結果: 10
當作為一元運算符時,則是取負
的意思。如下代碼:
int num=-10; int num01=-num; cout<<num01<<endl; //輸出結果為 10,負負為正
同理,+
運算符也存在重載。
運算符重載是C++
中的一個特色。
對於有符號數據類型而言,如果在字面常量前面沒有顯示提供正、負符號,則默認為 +(正)
符號。
3.3 兩數相除的問題
當/
運算符作用於2個整型數字時,會得到舍棄小數點後的整數部分數值,或稱為兩數相除的商,意味著會丟失精度。
如下代碼:
int num1=7; int num2=3; int res=num1/num2; cout<<res<<endl; //輸出結果:2,丟失精度
如果要保留兩個數字相除的精度,則應該以浮點數據類型的身份進行相除。
double num1=7; double num2=3; double res=num1/num2; cout<<res<<endl; //輸出結果:2.33333
%
運算符作用於2個整型類型的數據時,運算結果是2個數字相除之後的餘數部分。如下代碼:
int num1=5; int num2=3; int res=num1 % num2; cout<<res<<endl; //輸出結果:2 。
%
用於浮點數據類型相除時,會出現編譯錯誤。也就是 %
隻能用於整型數據的運算,不能用於浮點數據類型。
3.4 關 於/和%運算符的正、負問題
當2個操作數據都是正數時
int num1=21; int num2=8; int res=num1 / num2; cout<<" / 運算:"<<res<<endl; res=num1 % num2; cout<<" % 運算:"<<res<<endl;
/
和%
動算符的輸出結果都是正數。
/ 運算:2
% 運算:5
當2個操作數都為負數時
int num1=-21; int num2=-8; int res=num1 / num2; cout<<" / 運算:"<<res<<endl; res=num1 % num2; cout<<" % 運算:"<<res<<endl;
輸出結果,一個是正數,一個是負數。
/ 運算:2
% 運算:-5
當2個操作數中被除數為負,除數為正時
int num1=-21; int num2=8; int res=num1 / num2; cout<<" / 運算:"<<res<<endl; res=num1 % num2; cout<<" % 運算:"<<res<<endl;
輸出結果都是負數。
/ 運算:-2
% 運算:-5
當2個操作數中被除數為正,除數為負時
int num1=21; int num2=-8; int res=num1 / num2; cout<<" / 運算:"<<res<<endl; res=num1 % num2; cout<<" % 運算:"<<res<<endl;
輸出結果為一負一正。
/ 運算:-2
% 運算:5
結論:
- 當2個數字使用
%
運算符進行相除操作時,運算結果的正負號與num1
操作數(被除數)的正負號保持一致。 /
運算符運算結果的正負號和數學上的語義一致。兩個操作數都為正或為負時則正正得正,負負得正。兩個操作數為一正一負時:則正負得負。
3.5 數據溢出問題
在使用算術運算符時,有可能出現數據溢出現象。如下代碼:
short num=32767; short num01=num+1; cout<<num01<<endl;
輸出結果:
數字:-32768
無符號short(16位)
的類型數據的最大值是 32767
,在此數字上加一,num01
的值理論是上 32768
。但實際結果是 -32768
。因為 32768
已經超過short
范圍,編譯器會重新計算出一個新的結果(並不是預期值)。這種現象叫數據溢出。
對於無符號 short
,可以認為其有 2
部分,一部分為負數,一部分為正數。當正數溢出後,會進入負數部分。
如下代碼,因溢出,超過瞭負數區域最小值,會溢出到正數區域。
short num1=-32768; short num2=num1-1; cout<<num2; //輸出結果:32767
數據溢出發生在當把數據類型范圍大的數據存儲到數據類型小的類型變量中時。
double
數據存儲到int
類型變量中。int
類型的數據存儲到short
類型變量中。long long int
類型的數據存儲到int
類型變量中時。- ……
數學運算符也可以用於指針類型運算,因指針變量其數據本質就是數字數據。但指針變量不能用於乘法和除法,加、減的語義是指針的向前後後移動,乘法、除法沒有語義價值。
3.6 類型轉換
根據運算符的基本使用原則,要求所有操作數的類型必須相同。
有時,在一個表達式中,即使存在多個操作數的類型不一致,也能正常工作。那是因為,編譯器會把不同的數據類型轉換成一致,然後再進行運算。
由編譯器完成的類型轉換,稱為自動(隱式)類型轉換:
- 整型提升:
C++
將bool
、char
、unsigned char
、signed char
和short
值轉換為int
。這些轉換被稱為整型提升。 - 浮點提升:整型類型自動向浮點類型轉換,如
int
向double
轉換。這種轉換是不會存在數據丟失問題,但會產生空間浪費。 - 向下縮窄: 當目標類型小於原類型時,如
double
向int
轉換,int
類型向short
轉換時,這種轉換是可以的,但會發生數據丟失的情況。可能會得不到預期結果。
碗裡的水倒到缸裡,不會丟失水。
缸裡面的水倒到碗裡,如果缸裡面的水很少,不夠或者剛夠一碗水,不會發生水丟失。但是,這裡會有潛在丟失問題,因為生活常識告訴我們,缸裡面的水往往是要超過一個碗所能盛下的容量。
所以,向下縮窄存在潛在的數據丟失風險。
如下代碼,其中發生瞭2次自動類型轉換,有數據丟失的潛在風險。
double num1=7; int num2=3; int res=num1/num2; cout<<res<<endl; //輸出結果: 2
- 浮點提升:
num2
中的數據會被轉換成double
數據類型,讓右邊的表達式符合同類型原則。此時,右邊表達式運算後的結果類型為double
。這一步不會發生數據丟失問題。 - 向下縮窄: 左邊的
res
變量類型為int
,編譯器會把右邊的double
類型結果轉換成int
。如果數值大於int
類型范圍時,則會出現丟失精度問題。
如下代碼,則不會發生數據丟失問題:
double num1=7; int num2=3; double res=num1/num2; cout<<res<<endl; //輸出結果:2.33333
如下的代碼,也會發生自動類型轉換。
int num1=20; char num2='A'; int res=num1+num2; cout<<res<<endl; //輸出結果: 85
char
類型會轉換成int
類型。- 字符保存在計算機上時,需要對其進行數字編碼,字符轉換成
int
的數字是底層的編碼數字。
如下代碼,也會發生自動類型。
int num1=20; bool num2=true; int res=num1+num2; cout<<res<<endl;
C++
中,bool
數據類型本質上就是int
類型。true
會轉換為1
。false
會轉換為0
。
3.7 {}賦值語法
C++
在進行自動類型轉換時,如果目標類型小於原類型時,也是能夠轉換的,這種現象叫縮窄。縮窄會存在潛存數據安全問題。C++11
提供瞭{}
賦值語法,會對超過范圍的縮窄進行編譯提示。如下代碼。
因 44555
數字已經超過 char
范圍,向下縮窄不被允許。
char c1= {44555};
因 X
是一個變量,在運行時,x
有可能被修改,並讓其值大於 char
數字范圍,向下縮窄不被允許。
int x=66; char c4={x};
3.8 強制類型轉換
C++
允許開發者顯式地進行類型轉換。語法格式有2種:
- (目標類型名)變量。
- 目標類型名(變量)。
強制類型轉換不會修改變量本身,而是創建一個新的值。用於表達式中進行計算。
double num1=23.6; //C++強制類型轉換語法 int num2=double(num1); cout<<num2<<endl; //C 強制類型轉換語法 num2=(double)num1; cout<<num2<<endl;
C++
還提供瞭 4 個類型轉換運算符,使得轉換過程更規范。這裡隻做簡要介紹,有興趣者可以深入瞭解一下。
dynamic_cast
。在類層次結構中進行向上轉換。const_cast
。用於執行隻有一種用途的類型轉換,即改變值為const
或volatile
。static_cast
。隻有當類型之間可以隱式轉換時才能轉換。reinterpret_cast
。用於一些有很大潛在危險的類型轉換。
3.9 auto 語法
auto
關鍵字在C++
的作用是自動類型推導。在聲明變量時,可以使用 auto
關鍵字,不指定變量的類型說明。編譯器會根據變量中所存儲的數據的類型自動推導出數據類型。
// num 是浮點數據類型 auto num=5.3; //num1 是整型數據類型 auto num1=4;
如 Python
、JS
就是一種動態語言,表現在數據類型可以底層編譯器自動識別。
雖然C++
有 auto
語法,但C++
歸屬於弱類型語言,在數據類型識別上,一半依賴於開發者的語法約束,一半依賴編譯器的自動識別。
4. 總結
因C++
語言的開放性,數據類型的自我適應性非常靈活。在一個表達式,當出現類型不同的情況時,編譯器會試圖進行各種類型上的轉換,讓表達式符合類型相同的運算原則。
寬松的好處是速度快,但也會帶來潛在的風險,開發者應該盡可能在語法上對數據類型進行約束,不要過於依賴編譯器。養成良好的編碼習慣。
以上就是C++學習之算術運算符使用詳解的詳細內容,更多關於C++算術運算符的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C++強制類型轉換(static_cast、dynamic_cast、const_cast、reinterpret_cast)
- C++ 數據類型強制轉化的實現
- C++ 強制類型轉換詳解
- 淺析C++中dynamic_cast和static_cast實例語法詳解
- C++ RTTI與4種類型轉換的深入理解