最新C語言自定義類型詳解
前言
隨著學(xué)習(xí)的進(jìn)一步深入,我們不再滿足于做簡(jiǎn)單的計(jì)算題,想著做一些像樣的東西,如學(xué)生管理系統(tǒng)、酒店入住系統(tǒng)等等......而C語言提供的基礎(chǔ)數(shù)據(jù)類型已經(jīng)不足以支撐我們的想法,于是便要學(xué)習(xí)自定義類型,根據(jù)自己的需求來創(chuàng)建類型。
結(jié)構(gòu)體
生活當(dāng)中有很多物品是不能簡(jiǎn)單的用整型、浮點(diǎn)型、字符型來區(qū)分,它們常常是復(fù)雜的集合,比如人,一個(gè)人擁有年齡,身高、體重、學(xué)歷......等信息。我們可以用結(jié)構(gòu)體來實(shí)現(xiàn)準(zhǔn)確描述人這種復(fù)雜集合。
結(jié)構(gòu)體的基礎(chǔ)知識(shí)
結(jié)構(gòu)和數(shù)組的區(qū)別
結(jié)構(gòu)是一些值的集合,這些值稱為成員變量,每個(gè)成員變量可以是不同類型。而數(shù)組是同類型值得集合
結(jié)構(gòu)體的聲明
對(duì)于結(jié)構(gòu)體的聲明,需要有結(jié)構(gòu)體名、成員列表、變量列表(可省略)。
struct tag { member_list;//成員列表 }variable_list;//變量列表
我們以描述一個(gè)學(xué)生為例。
struct student { int id;//學(xué)號(hào) char name[20];//姓名 char sex;//性別 int age;//年齡 }s1,s2,s3;
結(jié)構(gòu)體的特殊聲明
在聲明結(jié)構(gòu)體的時(shí)候,我們可以以不完全聲明(匿名結(jié)構(gòu)體類型),也就是省略掉結(jié)構(gòu)體的標(biāo)簽。
就像這樣:
struct { int a; char b; float c; }d;
但這種聲明并不安全。
例如:
struct { int a; char b; float c; }d; struct { int a; char b; float c; }*p;
我們令*p=&d 是非法的。因?yàn)榫幾g器會(huì)將這兩個(gè)聲明當(dāng)成完全不同的兩個(gè)類型。
結(jié)構(gòu)體的自引用
我們能不能結(jié)構(gòu)體套結(jié)構(gòu)體呢?
是可以的,比如我們描述一輛車的構(gòu)造,它的成員變量分別是車門、輪子、發(fā)動(dòng)機(jī)、玻璃......等,而這些成員也是復(fù)雜的集合,比如車門的內(nèi)部也是復(fù)雜的,這就需要結(jié)構(gòu)體套結(jié)構(gòu)體。我們稱為結(jié)構(gòu)體的自引用。
那么怎么實(shí)現(xiàn)結(jié)構(gòu)體自引用呢?
先看一段錯(cuò)誤示范:
我們結(jié)構(gòu)體變量的創(chuàng)建,是要在結(jié)構(gòu)體變量聲明過后,這種寫法屬于是在這個(gè)結(jié)構(gòu)體類型還沒有創(chuàng)建完成的時(shí)候就使用了該類型的結(jié)構(gòu)體變量。
struct Node { int data; struct Node next; };
正確示例:
將該類型的結(jié)構(gòu)體變量改成該類型的結(jié)構(gòu)體指針就能解決這一問題了~。
struct Node { int data; struct Node* next; };
結(jié)構(gòu)體變量的定義和初始化
我們嘗試一下初始化上面的學(xué)生結(jié)構(gòu)體變量。
#include<stdio.h> struct student { int id;//學(xué)號(hào) char name[20];//姓名 char sex;//性別 int age;//年齡 }s1, s2, s3; int main() { s1 = { 1,"aa",'M',18 }; printf("%d %s %c %d", s1.id, s1.name, s1.sex, s1.age); }
是不是發(fā)現(xiàn)和數(shù)組的初始化差不多? 定義一個(gè)結(jié)構(gòu)體變量和定義一個(gè)整型變量的方式差不多,這里就不列舉了。
結(jié)構(gòu)體內(nèi)存對(duì)齊
我們已經(jīng)掌握了結(jié)構(gòu)體的基本使用,現(xiàn)在我們進(jìn)入到下一個(gè)問題——結(jié)構(gòu)體的大小怎么計(jì)算?
猜想:結(jié)構(gòu)體的大小就是結(jié)構(gòu)體內(nèi)的成員大小加起來
還是以學(xué)生結(jié)構(gòu)體類型為例:
struct student { int id;//學(xué)號(hào) char name[20];//姓名 char sex;//性別 int age;//年齡 }s1,s2,s3;
我們按照猜想一計(jì)算一下,計(jì)算出該結(jié)構(gòu)體類型大小為29個(gè)字節(jié),事實(shí)果真如此?
用代碼驗(yàn)證一下:
#include<stdio.h> struct student { int id;//學(xué)號(hào) char name[20];//姓名 char sex;//性別 int age;//年齡 }s1, s2, s3; int main() { printf("%d", sizeof(struct student)); }
我們發(fā)現(xiàn),結(jié)果是32個(gè)字節(jié),說明猜想一錯(cuò)誤。
結(jié)構(gòu)體的對(duì)齊規(guī)則
一、第一個(gè)成員在于結(jié)構(gòu)體變量偏移量為0的地址處。
二、其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。(ps:對(duì)齊數(shù)=編譯器默認(rèn)的一個(gè)對(duì)齊數(shù)與改成員大小的較小值,vs編譯器的默認(rèn)對(duì)齊數(shù)為8)
三、結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)的整數(shù)倍。
四、如果是嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
對(duì)結(jié)構(gòu)體對(duì)齊規(guī)則的解析
id變量便是學(xué)生結(jié)構(gòu)體變量的第一個(gè)成員,它對(duì)齊的位置應(yīng)該在于該結(jié)構(gòu)體變量的地址偏移量為0處,又因?yàn)閕d是int類型,占4個(gè)字節(jié)。
看圖,紅色所占空間就是id所占空間:
第二個(gè)成員name變量每個(gè)元素所占大小為1個(gè)字節(jié),與默認(rèn)值對(duì)齊數(shù)8個(gè)字節(jié)相比,1<8,所以name要對(duì)齊到1的整數(shù)倍數(shù)處,也就是4的位置,又因?yàn)閚ame占20個(gè)字節(jié),其所占空間如圖中綠色區(qū)域所示。
第三個(gè)成員sex,類型所占字節(jié)數(shù)為1,1<8同樣是對(duì)齊到1的整數(shù)倍處,且sex占一個(gè)字節(jié),sex所占區(qū)域便是黃色位置。
第四個(gè)成員為age,為int類型,所占大小為4個(gè)字節(jié),4<8,所以age要對(duì)齊到4的整數(shù)倍出,也就是28處,又因?yàn)樗旧碚?個(gè)字節(jié),所以age所占空間便是藍(lán)色的區(qū)域。
31位置到0位置,中間有32二個(gè)字節(jié),這就是為什么student結(jié)構(gòu)體變量大小不是29個(gè)字節(jié)而是32個(gè)字節(jié)。
為什么存在內(nèi)存對(duì)齊
一是平臺(tái)原因:不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)。某些硬件平臺(tái)只能在某些地址處取某些待定類型的數(shù)據(jù),否則拋出硬件異常。
二是性能原因數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能的在自然邊界上對(duì)齊,原因在于,對(duì)于訪問未對(duì)齊的內(nèi)存,需要處理器訪問兩次內(nèi)存,而對(duì)齊的話訪問一次就行了。以空間換取了時(shí)間。
修改默認(rèn)對(duì)齊數(shù)
內(nèi)存對(duì)齊是空間換時(shí)間的辦法,那么我們能不能更進(jìn)一步,在特定情況下又省空間又省時(shí)間呢?修改默認(rèn)對(duì)齊數(shù)便是一種辦法。
修改默認(rèn)對(duì)齊數(shù)需要使用#pragma
#pragma pack(2)就是將默認(rèn)對(duì)齊數(shù)修改成了2,使結(jié)構(gòu)體的大小進(jìn)一步精簡(jiǎn)。我們來看看效果圖(依然是一學(xué)生結(jié)構(gòu)體變量為例)
第一個(gè)成員id不變,依然是占紅色區(qū)域。
第二個(gè)成員name同樣不變,因?yàn)?個(gè)字節(jié)仍然小于2個(gè)字節(jié),占綠色區(qū)域。
第三個(gè)成員sex還是沒變,原因與第二個(gè)相同,占黃色區(qū)域。
第四個(gè)成員age改變,2<4,age一個(gè)對(duì)齊至2的整數(shù)倍處,占藍(lán)色區(qū)域
由圖我們可以發(fā)現(xiàn),這次的學(xué)生結(jié)構(gòu)體類型大小只占了30個(gè)字節(jié),縮短了兩個(gè)字節(jié),我們用程序來驗(yàn)證一下。
驗(yàn)證
和我們所設(shè)想的一樣。
結(jié)構(gòu)體傳參
直接上代碼,print1使用的是傳值,print2使用的是傳址。那種方法更好呢?
#include<stdio.h> struct A { int data[1000]; int num; }; struct A a = { {1,2,3,4},666 }; void print1(struct A a) { printf("%d\n", a.num); } void print2(struct A* a) { printf("%d\n", a->num); } int main() { print1(a); print2(&a); return 0; }
print2的方法要優(yōu)于print1,對(duì)于print1,我們需要?jiǎng)?chuàng)建一塊內(nèi)存空間來接收傳過來的參數(shù),是需要壓棧的,則在空間和時(shí)間上會(huì)有更多的系統(tǒng)開銷,如果傳遞的結(jié)構(gòu)體對(duì)象過大,會(huì)導(dǎo)致性能下降。但是對(duì)于print2的方法來說,不需要而外的開銷,使用的是之前的內(nèi)存,不需要新創(chuàng)建。
所以結(jié)構(gòu)體傳參的時(shí)候,要傳結(jié)構(gòu)體的地址。
位段
上面我們了解了基礎(chǔ)的結(jié)構(gòu)體知識(shí),這里我們介紹一下結(jié)構(gòu)體實(shí)現(xiàn)位段的能力。
什么是位段?
位段和結(jié)構(gòu)類似,但多了兩條限制。
一是位段的成員必須是int、unsigned int 或者是signed int,也可以出現(xiàn)char。
二是成員名字后面要出現(xiàn)冒號(hào)和數(shù)字。
例如:
struct A { int a : 2; int b : 5; int c : 10; int d : 30; };
這就是一個(gè)位段類型,冒號(hào)后面的數(shù)字是指該成員占多少個(gè)比特位,并且該數(shù)字不能超過限制范圍,位段空間的開辟是根據(jù)類型來開辟的。
位段A的大小是多少?
我們可以看見,位段A占8個(gè)字節(jié)。
#include<stdio.h> struct A { int a : 2; int b : 5; int c : 10; int d : 30; }; int main() { printf("%d", sizeof(struct A)); return 0; }
如何算位段的大小呢?
猜想:是比特位數(shù)加起來/8,算出來字節(jié)數(shù)。
以位段A為例,位數(shù)和為47,按照猜想推算,一個(gè)占6個(gè)字節(jié),但是結(jié)果是8,說明猜想錯(cuò)誤
位段的內(nèi)存分配
對(duì)于位段A,先開辟4個(gè)字節(jié)(因?yàn)槌煞殖蓡T類型是int),將a,b,c的位數(shù)相加,得到17位,再將d的位數(shù)加上去,發(fā)現(xiàn)位數(shù)超過了開辟的空間,所以需要新開一個(gè)4個(gè)字節(jié)的空間(按照類型來開辟空間),這時(shí)能夠?qū)⑽欢蜛所有成員放進(jìn)開辟的內(nèi)存之中,所以是8個(gè)字節(jié)。
位段的缺點(diǎn)
位段不適用于需要跨平臺(tái)的程序。
一是int位段被當(dāng)成有符號(hào)數(shù)還是無符號(hào)數(shù)并沒有明文規(guī)定。
二是位段中最大為的數(shù)目不確定(16位的機(jī)器是16,32位的機(jī)器是32)。
三是位段中成員在內(nèi)存中從左向右分配,還是從右向左分配沒有明文規(guī)定。
四是當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)位段,第二個(gè)位段成員比較大,無法容納與第一個(gè)位段剩余位的時(shí)候,是舍棄剩余的位還是利用,這是不確定的。
位段的意義
就是節(jié)約內(nèi)存,省空間,在緩解網(wǎng)絡(luò)擁擠的時(shí)候有作用。
枚舉
枚舉類型的定義
直接看代碼吧。類型+標(biāo)簽+可能的情況。
enum sex { MALE, FEMALE };
枚舉的優(yōu)點(diǎn)
一是增加代碼的可讀性和可維護(hù)性。
二是和#define定義的標(biāo)識(shí)符比較,枚舉有類型檢查,更加嚴(yán)謹(jǐn)。
三是防止了命名污染(封裝)。
四是便于調(diào)試。
五是方便使用,一次可以定義多個(gè)變量,提高編程效率。
六是可以將數(shù)字換成符號(hào)(例如switch語句中,將case后面的數(shù)字換成便于理解的字符)
聯(lián)合(共用體)
聯(lián)合也是一種被特殊的自定義類型。
這種類型定義的變量也包含一系列的成員,特征是這些成員共用一塊空間(所以聯(lián)合也叫共用體)
聯(lián)合類型的定義
#include<stdio.h> //聯(lián)合體的聲明 union un { char c; int i; }; //聯(lián)合體的定義 union un u; int main() { //計(jì)算聯(lián)合體的大小 printf("%d", sizeof(u)); return 0; }
聯(lián)合體的特點(diǎn)
聯(lián)合的成員是共用一塊內(nèi)存空間的,這樣一個(gè)聯(lián)合變量的大小,至少是最大成員的大?。ㄒ?yàn)槁?lián)合至少要有能力保障最大的那個(gè)成員)。
當(dāng)最大成員大小不是最大對(duì)齊數(shù)的整數(shù)倍時(shí),就要對(duì)齊到最大對(duì)齊數(shù)的整數(shù)倍。
到此這篇關(guān)于C語言 自定義類型的文章就介紹到這了,更多相關(guān)C語言 自定義類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言中函數(shù)指針與軟件設(shè)計(jì)經(jīng)驗(yàn)總結(jié)
今天小編就為大家分享一篇關(guān)于C語言中函數(shù)指針與軟件設(shè)計(jì)經(jīng)驗(yàn)總結(jié),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Qt專欄之模態(tài)與非模態(tài)對(duì)話框的實(shí)現(xiàn)
這篇文章主要介紹了Qt專欄之模態(tài)與非模態(tài)對(duì)話框的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04MFC控件之CListCtrl的應(yīng)用實(shí)例教程
這篇文章主要介紹了MFC控件中CListCtrl的應(yīng)用方法,包括了針對(duì)表格的一些操作,是MFC中比較重要的一個(gè)控件類,需要的朋友可以參考下2014-08-08C++實(shí)現(xiàn)LeetCode(56.合并區(qū)間)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(56.合并區(qū)間),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07深入學(xué)習(xí)C++智能指針之shared_ptr與右值引用的方法
智能指針的核心實(shí)現(xiàn)技術(shù)是引用計(jì)數(shù),每使用它一次,內(nèi)部引用計(jì)數(shù)加1,每析構(gòu)一次內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),刪除所指向的堆內(nèi)存,今天通過本文給大家分享C++智能指針之shared_ptr與右值引用的方法,需要的朋友跟隨小編一起看看吧2021-07-07