1. 程式人生 > >4. C語言 -- 資料型別和取值範圍

4. C語言 -- 資料型別和取值範圍

本部落格主要內容為 “小甲魚” 視訊課程《帶你學C帶你飛》【第一季】 學習筆記,文章的主題內容均來自該課程,在這裡僅作學習交流。在文章中可能出現一些錯誤或者不準確的地方,如發現請積極指出,十分感謝。 也歡迎大家一起討論交流,如果你覺得這篇文章對你有所幫助,記得評論、點贊哦 ~(。・∀・)ノ゙

1. 資料型別

  在 C 語言裡,資料型別即說明了它是什麼型別的資料,更重要的是儲存這類資料所需的記憶體的大小,C 語言允許使用的型別如下: 在這裡插入圖片描述 在基本型別中的整數型別、浮點數型別和字元型別都已經在之前的文章中使用過了,這裡面的_Bool是布林型,只能取 0 和 1 兩個值;另一個是列舉型別(enum),這個型別將在後面的部分進行介紹。

  其餘的資料型別,如指標型別、構造型別和空型別也將在後面的部分進行介紹。

1.1 資料型別的限定符

  • short , long, long long

  我們可以為這些基本資料型別加上一些限定符,比如表示長度的 shortlong。比如 int 經過限定符修飾之後,可以是 short intlong int,還可以是 long long int。其中 short int表示所佔記憶體比int小的資料型別,而long int表示所佔記憶體比int大的資料型別。

  在 C 語言並沒有限制 int 的大小,更沒有限制short int等帶限定符的資料型別的大小,只是規定了

`short int` <= `int` <= `long int` <= `long long int`
  • signed 和 unsigned

  還有一對型別限定符是 signedunsigned,它們用於限定 char 型別和任何 int 型別變數的取值範圍。signed 表示該變數是帶符號位的,而 unsigned 表示該變數是不帶符號位的。帶符號位的變數可以表示負數,而不帶符號位的變數只能表示正數,它的儲存空間也就相應擴大一倍。預設所有的整型變數都是 signed 的,也就是帶符號位的。

  對於 int 型別的變數來說,有四種表示長度的限定符(除int本身外,還有 shortlonglong long),在加上符號位的限定signedunsigned,所以一共存在著 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的要求;通過最後兩行可以看出,對於同一種資料型別,signedunsigned只是最高位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(即 281=2552^8-1 = 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),然後我們可以驗證一下上面給出的結果是否正確。通過計算器可以知道 23212^{32} -1 的正確結果是 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 是一個無符號數。

參考

歡迎大家關注我的知乎號(左側)和經常投稿的微信公眾號(右側) 在這裡插入圖片描述