指標和指標運算子一起時的運算規則(比如*p++和*++p的區別)
接下來,通過示例徹底理解自增運算子的兩種用法(自減的用法與之類似,只不過是加1變成了減1)。
1、++i和i++的區別
如清單1(注意程式碼中的註釋):
- #include <stdio.h>
- int main(void)
- {
- int a, b, i = 7;
- i++; //等價於i = i + 1;
- ++i; //等價於i = i + 1;
- a = i++; //等價於a = i; i = i + 1;
- b = ++i; //等價於i = i + 1; b = i;
-
printf("a = %d, b = %d\n"
- return 0;
- }
例子輸出結果:
- a = 9, b = 11
在例子中,第7和第8行的作用一樣,僅僅是為變數i加1,這時i的值已經增加為9,接下來第10行變數a先獲得i的值(即9),然後i加1,第11行變數i先再加1,然後把得到的值賦給b,所以b的值為11。
稍微複雜的例子,如清單2:
- #include <stdio.h>
- int main(void)
- {
- int a = 5;
- int *p = &a;
-
int b = (*p)++; //等價於b = a++; 即b = a; a = a + 1;
- int c = ++(*p); //等價於c = ++a; 即a = a + 1; c = a;
- printf("b = %d, c = %d\n", b, c);
- printf("(*p)++ = %d, ++(*p) = %d\n", (*p)++, ++(*p));
- return 0;
- }
例子輸出結果:
- b = 5, c = 7
- (*p)++ = 8, ++(*p) = 8
在這個例子中,只不過是通過*p來間接地操作a,其他關於自增運算子的用法與清單1類似。第9行的*p一定要用小括號括起來,否則含義就不一樣了。而第11行的++(*p)也可以寫成++*p(用GCC驗證過),那是因為對運算元p來說它只有一個運算子*在計算它,所以無關乎運算子優先順序和結合性的問題。
值得注意的是,由於C語言沒有指定函式各引數的求值順序,所以第15行的程式碼是不可移植的,用不同的編譯器可能會產生不同的結果(對於這個例子,GCC是先計算++(*p),後計算(*p)++,所以兩者都等於8)。
知識點:
(1)、副作用
在對錶達式求值的同時,修改了某些變數的值,其中修改值的行為在C語言中被叫作副作用,那是因為對C語言而言,計算的目的就是對錶達式求值,如語句int a = 5,它的含義是先求值得到5,然後把5賦值給變數a,後一步的賦值就是此表示式的副作用。自增和自減運算子就是因為副作用而被使用,除了加1或減1之外,還給自身賦值。
(2)、運算子的優先順序
在C語言中,把運算子的優先順序分為15級,如下表,從上到下,依次為從最高優先順序到最低優先順序(為了方便記憶,將15級分成11類,並對每類進行了命名)。
初等運算子 |
包括小括號 ()、中括號 [] 、成員訪問運算子 . 和 -> 。 |
一元運算子 |
包括自增++和自減--、正負號+ 和-、間接運算*和取址運算& 、型別轉換(type)、 sizeof 、邏輯反! 、位取反~等。 |
算術運算子 |
包括兩級,先乘除(*、/、%)後加減(+、-)。 |
位移運算子 |
包括左移 << 和右移 >> 。 |
關係運算符 |
包括小於 < 、小於等於 <= 、大於 > 、大於等於 >= 。 |
判等運算子 |
包括相等 == 和不相等 != 。 |
位邏輯運算子 |
分三級,依次為位與 &、位異或 ^ 和位或 | 。 |
邏輯運算子 |
分兩級,依次為邏輯與 && 和邏輯或 || 。 |
條件運算子 |
? : |
賦值運算子 |
包括= 、+= 、-=、 *=、 /=、 %= 、&= 、^=、 |= 、<<= 、>>= 。 |
逗號運算子 |
, |
(3)、結合性
對於同一運算元,在具有兩個相同優先順序的操作符時決定先執行哪個操作符的問題就是由結合性決定的。
相同優先順序的操作符具有同樣的結合性。右結合性就是說表示式中最右邊的操作最先執行,然後從右到左依次執行。在C語言中,具有右結合性的操作符只有相應的三類,分別為一元運算子、條件運算子和賦值運算子。
注意:C語言中的優先順序和結合性都是針對同一運算元而言的。如表示式24/8*2,對於運算元8而言,/ 和*的優先順序相同,所以再根據它們的左結合性可知,表示式是先計算24/8得到3,然後計算3*2得到6。
C語言並沒有規定同一運算子相關的多個運算元的計算順序(&&、|| 、? : 和 , 運算子除外),如式子a = 8 * 9 + 20 * 4,對運算元9和20而言,根據優先順序就可判斷先乘後加,但表示式中的兩個*並不共享同一運算元,所以從左到右的結合性並不適用它,8 * 9 和20 * 4的計算順序是不定的,到底先計算8 * 9還是20 * 4由編譯器決定。
在上面例子中,8 * 9和20 * 4誰先執行都不影響最後結果的一致,但有些情況下就未必了,如“ b = 3; a = (b++) * (b++); ”這樣的例子,對於不同的編譯器最後a的值可能等於9,也可能等於12,甚至可能等於16。因此,在實際應用中不能出現這樣的未確定性,根據自己的需要,可以把它改成類似“b = 3; c = b++; a = c * c;”這樣的形式。
2、*p++和*++p的區別
舉例,如清單3:
- #include <stdio.h>
- int main(void)
- {
- int arr[] = {1, 2, 3, 4};
- int *p = arr;
- int a = *p++; //等價於a = *(p++); 即a = *p; p = p + 1;
- int b = *++p; //等價於b = *(++p); 即p = p + 1; b = *p;
- printf("a = %d, b = %d\n", a, b);
- return 0;
- }
例子輸出結果:
- a = 1, b = 3
對於第8行的運算元p而言,*和++的優先順序相同,但根據它們的右結合性可知,在這個表示式裡可認為++的優先順序高於*,即*p++等價於*(p++)。
而對於第10行的運算元p而言,它只有一個運算子++,所以先計算++p得出結果,然後間接運算。