標籤:
在《電腦眼裡的數字》這篇文章中,我曾提到,位元組是電腦最小的可定址的單位,地址對應的是一個個位元組,而不是位元組的每個位。這樣編址的原因很簡單——單個的位所能表示的資訊量太少了,只有兩種狀態0和1,只有把足夠多的位組合起來才能表示足夠豐富的資訊。那麼為什麼一定要是8呢?因為大家都這麼做。。。好吧,肯定一定的曆史原因,我就不深究了。
但是,即使是8個位組成的位元組,其所能表示的資訊量仍然是有限的,因為最多隻有256中狀態組合,如果用每種狀態對應0~255之間的數位話,那麼就無法表示256這個數。這時候只好用兩個位元組來表示大於255的數。同樣的道理,當數字大到超過兩個位元組所能表示的數的極限後,就用四個位元組。。。是的,你也發現了,位元組數目總是翻倍增長,為什麼不用3個位元組呢?甚至,為啥不用1.5個位元組呢?對於前者,是因為考慮到對齊的原因,這其中有太多東西要說,我就不展開了;而後者,前面其實說到了,因為沒有位的編址,無法深入到位元組內部把資料揪出來。。所以,儲存肯定會有一定的浪費,在所難免。
上面羅嗦了那麼多,其實只是為了引出本文的豬腳——C語言資料類型,為什麼C語言要分那麼多類型呢?因為對於不同大小的數,所需要的儲存空間大小不同。如果都用4個位元組儲存,那麼肯定不用分資料類型,但是好浪費哦~所以,本著節省記憶體的考慮,資料類型就誕生了。C的資料類型分為基礎資料型別 (Elementary Data Type)和複合資料型別,後者只是前者的某種組合。基礎資料型別 (Elementary Data Type)按照其在電腦中的儲存方式又分為整數類型和浮點數類型。
一、整數類型
整數類型包括char、short、int、long、long long,它們沒有小數部分。char雖然是字元類型,但是由於儲存的是ASCII碼,本質上也是按整數儲存的,所以歸為這一類。不同的整數類型具有不同的位元組數,從而所佔用的儲存空間不同。然而,這一點是不確定的,準確的位元組數依賴於具體的機器和編譯器——機器不僅分品牌,還有32位和64位之分。下面的表格給出不同類型所佔有的典型的位元組數(來源《深入理解電腦系統》):
| 類型 |
32位機器 |
64位機器 |
| char |
1 |
1 |
| short |
2 |
2 |
| int |
4 |
4 |
| long |
4 |
8 |
| long long |
8 |
8 |
| char * |
4 |
8 |
| float |
4 |
4 |
| double |
8 |
8 |
注意,上表只是典型值,以32位為例:在有的機器上,short和int都是2個位元組,long是4個位元組;而有的機器上,short是2個位元組,而int和long是4個位元組。C語言僅僅規定:short <= int <= long,然後char是1個位元組。不過,對於大部分機器,上表夠用了。要查看自己機器上每種類型所佔的位元組數,請用sizeof(類型)來查看。
對於整數類型,C還用了signed和unsigned來修飾它們,以獲得有符號數和無符號數,預設時,認為是有符號數。正如在《電腦眼裡的數字》中提到的那樣,有符號數和無符號數的二進位表示可能是一樣的,區別僅僅是解讀方式。
有了各種類型之後(即位元組數確定之後),就可以規定它們所表示的數的範圍。下表是32位機器各種類型的典型取值範圍:
| |
signed |
unsigned |
| char |
-128 ~ 127 |
0 ~ 255 |
| short |
-32768 ~ 32767 |
0 ~ 65535 |
| int |
-2147483648 ~ 2147483647 |
0 ~ 4294967295 |
| long |
-2147483648 ~ 2147483647 |
0 ~ 4294967295 |
| long long |
-9223372036854775808 ~ 9223372036854775807 |
0 ~ 18446744073709551615 |
這些界限值可以通過包含limits.h標頭檔加以查看,比如對於int值的各種範圍,可以通過列印INT_MAX INT_MIN
UINT_MAX UINT_MIN 來查看。
我們用幾個例子來看一下相同類型之間signed和unsigned的轉換。
1、將signed強制轉換成unsigned:
<span style="font-size:18px;">#include <stdio.h>int main(void){ int a = -1; unsigned int b = (unsigned int)a; printf("a = %d, b = %d\n", a, b); printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0;}</span>
運行結果如下:
<span style="font-size:18px;">a = -1, b = -1a = 4294967295, b = -1a = -1, b = 4294967295a = 0xffffffff, b = 0xffffffff</span>
可以明顯的看出,將一個負數強制轉換為無符號數,並沒有改變其位元模式(二進位表示),它仍然按照原來的模樣儲存,第四行的結果證明了這一點;而前三行的結果表明,即使不做signed到unsigned的強制類型轉換,只需要在列印時改變一下輸出格式,就能達到同樣的效果。(這裡我開始懷疑把一個變數聲明為unsigned有啥意義?)
而將一個有符號的正數轉換為同類型的無符號數又如何呢?
2、將unsigned強制轉換成signed:
<span style="font-size:18px;">#include <stdio.h>int main(void){ unsigned short a = 32767; short b = (short)a; printf("a = %u, b = %u\n", a, b); printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0;}</span>結果如下:
<span style="font-size:18px;">a = 32767, b = 32767a = 32767, b = 32767a = 32767, b = 32767a = 0x7fff, b = 0x7fff</span>
可以看出,當一個非負數處於0~TMax(TMax代表某一類型有符號數的最大值)的範圍時,無論把它的二進位表示解讀成signed還是unsigned,結果都是一樣的。這是因為,處於這部分範圍內的數,轉換成二進位時,最高為都為0,如果按signed解讀,它是一個正數,數字部分就是7fff,;而按unsigned解讀,07fff == 7fff,結果總是一樣的。
那如果正數的範圍超過了TMax呢?
<span style="font-size:18px;">#include <stdio.h>int main(void){ unsigned int a = 2147483648u; int b = (int)a; printf("a = %u, b = %d\n", a, b); printf("a = %d, b = %u\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0;}</span>
結果:
<span style="font-size:18px;">a = 2147483648, b = -2147483648a = -2147483648, b = 2147483648a = 0x80000000, b = 0x80000000</span>
可以看到,底層的二進位表示仍然是一致的,只是解讀方式發生了變化:由於最高位是1,所以解讀成signed時,是一個負數;解讀成unsigned仍然是正數,結果不同。
注意:因為變數分為signed和unsigned,對應的常量也要分為signed和unsigned;類型前沒有修飾時,預設為signed,對應的,一個常量數字預設為signed,即有符號數。如果希望一個常量數字被當成無符號數,就要在其末尾添加字母‘u‘或‘U’。
C語言筆記之資料類型(一)