摘要:結(jié)構(gòu)體類型的特殊聲明在初階結(jié)構(gòu)體中,我們已經(jīng)將了結(jié)構(gòu)體類型是如何進(jìn)行聲明的,那么在這里,我們將講一些特殊的結(jié)構(gòu)體聲明不完全的聲明。所以我們應(yīng)該這樣寫通過(guò)指針來(lái)找到下一個(gè)同類型結(jié)構(gòu)體的寫法,我們就稱之為結(jié)構(gòu)體的自引用。
char
short
int
long
float
double
那么除了這些內(nèi)置的類型,C語(yǔ)言還為我們提供了一些自定義類型(構(gòu)造類型),讓我們可以自己創(chuàng)造自己需要的類型。
其中包含我們結(jié)構(gòu)體、枚舉、聯(lián)合體。
那么今天我們就來(lái)詳細(xì)地學(xué)習(xí)一下這三種類型。
在看到這類代碼,別再說(shuō)你不認(rèn)識(shí)了!手把手帶你認(rèn)識(shí)初階結(jié)構(gòu)體(結(jié)構(gòu)體類型的聲明、初始化、成員訪問(wèn)與傳參,全在這篇文章里)一文中,我們已經(jīng)對(duì)結(jié)構(gòu)體有了一個(gè)初步的認(rèn)識(shí),學(xué)習(xí)了結(jié)構(gòu)體類型的聲明、定義和初始化,(忘記了的同學(xué)趕緊點(diǎn)擊文章鏈接去復(fù)習(xí)一下!!)那么今天,我們將在這個(gè)基礎(chǔ)上,對(duì)結(jié)構(gòu)體的使用進(jìn)行更多的講解。
在初階結(jié)構(gòu)體中,我們已經(jīng)將了結(jié)構(gòu)體類型是如何進(jìn)行聲明的,那么在這里,我們將講一些特殊的結(jié)構(gòu)體聲明——不完全的聲明。
比如:當(dāng)我們進(jìn)行結(jié)構(gòu)體聲明的時(shí)候,沒(méi)有聲明結(jié)構(gòu)體的名字。
下面我們寫了兩個(gè)內(nèi)容相同的匿名結(jié)構(gòu)體,在第一個(gè)結(jié)構(gòu)體中創(chuàng)建了結(jié)構(gòu)體變量,在第二個(gè)結(jié)構(gòu)體中創(chuàng)建了指針變量,那么第一個(gè)結(jié)構(gòu)體的結(jié)構(gòu)體變量的地址能否放到第二個(gè)結(jié)構(gòu)體創(chuàng)建的指針變量呢?
其實(shí)這個(gè)問(wèn)題的本質(zhì)就是,這兩個(gè)匿名的結(jié)構(gòu)體雖然內(nèi)容是一樣的,但是編譯器會(huì)認(rèn)為它們是相同的類型嗎?
從上圖的結(jié)果中我們可以看到,雖然程序跑起來(lái)了,但是編譯會(huì)報(bào)警告,這是因?yàn)榫幾g器把這兩個(gè)內(nèi)容完全相同的結(jié)構(gòu)體類型當(dāng)成是完全不同的兩個(gè)類型。
所以我們?cè)诎?a1賦值給p1時(shí),實(shí)際上是非法的。
在初階結(jié)構(gòu)體中,我們提到了結(jié)構(gòu)體中可以包含結(jié)構(gòu)體,那么今天我們?cè)僦v一下結(jié)構(gòu)體包含結(jié)構(gòu)體的進(jìn)階——結(jié)構(gòu)體的自引用。即結(jié)構(gòu)體中不僅可以包含別的結(jié)構(gòu)體,還可以包含自己的結(jié)構(gòu)體。
那么它有什么意義呢?
相信大家應(yīng)該聽(tīng)過(guò)數(shù)據(jù)結(jié)構(gòu)的概念。
數(shù)據(jù)結(jié)構(gòu)其實(shí)描述的是數(shù)據(jù)在內(nèi)存中存儲(chǔ)的結(jié)構(gòu),它在內(nèi)存中可能是按順序存放的,稱為順序表。
也可能是在內(nèi)存中散亂地存放著,但是我們可以把這些數(shù)據(jù)依次鏈接起來(lái),通過(guò)一個(gè)數(shù)據(jù)來(lái)找到另一個(gè)數(shù)據(jù),這種結(jié)構(gòu)我們稱為鏈表。
所以鏈表其實(shí)就是通過(guò)鏈條把數(shù)據(jù)串聯(lián)起來(lái)。
所以,在鏈表中,我們可以把1看做一個(gè)節(jié)點(diǎn),通過(guò)節(jié)點(diǎn)就能找到2。
那么在結(jié)構(gòu)體中,我們是否就可以通過(guò)自引用來(lái)從一個(gè)節(jié)點(diǎn)出發(fā),找到下一節(jié)點(diǎn),依次把所有數(shù)據(jù)都找到呢?
答案好像是肯定的,那么落到代碼中,又應(yīng)該如何寫呢?
我們看上面這段代碼,我們?cè)诮Y(jié)構(gòu)體Node中放入了一個(gè)數(shù)據(jù),表示這個(gè)節(jié)點(diǎn)中的數(shù)據(jù),然后又放入了該結(jié)構(gòu)體的變量n,這樣我們就能通過(guò)一個(gè)結(jié)構(gòu)體找到另一個(gè)結(jié)構(gòu)體了。
但是大家思考一下,上面這段代碼有什么問(wèn)題嗎?
是不是如果一個(gè)結(jié)構(gòu)體包含另一個(gè)結(jié)構(gòu)體,另一個(gè)結(jié)構(gòu)體又包含另外一個(gè)結(jié)構(gòu)體,如此嵌套下去,那么這個(gè)結(jié)構(gòu)體就會(huì)變得非常大。所以這種寫法是不正確的。
那么正確的應(yīng)該是怎樣的呢?
我們要訪問(wèn)下一個(gè)節(jié)點(diǎn),其實(shí)并不一定要把整個(gè)結(jié)構(gòu)體的信息都存起來(lái),我們只需要把這個(gè)節(jié)點(diǎn)的地址存起來(lái),通過(guò)地址就能訪問(wèn)它的信息了,這樣就可以大大減少空間的浪費(fèi)了。
所以我們應(yīng)該這樣寫:
通過(guò)指針來(lái)找到下一個(gè)同類型結(jié)構(gòu)體的寫法,我們就稱之為結(jié)構(gòu)體的自引用。而這實(shí)際上就是我們實(shí)現(xiàn)鏈表的思路。
我們?cè)倏纯聪旅孢@種寫法:
雖然我們寫了一個(gè)匿名的結(jié)構(gòu)體,但是我們給它類型重定義為了Node,即給它重新進(jìn)行了命名,但是要注意,上面這種寫法是不可行的!!
因?yàn)槲覀儗?duì)結(jié)構(gòu)體類型重定義時(shí),實(shí)際上是在后面重定義的,而成員變量是定義在了類型重定義之前的,即這里成員變量Node*是找不到Node類型的。
所以,正確的寫法還是要求我們老老實(shí)實(shí)把結(jié)構(gòu)體的名字寫在前面,并把結(jié)構(gòu)體指針的類型名寫全,不要再悄悄匿名啦!!
下面我們討論一個(gè)很重要的問(wèn)題:結(jié)構(gòu)體的大小是如何計(jì)算的?
這實(shí)際上是結(jié)構(gòu)體中一個(gè)非常重要的問(wèn)題,因?yàn)橛?jì)算結(jié)構(gòu)體大小的時(shí)候涉及到內(nèi)存對(duì)齊的問(wèn)題,所以它也是一個(gè)特別熱門的考點(diǎn)。
首先,我們通過(guò)sizeof()操作符計(jì)算一下一個(gè)結(jié)構(gòu)體的大小是多少。
在運(yùn)行代碼之前,我們先猜測(cè)一下,s的大小應(yīng)該是多少呢?會(huì)是6嗎?
從屏幕上打印的結(jié)果中,我們可以看到,結(jié)構(gòu)體s的大小是12個(gè)字節(jié),比我們猜測(cè)的6打了整整一倍,這是為什么呢?
帶著疑問(wèn),我們把結(jié)構(gòu)體類型中的成員變量做一個(gè)微調(diào),再計(jì)算一下它的大小。
再次運(yùn)行程序,我們得到了如下結(jié)果:
屏幕上打印出來(lái)的結(jié)構(gòu)體s的大小變成了8。
那么為什么成員一樣,在僅改變順序的情況下結(jié)構(gòu)體的大小就發(fā)生了變化呢?下面我們就帶著疑惑來(lái)找答案。
實(shí)際上,出現(xiàn)上述情況是因?yàn)樵诮Y(jié)構(gòu)體中,存在著內(nèi)存對(duì)齊的情況。
由于內(nèi)存對(duì)齊在結(jié)構(gòu)體這一塊中體現(xiàn)得特別明顯,所以我們也經(jīng)常稱其為結(jié)構(gòu)體內(nèi)存對(duì)齊。
那么什么是結(jié)構(gòu)體內(nèi)存對(duì)齊呢?
首先我們通過(guò)第一個(gè)結(jié)構(gòu)體來(lái)了解一下結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則:
- 結(jié)構(gòu)體的第一個(gè)成員永遠(yuǎn)放在結(jié)構(gòu)體起始位置偏移量為0的位置。
- 結(jié)構(gòu)體成員從第二個(gè)開(kāi)始,總是放在偏移量為一個(gè)對(duì)齊數(shù)的整數(shù)倍處。
其中:對(duì)齊數(shù)=編譯器默認(rèn)的對(duì)齊數(shù)和變量自身大小的較小值
ps:在Linux環(huán)境下,沒(méi)有默認(rèn)對(duì)齊數(shù);在VS環(huán)境下,默認(rèn)對(duì)齊數(shù)為8
那么在這個(gè)結(jié)構(gòu)體中,第二個(gè)成員是a,類型為int,大小是4個(gè)字節(jié),比VS環(huán)境下的默認(rèn)對(duì)齊數(shù)8小,所以第二個(gè)成員的對(duì)齊數(shù)是4。
那么按照第二條規(guī)則(第二個(gè)及以后的成員變量放在偏移量為對(duì)齊數(shù)整數(shù)倍處),我們畫出第二個(gè)成員變量a在內(nèi)存中的存放。
同理,第三個(gè)成員c2的對(duì)齊數(shù)是1,而a的后面是偏移量為8的位置,8是1的整數(shù)倍數(shù),所以c2就放在偏移量為8的位置。
- 結(jié)構(gòu)體的總大小必須是各個(gè)成員的對(duì)齊數(shù)中最大的那個(gè)對(duì)齊數(shù)的整數(shù)倍。
那么我們看在結(jié)構(gòu)體s中,按個(gè)成員變量的對(duì)齊數(shù)分別是1、4、1,所以最大的對(duì)齊數(shù)是4。所以結(jié)構(gòu)體的總大小就應(yīng)該是4的整數(shù)倍。
而剛才我們畫完三個(gè)成員在內(nèi)存中的存放之后,內(nèi)存總大小是9,不是4的整數(shù)倍數(shù),所以結(jié)構(gòu)體的大小應(yīng)該是一個(gè)大于9并且是4的整數(shù)倍的數(shù),即12。
我們可以看到,結(jié)構(gòu)體s的成員變量只用了6個(gè)字節(jié)的空間,而其余的6個(gè)字節(jié)的空間實(shí)際上是被浪費(fèi)掉了 。
那么同理,我們?cè)賮?lái)看看調(diào)換了順序之后的結(jié)構(gòu)體s的大小。
(這里大家快動(dòng)手自己算一下這個(gè)結(jié)構(gòu)體s的總大小吧~)
由于內(nèi)存對(duì)齊,調(diào)換位置之后的結(jié)構(gòu)體s的總大小變?yōu)榱?,浪費(fèi)的空間為2個(gè)字節(jié)。
那么當(dāng)結(jié)構(gòu)體中包含結(jié)構(gòu)體時(shí),我們應(yīng)該如何計(jì)算呢?
這時(shí)候,我們就要引入結(jié)構(gòu)體內(nèi)存對(duì)齊的第四條規(guī)則:
- 如果存在嵌套,則嵌套結(jié)構(gòu)體對(duì)齊到自己內(nèi)部的成員變量的最大對(duì)齊數(shù)的整數(shù)倍數(shù),結(jié)構(gòu)體的整體大小就是所有成員的最大對(duì)齊數(shù)(包含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍數(shù)。
所以我們可以寫出上面結(jié)構(gòu)體的對(duì)齊數(shù)。
那么根據(jù)以上4條結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則,我們可以畫處并計(jì)算出結(jié)構(gòu)體s4的總大小。
運(yùn)行下面程序,可以驗(yàn)證結(jié)構(gòu)體s4的大小確實(shí)是32。
ps:當(dāng)變量的大小超過(guò)編譯器默認(rèn)的對(duì)齊數(shù)時(shí),對(duì)齊數(shù)為編譯器默認(rèn)的對(duì)齊數(shù)。(因?yàn)閷?duì)齊數(shù)取的是這二者中的較小值)
例如:在VS環(huán)境下(默認(rèn)對(duì)齊數(shù)為8),如果成員變量的大小超過(guò)了8,則該成員變量的對(duì)齊數(shù)仍然是8。
以上就是結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則,你弄懂了嗎??
從上面的計(jì)算中我們知道,結(jié)構(gòu)體內(nèi)存對(duì)齊實(shí)際上浪費(fèi)了很多內(nèi)存的空間,那么為什么我即使浪費(fèi)了內(nèi)存空間,也要內(nèi)存對(duì)齊呢?
雖然這個(gè)對(duì)于結(jié)構(gòu)體內(nèi)存對(duì)齊的意義并沒(méi)有一個(gè)官方的解釋,但是我們我們可以從兩個(gè)角度來(lái)思考內(nèi)存對(duì)齊的原因。
- 平臺(tái)原因(移植原因)
不是所有平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的,某些硬件平臺(tái)只能在某些地址處訪問(wèn)某些特定類型的數(shù)據(jù),否則就會(huì)拋出硬件異常。
即一些硬件可能只能在一些特定的位置上讀取數(shù)據(jù)(比如4的整數(shù)倍數(shù)),那么如果我們把數(shù)據(jù)放在了偏移量為2或者3的位置的時(shí)候,它就讀不到我們的數(shù)據(jù)了,所以為了保證硬件能夠讀到我們的數(shù)據(jù),我們最好把數(shù)據(jù)對(duì)齊放在硬件能讀取的位置上。
2.性能原因
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。
如果內(nèi)存未對(duì)齊,則處理器訪問(wèn)內(nèi)存時(shí)可能需要訪問(wèn)兩次,而對(duì)齊的內(nèi)存則僅需要一次訪問(wèn)。
ps:我們通常認(rèn)為的自然邊界通常是4、8、16等等4的倍數(shù)。
舉例說(shuō)明:
struct s{ char c;//1 int a;//4};
如上圖,我們可以很直觀地看到內(nèi)存對(duì)齊對(duì)于讀取數(shù)據(jù)的好處,它實(shí)際上提高了程序運(yùn)行的效率。
所以總體來(lái)說(shuō):我們可以認(rèn)為結(jié)構(gòu)體內(nèi)存對(duì)齊相當(dāng)于是拿空間換取時(shí)間的做法。
但是,雖然有時(shí)候我們會(huì)為了換取更高的效率而犧牲掉一些空間,但是從前面兩個(gè)內(nèi)容相同而大小不同的結(jié)構(gòu)體中,我們也可以發(fā)現(xiàn),通過(guò)設(shè)計(jì),我們是可以在盡量節(jié)省空間的情況下做到內(nèi)容對(duì)齊的。
也就是說(shuō),我們?cè)谠O(shè)計(jì)結(jié)構(gòu)體的時(shí)候,應(yīng)該既要滿足內(nèi)存對(duì)齊,又要節(jié)省空間。
那么我們應(yīng)該如何做呢?
答案就是:讓占用空間小的成員盡量集中在一起。
這樣我們就能盡量把可能會(huì)被浪費(fèi)掉的空間利用上。
我們知道,VS的默認(rèn)對(duì)齊數(shù)是8,那我們能不能對(duì)默認(rèn)對(duì)齊數(shù)進(jìn)行修改呢?
其實(shí)是可以的。
下面我們就來(lái)看看修改的方法。
那么如果我們把默認(rèn)對(duì)齊數(shù)設(shè)置為1,那么結(jié)構(gòu)體中每個(gè)成員的對(duì)齊數(shù)都將變?yōu)?,相當(dāng)每個(gè)成員都是依次存在內(nèi)存中,沒(méi)有內(nèi)存對(duì)齊的情況。而結(jié)構(gòu)體的大小就是每個(gè)成員的大小之和。
所以,如果在對(duì)齊方式不合適的時(shí)候,我們可以根據(jù)自己的需要來(lái)修改默認(rèn)對(duì)齊數(shù)。
當(dāng)然,內(nèi)存對(duì)齊是為了提升效率,所以盡管我們可以設(shè)置默認(rèn)對(duì)齊數(shù),但是我們也要合理地對(duì)它進(jìn)行應(yīng)用,一般我們?cè)O(shè)置的默認(rèn)對(duì)齊數(shù)為2n。
最后我們?cè)僦v一個(gè)跟結(jié)構(gòu)體相關(guān)的內(nèi)容:結(jié)構(gòu)體實(shí)現(xiàn)位段的能力。
那么究竟什么是位段呢?我們先來(lái)看看它的“真容”。
位段的聲明和結(jié)構(gòu)體類似,但是有兩點(diǎn)不同:
- 位段的成員必須是int、unsigned int或signed int。
- 位段的成員名后邊有一個(gè)冒號(hào)和一個(gè)數(shù)字。
如上圖中的A就是一個(gè)位段類型。
雖然我們說(shuō)位段的成員必須是int、unsigned int、或signed int。但是寫在代碼的過(guò)程中,位段的成員也可以是char,所以實(shí)際上位段的成員可以是整形家族中的成員。
那么位段到底是什么意思呢?
首先我們先計(jì)算一下struct A的空間大小。
我們發(fā)現(xiàn)A的大小是8個(gè)字節(jié),而A中包含4個(gè)int類型,按我們平常的計(jì)算A的大小應(yīng)該是16個(gè)字節(jié),而屏幕輸出的卻是8。
這說(shuō)明了位段是可以節(jié)省空間。
那么它到底是怎么節(jié)省空間的呢?我們?cè)賮?lái)看。
其實(shí)位段中的位指的是二進(jìn)制位,位段成員后面數(shù)量表示分給該成員的二進(jìn)制位數(shù)。
那么為什么我們這樣分配呢?
在生活生我們并不是所有的數(shù)據(jù)都需要占用那么大的內(nèi)存空間,如果我們根據(jù)需要給數(shù)據(jù)分配合理適用的內(nèi)存,那么就可以節(jié)省很多空間。
舉個(gè)栗子:假如我們現(xiàn)在想表示性別,而性別只有男、女、其他三種狀態(tài),那我們其實(shí)可以用兩個(gè)bit位來(lái)表示:
男 - 00
女 - 01
其他 - 11
我們會(huì)發(fā)現(xiàn),我們只需要兩個(gè)bit位就足以表示性別,而并不需要一塊很大的空間,那么這時(shí)候如果我們用位段來(lái)實(shí)現(xiàn),就能給內(nèi)存節(jié)省很大的一塊空間。
那么我們?cè)倩剡^(guò)頭來(lái)看前面位段A,我們一共給A中的成員分配了47個(gè)bit位,大約占6個(gè)字節(jié)的空間,那struct A的大小應(yīng)該是6個(gè)字節(jié)才對(duì)啊?可是屏幕上輸出的大小是8,這又是為什么呢?
在這里我們要注意,位段只是在一定程度上節(jié)省了空間,但并不意味著它一點(diǎn)也不浪費(fèi)。
下面我們就來(lái)看看位段在內(nèi)存中到底是怎么分配的。
- 位段的成員可以是int、unsigned int、signed int或者是char(屬于整型家族)類型
- 位段的空間上是按照需要以4字節(jié)(int)或者1個(gè)字節(jié)(char)的方式來(lái)開(kāi)辟的。
- 位段涉及很多不確定因素,位段是不跨平臺(tái)的,注意可移植的程序應(yīng)該避免使用位段。
那么我們就以前面的struct A作為例子來(lái)講。
首先我們看到A中的成員都是int,所以它在空間上是以4字節(jié)(int)的方式來(lái)開(kāi)辟的。即一次開(kāi)辟一個(gè)int的大小,當(dāng)空間不夠的時(shí)候,再開(kāi)辟一個(gè)int大小的空間。
所以這里我們可以看到,A的大小是8個(gè)字節(jié)。
那么這里有一個(gè)問(wèn)題:_d的30個(gè)bit位是怎么存放在內(nèi)存中的呢?
是其中15個(gè)bit位存放在第一個(gè)int中剩下的位置,另外的15個(gè)bit位存放在新開(kāi)辟的int空間上嗎?
還是_d的30個(gè)bit位都存放在新開(kāi)辟的int空間上呢?
對(duì)于這個(gè)問(wèn)題,標(biāo)準(zhǔn)并沒(méi)有給出說(shuō)明,但是我們不妨對(duì)它進(jìn)行一個(gè)猜測(cè)。
首先我們拿下面這段代碼來(lái)作為例子進(jìn)行猜測(cè)。
我們用位段S創(chuàng)建了一個(gè)變量s,那么內(nèi)存是怎么為s分配空間的呢?
首先,內(nèi)存開(kāi)辟了一個(gè)char的大小的空間,然后我們先分配其中的3個(gè)bit給a。
但是問(wèn)題又來(lái)了,在分配的時(shí)候,是從高地址端開(kāi)始分配,還是從低地址端開(kāi)始分配呢?
這個(gè)問(wèn)題標(biāo)準(zhǔn)仍然沒(méi)有說(shuō)明,那我們就先假設(shè)是從右邊(低地址)開(kāi)始分配的內(nèi)存,那么分配了3個(gè)bit給a之后,剩下了5個(gè)bit,其中又分了4個(gè)bit給b。
現(xiàn)在還剩下1個(gè)bit,而c需要5個(gè)bit位,所以我們?cè)匍_(kāi)辟一個(gè)char的空間,
那么問(wèn)題又又又來(lái)了,第一塊開(kāi)辟的空間中剩余的1個(gè)bit是浪費(fèi)掉了呢?還是存了c中的一個(gè)bit呢?
這個(gè)問(wèn)題標(biāo)準(zhǔn)仍然是沒(méi)有告訴我們,所以我們還是繼續(xù)猜測(cè):這塊空間被浪費(fèi)掉了,c中的5個(gè)bit全部放在新開(kāi)辟的空間上。
這時(shí)候我們第二塊開(kāi)辟的空間還剩下3個(gè)bit,不足以放d,所以緊接著我們又開(kāi)辟了一塊空間來(lái)存放d。
那么現(xiàn)在我們把a(bǔ)、b、c、d的值放進(jìn)去。
那么內(nèi)存中到底是不是我們猜測(cè)的樣子呢?
現(xiàn)在我們把它轉(zhuǎn)換成16進(jìn)制,得到62 03 04。
然后再回到程序中,一邊調(diào)試一邊查看內(nèi)存。
程序進(jìn)一步調(diào)試,我們看看s的內(nèi)存空間上的值是不是我們剛剛得到的62 03 04。
我們可以看到,這和我們得到的數(shù)是完全吻合的,說(shuō)明我們之前的猜測(cè)都是正確的。
位段在內(nèi)存中是先開(kāi)辟了一塊空間,然后把數(shù)據(jù)從低位向高位存放,如果高位中的空間不夠用了,那我們?cè)倮^續(xù)開(kāi)辟下一塊空間,然后再在新的空間中存放數(shù)據(jù),并且每次剩余的不夠存放一個(gè)數(shù)據(jù)的空間是被浪費(fèi)掉了。
但是注意,我們的猜測(cè)僅僅是在VS環(huán)境下得到了驗(yàn)證,這并不意味著在其他環(huán)境下我們也可以得到這樣的結(jié)論,因?yàn)闃?biāo)準(zhǔn)并沒(méi)有給出一個(gè)統(tǒng)一的說(shuō)明。
所以我們要注意位段在內(nèi)存分配的第三條,即位段中存在許多模糊地帶,我們?cè)谑褂梦欢蔚臅r(shí)候應(yīng)該十分謹(jǐn)慎,避免在可移植程序上使用位段,以防程序移植到另一個(gè)平臺(tái)上時(shí)出現(xiàn)錯(cuò)誤。
從前面我們已經(jīng)知道,位段中有許多標(biāo)準(zhǔn)未定義的地方,所以它是不能跨平臺(tái),具體我們總結(jié)了以下幾點(diǎn)。
- int位段是有符號(hào)數(shù)還是無(wú)符號(hào)數(shù)是模糊的。
- 位段中的最大數(shù)目是模糊的。不同機(jī)器上允許存放的位段的最大bit數(shù)是不同,這有可能導(dǎo)致位段從一個(gè)平臺(tái)移植到另一個(gè)平臺(tái)上后出現(xiàn)錯(cuò)誤。(16位機(jī)器上最大數(shù)是16,32位機(jī)器上最大數(shù)是32)
- 位段中開(kāi)辟的空間是從左向右使用愛(ài)是從右向左使用是模糊的。
- 當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)或以上的位段成員時(shí),第二個(gè)位段成員比較大,無(wú)法被第一塊開(kāi)辟的空間容納時(shí),第一塊空間中剩余的空間是被舍棄還是繼續(xù)利用,這也是模糊的。
所以,對(duì)比結(jié)構(gòu),位段可以和結(jié)構(gòu)達(dá)到相同的效果。
理論上講,結(jié)構(gòu)可以出現(xiàn)的地方,位段也可以出現(xiàn)。相比結(jié)構(gòu),位段有它的優(yōu)點(diǎn)——節(jié)省空間,也有它的缺點(diǎn)——存在跨平臺(tái)問(wèn)題。
那么我們認(rèn)識(shí)了位段之后,它到底是怎么用的呢?它通常在什么時(shí)候用呢?
我們可以看看下面這張圖。
我們?cè)诰W(wǎng)絡(luò)上使用數(shù)據(jù)的時(shí)候,需要對(duì)數(shù)據(jù)進(jìn)行封裝,這時(shí)候就需要存放一些與數(shù)據(jù)相關(guān)的信息,我們暫且把它成為一個(gè)包。
假如我們不使用位段,那么這個(gè)包相對(duì)來(lái)說(shuō)就會(huì)比較大, 而如果網(wǎng)絡(luò)上所有的數(shù)據(jù)都背著一個(gè)大包,那么網(wǎng)絡(luò)上那么龐大的數(shù)據(jù)量,就會(huì)造成許多空間的浪費(fèi),整個(gè)網(wǎng)絡(luò)也會(huì)顯得很擁擠。
而如果我們使用位段,包中的每個(gè)信息我們只需要給它分配足夠的空間,那么這個(gè)包的就會(huì)小很多,這個(gè)數(shù)據(jù)背著小包在網(wǎng)絡(luò)上通行的時(shí)候也會(huì)走的更加輕快。
所以實(shí)際上,利用位段可以很好地搭建網(wǎng)絡(luò)底層的一些架構(gòu)。
在我們的生活中,有一些值是有限的,我們可以一一列舉出來(lái),比如性別、月份等等,我們把這些值一一列出來(lái),就是枚舉。
枚舉中我們要使用到一個(gè)關(guān)鍵字enum,我們以一周的星期來(lái)舉例。
上面這個(gè)day就是一個(gè)枚舉類型,而{ }內(nèi)的內(nèi)容就是我們之前提到的枚舉常量。
我們把上面的值用整型的形式打印出來(lái)。
我們可以看到,這些枚舉常量是有值的,它默認(rèn)從0開(kāi)始,依次遞增。
那么如果我們不想讓它的值從0開(kāi)始,我們可以在定義枚舉類型的時(shí)候給它進(jìn)行賦值。
我們要注意,day是一個(gè)枚舉類型,其中放的是枚舉常量,而我們給它賦的值是未來(lái)我們創(chuàng)建了一個(gè)枚舉常量之后可能的取值。我們只能在定義枚舉類型的時(shí)候給它們賦初值,但是不能再后面創(chuàng)建變量的時(shí)候修改它的值。
注意:當(dāng)我們創(chuàng)建了一個(gè)枚舉常量之后,我們應(yīng)該給它賦相應(yīng)的枚舉類型的可能取值,而不是直接賦上一個(gè)數(shù)值。
上面的代碼雖然在C中可能通過(guò),但是在.cpp文件中是非法的。
那么我們?yōu)槭裁匆褂妹杜e呢?
這是因?yàn)椋妹杜e相當(dāng)于給這些值賦上了一些相應(yīng)的意義,當(dāng)我們比如上面的1賦給了Mon,那么我們就能知道1表示的就是星期一。這實(shí)際上增加了代碼的可讀性和可維護(hù)性。
那么我們之前也學(xué)過(guò)#define定義的標(biāo)識(shí)符常量,枚舉和它相比又有上面區(qū)別呢?
實(shí)際上,#define定義的是一個(gè)標(biāo)識(shí)符,即用一個(gè)符號(hào)來(lái)代表某個(gè)有特定含義的數(shù),這樣未來(lái)在這個(gè)數(shù)發(fā)生變化時(shí),可以通過(guò)#define來(lái)進(jìn)行快速修改。
而枚舉不僅可以提供這樣的功能,枚舉的定義讓這些常量具有了類型——枚舉類型,相比只下枚舉會(huì)更加嚴(yán)謹(jǐn)。
不僅如此,#define定義的常量是直接暴露在全局范圍內(nèi)的,而我們定義的枚舉常量是被封裝在一個(gè)類型里面的,它能防止命名的污染。
并且我們可以通過(guò)枚舉類型創(chuàng)建變量,于是我們可以在程序中通過(guò)調(diào)試來(lái)觀察,而#define定義的標(biāo)識(shí)符常量實(shí)際上只是一種等價(jià)替換,我們是無(wú)法通過(guò)調(diào)試來(lái)觀察它的。
枚舉可以一次定義多個(gè)常量,而#define一次僅能定義一個(gè)值,所以從使用的角度來(lái)講,枚舉更便于使用。
拿我們之前寫過(guò)的一個(gè)簡(jiǎn)易計(jì)算機(jī)來(lái)舉例:
如果我們這樣寫,數(shù)值和選項(xiàng)要通過(guò)菜單一一對(duì)應(yīng),比較別扭。
但是如果我們定義了一個(gè)枚舉類型,我們就可以直接這樣寫:
我們知道枚舉類型是一種自定義類型,我們根據(jù)這個(gè)類型創(chuàng)建了一個(gè)變量才在內(nèi)存上分配了空間,而枚舉常量的值都是一些整數(shù),所以我們可以得到,枚舉類型的大小就是一個(gè)整型的大小。
接下來(lái)我們?cè)僦v一種特殊的自定義類型——聯(lián)合體。
聯(lián)合體也叫共用體,它的變量包含一系列的成員,而這些成員是共用同一塊空間的。(具體如何共用,別急,后面會(huì)講到~)
那么聯(lián)合類型是如何定義的呢?
首先聯(lián)合的關(guān)鍵字是union。
前面我們已經(jīng)說(shuō)到,聯(lián)合的特點(diǎn)就是成員共用一塊空間。
下面我們先計(jì)算一下聯(lián)合變量的大小。
我們可以看到,聯(lián)合體Un中包含一個(gè)char c,一個(gè)int i,它們加起來(lái)應(yīng)該是5,而屏幕上輸出的聯(lián)合體的大小是4,這里就體現(xiàn)了聯(lián)合體的特點(diǎn),因?yàn)閏和i是共用一塊空間的,所以聯(lián)合體Un的大小是聯(lián)合體中最大成員的大小。
接下來(lái),我們?cè)賮?lái)看一下聯(lián)合體和其中的成員的地址。
我們可以看到,聯(lián)合體和它的成員的地址是一樣的,進(jìn)一步體現(xiàn)了聯(lián)合體中的成員是共用一塊空間的。
所以這里我們也要注意,我們改變i的時(shí)候,可能會(huì)改變c,改變c的時(shí)候,也會(huì)改變了 i 的值。
同時(shí),一個(gè)聯(lián)合變量的大小,至少是最大成員的大小,因?yàn)槁?lián)合體得有能力保存最大的那個(gè)成員。
那么利用聯(lián)合體,我們也可以判斷當(dāng)前機(jī)器的大小端。(關(guān)于機(jī)器大小端字節(jié)序的內(nèi)容,忘記的同學(xué)可以翻看這篇噢C語(yǔ)言進(jìn)階第一問(wèn):數(shù)據(jù)在內(nèi)存中是如何存儲(chǔ)的?(手把手帶你深度剖析數(shù)據(jù)在內(nèi)存中的存儲(chǔ),超全解析,碼住不虧))
那么我們什么時(shí)候適合用聯(lián)合體呢?
當(dāng)聯(lián)合體中的所有成員在同一時(shí)間中只使用一個(gè),并且它們可以共用同一塊的空間時(shí)候,我們就可以使用聯(lián)合體。
比如學(xué)校要開(kāi)發(fā)一個(gè)教務(wù)管理系統(tǒng),那么教務(wù)管理系統(tǒng)中的人的身份就分為學(xué)生和老師,而一個(gè)人不可能既是學(xué)生,又是老師,而我們可以用同一塊空間來(lái)表示學(xué)生和老師,這時(shí)候我們就可以用結(jié)構(gòu)體來(lái)描述教務(wù)系統(tǒng)中的人的身份。
那么聯(lián)合大小應(yīng)該如何計(jì)算呢?
- 聯(lián)合體的大小至少是最大成員的大小
- 聯(lián)合體中也存在內(nèi)存對(duì)齊。當(dāng)最大成員的大小不是最大對(duì)齊數(shù)的大小時(shí),聯(lián)合體的大小就要對(duì)齊到最大對(duì)齊數(shù)的整數(shù)倍。
我們通過(guò)例子來(lái)看看。
以上就是C語(yǔ)言中的自定義類型,有了這些自定義類型,我們就可以根據(jù)自己的需要寫出自己想要的類型啦!
好啦,今天的文章就到這里啦!如果你喜歡博主的文章,記得點(diǎn)贊評(píng)論收藏一波哦!
你的鼓勵(lì)將是我繼續(xù)努力碼字的巨大動(dòng)力!!!!
關(guān)注我,一起精進(jìn)C語(yǔ)言吧!~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/121673.html
摘要:在符號(hào)位中,表示正,表示負(fù)。我們知道對(duì)于整型來(lái)說(shuō),內(nèi)存中存放的是該數(shù)的補(bǔ)碼。在計(jì)算機(jī)系統(tǒng)中,數(shù)值一律用補(bǔ)碼來(lái)表示和存儲(chǔ)。表示有效數(shù)字,。規(guī)定對(duì)于位的浮點(diǎn)數(shù),最高的位是 ...
摘要:簡(jiǎn)介比更強(qiáng)大的開(kāi)源語(yǔ)言,簡(jiǎn)稱,親爸是微軟。大彬哥就愛(ài)吃剁椒魚頭。接口,范型,命名空間,以及模塊化管理,并講在框架和工作流中的應(yīng)用等更多精彩內(nèi)容歡迎大家觀看我的講座極速完全進(jìn)階指南 +TypeScript簡(jiǎn)介 ? 1.比javascript更強(qiáng)大的開(kāi)源語(yǔ)言,簡(jiǎn)稱TS,親爸是微軟。 ? 2.官網(wǎng) ? 英文官網(wǎng):https://www...
摘要:插件鍵位映射技巧性的配置等等都是錦上添花,它們有助于你進(jìn)一步提高效率以及個(gè)性化你的工作環(huán)境,但是對(duì)于哲學(xué)的理解幫助甚少。為你開(kāi)啟語(yǔ)法高亮。你可以自定義各種語(yǔ)言的語(yǔ)法高亮,無(wú)非就是根據(jù)這些規(guī) 如果沒(méi)有挑戰(zhàn),人生將多么無(wú)趣! 兩種副本 在我的硬盤上總是保留著(至少)兩份 Vim 的配置文件。其中一份是所謂完全正式版,它的文件名是 .vimrc,到本系列結(jié)束的時(shí)候,我們將了解其中...
閱讀 2739·2023-04-25 22:15
閱讀 1812·2021-11-19 09:40
閱讀 2158·2021-09-30 09:48
閱讀 3230·2021-09-03 10:36
閱讀 2033·2021-08-30 09:48
閱讀 1862·2021-08-24 10:00
閱讀 2735·2019-08-30 15:54
閱讀 709·2019-08-30 15:54