標籤:
本文目錄
- 一、取值範圍
- 二、char
- 三、說明符
- 四、自動型別提升
- 五、強制類型轉換
C語言有豐富的資料類型,因此它很適合用來編寫資料庫,如DB2、Oracle等大型資料庫都是C語言寫的。其中,提供了4種最常用的基礎資料型別 (Elementary Data Type):char、int、float、double,使用這些資料類型,我們就可以定義相應的變數來儲存資料。這講就來深入研究一下基礎資料型別 (Elementary Data Type)的一些使用細節。
回到頂部一、取值範圍
我們已經知道,不同資料類型所佔的儲存空間是不一樣的。比如在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類型的取值範圍已經會算了,那麼其他資料類型的取值範圍就能夠以此類推。
(註: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還大的整數,這該怎麼辦呢?這個就要用到類型說明符,這這講的後面會討論。
回到頂部二、char1.簡單使用
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個long10 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.不同資料類型所佔用的儲存空間
回到頂部四、自動型別提升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類型。知道有這用法就行了,以後某些地方會用得上。
【零基礎學習iOS開發】【02-C語言】07-基礎資料型別 (Elementary Data Type)