1. 程式人生 > >C語言中的型別提升——基礎概念,但還有很多人搞不清

C語言中的型別提升——基礎概念,但還有很多人搞不清

每天都會看CU的部落格,尤其是CU首頁上面的部落格。個人感覺有很多同學並不關注基礎知識, 在遇到問題時,經常會捨本求末。遇到問題,總是找不到根本原因,得出了一些結論。但這些結論並不是真正的原因,整個兒過程,也把真正的原因給掩蓋了。 今天主要說一下C語言的型別提升的事情。 下面是引用的一個例子——這個程式碼是從一個朋友的博文中複製過來的,但是當時這位朋友沒有去說明型別提升的問題,而是闡述彙編的過程。 /***************************************************************/ int main() { int i; unsigned char *p; char *p1; int a[] = {0xffffffff, 0xffffffff, 0xffffffff}; p = a; p1 = a; for(i = 0 ; i < 8 ; i++) { printf(" 0x%02x  0x%02x \n", p[i], p1[i]); } } $ gcc main.c  main.c: In function ‘main’: main.c:10: warning: assignment from incompatible pointer type main.c:11: warning: assignment from incompatible pointer type $ ./a.out   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff   0xff  0xffffffff  。。。。。。 。。。。。。 /***************************************************************/
根本原因其實很簡單。 %x是列印無符號整數的16進位制,而例子中傳遞的型別是字元型,那麼這裡就有一個字元提升的問題,將型別提升為無符號整形。 *p是unsigned char,其值為0xff,那麼對應的無符號整形的值仍然是0xff。 而*p1確實char,其值為0xff,其對應的無符號整形的值為0xffffffff。為什麼這次是0xffffffff呢? 因為*p1為-1,而無符號整數的-1則是0xffffffff。 為什麼是這樣呢? 因為在在編碼為補碼的情形下,型別提升有兩種情況: 1. 符號擴充套件:對於有符號數,擴充套件儲存位數的方法。在新的高位位元組使用當前最高有效位即符號位的值進行填充。
2. 零擴充套件:對於無符號數,擴充套件儲存位數的方法。在新的高位直接填0. 對於這個例子來說。*p是無符號數,所以填充的是0,即為0x000000ff。而*p1是有符號數,所以填充的是1,即為0xffffffff。 因此,從char型到unsigned int,是對有符號數的提升,因此用的是符號擴充套件,oxff被擴充套件為oxffffffff;而從unsigned char型到unsigned int型,是對無符號數的擴充套件,使用零擴充套件,oxff被擴充套件為ox000000ff,而填充的這些零是不會被打印出來的。 如果說這樣教科書式的概念不容易理解。還有這樣一種理解方式,也許不一定準確,但更容易理解。
對於這裡的型別提升,整個步驟可以這樣理解: 1. %x要求引數為無符號整數,需要引數為4個位元組; 2. *p, *p1為(unsigned) char型,只佔1個位元組; 3. 因為引數的型別不符,需要擴充套件; 4. 定位需要擴充套件到4個位元組; 5. 那麼就需要填充增加的3個位元組; 6. 這3個位元組需要什麼值?這裡就需要上面所需要的概念了。針對有符號數和無符號數,進行不同值的填充。

這就是為什麼在程式設計的過程中,要避免有符號數和無符號數的混用。我個人認為,在我們解決問題的時候,不要一味兒的想著怎麼用高階的技術解決。其實最重要的是基礎。一般情況下,大部分的問題都可以由C語言基礎解決。

接下來再看一個例子:

     1  #include <stdio.h>
     2
     3  int main(int argc, char **argv){
     4          int a=1000000,b=1000000;
     5          long int c,c1,c2,c3;
     6          c=a*b;//note1
     7          c1=(long)a*b;//note2
     8          c2=(long)a*(long)b;//note3
     9          c3=(long)(a*b);//note4
    10          printf("c=%ld, c1=%ld,c2=%ld, c3=%ld\n",c,c1,c2,c3);
    11          return 0;
    12  }

執行結果:

c=-727379968, c1=1000000000000,c2=1000000000000, c3=-727379968

note1和note4的列印結果是一樣的,原理也是一樣的,只不過在程式碼裡note1是隱式型別轉換,note4是顯式型別轉換,此處a*b的結果位數大於int的32位的範圍,發生了溢位,高於第31位的數值丟棄,剩下的在進行符號擴充套件,最後顯示為-727379968。而note2和note3,在計算出a*b的結果之前將a或b擴充套件為long型,提前避免了溢位問題,最後顯示為1000000000000。

結束!