4. C語言 -- 資料型別和取值範圍
本部落格主要內容為 “小甲魚” 視訊課程《帶你學C帶你飛》【第一季】 學習筆記,文章的主題內容均來自該課程,在這裡僅作學習交流。在文章中可能出現一些錯誤或者不準確的地方,如發現請積極指出,十分感謝。 也歡迎大家一起討論交流,如果你覺得這篇文章對你有所幫助,記得評論、點贊哦 ~(。・∀・)ノ゙
1. 資料型別
在 C 語言裡,資料型別即說明了它是什麼型別的資料,更重要的是儲存這類資料所需的記憶體的大小,C 語言允許使用的型別如下:
在基本型別中的整數型別、浮點數型別和字元型別都已經在之前的文章中使用過了,這裡面的_Bool
是布林型,只能取 0 和 1 兩個值;另一個是列舉型別(enum),這個型別將在後面的部分進行介紹。
其餘的資料型別,如指標型別、構造型別和空型別也將在後面的部分進行介紹。
1.1 資料型別的限定符
- short , long, long long
我們可以為這些基本資料型別加上一些限定符,比如表示長度的 short
和 long
。比如 int 經過限定符修飾之後,可以是 short int
,long int
,還可以是 long long int
。其中 short int
表示所佔記憶體比int
小的資料型別,而long int
表示所佔記憶體比int
大的資料型別。
在 C 語言並沒有限制 int
的大小,更沒有限制short int
等帶限定符的資料型別的大小,只是規定了
- signed 和 unsigned
還有一對型別限定符是 signed
和 unsigned
,它們用於限定 char 型別和任何 int 型別變數的取值範圍。signed 表示該變數是帶符號位的,而 unsigned 表示該變數是不帶符號位的。帶符號位的變數可以表示負數,而不帶符號位的變數只能表示正數,它的儲存空間也就相應擴大一倍。預設所有的整型變數都是 signed 的,也就是帶符號位的。
對於 int
型別的變數來說,有四種表示長度的限定符(除int
本身外,還有 short
,long
和 long long
),在加上符號位的限定signed
和 unsigned
,所以一共存在著 8 種int
1.2 sizeof 運算子
sizeof
用於獲得資料型別或表示式的長度,它有三種使用方式:
- sizeof(type_name); //sizeof(型別),即某一種型別的變數所佔記憶體大小;
- sizeof(object); //sizeof(物件),即某一個物件所佔記憶體大小;
- sizeof object; //sizeof 物件,檢視物件佔用記憶體大小的另一種表達方式;
1.3 舉例說明
下面的程式將使用sizeof
輸出每一種資料型別或者每一個變數的在記憶體中所佔的大小,具體地是使用8 種int
型別的變數進行說明。
#include <stdio.h>
int main()
{
unsigned int a;
signed int b;
a = 123;
printf("size of i is %d\n", sizeof(a));
printf("size of int is %d\n", sizeof(unsigned int));
printf("size of short int is %d\n",sizeof(short int));
printf("size of int is %d\n",sizeof(int));
printf("size of long int is %d\n",sizeof(long int));
printf("size of long long int is %d\n",sizeof(long long int));
printf("size of signed int is %d\n", sizeof(signed int));
printf("size of unsigned int is %d\n", sizeof(unsigned int));
return 0;
}
在 64 位的 Ubuntu 使用 gcc 編譯執行上面的程式碼可以看到如下的結果
如上圖所示,我們可以看到許多的 Warning
,根據提示我們可以知道,這是由於sizeof
返回的是一個long unsigned int
的變數,所以使用 %d
作為佔位符就有可能出現溢位的現象,這裡修改的方法是將上面的%d
改為%ld
。
分析輸出的結果,通過第 1 行和第 2 行輸出可以看出對於某一種資料類的變數,變數和資料型別的大小是相同的,這是很顯然的;其次通過第 3 行到第 6 行可以看到,資料型別的長度滿足上面的不等式short int
<= int
<= long int
<= long long int
的要求;通過最後兩行可以看出,對於同一種資料型別,signed
和 unsigned
只是最高位bit的意義,資料長度不會被改變的。
在上面給我們將有符號數賦值為負數,將無符號數賦值為整數,但是我們如果強制將無符號數賦值為負數
#include <stdio.h>
int main()
{
short a;
unsigned short b;
a = -1;
b = -1;
printf("a is %d\n", a);
printf("b is %d\n", b);
return 0;
}
輸出的結果如下圖所示
我們可以看到無符號數 b
果然沒有輸出對應的 -1 ,但是為什麼輸出 65535 呢?這就與資料型別的取值範圍有關了。下面的一小節將介紹資料的取值範圍。
2. 取值範圍
2.1 位元與位元組
CPU能讀懂的最小單位 —— 位元位,bit,b,即 0 1 兩個數字;記憶體機構的最小定址單位 —— 位元組,Byte,B。如下圖所示,為位元組和位元之間的關係
因此一個位元組所能儲存的最大數字是二進位制的11111111
。那這個二進位制的數字對應十進位制的數字是多少呢?是不是 255 呢?這還要取決於改二進位制數字的符號位。
2.2 符號位
對於上面的 11111111
,如果他對應一個無符號變數,那麼與他對應的是十進位制的數字255(即 )。但是對於存放signed型別的儲存單元中的資料,左邊第一位表示符號位。如果該位為0,表示該整數是一個正數;如果該位為1,表示該整數是一個負數。一個32位的整型變數,除去左邊第一位符號位,剩下表示值的只有31個位元位。
事實上計算機是用補碼的形式來存放整數的值,其中正數的補碼是該數的二進位制形式,而負數的補碼需要通過以下幾步獲得:
(1) 先取得該數的絕對值的二進位制形式,符號位置為1; (2) 符號位不變,將第1步的值按位取反(即將 0 都變為 1,1 都變為 0); (3) 符號位不變,最後將第2步的值加1。
如下圖為正數 7 和負數 -7 的補碼
一個位元組的有符號數的取值範圍如下圖所示 其中我們可以看到負數最高可以到 -128,而正數最高只能到127,這是為什麼呢?主要因為 0 也佔據了整數中的一部分,所以導致正數最高只能到127。
現在 1.3 中的問題就很好解釋了,-1 的補碼是 11111111,這個資料正好對應著無符號數中的 65535,所以將 -1(也就是 11111111)賦值給無符號數 b 之後輸出的是 65535。
2.3 基本資料型別的取值範圍
基本資料型別的取值範圍如下面的兩張圖所示,一張圖主要是字元型和整數型,另一張圖主要是小數型。
2.4 舉例說明
下面是一個通過 “計算指數值” 的程式來說明取值範圍這一概念,如下所示
#include <stdio.h>
#include <math.h>
int main()
{
int result = pow(2,32) - 1;
printf("result is %d\n", result);
return 0;
}
在Ubuntu16.04下面使用 gcc 編譯執行可以使用下面這條命令
gcc -lm tmp.c && ./a.out
其中的 lm
表示表式我們使用了<math.h>
這個標頭檔案,&&
省略了原本的 -o
的操作,此時生成的可執行檔名為 a.out
,通過上面的語句進行編譯執行得到如下的結果
可以看到 gcc 給出了 Warning
中指出了常量轉換溢位(overflow),然後我們可以驗證一下上面給出的結果是否正確。通過計算器可以知道 的正確結果是 4294967295,與上面給出的結果不符。
出現這個的問題在於,在預設情況下 int 為有符號型,所以第一位是符號位,不能用來存放數字,所以如果我們將 32 位都拿來存放數字很容易溢位的現象。所以這個時候可以將 result
宣告為 unsigned int
,如下所示
#include <stdio.h>
#include <math.h>
int main()
{
unsigned int result = pow(2,32) - 1;
printf("result is %u\n", result);
return 0;
}
這時的結果為 4294967295 ,即正確答案。但是細心的同學也可以發現我們不僅僅將 int
修改為 unsigned int
,在 printf
語句中也將佔位符 %d
改變為 %u
,因為此時的 result
是一個無符號數。
參考
歡迎大家關注我的知乎號(左側)和經常投稿的微信公眾號(右側)