最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 20 講:結(jié)構(gòu)體

2021-09-26 23:32 作者:SunnieShine  | 我要投稿

何為結(jié)構(gòu)體?

前文我們學(xué)習(xí)了非常多的數(shù)據(jù)類型,比如基本的數(shù)據(jù)類型 int、float 等等,也學(xué)習(xí)了指針類型 int *、char * 類型,以及無(wú)類型指針類型 void *,數(shù)組 [] 和函數(shù) ()。不過,這些基礎(chǔ)數(shù)據(jù)類型并不能夠滿足我們的所有需求。比如,我們把“學(xué)生”看作一種類型,這個(gè)類型里包含一些基本信息,諸如成績(jī),學(xué)號(hào)姓名等等。這可能嗎?

當(dāng)然,今天要講到的結(jié)構(gòu)體(Structure)就是為了解決這種復(fù)合結(jié)構(gòu)類型的基本知識(shí)點(diǎn)。

結(jié)構(gòu)體的聲明格式如下:

比如,學(xué)生類型的書寫格式是這樣的:

也可以簡(jiǎn)寫:

這樣就定義好了一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體類型是 student 類型的。

請(qǐng)注意,這種特殊類型和函數(shù)一樣,需要放在獨(dú)立的位置上,而不是嵌套到函數(shù)里,這樣全局級(jí)別才能訪問到,否則我們就無(wú)法在想要的位置使用它們。當(dāng)然,C 語(yǔ)言確實(shí)不允許把結(jié)構(gòu)體的聲明(定義)語(yǔ)句(上面那一坨)放進(jìn)函數(shù)之中。


結(jié)構(gòu)體的使用

我們嘗試為結(jié)構(gòu)體里的每一個(gè)成員賦值,并輸出它們。不過,我們需要開始復(fù)雜一點(diǎn)的邏輯了。前文學(xué)過了如何聲明堆內(nèi)存,所以這里我們也可以使用這種東西來在堆內(nèi)存里創(chuàng)建結(jié)構(gòu)體變量。不過請(qǐng)注意,結(jié)構(gòu)體是無(wú)法在棧內(nèi)存里分配的,所以我們必須為其賦值。

變量類型是 student 類型,而這里作為一種結(jié)構(gòu)體類型,我們需要書寫的格式是

首先為堆內(nèi)存里分配 student 類型足夠使用的內(nèi)存空間,然后把這塊內(nèi)存的地址返回給變量 a。注意,返回的類型是 void *,所以需要經(jīng)過一次強(qiáng)制轉(zhuǎn)換。

在完成分配后,判斷一下它是否為 NULL 是一個(gè)好習(xí)慣。如果不為 NULL 則說明分配內(nèi)存是成功的,于是我們開始為內(nèi)部的所有信息點(diǎn)進(jìn)行賦值。

它和數(shù)組不同,數(shù)組分配的空間是連續(xù)的,但由于數(shù)組內(nèi)的所有元素的類型全是一樣的,所以有索引這種東西的存在;而結(jié)構(gòu)體里,我們不能保證所有自己寫的字段的類型一樣,所以我們確實(shí)沒有辦法通過索引器 [] 來獲取每一個(gè)成員的信息,而是通過成員訪問運(yùn)算符 .(Member Access Operator)來取得它們。

不過,請(qǐng)注意,前文使用的是堆內(nèi)存分配的模式,所以得到的分配結(jié)果顯然是指針類型的,于是要將指針“拆解”為普通類型(取得內(nèi)容),需要使用間接訪問運(yùn)算符 * 對(duì)指針變量加以修飾:*xiaoMing,這才是真正的“內(nèi)容”。然后,我們對(duì)這個(gè)具體的結(jié)果再次使用成員訪問運(yùn)算符來取值:(*xiaoMing).age。請(qǐng)注意,這種取值需要為變量名和取內(nèi)容運(yùn)算符用小括號(hào)括起來。如果不括起來,C 語(yǔ)言將認(rèn)為執(zhí)行內(nèi)容是先獲取 xiaoMing.age,然后再對(duì)這個(gè)變量取內(nèi)容(執(zhí)行 * 取內(nèi)容),而顯然取內(nèi)容是針對(duì)于指針變量而言的,但此時(shí)就算 xiaoMing.age 奏效,沒有語(yǔ)法問題,而 age 字段是我們規(guī)定的數(shù)值類型的,而不是指針,所以 C 語(yǔ)言會(huì)為此報(bào)錯(cuò),提示你“xiaoMing.age 不是一個(gè)指針類型”。

同理,其它的元素都可以使用這種方式進(jìn)行賦值,但需要注意的是,字符串的賦值方式,一般我們不要去使用 scanf("%s", &字符指針或字符數(shù)組變量) 的模式,因?yàn)?scanf 函數(shù)向來都是不處理空格的,所以它遇到空格直接就認(rèn)為是輸入結(jié)束了;而名字里可能是英文名,所以此時(shí)是可能含有空格的,故我們需要使用 gets 函數(shù)來輸入。

另外,gets 函數(shù)需要給定一個(gè)參數(shù),這個(gè)參數(shù)必須是一個(gè)字符數(shù)組變量,而不是指針。指針并不能保證這個(gè)指向的內(nèi)存塊是可以被修改的,甚至不能保證這一塊內(nèi)存是可以被修改的。字符數(shù)組在聲明期間就已經(jīng)創(chuàng)建好了一系列內(nèi)存空間,所以我們完全可以對(duì)其進(jìn)行修改和使用。所以字符指針和字符數(shù)組的區(qū)別在于這一點(diǎn):字符數(shù)組可修改內(nèi)部元素,而字符指針不一定表示的是一個(gè)數(shù)組,而可能僅僅是一個(gè)普通的指針變量。前文說過,數(shù)組可以在賦值的時(shí)候退化為指針,但反之不然。

記得最后添加 free 函數(shù)釋放資源,否則會(huì)造成內(nèi)存泄漏的 bug。


我覺得 (*v).f 的訪問模式太難寫代碼了,有沒有簡(jiǎn)化版?

我也這么覺得,我們每次為字段賦值都得“無(wú)情地”加入小括號(hào),然后才能往后加字段信息。這樣寫確實(shí)麻煩了。所以,C 語(yǔ)言提供了一個(gè)語(yǔ)法糖(簡(jiǎn)化版書寫格式),叫指針成員訪問成員運(yùn)算符 ->(Pointer Member Access Operator)。

仔細(xì)體會(huì)兩種寫法,這兩種寫法是等價(jià)的。由于 -> 符號(hào)和 . 符號(hào)都是訪問運(yùn)算符,所以這兩個(gè)運(yùn)算符挨著兩邊的英文單詞(標(biāo)識(shí)符),而不用在中間添加額外的空格。


一些燒腦的結(jié)構(gòu)體使用模型

來一個(gè)可能會(huì)犯錯(cuò)的用法

我們嘗試不為變量賦值,就開始為字段里放數(shù)據(jù),看看會(huì)怎么樣。

請(qǐng)注意,這種用法是錯(cuò)誤的,而且很可能會(huì)引起報(bào)錯(cuò)。因?yàn)?variable 并沒有賦值就開始用了,在 C 語(yǔ)言里規(guī)定,沒有(成功)賦初始值的時(shí)候,變量都是不起作用的,即沒有真正的內(nèi)存可以對(duì)應(yīng)上這個(gè)變量。于是我們對(duì)一個(gè)根本就不知道是哪里的對(duì)象進(jìn)行賦值,或字段賦值,顯然是不可能找到正確答案的。

其實(shí),不只是這種情況,就連普通的變量也是如此:

這兩種輸出都不會(huì)得到你想要的結(jié)果。我知道你可能會(huì)認(rèn)為這里的輸出可能是 0,不過……

確實(shí)不一定真的是 0,有時(shí)候是一個(gè)莫名其喵的數(shù)字,看得頭皮發(fā)麻。


結(jié)構(gòu)體的嵌套聲明模式

假設(shè)我們有兩個(gè)結(jié)構(gòu)體:

那么上文給出的 b 結(jié)構(gòu)體類型是被允許的嗎,即 b 結(jié)構(gòu)體的某一個(gè)字段是別的結(jié)構(gòu)體類型?

答案是,是的。我們完全可以創(chuàng)建一個(gè)結(jié)構(gòu)體,使得這個(gè)結(jié)構(gòu)體的 field 字段是另外一個(gè)結(jié)構(gòu)體的類型。從邏輯上講,這完全是不矛盾的。

不過,如果你創(chuàng)建這種類型的結(jié)構(gòu)體,你需要注意初始化。假如我們現(xiàn)在有這樣兩個(gè)結(jié)構(gòu)體:

并初始化一個(gè) second 結(jié)構(gòu)體類型的變量:

然后對(duì)其進(jìn)行賦值。

首先,p->d 是為了取出 p 指針指向的字段 d 的信息,它是 first 結(jié)構(gòu)體類型的,而且不是指針;然后,對(duì)這個(gè)得到的結(jié)果再使用 . 成員訪問運(yùn)算符來獲取字段 ab 的信息,或者說賦值。

這樣便可完成真正的賦值操作。


結(jié)構(gòu)體的遞歸聲明模式

前文我們說到,結(jié)構(gòu)體內(nèi)部是可以存放指針,也可以放普通變量的。那么,是否存在一種可能,讓結(jié)構(gòu)體(假設(shè)為 a 類型)內(nèi)部也有一個(gè) a 類型的字段呢,即:

這種寫法是可以的嗎?顯然,我們從邏輯上說不通,因?yàn)槲覀円獎(jiǎng)?chuàng)建好一個(gè) field 字段,由于它是 a 類型的結(jié)構(gòu)體類型,所以我們就不得不先為結(jié)構(gòu)體 a 類型創(chuàng)建好。但顯然此時(shí)還在創(chuàng)建其中的字段,整個(gè)結(jié)構(gòu)體并未完成創(chuàng)建,所以這是相違背的。

實(shí)際上確實(shí)如此。C 語(yǔ)言不允許遞歸聲明:結(jié)構(gòu)體類型內(nèi)的字段不允許是當(dāng)前結(jié)構(gòu)體類型的,不過,你可以聲明同樣類型的結(jié)構(gòu)體指針變量。

這樣書寫和存儲(chǔ)是沒有問題的,因?yàn)閺牡览砩现v,ptr 字段在這里僅僅表示的是一個(gè)可以指向這個(gè)類型的地址信息罷了,它是一個(gè)地址,所以并不和結(jié)構(gòu)體類型的本身有關(guān),所以這樣的聲明方式是被允許的。而且,這種聲明模式廣泛存在于一些復(fù)雜的結(jié)構(gòu)里,例如鏈表(Linked List),它具有一個(gè)數(shù)據(jù)字段(data)和一個(gè)下一節(jié)點(diǎn)地址的字段(next);而這里的 data 字段就是指向下一個(gè)鏈表節(jié)點(diǎn)的,它依然是當(dāng)前類型的,只不過是指針。

下面代碼可以展示這一點(diǎn):

輸出的結(jié)果如下:

結(jié)構(gòu)體的內(nèi)存大小

對(duì)一個(gè)結(jié)構(gòu)體變量使用 sizeof 會(huì)如何呢?前文不是就用到堆內(nèi)存分配了嗎?

這個(gè)數(shù)值實(shí)際上并不需要關(guān)心,但一般在考試題里會(huì)認(rèn)為所有字段占據(jù)的內(nèi)存空間的總和。但實(shí)際上并不一定是這樣,結(jié)果可能比這個(gè)數(shù)值大一些,當(dāng)然也可能是一樣的。

舉個(gè)例子。就前文的學(xué)生類型結(jié)構(gòu)體,它由四個(gè)字段構(gòu)成:

在內(nèi)存里,很有可能存儲(chǔ)的方式是這樣的:

當(dāng)然,我也只能說是“可能”,因?yàn)榫唧w的分配模式是根據(jù)內(nèi)存怎么樣找起來快怎么分配的模式來分配的。如果這種分配方式,使得代碼運(yùn)算速度快的話,那么這種肯定就是內(nèi)存里存儲(chǔ)這個(gè)結(jié)構(gòu)體的方式。也有可能把 age 拿到下面來,也可能把 isGirl 拿下來,等等。

不過,按照基本考試的方法,把它們所占字節(jié)數(shù)加起來就是整個(gè)結(jié)構(gòu)體所占大小了:4 + 4 + 1 + 20 = 29 字節(jié),所以,sizeof(struct student) = sizeof(variable) = 29。


第 20 講:結(jié)構(gòu)體的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
合江县| 南涧| 深泽县| 华宁县| 临西县| 垫江县| 宕昌县| 阳朔县| 宜州市| 盐城市| 顺平县| 德江县| 通州区| 兴安县| 澄城县| 大化| 松阳县| 辽中县| 海阳市| 沂源县| 武城县| 中西区| 墨脱县| 潢川县| 五原县| 八宿县| 浦东新区| 望城县| 清河县| 六枝特区| 涟水县| 长岛县| 苍溪县| 临沧市| 东明县| 江北区| 富锦市| 大英县| 定远县| 察雅县| 三亚市|