本文目錄
- [一、取值范圍](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#label0)
- [二、char](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#label1)
- [三、說明符](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#label2)
- [四、自動類型提升](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#label3)
- [五、強制類型轉換](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#label4)
C語言有豐富的數據類型,因此它很適合用來編寫數據庫,如DB2、Oracle等大型數據庫都是C語言寫的。其中,提供了4種最常用的基本數據類型:char、int、float、double,使用這些數據類型,我們就可以定義相應的變量來存儲數據。這講就來深入研究一下基本數據類型的一些使用細節。
[回到頂部](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#labelTop)
##一、取值范圍
我們已經知道,不同數據類型所占的存儲空間是不一樣的。比如在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(為什么是這個值呢?可以根據[前面章節](http://www.cnblogs.com/mjios/archive/2013/05/08/3068114.html)提到的負數的二進制形式,自己去換算一下,看看1000 0000 0000 0000 0000 0000 0000 0000是不是-231。算不出也不用去糾結,不影響寫代碼,知道有這么一回事就完了)。因此,int類型的取值范圍是-231?~?231?- 1。
注意:這個推算過程是不用掌握的,大致知道過程就行了,而且這個結論也不用去記,大致知道范圍就行了。
### 2.各種數據類型的取值范圍
int類型的取值范圍已經會算了,那么其他數據類型的取值范圍就能夠以此類推。

(注:float和double由于是小數,它們的存儲方式是特別不一樣的,所以它們取值范圍的算法也很不一樣,這里不做介紹,也不用去掌握。e38表示乘以10的38次方,e-38表示乘以10的負38次方。)
上面表格中列出的只是64bit編譯器環境下的情況。如果你的編譯器是16bit或者32bit,這些數據類型的取值范圍肯定是不一樣的。比如int類型,在16bit編譯器環境下是占用2個字節的,共16bit,所以int類型的取值范圍是:-215?~?215?- 1。
### 3.數值越界
#### 1> 例子演示
前面已經看到,每種數據類型都有自己的取值范圍。如果給一個變量賦值了一個超出取值范圍的數值,那后果會不堪設想。
~~~
#include <stdio.h>
int main()
{
int c = 1024 * 1024 * 1024 * 4;
printf("%d\n", c);
return 0;
}
~~~
我們都知道,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還大的整數,這該怎么辦呢?這個就要用到類型說明符,這這講的后面會討論。
[回到頂部](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#labelTop)
##二、char
### 1.簡單使用
char是C語言中比較靈活的一種數據類型,稱為“字符型”。既然叫“字符型”,那肯定是用來存儲字符的,因此我們可以將一個字符[常量](http://www.cnblogs.com/mjios/archive/2013/05/07/3065522.html)賦值給一個字符型變量。
~~~
#include <stdio.h>
int main()
{
char c = 'A';
printf("%c\n", c);
return 0;
}
~~~
在第5行定義了一個char類型變量c,并將字符常量'A'賦值給了c。在第7行將字符變量c輸出到屏幕,%c的意思是以字符的格式輸出。
輸出結果:
### 2.字符常量一定要用單引號括住
1> 下面的寫法是錯誤的:
~~~
int main()
{
char c = A;
return 0;
}
~~~
編譯器會直接報第3行的錯,錯誤原因是:標識符A找不到。你直接寫個大寫A,編譯器會認為這個A是一個變量。因此寫成'A'才是正確的,或者在第3行代碼的前面再定義1個變量名叫做A的char類型變量。
2> 下面的寫法也是錯誤的:
~~~
int main()
{
char c = "A";
return 0;
}
~~~
第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,表示以十進制整數格式輸出,輸出結果:
[回到頂部](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#labelTop)
##三、說明符
### 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的取值范圍和占用的長度又是不一樣的。比如在16bit編譯器環境下,long只占用4個字節。不過幸運的是,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.不同數據類型所占用的存儲空間

[回到頂部](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#labelTop)
##四、自動類型提升
### 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> 這個自動類型提升,知道有這么一回事就行了,不用死記這規則,因為系統會自動執行這個操作。
[回到頂部](http://www.cnblogs.com/mjios/archive/2013/06/06/3069571.html#labelTop)
##五、強制類型轉換
### 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類型。知道有這用法就行了,以后某些地方會用得上。
- 前言
- C語言入門教程1-概述
- C語言入門教程2-第一個C程序
- C語言入門教程3-關鍵字、標識符、注釋
- C語言入門教程4-變量與常量
- C語言入門教程5-進制
- C語言入門教程6-變量與內存
- C語言入門教程7-基本數據類型
- C語言入門教程8-運算符
- C語言入門教程9-流程控制
- C語言入門教程10-函數
- C語言入門教程11-函數的聲明定義
- C語言入門教程12-scanf與printf輸入輸出函數
- C語言入門教程13-數組-批量數據存儲
- C語言入門教程14-字符串
- C語言入門教程15-字符與字符串常用處理函數
- C語言入門教程16-指針
- C語言入門教程17-指向一維數組元素的指針
- C語言入門教程18-指針與字符串
- C語言入門教程19-預處理指令1-宏定義
- C語言入門教程20-預處理指令2-條件編譯
- C語言入門教程21-預處理指令3-文件包含
- C語言入門教程22-變量類型與作用域
- C語言入門教程23-枚舉
- C語言入門教程24-結構體
- C語言入門教程25-typedef類型別名