程序說到底就是對數(shù)據(jù)的處理,所以首先要弄清楚需要處理哪些數(shù)據(jù),計算機如何存儲這些數(shù)據(jù)。C語言根據(jù)需要,抽象出了一些基本數(shù)據(jù)類型和衍生數(shù)據(jù)類型。這些可以滿足大部分需求,更復(fù)雜的抽象數(shù)據(jù)類型亦可通過它們來組成。
1. 數(shù)據(jù)存儲
計算機存儲的最小單位是bit,它表示0或1。而計算機可尋址的最小單位是byte,它至少由8個bit組成,內(nèi)存就是由許多個byte組成并編址的。有OS時,C操作的是邏輯地址,OS會最終轉(zhuǎn)為物理地址。
一個數(shù)據(jù)由多個bit組成,按照二進制的表示習(xí)慣,將最左側(cè)的bit叫做MSB(Most Significant Bit),最右側(cè)的bit叫做LSB(Least Significant Bit),這里的significant當然是指該bit表示的數(shù)量級。這些bit會劃分到連續(xù)的byte中,存儲時byte的順序基于系統(tǒng)或平臺。這又引出了大小端(Endian)的概念,LSB存儲在高地址時叫Big Endian,否則叫Little Endian。但無論如何,該數(shù)據(jù)的地址都是指最低地址。而且進一步講,數(shù)組和結(jié)構(gòu)成員的地址也是按它們的指數(shù)或定義順序向高地址增長的,即使在棧里也不例外(棧底一般在高地址,棧頂向低地址增長)。
C提供了關(guān)鍵字sizeof獲取數(shù)據(jù)或類型占用內(nèi)存的大小,結(jié)果以byte為單位。sizeof后如果跟括號,里面可以是類型或表達式,否則后面只能跟表達式。sizeof是運算符而不是函數(shù),它的結(jié)果在編譯時確定。如果操作對象是表達式,則返回其對應(yīng)類型的大小,而不會執(zhí)行表達式。
計算機處理數(shù)據(jù)的單位叫word,我們一般說的32位64位計算機就是指word。為了提高處理效率,數(shù)據(jù)盡量不要或少跨word存儲。這就需要數(shù)據(jù)的存儲地址是數(shù)據(jù)長度的整數(shù)倍,和類型長度一樣,對齊的單位一般是2的冪?;绢愋蛯R單位是其類型長度,組合類型(數(shù)組、結(jié)構(gòu)、聯(lián)合)的對齊單位是其成員的最大對齊單位。由于默認對齊的存在,組合類型的成員之間可能有一些空隙,sizeof的結(jié)果可能不是簡單的累加了。但要注意,組合類型的成員總是盡量向低地址靠齊,所以組合類型的開頭是不會有空隙的。
新規(guī)范中引入了關(guān)鍵字_Alignof和_Alignas(同sizeof一樣不是函數(shù)),新引入的關(guān)鍵字一般以'_'和大寫字母開頭(為了不和編譯器的擴展或用戶自定義沖突),如果想用小寫字母開頭的,需要include對應(yīng)的標準庫(宏定義)。_Alignof后跟類型,得到類型的對齊單位。_Alignas后跟類型或整數(shù)常量,用來修飾類型或變量定義,但它不能小于其原有的對齊單位。
C中數(shù)據(jù)一般叫對象(object),不同的數(shù)據(jù)會有不同的類型(type)。類型決定了數(shù)據(jù)的長度和格式,除此之外的類型屬性(比如const)只有編譯器能看到,而對計算機是透明的。C定義了char、int、float、double四種基本型,還有兩個特殊類型void和枚舉,以及它們的衍生(derived)類型(指針、數(shù)組、結(jié)構(gòu)、聯(lián)合、函數(shù))?;拘秃兔杜e并稱為代數(shù)型(arithmetic),代數(shù)型和指針并稱為度量型(scalar),數(shù)組和結(jié)構(gòu)并稱為聚合型(aggregate)。整型(interger)包括char、int和枚舉,浮點型包括float和double,整型和浮點型并稱實數(shù)型(real)。新規(guī)范中還定義了可選關(guān)鍵字_Bool、_Complex和_Imaginary,個人認為可以當做基本型,而且_Bool可以劃到整型里。
類型前可以有多種修飾符,它們有不同種類和用途,這里先介紹一類叫類型說明符(非規(guī)范定義)。包括short、long、long long(新規(guī)范)、signed和unsigned,它們僅作用于基本型,且長度和符號可以組合使用。所有的說明符都可以修飾int,int默認為signed(可不寫),有說明符時int可不寫。signed、unsigned可修飾char,long可修飾double,其它用法皆非法。
2. 數(shù)據(jù)類型
2.1 整型
整型數(shù)有不同的長度,其中char始終為1,int一般為字長,枚舉與int一樣,_Bool基于實現(xiàn)。short至少16bit且不超過int,long至少32bit且不低于int,long long至少64bit且不低于long。無符號整型的值即它的二進制數(shù)的值,有符號整型的值基于平臺實現(xiàn)。少數(shù)老的平臺采用的是反碼表示法(1's complement),它概念簡單直觀,但卻不方便計算,個人認為注定被淘汰,沒有討論價值。現(xiàn)在幾乎所有的平臺都使用補碼表示法(2's complement),該模型更符合問題的本質(zhì),自然也更便于計算。對于負數(shù),補碼即0減去它的絕對值(有underflow),實現(xiàn)了正負數(shù)的平滑連接。
字符型包含char、wchar_t、char16_t和char32_t,其中char是基本類型,其它為int(帶說明符)的宏定義。char類型永遠是1個byte,可表示basic字符集,它的符號是基于實現(xiàn)的。字符常量用一對單引號表示,引號里為字符或轉(zhuǎn)義序列,引號前有可選前綴L、u和U(分別對應(yīng)后3種字符型)。字符常量本身的類型為int或unsigned int,它的值為引號中字符的編碼或轉(zhuǎn)義序列的值。引號中可以有多個字符,但它們在int中的存儲位置是不定義的。下表中列出了轉(zhuǎn)義序列及其含義:
int型常量以非0數(shù)字開頭則表示10進制數(shù),0x或0X開頭表示16進制數(shù),0開頭表示8進制數(shù)。后面可以有后綴U、L或LL(不限大小寫),分別表示unsigned、long和long long。U可以和L或LL組合,且順序隨意。int型可以按它能表示的最大數(shù)來排序,如果能表示的最大數(shù)相同,則按int、long、long long排序。一個可能的隊列為int、long、unsigned int、unsigned long、long long、unsigned long long。int型常量的類型以后綴類型(默認為int)為起點,從隊列中尋找第一個滿足規(guī)則且能包含其值的類型。規(guī)則是:(1)如果起點為unsigned,則嘗試signed;(2)如果起點為signed且為10進制數(shù),則不嘗試unsigned。另外要注意,不存在負常量,它只是對正常量的負運算。
枚舉(enumeration)是一個特殊的自定義的類型,它為其每一個常量定義了名字,這些常量的值都是int型(可為負數(shù))。枚舉常量的值可以顯式指明,或者為其前一常量加1,第1個常量的隱式值為0。枚舉常量可以像宏一樣使用,往往用于簡單的編號。
2.2 浮點型
浮點型的計算機表示由平臺決定,但幾乎所有實現(xiàn)都遵循規(guī)范IEC-60559(IEEE-754),這里簡單介紹一下該規(guī)范中的浮點表示。根據(jù)數(shù)據(jù)長度(精度),規(guī)范將浮點分為single、double和double-extended,分別對應(yīng)C中的float、double和long double。float和double的長度分別為32和64,long double未作明確定義。
從MSB到LSB,浮點型被分為符號位(sign,1位)、指數(shù)(exponent,8或11位)和尾數(shù)(fraction,23或52位),它可表示為以2為底的科學(xué)計數(shù)。
符號位是整個浮點數(shù)的符號,所以會存在正負0和正負無窮。指數(shù)被解釋為無符號整數(shù),并減去一個偏差(bias)。偏差值會導(dǎo)致emaxemax比eminemin絕對值大1(考慮到倒數(shù)的溢出),比如float類型的偏差為127,emaxemax = 128,eminemin = -127。指數(shù)取最大值時,若尾數(shù)為0,整個數(shù)解釋為正負無窮大,否則為NaN(not a number)。指數(shù)為最小值時,尾數(shù)解釋為0.f0.f(denormalized),否則解釋為1.f1.f(normalized)。
實數(shù)是連續(xù)的,無法用計算機精確表示,整個浮點數(shù)空間可以看做是實數(shù)的采樣。對normalized區(qū)段,每個數(shù)量級的采樣數(shù)是相等的(ff的每一位都有意義),對denormalized區(qū)段,每個數(shù)量級的采樣數(shù)遞減(ff的實際有效位)。denormalized區(qū)段指數(shù)采用的是emin+1emin+1,而非eminemin,保證了和normalized區(qū)段的平滑過渡,很是巧妙!浮點數(shù)也支持正負無窮和NaN這類抽象值,為錯誤處理提供了很好的工具。超出表示邊界的值都是無窮大,無意義的數(shù)(0/00/0,0?∞0?∞)都表示為NaN,它們亦可參加運算,值視情況而定。浮點運算在運算時保持精度,但對結(jié)果進行舍入(每一個運算),舍入的方法是尾數(shù)為偶數(shù)(round to even)。
由于舍入或正負0的存在,和0的比較變得不可靠,所以不要判斷某浮點數(shù)是否為0。在比較兩個浮點數(shù)是否相等時,直接用a == b或a != b,不要將a - b與0比較。浮點的舍入會導(dǎo)致原本相近兩個數(shù)的差被掩蓋,有時調(diào)整算法可以減少誤差,讀者可以思考以下公式變換帶來的精度提高。
浮點常量一般以10為底,新規(guī)范支持以2為底的浮點常量(0x開頭),它可以避免精度的丟失。浮點常量的尾數(shù)部分不可少,10為底用10進制數(shù),2為底用16進制數(shù)。尾數(shù)可以為整數(shù)或小數(shù),若小數(shù)點前或后為0可不寫,但不能同時不寫。指數(shù)部分從字母e、E(10為底)或p(2為底)開始,后面都跟10進制數(shù)(為了和后綴中的F區(qū)分開來)。10為底的浮點常量可以沒有指數(shù)部分,但這時尾數(shù)不能為整數(shù),否則會被解析為整型。2為底的浮點常量必須有指數(shù),否則后綴F無法與尾數(shù)區(qū)分。浮點常量默認為double,后綴F和L(不分大小寫)分別表示float和long double,無類型提升(和常整型不同)。
新規(guī)范中支持復(fù)數(shù)(complex)和虛數(shù)(imaginary),但是可選功能。多數(shù)編譯器是用結(jié)構(gòu)來定義復(fù)數(shù)的,而不是用關(guān)鍵字_Complex和_Imaginary。復(fù)數(shù)和虛數(shù)必須由浮點型修飾,虛數(shù)常量ii是基于實現(xiàn)的。除ii外沒有復(fù)數(shù)常量,是由浮點數(shù)的類型提升完成的。
2.3 組合類型
這里的組合類型非標準定義,它指數(shù)組、結(jié)構(gòu)和聯(lián)合(以后將結(jié)構(gòu)和聯(lián)合叫復(fù)合類型)。組合類型由多個成員組成,每個成員可以為除void和函數(shù)之外的任何一種類型。組合類型是表示復(fù)雜類型的重要工具,也是抽象類型的必要元素。
數(shù)組由多個相同類型的成員組成,它們在內(nèi)存中連續(xù)分布,可以通過index獲取。C中沒有多維數(shù)組的概念,只有數(shù)組的數(shù)組,僅此而已。數(shù)組的長度不能為0,新規(guī)范支持結(jié)構(gòu)末尾的可變數(shù)組a[](flexible array member),它相當于預(yù)定了一個數(shù)組的位置,而不一定有成員。含可變數(shù)組的結(jié)構(gòu)至少還有另外一個成員,且它不可以再做組合類型的成員。由于align的作用,可變數(shù)組與結(jié)構(gòu)最后一個成員之間可能有空隙,該結(jié)構(gòu)的sizeof結(jié)果包含該空隙。
字符串是一類特殊的數(shù)組,它的成員是字符型。字符串常量定義在一對雙引號中,其中可以是字符或轉(zhuǎn)義序列,也可以有L、u或U作前綴。字符串常量中不能換行,但可以有空格或tab。連續(xù)的字符串常量(中間可有空白)在預(yù)處理時會被拼接為一個,該特點便于書寫長字符串和分開轉(zhuǎn)義序列與普通字符,單字節(jié)字符串和寬字節(jié)字符串拼接的結(jié)果為寬字符。字符串常量(拼接后)末尾有一個隱式的字符'\0',字符串函數(shù)把它當做結(jié)束符。任何字符串常量(包括初始化字符串)都會存儲在常量區(qū),編譯器可以選擇合并相同的字符串常量。字符串常量可以直接當數(shù)組使用,但對其修改是未定義的。
結(jié)構(gòu)體中可以包含不同類型的成員,由它可以組成更復(fù)雜的抽象類型。如果從面向?qū)ο蟮慕嵌日f,對象的屬性體現(xiàn)在結(jié)構(gòu)的普通成員上,對象之間的關(guān)系則體現(xiàn)在結(jié)構(gòu)的指針成員上。結(jié)構(gòu)中還可以定義位域(bit field),位域是指定長度的整型,它被放置在word中。空間足夠時,相鄰位域緊靠之前的位域,否則未定義。未做顯式說明時,int位域的符號基于實現(xiàn)。對位域的操作效率比普通整型低,它往往用于緊湊空間或一致性需求(傳輸協(xié)議)。
聯(lián)合也可以包含不同類型的成員,但它們的地址都是一樣的(等于聯(lián)合本身的地址),也就是說成員在內(nèi)存里有重疊。聯(lián)合中也可以有位域,它們也是互相重疊的。聯(lián)合一般用于結(jié)構(gòu)的分情況解釋或數(shù)據(jù)的局部操作,后者的一個例子是大小端判斷。
基本類型和字符串都有常量表示,新規(guī)范中還支持復(fù)合常量(compound literal),它的定義方法是類型后加初始化序列。但復(fù)合常量本質(zhì)上是匿名變量,它和變量的性質(zhì)幾乎一樣,唯一的差別是const的復(fù)合常量和字符串常量有一樣的存儲屬性。
2.4 其它類型
void嚴格來說不是一個類型,單獨使用時表示“沒有”,僅用于表示函數(shù)返回、函數(shù)參數(shù)和表達式。void指針僅表示數(shù)據(jù)地址,而無數(shù)據(jù)的類型信息,它取代了舊規(guī)范庫中一些場合的char指針。
函數(shù)也是一種對象,它的類型由返回類型和參數(shù)類型共同決定。而且函數(shù)可以看作const的,因為它在代碼區(qū),不可修改。
指針即帶有類型的地址,指針的長度是未定義的,但相同類型的指針長度是相同的。機器碼中的變量使用地址表示的,所以指針變量只不過是地址的地址,無需多做探討。如果說變量是對數(shù)據(jù)的一般化,則指針變量是對變量的一般化。這種更深層次的功能使得C更靈活更強大,但同時也更危險,因為它影響的不是某個數(shù)據(jù)。避免問題的關(guān)鍵就是一定要“指有所指”,未初始化的指針(dangling pointer)可能破壞任何數(shù)據(jù)。
空指針是一個特殊的指針常量,它不等于任何有意義的地址,它無法直接表示。常量0在特定語境下表示空指針,但最終的編譯結(jié)果未必是0。NULL大部分時候指空指針,宏NULL一定要定義成對0的強制轉(zhuǎn)換,以避免0的多義性。
還有一類指針常量就是靜態(tài)存儲的變量的地址以及函數(shù)的地址,它們可以用于初始化靜態(tài)存儲的變量(必須用常量初始化,見下章)。
3. 類型轉(zhuǎn)換
表達式中的操作符有時會引起操作數(shù)的類型轉(zhuǎn)換(type conversion),本節(jié)對這類轉(zhuǎn)換做一些總結(jié)。當新類型可以表示操作數(shù)時,轉(zhuǎn)換后的值不變。任何度量類型轉(zhuǎn)換為_Bool時,如果為0則轉(zhuǎn)為0,否則轉(zhuǎn)為1。向unsigned整型轉(zhuǎn)換時,若操作數(shù)是整數(shù)則取模,若為浮點則去掉小數(shù)部分(整數(shù)部分若超出新類型則未定義),若為復(fù)數(shù)則先去掉虛部。向signed整型轉(zhuǎn)換時,若操作數(shù)超出表示則未定義。向浮點數(shù)轉(zhuǎn)換時,若無法精確表示則舍入方法未定義。向復(fù)數(shù)轉(zhuǎn)換時,若操作數(shù)無虛部則結(jié)果虛部為0。
整型和指針可以互相轉(zhuǎn)換,但結(jié)果是未定義的。指針之間的轉(zhuǎn)換也可以,但align不一致時未定義。甚至不同類型的函數(shù)指針也可以轉(zhuǎn)換,但無意義。指針轉(zhuǎn)換并不改變所指向地址的內(nèi)容,用新指針取值時數(shù)據(jù)將被重新解釋。
除了顯式的強制轉(zhuǎn)換外,還會發(fā)生一些隱式的轉(zhuǎn)換,尤其常見于代數(shù)運算中。整型提升(integer promotion)將小整型提升為int或unsigned int,若int包含該小整型則轉(zhuǎn)為int,否則轉(zhuǎn)為unsigned int。整型提升僅發(fā)生于前置+-、~和移位的所有操作數(shù),以及switch語句和常用代數(shù)轉(zhuǎn)換中。常用代數(shù)轉(zhuǎn)換(usual arithmatic conversion)發(fā)生在比較、算術(shù)(+-*/%)和位(&|^)運算中,它將兩個操作數(shù)轉(zhuǎn)換為相同的類型:
(1)若操作數(shù)中有浮點數(shù),則向最高精度浮點數(shù)轉(zhuǎn)換。但整數(shù)無需做整型提升,實數(shù)無需向復(fù)數(shù)轉(zhuǎn)換。
(2)若操作數(shù)中沒有浮點數(shù),則先做整型提升。提升后若一個類型包含另一個,則轉(zhuǎn)換為較大范圍者,否則轉(zhuǎn)換為較大范圍的無符號類型。
賦值操作、函數(shù)的參數(shù)傳遞和值返回也可以看做是隱式轉(zhuǎn)換,當不知道函數(shù)參數(shù)的類型時,整型要做整型提升,float要轉(zhuǎn)換為double(default argument promotion)。
另外還有兩個重要的隱式類型轉(zhuǎn)換必須弄清楚,就是數(shù)組名和函數(shù)名。數(shù)組名即表示整個數(shù)組,但在除sizeof、_Alignof和&作用下,數(shù)組名自動轉(zhuǎn)換為首元素的指針。字符串常量在以上3種情況和用作為數(shù)組初始化之外,也自動轉(zhuǎn)換為首元素的指針。函數(shù)名是函數(shù)類型,在除sizeof、_Alignof和&作用下,函數(shù)名自動轉(zhuǎn)換為相應(yīng)函數(shù)類型的指針。
“我是一名從事了10年開發(fā)的老程序員,最近我花了一些時間整理關(guān)于C語言、C++,自己有做的材料的整合,一個完整的學(xué)習(xí)C語言、C++的路線,學(xué)習(xí)材料和工具。C/C++、編程愛好者的聚集地就在我這里 企鵝進
關(guān)注我,帶你遨游代碼世界!
最后分享一張C/C++學(xué)習(xí)路線圖給愛學(xué)習(xí)的小伙伴們
-
計算機
+關(guān)注
關(guān)注
19文章
7488瀏覽量
87852 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3019瀏覽量
74005 -
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136692
發(fā)布評論請先 登錄
相關(guān)推薦
評論