1、什么是數(shù)據(jù)類型?
計算機編程語言是用來控制計算機的行為及操作,協(xié)助人們解決現(xiàn)實中的問題,其能表達的數(shù)據(jù)類型也是從實際中提取并抽象出來形成的數(shù)據(jù)結(jié)構(gòu)描述。
例如:數(shù)學中數(shù)的基礎分類有正整數(shù)、負整數(shù)、小數(shù)等類別,數(shù)學中所有關于數(shù)的運算都是在基礎分類上進行的。計算機出現(xiàn)之前,數(shù)學家們用稿紙進行大量的數(shù)學運算以求證數(shù)學問題和科學計算,這耗費了數(shù)學家們太多的精力。隨著計算機科技的發(fā)展,大量復雜的數(shù)學運算交給計算機來執(zhí)行,極大提高了計算效率,也讓數(shù)學家們從復雜的數(shù)學運算中擺脫出來。
數(shù)學運算包含大量的計算表達式,計算機程序需要有連續(xù)處理算式和計算數(shù)據(jù)的能力,下面是一個簡單的四則運算算式:
15.8+20*31.5-30
計算機程序要處理上述算式,就需要具備存儲小數(shù)、整數(shù)、運算符的能力。C語言提供了存儲小數(shù)、整數(shù)、運算符的基本數(shù)據(jù)類型。下圖是算式中數(shù)據(jù)類型到C數(shù)據(jù)類型的映射圖。
圖 2-1數(shù)學算式數(shù)據(jù)類型到C語言數(shù)據(jù)類型的映射
2、變量與數(shù)據(jù)類型
使用變量可以將數(shù)學算式中的數(shù)存儲起來,下面討論如何聲明或定義一個變量。在C語言中聲明一個變量和定義一個變量存在區(qū)別:聲明變量是通知編譯器變量的類型和名字,編譯解析聲明變量時,不會為該變量分配存儲空間,直至該變量被賦值時,才會為變量分配存儲空間;定義變量是在聲明變量的同時,為變量賦一個初始化值,編譯器解析定義變量時,將為該變量分配存儲空間。
聲明一個變量
語法規(guī)則: 數(shù)據(jù)類型 變量名;
其中,數(shù)據(jù)類型可以是C語言支持的任何數(shù)據(jù)類型;變量名為聲明的變量名稱。
示例:聲明整型變量
int number;
示例:聲明字符型變量
char op;
定義一個變量
聲明變量的同時并對變量直接賦值,稱為定義一個變量。如果在聲明變量時沒有對變量進行賦值,則應在后面的程序語句中為變量賦值。
示例:定義整型變量
int number= 30;
示例:為已聲明的變量賦值
op=’+’;
下面簡要說明一個解析數(shù)學算式并存儲運算符和運算數(shù)的示例,為說明問題起見,給出一個簡單數(shù)學算式:8.25+30。
聲明三個變量用來存儲運算數(shù)和運算符
float floatNum;
int intNum;
char op;
程序在計算上述數(shù)學算式時,首先從左到右掃描數(shù)學算式。假設本次掃描不考慮優(yōu)先級運算,只是完成提取運算數(shù)和運算符的功能。掃描過程如下:如果是運算數(shù),判斷是整數(shù)還是小數(shù),整數(shù)賦值給intNum,如果是小數(shù)賦值給floatNum,如果是運算符賦值給op。下圖是掃描完成后,變量在內(nèi)存儲器的存儲情況。
圖 2-2 不同數(shù)據(jù)類型的變量在存儲器的存儲情況
從圖2-2可以看出,不同數(shù)據(jù)類型的變量在存儲器占用的空間也不相同。數(shù)據(jù)類型為字符型的變量在存儲器占用一個字節(jié)的空間。C語言對整數(shù)類型并沒有嚴格規(guī)定其長度(占用存儲空間的字節(jié)數(shù)),只做了寬泛的限制。圖2-4整數(shù)類型占用4個字節(jié)的存儲空間,是指在32位操作系統(tǒng)下,整數(shù)類型占用4個字節(jié)的存儲空間。
3、基礎數(shù)據(jù)類型
C語言基本數(shù)據(jù)類型見圖2-3。
圖 2-3 C語言基本數(shù)據(jù)類型
C語言基本數(shù)據(jù)類型包括數(shù)值、非數(shù)值兩種類型。數(shù)值類型又分為整數(shù)類型和非整數(shù)類型,整數(shù)類型包括整型、短整型和長整型,非整數(shù)類型包括單精度浮點和雙精度浮點。非數(shù)值包括字符類型。
4、整數(shù)類型
C語言的整數(shù)類型可以說是復雜多樣,根據(jù)存儲空間和數(shù)值范圍可分short(短整型,占用2個字節(jié))、int(整型,機器字長)、long(長整型,32位環(huán)境占用4個字節(jié),64位環(huán)境占用8個字節(jié))、long long(加長整型,占用8個字節(jié)),根據(jù)整數(shù)分類又可分為有符號整數(shù)和無符號整數(shù)。
對整數(shù)而言,C語言為什么要定義這么多不同的類型呢?
在發(fā)明C語言的年代,計算機的存儲資源是非常珍貴的,程序員設計和編寫程序?qū)Υ鎯Y源要精打細算,否則存儲資源就不夠用了。在存儲資源緊張的情況下,程序員優(yōu)先選用占用存儲空間小且能夠滿足數(shù)據(jù)處理要求的整數(shù)類型。即使在當前存儲資源較為寬松的環(huán)境下,程序員在編寫代碼時,通常能預想到需要使用到的數(shù)據(jù)范圍的大小,這樣在處理一個數(shù)據(jù)時,可以從語言所提供的類型中選用最合適的類型來存儲數(shù)據(jù)。
實際上,C語言對short、int、long 并沒有嚴格規(guī)定其長度(占用存儲空間的字節(jié)數(shù)),只做了寬泛的限制:short 至少占用 2 個字節(jié);int 建議為一個機器字長。32 位環(huán)境下機器字長為 4 字節(jié),64 位環(huán)境下機器字長為 8 字節(jié);short 的長度不能大于 int,long 的長度不能小于 int。
由此可見,short 并不一定真的”短“,long 也并不一定真的”長“,它們有可能和 int 占用相同的字節(jié)數(shù)。在 16 位系統(tǒng)下,short 的長度為 2 個字節(jié),int 也為 2 個字節(jié),long 為 4 個字節(jié)。在32位和64位操作系統(tǒng)下,short 的長度為 2 個字節(jié),int 為 4 個字節(jié),long 也為 4 個字節(jié),long long類型為8個字節(jié)。
那么問題來了,在實際編程中如何確定一個整型占用的存儲空間大小呢?
C語言提供了sizeof運算符來獲取數(shù)據(jù)類型占用的字節(jié)數(shù),sizeof運算符傳入的參數(shù)可以是數(shù)據(jù)類型,也可以是變量名稱。例如下面的代碼分別輸出了不同數(shù)據(jù)類型的字節(jié)數(shù):
#include
void main()
{
char ch = 'a';
printf("short:%d字節(jié)n",sizeof(short));
printf("int:%d字節(jié)n",sizeof(int));
printf("long:%d字節(jié)n",sizeof(long));
printf("long:%d字節(jié)n",sizeof(long long));
printf("ch:%d字節(jié)n",sizeof(ch));
}
執(zhí)行上述代碼,輸出結(jié)果為:
整數(shù)類型有正整數(shù)和負整數(shù)之分,在C語言中,規(guī)定整型的最高位為符號位,最高位為“0”表示正數(shù),最高位為“1”表示負數(shù),其它位表示數(shù)值。因此整型類型的數(shù)據(jù)能夠表示的最小值為:-2n-1 —2n-1-1(n為該類型所占存儲空間的二進制位數(shù))。
一個數(shù)在計算機中的二進制表示形式, 叫做這個數(shù)的機器數(shù)。例如:十進制數(shù)+3,使用8個二進制位表示,轉(zhuǎn)換成二進制就是00000011。如果是 -3 ,就是10000011 。00000011和10000011就是機器數(shù)。
因為最高位是符號位,因此機器數(shù)的形式值不一定等于真正的數(shù)值。例如上面的有符號數(shù) 10000011,其最高位1代表負,其真正數(shù)值是 -3 而不是形式值131(10000011轉(zhuǎn)換成十進制等于131)。為區(qū)別起見,后面的內(nèi)容將帶符號位的機器數(shù)對應的真正數(shù)值稱為機器數(shù)的真值。
下面我們編寫一個簡單的程序,分別輸出+3和-3的機器數(shù),查看機器數(shù)的形式值是否為機器數(shù)的真值。
#include
void printf_binary(char n);
void main()
{
char positive = 3;
char negative = -3;
printf_binary(positive);
printf_binary(negative);
}
// 輸出二進制數(shù)
void printf_binary(unsigned char n)
{
char i=0;
for(i=0; i< 8; i++ ){
if(n&(0x80 >?>i)){
printf("1");
}else{
printf("0");
}
}
printf("n");
}
char為字符類型,占用一個字節(jié)的存儲空間,值范圍為-128~127。printf_binary(unsigned char n)函數(shù)將十進制數(shù)轉(zhuǎn)換為二進制數(shù)輸出,函數(shù)及函數(shù)內(nèi)的語句同學們先不要急于了解,后面的課程都會講到,重點關注程序的輸出結(jié)果
從輸出結(jié)果可以看出,+3的機器數(shù)為0000011,其形式值為3,機器數(shù)的形式值和真值相同。-3的機器數(shù)為11111101,其形式值為253,真值為-3,機器數(shù)的形式值和真值不相同,若把+3和-3的機器數(shù)相加,結(jié)果為0,運算結(jié)果正確。實際上,計算機是以補碼的方式來存儲數(shù)值的。
計算機為什么以補碼方式來存儲數(shù)值呢?對于一個數(shù),計算機要使用一定的編碼方式進行存儲,原碼、反碼、補碼是機器存儲一個具體數(shù)字的編碼方式。
原碼就是符號位加上真值的絕對值,即最高位是符號位,其余位用來表示值。例如1個8位的二進制數(shù):
[+3] 原碼:0000 0011
[-3] 原碼:1000 0011
原碼比較容易理解,但不利于計算機的加減計算,因為原碼帶符號位,若對兩個原碼表示的機器數(shù)直接進行加減運算,運算結(jié)果并不正確。若讓計算機能夠識別符號位,計算機的運算電路設計就會非常復雜。因此計算機一般不采用原碼的方式來存儲數(shù)值。
既要讓符號位參與運算,又要讓運算電路設計簡單,使用補碼存儲數(shù)值就會解決這個問題。
補碼應用了數(shù)學中同余的概念,同余的兩個數(shù)具有互補關系。給定一個正整數(shù)m,如果兩個整數(shù)a和b滿足a-b能夠被m整除,即(a-b)/m得到一個整數(shù),那么就稱整數(shù)a與b對模m同余,a和b具有互補關系。
回到整數(shù)類型, 補碼的計算規(guī)則為最高位是符號位,0表示正數(shù),1表示負數(shù),正數(shù)的補碼等于本身,負數(shù)的補碼等于反碼(正數(shù)的反碼等于本身,負數(shù)的反碼除符號位外,各位取反)+1。
例如:
[-3] 原碼:1000 0011 反碼:1111 1100 補碼:1111 1101
前面討論的都是有符號整數(shù),可以表示正負數(shù)。若只需要處理正整數(shù),可以在上述類型關鍵字前面添加unsigned關鍵字表示無符號整數(shù),兩個關鍵字用空格隔開,因為不需要符號位,因此無符號整數(shù)表示的范圍為:0 —2n-1。下表列出了符號整數(shù)的類型。
整型變量可按如下方式聲明:
int pageNumber;
long int size;
short age;
unsigned short readCount;
在一條語句中,可以聲明多個同一類型的整型變量,每個變量之間用逗號分隔:
int pageNumber, likeNumber,readCount;
整型變量可按如下方式定義:
int pageNumber=230;
short age = 21;
unsigned short readCount=1260;
在定義變量或為變量賦值時,常常會用到一些數(shù)值,這些值通常稱為字面值。字面值有三種不同的表示形式:十進制、八進制和十六進制。
八進制表示:在八進制數(shù)值前面加前綴數(shù)字0,其數(shù)碼取值為0—7,例如:023、0457、01329等;
十六進制表示:前綴為“0X”或“0x”,數(shù)碼取值0—9、A—F、或a—f。例如:0X2A、0XA0、0Xffff等;
十進制表示:既無前綴也無后綴。例如:236、56、7890等。
在字面值后面可以添加u或U(unsigned)、l或L(long)、u/U與l/L的組合(如:ul、lu、Lu等),表示該字面值的類型。例:100u; 123u; 0x123L;
5、浮點類型
浮點類型用來存儲實數(shù),為什么在C中把實數(shù)稱為浮點數(shù)呢?在C語言中實數(shù)是以指數(shù)形式存放在存儲單元中,類似于科學計數(shù)法形式,如:2.1E5、3.7e-2等,其中e或E之前必須有數(shù)字,且e或E后面的指數(shù)必須為整數(shù)。
一個大于0的實數(shù)可以用下面指數(shù)的方式來表示:
a * 10n (1<=a<10,n為整數(shù))
其中,a是該數(shù)值的有效位數(shù),有效位數(shù)從左邊第一個不是0的數(shù)起,到末尾數(shù)字為止,所有的數(shù)字(包括0,科學計數(shù)法不計10的n次方),稱為有效數(shù)字。例如:光速是3E8,其有效數(shù)字是1位,值是3;世界人口數(shù)大約是6.1E9,其有效數(shù)字是2位,值是6.1。
n是該數(shù)值的整數(shù)部分減1的正整數(shù)。
一個小于0的實數(shù)可以用下面指數(shù)的方式來表示:
a * 10-n (1<=a<10,n為整數(shù))
a的取值同上面相同,n的取值為原數(shù)中左邊第一個不為0的數(shù)字前面所有的0的個數(shù)(包括小數(shù)點前面的0)。
一個實數(shù)表示為指數(shù)形式,通過移動小數(shù)點可以有多種表示方法。例如:實數(shù)3.14159可以表示為以下的指數(shù)形式:
3.14159E0
0.314159E1
314.159E-2
上面的指數(shù)形式因為小數(shù)點所在位置不同,指數(shù)也不同,但實際表示的數(shù)值都是3.14159。因此只要在小數(shù)點位置浮動的同時改變指數(shù)的值,就可以保證它的值不會改變。由于小數(shù)點位置可以浮動,所以實數(shù)的指數(shù)形式稱為浮點數(shù)。
在C語言中,浮點類型有float和double兩種,分別表示單精度浮點數(shù)和雙精度浮點數(shù)。精度是指描述一個數(shù)值的準確程度,在數(shù)學運算中,經(jīng)常會用到近似數(shù),近似數(shù)與原數(shù)值非常相近,但又不完全符合原數(shù)值,只能說在某種程度上近似。精度與近似數(shù)相似,也是用一個與原數(shù)值非常相近的數(shù)代替原來的數(shù)值。
前面說過存儲一個數(shù)值所用的字節(jié)越多,其精度越高,數(shù)值范圍也越大。由此看來,精度與存儲字節(jié)數(shù)密切相關,float類型的存儲空間是4個字節(jié),其表示的值范圍約為10-38到1038,double類型的存儲空間是8個字節(jié),其表示的值范圍約為10-308到10308,float存儲數(shù)值的精度和范圍要小于double存儲數(shù)值的精度和范圍。因此,float是單精度數(shù)值,double是雙精度數(shù)值。
圖 2-4浮點型變量占用的存儲空間
實數(shù)的指數(shù)形式有多種表示方法,計算機在存儲實數(shù)時,一般采用標準化的指數(shù)形式,即小數(shù)點前的數(shù)字為0,小數(shù)點后第1為數(shù)字不為0。例如:0.314159E1就是3.14159的標準化的指數(shù)形式。
float類型的變量占用4個字節(jié)的存儲空間,系統(tǒng)存儲float類型的實數(shù)時,將實數(shù)分為小數(shù)部分和指數(shù)部分。最高位為符號位,小數(shù)部分占23位,剩余的8位存儲實數(shù)的指數(shù)部分(包括指數(shù)的符號)。由于用二進制形式表示一個實數(shù)以及存儲單元的長度是有限的,因此不可能得到完全精確的值,只能存儲成有限的近似值。小數(shù)部分占的位數(shù)愈多,數(shù)的有效數(shù)字愈多,精度也就愈高。指數(shù)部分占的位數(shù)愈多,則能表示的數(shù)值范圍越大。
double類型的變量占用8個字節(jié)的存儲空間,最高位為符號位,小數(shù)部分占52位,剩余的11位存儲實數(shù)的指數(shù)部分(包括指數(shù)的符號)。
float變量可按如下方式定義:
float price = 12.35f;
float average = 89.2f;
double變量可按如下方式定義:
double pi = 3.14159265;
public double average = 89.2987017;
字面值賦值給float變量時,數(shù)值尾部要加上小寫“f”或大寫“F”聲明為float類型的數(shù)值,不然編譯器會給出從“double”到“float”截斷的警告。因為在C語言中,帶小數(shù)的字面值默認為是double類型,double類型轉(zhuǎn)換為float類型,自然要損失精度,位數(shù)被截斷了。
C語言還提供了long double類型,表示長雙精度浮點數(shù),但C語言并沒有l(wèi)ong double的確切精度,對于不同系統(tǒng)平臺可能有不同的實現(xiàn),有的是8字節(jié),有的是10字節(jié),有的是12字節(jié)或16字節(jié),C語言函數(shù)sizeof(long double)可以輸出當前系統(tǒng)平臺下long double占用的字節(jié)數(shù)。
浮點類型字面值的后綴有:f或F(單精度浮點數(shù))、l或L(長雙精度浮點數(shù)),因為浮點型常數(shù)總是有符號的,所以沒有u或U后綴。例:1.23e5f; 1.23L; -123.45f。
6、字符類型
在編寫程序時,我們還經(jīng)常會遇到需要存儲并操縱字符型數(shù)據(jù)的情況。例如:計算數(shù)學算式時,需要存儲運算符,這時就需要一種可以存儲單個字符數(shù)據(jù)(字符包括字母、數(shù)字、運算符號、標點符號和其他符號,以及一些功能性符號)的數(shù)據(jù)類型。C語言提供了一種char數(shù)據(jù)類型,可以滿足存儲單個字符的需要。
計算機中的字符一般采用ASCII碼表示,ASCII碼是一種標準的字符編碼方式,規(guī)定每個字符對應一個數(shù),例如:十進制數(shù)65對應大寫字母A,97對應小寫字母a。ASCII編碼最后一次更新是在1986年,到目前為止共定義了128個字符。
ASCII字符與編碼映射范圍表:
標準ASCII碼使用7位二進制數(shù)來表示所有的大寫和小寫字母,數(shù)字0 到9、標點符號,以及在美式英語中使用的特殊控制字符。char數(shù)據(jù)類型占用一個字節(jié)的存儲空間,可以表示8位二進制數(shù),完全可以存儲ASCII碼。
char變量可按如下方式定義:
char code=’a’, op=’*’,digit=’0’;
任意單個字符,加單引號。
char code =97
可以直接把ASCII碼十進制數(shù)97賦值給char型變量code。
上面例句中的‘a(chǎn)’, ’*’, ‘0’為字符字面值,字符字面值需要使用一對單引號括起來,括號內(nèi)只能包含一個字符。除了字符常量可以賦值給char型變量外,0~255的整數(shù)常量也可以賦值給char型變量。char型變量通常用來存儲字符,若存儲的數(shù)值超過ASCII碼范圍,變量的值沒有實際意義。
標準C語言沒有提供byte數(shù)據(jù)類型,byte占1個字節(jié)的存儲空間,表示的數(shù)值范圍是0~255。若需要使用byte數(shù)據(jù)類型,可以使用unsinged char來表示byte數(shù)據(jù)類型。
7、類型轉(zhuǎn)換
C語言是強類型語言,變量的數(shù)據(jù)類型被指定后,會一直保持該數(shù)據(jù)類型。同時C語言對參與賦值運算和算術(shù)運算的操作數(shù)數(shù)據(jù)類型要求必須一致,當參與運算的操作數(shù)數(shù)據(jù)類型不一致時,就需要對操作數(shù)的數(shù)據(jù)類型進行類型轉(zhuǎn)換,把參與運算的操作數(shù)轉(zhuǎn)換為同一數(shù)據(jù)類型后,再進行運算。
隱式類型轉(zhuǎn)換
例如:
double PI = 3.14;
int radius = 5;
double s;
s = PI * PI * radius;
在上面的例句中,表達式PI * PI * radius有二個操作數(shù),PI是double類型,radius是整數(shù)類型。因為操作數(shù)的數(shù)據(jù)類型不一致,C語言編譯器會對數(shù)據(jù)類型做強制轉(zhuǎn)換,將radius的整數(shù)類型強制轉(zhuǎn)換為double類型。
這種轉(zhuǎn)換是C編譯器自動進行的,開發(fā)者不需要進行任何操作,由C編譯器自動完成,這種類型的轉(zhuǎn)換也稱為隱式轉(zhuǎn)換。
由于不同的數(shù)據(jù)類型存儲空間和表示的數(shù)值精度是不同的,因此在數(shù)據(jù)類型的轉(zhuǎn)換過程中,就會存在數(shù)值精度丟失和數(shù)值溢出的問題。例如:double類型轉(zhuǎn)換為float類型,數(shù)值精度就會丟失;long類型轉(zhuǎn)換為int類型時,如果long類型變量存儲的數(shù)值超過了int類型能夠存儲的數(shù)值范圍,就會發(fā)生數(shù)值溢出。
為了避免在轉(zhuǎn)換過程中發(fā)生數(shù)值精度丟失和溢出的問題,隱式轉(zhuǎn)換都會遵循從低級數(shù)據(jù)類型到高級類型的轉(zhuǎn)換規(guī)則,也可以說是從數(shù)值存儲精度小的類型到存儲數(shù)值精度高的類型轉(zhuǎn)換。這些數(shù)據(jù)類型按數(shù)值存儲范圍大小依次為:
short->int->long->float->double
下表列出了數(shù)據(jù)類型隱式轉(zhuǎn)換的一般規(guī)則。
【例】類型的隱式轉(zhuǎn)換練習
程序清單 sample.c
#include
int main()
{
// 聲明char類型的變量
char chTemp = 65;
// 聲明int類型的變量
int nTemp = 34;
// 聲明float類型的變量
float fTemp = 29.6f;
// 聲明double類型的變量
double dTemp = 86.69;
printf("char類型的數(shù)據(jù)與float類型的進行相加運算,運算結(jié)果為:%.2fn",(chTemp+fTemp));
printf("float類型的數(shù)據(jù)與double類型的進行相加運算,運算結(jié)果為:%.2fn",(fTemp+dTemp));
printf("char類型的數(shù)據(jù)與int類型的進行相加運算,運算結(jié)果為:%dn",(chTemp+nTemp));
}
7、顯示類型轉(zhuǎn)換
顯示類型轉(zhuǎn)換是相對隱式轉(zhuǎn)換來說的,隱式轉(zhuǎn)換由C編譯器自動進行,不需要開發(fā)者做任何操作。顯示類型轉(zhuǎn)換需要開發(fā)者在代碼中對數(shù)據(jù)類型進行顯示類型轉(zhuǎn)換。
當進行數(shù)據(jù)類型的顯示轉(zhuǎn)換時,程序員需要自身判斷類型轉(zhuǎn)換過程中是否會發(fā)生數(shù)值溢出或精度丟失,當由精度高的類型轉(zhuǎn)換為精度低的類型時,會發(fā)生精度丟失。
顯示轉(zhuǎn)換的一般形式為:
(類型名)要轉(zhuǎn)換的變量或常量
例如:
// 將數(shù)值36.9強制轉(zhuǎn)換為int,精度丟失
int nTemp = (int)36.9;
// 聲明double類型的變量
double dTemp = 12.15;
// 將double類型強制轉(zhuǎn)換為int,精度丟失
int nV = (int)dTemp;
在上面的例句中,36.9是數(shù)值常量,默認為double類型,顯示轉(zhuǎn)換為int類型并賦值給nTemp,此時nTemp的值為36,精度丟失。
dTemp是double類型的變量,將dTemp的值賦值給int變量時,需要進行顯示轉(zhuǎn)換,int變量只存儲double變量的整數(shù)部分,小數(shù)部分丟失。
評論