自增運算符(++)
自增\自減運算符分為前綴形(++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進行了分析)