【零基礎學習iOS開發】【02-C語言】07-基本數據類型


C語言有豐富的數據類型,因此它很適合用來編寫數據庫,如DB2、Oracle等大型數據庫都是C語言寫的。其中,提供了4種最常用的基本數據類型:char、int、float、double,使用這些數據類型,我們就可以定義相應的變量來存儲數據。這講就來深入研究一下基本數據類型的一些使用細節。

一、取值范圍

我們已經知道,不同數據類型所占的存儲空間是不一樣的。比如在64bit環境下,char類型占用1個字節,int類型占用4個字節。字節長度不一樣,包含的二進制位數就不一樣,能表示的數據范圍也就不一樣。因此,int類型能表示的數據范圍肯定比char類型大。下面來簡單算算64bit環境下int類型的取值范圍。

1.推算int類型的取值范圍

int類型占用4個字節,所以一共32位,那么按理來說,取值范圍應該是:0000 0000 0000 0000 0000 0000 0000 0000~1111 1111 1111 1111 1111 1111 1111 1111,也就是10進制的0 ~ 232 - 1。但是int類型是有正負之分的,包括了正數和負數,那怎么表示負數呢?就是拿最高位來當符號位,當最高位為0就是正數,最高位為1則是負數。即:1000 0000 1001 1011 1000 0000 1001 1011就是一個負數,0000 1001 0000 1101 0000 1001 0000 1101是一個正數。由於最高位是0才代表正數,因此最大的正數是0111 1111 1111 1111 1111 1111 1111 1111,也就是231 - 1。而最小的負數就是1000 0000 0000 0000 0000 0000 0000 0000,也就是-231(為什么是這個值呢?可以根據前面章節提到的負數的二進制形式,自己去換算一下,看看1000 0000 0000 0000 0000 0000 0000 0000是不是-231。算不出也不用去糾結,不影響寫代碼,知道有這么一回事就完了)。因此,int類型的取值范圍是-231 ~ 231 - 1

注意:這個推算過程是不用掌握的,大致知道過程就行了,而且這個結論也不用去記,大致知道范圍就行了。

 

2.各種數據類型的取值范圍

int類型的取值范圍已經會算了,那么其他數據類型的取值范圍就能夠以此類推(下圖是64bit環境)。

(注:float和double由於是小數,它們的存儲方式是特別不一樣的,所以它們取值范圍的算法也很不一樣,這里不做介紹,也不用去掌握。e38表示乘以10的38次方,e-38表示乘以10的負38次方。)

上面表格中列出的只是64bit環境下的情況。如果你的設備是16bit或者32bit,這些數據類型的取值范圍肯定是不一樣的。比如int類型,在16bit環境下是占用2個字節的,共16bit,所以int類型的取值范圍是:-215 ~ 215 - 1

 

3.數值越界

1> 例子演示

前面已經看到,每種數據類型都有自己的取值范圍。如果給一個變量賦值了一個超出取值范圍的數值,那后果會不堪設想。

1 #include <stdio.h>
2 
3 int main()
4 {
5     int c = 1024 * 1024 * 1024 * 4;
6     
7     printf("%d\n", c);
8     return 0;
9 }

 

我們都知道,int類型能保存的最大值是231-1。在第5行給int類型的變量c賦值一個比231-1大的數值:232 (1024是210

先看看在終端中的輸出結果:,可以看出輸出的值是0。

 

2> 結果分析

我們可以簡單分析一下為什么將232賦值給變量c之后輸出的是0。232的 二進制形式是:1 0000 0000 0000 0000 0000 0000 0000 0000,一共有33位二進制數。變量c占用了4個字節,只能容納32位二進制數,而且內存尋址是從大到小的,因此,變量c在內存中的存儲形式是0000 0000 0000 0000 0000 0000 0000 0000,也就是0,最前面的那個1就不屬於變量c的了。

 

3> 結論

可以發現,如果超出了變量的取值范圍,那么將損失精度,得到“垃圾數據”(“垃圾數據”就是指並非我們想要的數據)。可是,有時候我們確實要存儲一個很大很大的整數,比231-1還大的整數,這該怎么辦呢?這個就要用到類型說明符,這這講的后面會討論。

 

二、char

1.簡單使用

char是C語言中比較靈活的一種數據類型,稱為“字符型”。既然叫“字符型”,那肯定是用來存儲字符的,因此我們可以將一個字符常量賦值給一個字符型變量。

1 #include <stdio.h>
2 
3 int main()
4 {
5     char c = 'A';
6     
7     printf("%c\n", c);
8     return 0;
9 }

在第5行定義了一個char類型變量c,並將字符常量'A'賦值給了c。在第7行將字符變量c輸出到屏幕,%c的意思是以字符的格式輸出。

輸出結果:

 

2.字符常量一定要用單引號括住

1> 下面的寫法是錯誤的:

1 int main()
2 {
3     char c = A;
4     return 0;
5 }

編譯器會直接報第3行的錯,錯誤原因是:標識符A找不到。你直接寫個大寫A,編譯器會認為這個A是一個變量。因此寫成'A'才是正確的,或者在第3行代碼的前面再定義1個變量名叫做A的char類型變量。

2> 下面的寫法也是錯誤的:

1 int main()
2 {
3     char c = "A";
4     return 0;
5 }

第3行中的"A"並不是字符常量,而是字符串常量,將字符串"A"賦值給字符變量c是錯誤的做法。字符串和字符的存儲機制不一樣,因此"A"和'A'是有本質區別的。

 

3.字符型變量還可以當做整型變量使用

1個字符型變量占用1個字節,共8位,因此取值范圍是-27~27-1。在這個范圍內,你完全可以將字符型變量當做整型變量來使用。

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char c1 = -10;
 6     
 7     char c2 = 120;
 8     
 9     printf("c1=%d  c2=%d \n", c1, c2);
10     return 0;
11 } 

由於第9行用的是%d,表示以十進制整數格式輸出,輸出結果:。因此,如果使用的整數不是很大的話,可以使用char代替int,這樣的話,更節省內存開銷。

 

4.字符型變量只能存儲單字節字符

其實字符有2種類型:單字節字符和雙字節字符。

  • 單字節字符:在內存中占用1個字節的字符。包括了26個英文字母的大小寫、10個阿拉伯數字等字符;
  • 雙字節字符:在內存中占用2個字節的字符。包括了中國、日本和韓國等國家的文字,比如漢字。

1個字符型變量只占用1個字節,所以1個字符型變量只能存儲1個單字節字符。

下面的寫法是錯誤的:

1 #include <stdio.h>
2 
3 int main()
4 {
5     char c = 'ABCD';
6     
7     printf("%c\n", c);
8     return 0;
9 }

編譯器會對上面的代碼發出警告,並不會報錯,因此程序還是能夠運行。由於變量c只能存儲1個單字節字符,最終變量c只存儲了'ABCD'中的'D'。

輸出結果:

 

5.字符型變量不能存儲漢字

在內存中,1個漢字需要用2個字節來存儲,而1個字符型變量只占用1個字節的存儲空間,所以字符型變量不能用來存儲漢字。

下面的寫法是錯誤的:

1 int main()
2 {
3     char c = '';    
4     return 0;
5 }

 編譯器會直接報第3行的錯誤。記住一個原則:單引號括住的必須是單字節字符

 

6.ASCII

說到字符,就不得不提ASCII這個概念

1> ASCII是基於拉丁字母的一套電腦編碼系統,是現今最通用的單字節編碼系統,全稱是“American Standard Code for Information Interchange”。編碼系統,看起來好像很高級,其實就是一個字符集---字符的集合。

2> ASCII字符集包括了:所有的大寫和小寫英文字母,數字0到9,標點符號,以及一些特殊控制字符:如退格、刪除、制表、回車,一共128個字符,全部都是“單字節字符”。

3> 在計算機中的任何數據都是以二進制形式存儲的,因此每個ASCII字符在內存中是以二進制形式存儲的,而且只占用1個字節,二進制數的值就稱為這個ASCII字符的ASCII值。比如大寫字母A在內存中的二進制形式是:0100 0001,那么它的ASCII值就是65。

4> 下面是一張ASCII碼字符表,ASCII碼值的范圍是0~127

5> 我們都知道1個char型變量只占用1個字節的存儲空間,而所有的ASCII字符都是單字節字符,因此char型變量能存儲任何ASCII字符。而且在使用char型變量存儲ASCII字符時,可以直接用ASCII字符,也可以用ASCII值。

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char c1 = 65;
 6     
 7     char c2 = 'A';
 8     
 9     printf("c1=%c  c2=%c \n", c1, c2);
10     return 0;
11 }

在第5、7行分別定義了字符型變量c1、c2。很明顯,變量c2存儲的是ACII字符'A';變量c1存儲的是65,而ASCII值65對應的ASCII字符就是'A',因此變量c1存儲的也是'A'。

由於第9行用的是%c,表示以字符格式輸出,輸出結果:

5> 經過上面的例子后,應該知道6和'6'的區別了吧

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char c1 = 6;
 6     
 7     char c2 = '6';
 8     
 9     printf("c1=%d  c2=%d \n", c1, c2);
10     return 0;
11 }

第5行給變量c1賦值了整數6,第7行給變量c2賦值了字符'6','6'的ASCII值是54。

由於第9行用的是%d,表示以十進制整數格式輸出,輸出結果:

 

三、說明符

1.什么是說明符

1> 我們已經知道,在64bit環境下,1個int類型變量取值范圍是-231 ~ 231 - 1,最大值是231-1。有時候,我們要使用的整數可能比231-1還大,比如234這個整數,如果還堅持用int類型變量來存儲這個值的話,就會損失精度,得到的是垃圾數據。為了解決這個問題,C語言允許我們給int類型的變量加一些說明符,某些說明符可以增大int類型變量的長度,這樣的話,int類型變量能存儲的數據范圍就變大了。

2> C語言提供了以下4種說明符,4個都屬於關鍵字:

  • short  短型
  • long  長型
  • signed  有符號型
  • unsigned  無符號型

按照用途進行分類,short和long是一類,signed和unsigned是一類。

 

2.用法演示

這些說明符一般就是用來修飾int類型的,所以在使用時可以省略int

 1 // 下面兩種寫法是等價的
 2  short int s1 = 1;
 3  short s2 = 1;
 4  
 5  // 下面兩種寫法是等價的
 6  long int l1 = 2;
 7  long l2 = 2;
 8  
 9  // 可以連續使用2個long
10  long long ll = 10;
11  
12  // 下面兩種寫法是等價的
13  signed int si1 = 3;
14  signed si2 = 3;
15  
16  // 下面兩種寫法是等價的
17  unsigned int us1 = 4;
18  unsigned us2 = 4;
19  
20  // 也可以同時使用2種修飾符
21  signed short int ss = 5;
22  unsigned long int ul = 5;

1> 第2行中的short int和第3行中的short是等價的。

2> 看第10行,可以連續使用兩個long。long的作用會在后面解釋。

3> 注意第21和22行,可以同時使用兩種不同的說明符。但是不能同時使用相同類型的修飾符,也就是說不能同時使用short和long 或者 不能同時使用signed和unsigned。

 

3.short和long

1> short和long可以提供不同長度的整型數,也就是可以改變整型數的取值范圍。在64bit環境下,int占用4個字節(32bit),取值范圍是-231~231-1;short占用2個字節(16bit),取值范圍是-215~215-1;long占用8個字節(64bit),取值范圍是-263~263-1

2> 總結一下:在64位環境下,short占2個字節(16位),int占4個字節(32位),long占8個字節(64位)。因此,如果使用的整數不是很大的話,可以使用short代替int,這樣的話,更節省內存開銷。

3> 不同平台下,int、short、long的取值范圍和占用的長度是不一樣的。ANSI \ ISO制定了以下規則:

  • short跟int至少為16位(2字節)
  • long至少為32位(4字節)
  • short的長度不能大於int,int的長度不能大於long
  • char一定為為8位(1字節),畢竟char是我們編程能用的最小數據類型

4> 可以連續使用2個long,也就是long long。一般來說,long long的范圍是不小於long的,比如在32bit環境下,long long占用8個字節,long占用4個字節。不過在64bit環境下,long long跟long是一樣的,都占用8個字節。

5> 還有一點要明確的是:short int等價於short,long int等價於long,long long int等價於long long

 

4.long的使用注意

1> 常量

long和int都能夠存儲整型常量,為了區分long和int,一般會在整型常量后面加個小寫字母l,比如100l,表示long類型的常量。如果是long long類型呢,就加2個l,比如100ll。如果什么都不加,就是int類型的常量。因此,100是int類型的常量,100l是long類型的常量,100ll是long long類型的常量。

 1 int main()
 2 {
 3     int a = 100;
 4     
 5     long b = 100l;
 6     
 7     long long c = 100ll;
 8     
 9     return 0;
10 } 

變量a、b、c最終存儲的值其實都是100,只不過占用的字節不相同,變量a用4個字節來存儲100,變量b、c則用8個字節來存儲100。

其實,你直接將100賦值給long類型的變量也是沒問題的,照樣使用。因為100是個int類型的常量,只要有4個字節,就能存儲它,而long類型的變量b有8個字節,那肯定可以裝下100啦。

1 int main()
2 {
3     long b = 100;
4     
5     return 0;
6 }

 

2> 輸出

1 #include <stdio.h>
2 
3 int main()
4 {
5     long a = 100000000000l;
6     
7     printf("%d\n", a);
8     return 0;
9 }

在第5行定義了long類型變量a,在第7行嘗試輸出a的值。注意了,這里用的是%d,表示以十進制整數格式輸出,%d會把a當做int類型來輸出,它認為a是4個字節的。由於a是long類型的,占用8個字節,但是輸出a的時候,只會取其中4個字節的內容進行輸出,所以輸出結果是:

又是傳說的垃圾數據

 

那怎樣才能完整地輸出long類型呢?應該用格式符%ld

1 #include <stdio.h>
2 
3 int main()
4 {
5     long a = 100000000000l;
6     
7     printf("%ld\n", a);
8     return 0;
9 }

注意第7行,雙引號里面的是%ld,表示輸出1個long類型的整數,這時候的輸出結果是:

 

如果是long long類型,應該用%lld

1 #include <stdio.h>
2 
3 int main()
4 {
5     long long a = 100000000000ll;
6     
7     printf("%lld\n", a);
8     return 0;
9 }

 

5.signed和unsigned

1> 首先要明確的:signed int等價於signed,unsigned int等價於unsigned

2> signed和unsigned的區別就是它們的最高位是否要當做符號位,並不會像short和long那樣改變數據的長度,即所占的字節數。

  • signed:表示有符號,也就是說最高位要當做符號位,所以括正數、負數和0。其實int的最高位本來就是符號位,已經包括了正負數和0了,因此signed和int是一樣的,signed等價於signed int,也等價於int。signed的取值范圍是-231 ~ 231 - 1
  • unsigned:表示無符號,也就是說最高位並不當做符號位,所以不包括負數。在64bit環境下面,int占用4個字節(32bit),因此unsigned的取值范圍是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 232 - 1

 

6.signed、unsigned也可以修飾char,long還可以修飾double

知道有這么一回事就行了。

1 unsigned char c1 = 10;
2 signed char c2 = -10;
3 
4 long double d1 = 12.0;

 

7.不同環境下基本數據類型的存儲長度

Mac 64bit是LP64,Windows 64bit是LLP64

 

 

四、自動類型提升

1.什么是自動類型提升

先來看看下面的一則運算

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     int a = 10;
 6     
 7     double d = a + 9.5;
 8     
 9     printf("%f \n", d);
10     
11     return 0;
12 }

1> 在第5行定義了一個int類型的變量a,賦值了一個整數10。

2> 接着在第7行取出a的值10,加上浮點數9.5,這里做了一個“加法運算”,並且將“和”賦值給d。所以d的值應該是19.5。

3> 在第9行使用格式符%f輸出浮點型變量d,默認是保留6位小數的。輸出結果為:

4> 看似這么簡單的運算,其實包含了一些語法細節在里面。嚴格來說,相同數據類型的值才能進行運算(比如加法運算),而且運算結果依然是同一種數據類型。第7行的情況是:變量a的值10是int類型(4字節),9.5是double類型(8字節)。很明顯,10和9.5並不是相同數據類型。按理來說,10和9.5是不允許進行加法運算的。但是,系統會自動對占用內存較少的類型做一個“自動類型提升”的操作,也就把10提升為double類型。也就是說,本來是用4個字節來存放10的,現在改為用8個字節來存放10。因此,10和9.5現在都是用8個字節來存放的,都是double類型,然后就可以進行運算了。並且把運算結果賦值給double類型的變量d。

5> 需要注意的是:經過第7行代碼后,變量a一直都還是int類型的,並沒有變成double類型。1個變量在它定義的時候是什么類型,那么就一直都是什么類型。“自動類型提升”只是在運算過程中進行的。

 

2.常見的自動類型提升

 1 int main()
 2 {
 3     float a = 10 + 3.45f;// int 提升為 float
 4     
 5     int b = 'A' + 32; // char 提升為 int
 6     
 7     double c = 10.3f + 5.7; // float 提升為 double
 8     
 9     return 0;
10 }

1> 注意第5行,系統會將字符'A'提升為int類型數據,也就是轉為'A'的ASCII值后再跟32進行加法運算。'A'的ASCII值是65,因此變量b的值為65+32=97。

2> 這個自動類型提升,知道有這么一回事就行了,不用死記這規則,因為系統會自動執行這個操作。

 

五、強制類型轉換

1.什么是強制類型轉換

先來看看下面的代碼

1 #include <stdio.h>
2 
3 int main()
4 {
5     int i = 10.7;
6     
7     printf("%d \n", i);
8     return 0;
9 }

1> 注意第5行,我們將一個8個字節的浮點數10.7賦值給了只有4個字節存儲空間的整型變量i。可以想象得到,把8個字節的內容塞給4個字節,肯定會損失精度。在第7行將變量i的值輸出,輸出結果是:

輸出值為10,這是必然的。

2> 這里面也有一點語法細節,其實第5行做了一個“強制類型轉換”的操作:由於左邊是int類型的變量i,那么就會強制把double類型的10.7轉換為int類型的10,並且把轉換后的值賦值給了整型變量i。由於C語言是語法限制不嚴格,所以系統會自動強制轉換,如果換做是其他語法嚴格的語言,比如Java,第5行代碼早就報錯了。

3> 如果寫得嚴格一點,明顯地進行“強制類型轉換”,應該這樣寫:

1 #include <stdio.h>
2 
3 int main()
4 {
5     int i = (int) 10.7;
6     
7     printf("%d \n", i);
8     return 0;
9 }

注意第5行,在10.7的前面加了個(int),表示強制轉換為int類型的數據。這樣就絕對不會有語法問題了。總之你將一個浮點型數據轉換為整型數據,就會丟失小數部分的值。

 

2.常見的強制類型轉換

 1 int main()
 2 {
 3     int a = 198l; // long 轉換為 int
 4     
 5     char b = 65; // int 轉換為 char
 6     
 7     int c = 19.5f; // float 轉換為 int
 8     
 9     return 0;
10 }

這個強制類型轉換,知道有這么一回事就行了,不用死記這規則,因為很多時候系統會自動執行這個操作。

 

3.其他用法

前面看到的強制轉換好像都是“大類型”轉為“小類型”,其實這是不一樣的,也可以由“小類型”轉為“大類型”

1 int main()
2 {
3     int a = 10;
4     
5     double b = (double)a  + 9.6;
6     
7     return 0;
8 }

注意第5行,先將a的值強制轉換為double類型后,再跟9.6進行加法運算。這樣的話,系統就不用執行“自動類型提升”的操作了。其實你不強轉也可以的,因為系統會做一個“自動類型提升”的操作,將變量a的值10提升為double類型。知道有這用法就行了,以后某些地方會用得上。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM