標籤:c++ c語言 編程 語言 教程
在之前幾節我們講過資料類型、講過函數、講過代碼執行順序以及一些添加簡單函數的方法。
這一節我們將著重講講運算子。包括運算子的含義以及優先順序的概念
在C語言中,以下運算子是被公認的:
C/C++語言運算子詳解
| 優先順序 |
運算子 |
名稱以及含義 |
運算目 |
使用樣本 |
結合方向 |
可否重載 |
附加說明 |
| 無 |
() |
圓括弧 |
單目 |
(運算式) |
無 |
否 |
括弧內的表達永遠先計算 |
| 無 |
dynamic_cast<>() |
類型動態轉化 |
單目 |
dynamic_cast<目標類型>(源) |
無 |
否 |
C++專有,不能轉換返回空 |
| 無 |
static_cast<>() |
類型靜態轉化 |
單目 |
static_cast<目標類型>(源) |
無 |
否 |
C++專有,不檢測轉換【注1】 |
| 無 |
reinterpret_cast<>() |
類型解讀轉化 |
單目 |
reinterpret_cast<目標類型>(源) |
無 |
否 |
C++專有,強制轉換【注1】 |
| 無 |
const_cast<>() |
去常量轉化 |
單目 |
const_cast<目標類型>(源) |
無 |
否 |
C++專有,可將常量轉非常量 |
| 0 |
:: |
範圍解析 |
雙目 |
網域名稱::目標名 |
從左向右 |
否 |
C++專有 |
| 1 |
[] |
下標運算子 |
雙目 |
數組名[運算式] |
從左向右 |
可 |
兩種語言都有 |
| 1 |
() |
函數調用 |
雙目 |
函數名(參數) |
從左向右 |
可 |
兩種語言都有 |
| 1 |
. |
對象成員選擇 |
雙目 |
對象.成員 |
從左向右 |
否 |
兩種語言都有 |
| 1 |
-> |
指標成員選擇 |
雙目 |
對象指標->成員 |
從左向右 |
否 |
兩種語言都有 |
| 1 |
{} |
組合運算 |
未知 |
{語句1;語句2;…語句n;} |
從左向右 |
否 |
兩種語言都有 |
| 2 |
++ |
自增運算 |
單目 |
變數++ ++變數【注2】 |
從左向右 從右向左 |
可 |
兩種語言都有 |
| 2 |
-- |
自減運算 |
單目 |
變數-- --變數【注2】 |
從左向右 從右向左 |
可 |
兩種語言都有 |
| 2 |
+ |
正號 |
單目 |
+變數 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
- |
負號 |
單目 |
-變數 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
! |
邏輯非 |
單目 |
!運算式 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
~ |
按位取反 |
單目 |
~變數 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
* |
指標解引用 |
單目 |
*指標 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
& |
取地址 |
單目 |
&變數 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
(類型) |
強制類型轉換 |
單目 |
(類型)源 |
從右向左 |
可 |
兩種語言都有 |
| 2 |
sizeof |
擷取變數佔用 空間大小記憶體 |
單目 |
sizeof(源) |
從右向左 |
否 |
兩種語言都有 |
| 2 |
new/new[] |
分配記憶體 |
單目 |
new 類型名 或 new[]類型名 |
從右向左 |
可 |
C++專有 |
| 2 |
delete/delete[] |
釋放記憶體 |
單目 |
delete 指標 或者 delete[]指標 |
從右向左 |
可 |
C++專有 |
| 3 |
.* |
成員對象選擇 |
雙目 |
對象.*成員名 或 對象.*成員指標 |
從左至右 |
可 |
C++專有 |
| 3 |
->* |
成員指標選擇 |
雙目 |
對象指標->*成員名 或 對象指標->.*成員指標 |
從左至右 |
可 |
C++專有 |
| 4 |
* |
乘法 |
雙目 |
變數1*變數2 |
從左至右 |
可 |
兩種語言都有 |
| 4 |
/ |
除法 |
雙目 |
變數1/變數2 |
從左至右 |
可 |
兩種語言都有 |
| 4 |
% |
取餘 |
雙目 |
變數1%變數2 |
從左至右 |
可 |
兩種語言都有 |
| 5 |
+ |
加法 |
雙目 |
變數1+變數2 |
從左至右 |
可 |
兩種語言都有 |
| 5 |
- |
減法 |
雙目 |
變數1-變數2 |
從左至右 |
可 |
兩種語言都有 |
| 6 |
<< |
按位左移 |
雙目 |
變數1<<變數2 |
從左至右 |
可 |
兩種語言都有 |
| 6 |
>> |
按位右移 |
雙目 |
變數1>>變數2 |
從左至右 |
可 |
兩種語言都有 |
| 7 |
< |
小於 |
雙目 |
變數1<變數2 |
從左至右 |
可 |
兩種語言都有 |
| 7 |
<= |
不大於 |
雙目 |
變數1<=變數2 |
從左至右 |
可 |
兩種語言都有 |
| 7 |
> |
大於 |
雙目 |
變數1>變數2 |
從左至右 |
可 |
兩種語言都有 |
| 7 |
>= |
不小於 |
雙目 |
變數1>=變數2 |
從左至右 |
可 |
兩種語言都有 |
| 8 |
== |
邏輯等 |
雙目 |
變數1==變數2 |
從左至右 |
可 |
兩種語言都有 |
| 8 |
!= |
不等於 |
雙目 |
變數1 != 變數2 |
從左至右 |
可 |
兩種語言都有 |
| 9 |
& |
與運算 |
雙目 |
變數1&變數2 |
從左至右 |
可 |
兩種語言都有 |
| 10 |
^ |
異或運算 |
雙目 |
變數1^變數2 |
從左至右 |
可 |
兩種語言都有 |
| 11 |
| |
或運算 |
雙目 |
變數1|變數2 |
從左至右 |
可 |
兩種語言都有 |
| 12 |
&& |
邏輯與 |
雙目 |
變數1&&變數2 |
從左至右 |
可 |
兩種語言都有 |
| 13 |
|| |
邏輯或 |
雙目 |
變數1||變數2 |
從左至右 |
可 |
兩種語言都有 |
| 14 |
?: |
條件運算 |
三目 |
變數1?變數2:變數3 |
從右至左 |
否 |
兩種語言都有 |
| 15 |
= |
賦值 |
雙目 |
變數1=變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
+= |
加後賦值 |
雙目 |
變數1+=變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
-= |
減後賦值 |
雙目 |
變數1-=變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
*= |
乘後賦值 |
雙目 |
變數1*=變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
/= |
除後賦值 |
雙目 |
變數1/=變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
%= |
餘後賦值 |
雙目 |
變數1 %= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
<<= |
左移賦值 |
雙目 |
變數1 <<= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
>>= |
右移賦值 |
雙目 |
變數1 >>= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
&= |
與後賦值 |
雙目 |
變數1 &= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
^= |
異或賦值 |
雙目 |
變數1 ^= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 15 |
|= |
或後賦值 |
雙目 |
變數1 |= 變數2 |
從右至左 |
可 |
兩種語言都有 |
| 16 |
throw |
拋異常 |
單目 |
throw 變數1 |
從右至左 |
否 |
C++專有 |
| 17 |
, |
逗號運算 |
雙目 |
變數1,變數2 |
從左至右 |
可 |
兩種語言都有 |
注1: reinterpret_cast<>()和static_cast<>()貌似都是強制轉換,但是兩者是不同的
觀察下面的代碼:
class A {public:A(){ m_a = 1; }int m_a;};class B {public:B(){ m_b = 2; }int m_b;};class C : public A, public B {};void OperatorPriority(){C c;printf("%p, %p, %p\n", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));B* b1 = &c, *b2 = reinterpret_cast<B*>(&c), *b3 = static_cast <B*>(&c);printf("%d, %d, %d\n", b1->m_b, b2->m_b, b3->m_b);}
執行OperatorPriority()函數後,輸出結果如下:
這是什麼意思?
這說明在轉換的時候reinterpret_cast<>()是無腦轉換,完全不管前因後果,進行強制轉換
而static_cast<>()會先嘗試匹配一下,能匹配則會做出處理,否則則進行強制轉換
所以m_b static_cast<>()會輸出正確值,而reinterpret_cast<>()會給出一個錯誤值
但是為什麼強制類型轉換又可以正確輸出m_b呢?這就涉及到c++的多態特性,以後我會講到的。這裡只討論運算子,不做深入講解。
注2:首碼自增/減運算 和 尾碼自增/減運算
首碼自增/減 是這樣的 ++變數 --變數
尾碼自增/減 是這樣的 變數++ 變數--
有很多資料說尾碼自增/減運算 優先順序高於首碼
這個說得太簡單,實際情況非常複雜(也就是說這種說法完全是拍腦袋,沒有實踐的扯淡!)
首先,來看代碼:
從上面的代碼可以很明顯的看出,如果認為尾碼自增運算有所謂的高優先順序,那麼完全就說不通
首先++a--;語句根本就是個語法錯誤!首碼和尾碼運算子不能同時進行,所以也無法比較他們的優先順序。
不過這不能說明網上的優先順序說明是錯誤的,因為我們可以拿它們同級的其他運算子進行比較
注意test002函數 !運算是和首碼自增運算同級的運算子,按理說應該是先計算尾碼自增然後再計算非,
這樣就應該等價於a=!(a++) 即結果應該為0 。但是為什麼輸出的卻是1呢?
答案很複雜。上面真實的運算是這樣進行:
先計算計算a++ 即0++=1 儲存結果到a 然後按照a=0計算!a即 !0=1,再次儲存結果到a 導致的結果就是a最後變成了1
下面是該代碼的反組譯碼
a = !a++;00113EBD 8B 45 F8 mov eax,dword ptr [a] 00113EC0 89 85 30 FF FF FF mov dword ptr [ebp-0D0h],eax 00113EC6 8B 4D F8 mov ecx,dword ptr [a] 00113EC9 83 C1 01 add ecx,1 00113ECC 89 4D F8 mov dword ptr [a],ecx 00113ECF 83 BD 30 FF FF FF 00 cmp dword ptr [ebp-0D0h],0 00113ED6 75 0C jne main+0B4h (0113EE4h) 00113ED8 C7 85 2C FF FF FF 01 00 00 00 mov dword ptr [ebp-0D4h],1 00113EE2 EB 0A jmp main+0BEh (0113EEEh) 00113EE4 C7 85 2C FF FF FF 00 00 00 00 mov dword ptr [ebp-0D4h],0 00113EEE 8B 95 2C FF FF FF mov edx,dword ptr [ebp-0D4h] 00113EF4 89 55 F8 mov dword ptr [a],edx
從表面上看,尾碼++好像是優先運算的。但是我們不能只關注這點,而是要關注它對結果的影響
運算並不是先計算的就是高優先順序的——如果該運算更不不能影響結果
這個運算式的運算a++的結果是被捨棄了,也就是!運算子屏蔽了尾碼++!!!
是不是感覺邏輯有點亂?沒有關係,我們再看其他函數,這樣你就會更加淩亂!
看函數test003 test004
003的計算是這樣進行的:先計算a+a 即 0+0 得到0,再計算自增 即0++ 得到1 。最後進行賦值,取a++的值1
下面是實際的計算過程
00DD3E55 8B 45 F8 mov eax,dword ptr [a] 00DD3E58 03 45 F8 add eax,dword ptr [a] 00DD3E5B 89 45 F8 mov dword ptr [a],eax 00DD3E5E 8B 4D F8 mov ecx,dword ptr [a] 00DD3E61 83 C1 01 add ecx,1 00DD3E64 89 4D F8 mov dword ptr [a],ecx
也就是說,這裡先計算了a+a,然後才計算的a++
這不是說明+運算子的優先順序高於尾碼++嗎!!
004的計算是這樣進行的:先計算2+a 即2+0 得到2,再計算自增,即2++,得到3
01383E89 8B 45 F8 mov eax,dword ptr [a] 01383E8C 83 C0 02 add eax,2 01383E8F 89 45 F8 mov dword ptr [a],eax 01383E92 8B 4D F8 mov ecx,dword ptr [a] 01383E95 83 C1 01 add ecx,1 01383E98 89 4D F8 mov dword ptr [a],ecx
得到同樣的邏輯結果 +運算子優先順序高於尾碼++
現在我們來總結一下:
尾碼運算子 和!運算子在一起的時候,尾碼運算子會被屏蔽
尾碼運算子和+-*/等運算子在一起的時候,先計算其他,再計算自增
現在你是不是已經徹底糊塗了?
很好,這樣就對了。
因為我們現在得到一個結論:
1 那些所謂的優先順序,全部是扯淡,千萬不要指望優先順序能夠完全按照你的預想來執行
2 請用圓括弧安排好優先順序——只有這個是唯一可靠的夥伴!
下一節開始我將逐個對運算子進行分析和講解,敬請期待……
zerglurker的C語言教程009——運算子詳解(一)