*p++與(*p)++與*(p++)------自增運算符常見誤區


 自增運算符(++)

自增\自減運算符分為前綴形(++a)和后綴形(a++),這里重點分析自增

大部分人對前綴和后綴的理解一般是,前綴形式是先++再使用(先變后用),后綴形式是先使用再++(先用后變)

 

(tips:自增運算符只能作用於變量,而不能作用於變量或表達式,例:(i+j)++就是非法的)

先來說一下一般情況

1 main() 2 { 3     int a = 3; 4     int b; 5 
6     b = a++; 7     printf("%d", b); 8 }

 上面這種應該大部分人都會,屬於常規情況,是先把a的值賦值給b,再a++,最后輸出值為3(大多數應該都是這么理解的)

 

但是按照優先級的說法,這里就不太好解釋,'++'的優先級比'='高,按理說'++'應該是在'='之前發生的,但表面上看來是'='先與'++'發生

類似的有

1 main() 2 { 3     int a = 3; 4     int b; 5     b = -a++; 6     printf("%d\n", b); 7 }

按照運算符優先級和結合性的說法,取反運算符'-'和'++'運算符屬於同一優先級,結合性是右結合

所以-a++應該等價於-(a++),但是實際運行結果是-3

 

筆者在網上找了一些解釋,如下

第一種解釋:

 

(圖片來源:http://forum.ubuntu.org.cn/viewtopic.php?t=301915

 

第二種解釋:

(圖片來源:https://blog.csdn.net/SunXiWang/article/details/78553933

 

 第三種解釋:

(圖片來源:https://www.cnblogs.com/weiyinfu/p/4836334.html

 

上述三種解釋中第二種和第三種解釋很相似,筆者也比較傾向於第二種和第三種解釋

按照這種解釋方式來對上面的代碼,重新梳理思路

1 int a = 3; 2 int b; 3 b = a++; 4 printf("%d", b);

這里b=a++的確是,先a++然后再賦值,但是a++后的返回值是改變之前的值,

可以把a++理解為一個函數,函數的返回值是改變之前的值

 

1 int temp = a; 2 a = a+1; 3 return temp;

這樣用優先級來解釋就說得通了,並且這個返回值是一個常量不是變量例如就是錯誤的

下面將自增運算符引入一些更復雜的表達式中

1 main() 2 { 3     char *p = "hello"; 4 
5     printf("%c", *p++); 6 }

 

按照優先級來解釋,'*'和'++'屬於同一優先級,結合性為右結合,所以說*p++等價於*(p++),先地址++,然后返回改變前的地址,然后*對p解引用得到p[0]的值,輸出值應該為h

(注意:這里很容易誤解為括號優先內的地址先++,然后取移動后值,不要被括號誤導了,在這里*p++和*(p++)效果是一樣的)

現在我們來對*(p++)進行驗證,代碼如下

1 main() 2 { 3     char *p = "hello"; 4 
5     printf("%c", *(p++)); 6 
7 }

 運行結果為,到這里可以確定*p++和*(p++)效果相同

(tips:上面的代碼char *p,不能改成char p[10]之類的char型數組,因為對char p[10]來說p指的是p[10]的首地址,是一個常量,常量值是不可修改的,如果還這么寫編譯器會報錯,

現在進入下一個階段,下面的代碼就有點迷惑性了,請讀者注意

1 main() 2 { 3     char p[10] = "hello"; 4 
5     printf("%c", (*p)++); 6 }

 

(這里不能再用char *p了,因為用char *p的話,"hello"就是常量,常量的值是不可更改的,繼續用(*p)++的話,編譯不會報錯,但是程序無法運行) 

請讀者想一下輸出的結果應該是什么?這里用括號()將*p括起來了,括號優先級最高,*先與p結合即解引用得到p[0]的值,看起來輸出結果應該是i,常規思維一般是p[0]+1即值+1

但實際的輸出結果是

表面上看起來貌似括號沒有起作用,其實不然,現在重新理解下(*p)++的過程

第一步:括號優先級最高*與p結合,解引用得到p[0]的值

第二步:*p的值(也就是p[0]的值)++,這里p[0]的值的確是+1了,但是返回值是+1之前的值,%c打印的返回值所以為h

到這里,就能夠解釋為什么輸出的值為h而不是i了

 

再用下述代碼對p[0]值進行檢查

1 main() 2 { 3     char p[10] = "hello"; 4 
5     printf("%c***%c\n", (*p)++, *p); 6 }

 運行結果為

這里的*p++可以改為p[0]++,效果是一樣的(到這里讀者應該基本明白了*p++、*(p++)、(*p)++的區別和運算順序)

 

下面進入最終階段,先上完整代碼

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void fun(char *t, char *s)  5 {  6     while(*t != '\0') t++;  7     while((*t++ = *s++) != '\0');  8 }  9 
10 main() 11 { 12     char ss[10] = "ppp",aa[10] = "abcd"; 13  fun(ss, aa); 14     printf("%s\n%s\n", ss, aa); 15 }

  在繼續往下看之前,讀者可以先想一下輸出的結果應該是什么?

上述代碼中,將ss和aa數組的首地址傳入fun函數中,ss和aa分別用char *t和char *s接收,然后while(*t != '\0')t++;就是將t指向ss數組的最后一個位置的地址該位置存儲的是'\0',

關鍵是下一句

while((*t++ = *s++) != '\0');

先分析下(*t++ = *s++),'*'和'++'的優先級相同,而且都是右結合,即等價於*(t++) = *(s++);

步驟如下:

第一步:t先與++結合,地址+1,此時t的值已經改變,但是t++返回值的是t+1前的值,即t[3]的地址(&t[3])

第二步:*與t++的返回值結合,得到t[3]的值(注意這里說的是返回值,而不是*與t結合--雖然不知道這樣說有沒有問題,為了方便理解先這樣說)

第三、四步:這里*s++的操作與*t++的操作相同這里就不再贅述了

第五步:將*s的值賦給*t,然后判斷*t是否不等於'\0'

最后輸出結果為

 

補充:C/C++ 語言的規定告訴我們,任何依賴於特定計算順序、依賴於在順序點之間實現修改效果的表達式,其結果都沒有保證。程序設計中應該貫徹的規則是:

如果在任何“完整表達式”(形成一段由順序點結束的計算)里存在對同一“變量”的多個引用,那么表達式里就不應該出現對這一“變量”的副作用。否則就不能保證得到預期結果。

 

參考鏈接:

https://bbs.csdn.net/topics/370153775

http://forum.ubuntu.org.cn/viewtopic.php?t=301915

https://blog.csdn.net/SunXiWang/article/details/78553933

https://blog.csdn.net/studyvcmfc/article/details/5630674

https://blog.csdn.net/frank_jb/article/details/52244318

https://bbs.csdn.net/topics/390722765

https://www.cnblogs.com/weiyinfu/p/4836334.html

 

最后提一下一個比較坑的地方:int a  = 0; a=a++;這條語句無論執行多少次,a的值是肯定不會變的,但是在Microsoft Visual C++ 2010 Express這個編譯器中a的值是在不斷變化的(就是一直在+1),在其他在線編譯器上結果都是0

a=a++;相關的分析的鏈接:深入剖析C函數參數的結合順序及a++和++a的區別(該篇使用了反匯編對a++、++a進行了分析)

              a = a++ 與 a = ++a

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM