摘要:解決方案三結(jié)構(gòu)體變量的定義和初始化有了結(jié)構(gòu)體類型,那要怎么樣來定義結(jié)構(gòu)體變量和初始化變量呢例聲明類型的同時(shí)定義變量定義結(jié)構(gòu)體變量初始化定義變量的同時(shí)賦初值。
目錄
六、結(jié)構(gòu)體實(shí)現(xiàn)位段(位段的填充&可移植性)
首先我們來了解一下結(jié)構(gòu)的基礎(chǔ)知識:
結(jié)構(gòu)是一些值的集合,這些值稱為成員變量。結(jié)構(gòu)的每個(gè)成員可以不同類型的變量。
如數(shù)組是一組相同類型的元素的集合,而結(jié)構(gòu)體也是一些值的集合,結(jié)構(gòu)體的每個(gè)成員可以是不同類型的。
struct tag{ member-list;}variable-list;//結(jié)構(gòu)體關(guān)鍵字:struct//結(jié)構(gòu)體的標(biāo)簽:tag//結(jié)構(gòu)體的類型:struct tag//結(jié)構(gòu)的成員列表:member_list//結(jié)構(gòu)體變量列表:variable_list
例:
#include //聲明一個(gè)結(jié)構(gòu)體類型//聲明一個(gè)學(xué)生類型,是想通過學(xué)生類型來創(chuàng)建學(xué)生變量(對象)//描述學(xué)生:屬性 - 名字+性別+年齡+電話號碼struct Stu{ char name[20]; //名字 char sex[10]; //性別 int age; //年齡 char phone[12];//電話}; //記住這里要加分號struct Stu s3;//全局變量int main(){ //創(chuàng)建的結(jié)構(gòu)體變量 struct Stu s1; struct Stu s2; return 0;}
在聲明結(jié)構(gòu)的時(shí)候,可以不完全的聲明。
例:
//匿名結(jié)構(gòu)體類型struct{ int a; char c;}sa;struct{ int a; char c;}* psa;//匿名結(jié)構(gòu)體指針類型
?思考:在上面代碼的基礎(chǔ)上,下面的代碼合法嗎?
int main(){ psa = &sa; return 0;}
執(zhí)行結(jié)果:
?警告:編譯器會把上面的兩個(gè)聲明當(dāng)成完全不同的兩個(gè)類型,所以是非法的。
結(jié)論:當(dāng)兩個(gè)匿名結(jié)構(gòu)體類型內(nèi)部的內(nèi)容一樣時(shí),仍然是兩個(gè)不同結(jié)構(gòu)體類型?
思考1:在結(jié)構(gòu)中包含一個(gè)類型為該結(jié)構(gòu)本身的成員是否可以呢?
//代碼1struct Node{ int data; struct Node next;};
思考2:可行嗎?如果可以,那sizeof(struct Node)是多少?
解答:不行。
假設(shè)代碼1中的方式可以執(zhí)行,那么在創(chuàng)建結(jié)構(gòu)體的過程中,struct Node next由于結(jié)構(gòu)體struct Node類型還沒創(chuàng)建完成,所以其類型的大小是未知的,而struct Node類型的是否能成功創(chuàng)建又依賴于struct Node next類型大小的確定性。
所以這兩者自相矛盾。因此上述方法不行!
//代碼2struct Node{ int data;//4 數(shù)據(jù)域 struct Node* next;//4/8 指針域};
思考3:這串代碼為什么可以成功呢?
解答:首先此處結(jié)構(gòu)體自應(yīng)用方式并不是直接利用結(jié)構(gòu)體來創(chuàng)建變量,而是創(chuàng)建指向該結(jié)構(gòu)體類型的指針。
我們知道,指針的大小跟其所指向的類型無關(guān),僅跟平臺環(huán)境有關(guān),32位平臺指針大小為4個(gè)字節(jié),64位平臺,指針大小為8個(gè)字節(jié)。
正因?yàn)橹羔槾笮〉拇_定性,所以再自引用的時(shí)候結(jié)構(gòu)體類型的整體大小也是可以確定的。
思考4:這樣寫代碼可行嗎?
//代碼3typedef struct{ int data; Node *next;}Node;
解答:不行
由于此時(shí)struct后面省略掉了Node,所以匿名重新命名結(jié)構(gòu)體為Node,那么此時(shí)編譯器就會不認(rèn)識Node。(就好比先有雞還是先有蛋)
因?yàn)榻Y(jié)構(gòu)體類型有重命名才能產(chǎn)生Node,而此時(shí)還未定義Node就在結(jié)構(gòu)體內(nèi)部使用了Node,所以會產(chǎn)生錯(cuò)誤。
解決方案:
typedef struct Node{ int data; struct Node *next;}Node;
有了結(jié)構(gòu)體類型,那要怎么樣來定義結(jié)構(gòu)體變量和初始化變量呢?
例1:
struct Point{ int x; int y;}p1; //聲明類型的同時(shí)定義變量p1struct Point p2; //定義結(jié)構(gòu)體變量p2//初始化:定義變量的同時(shí)賦初值。struct Point p3 = {x, y};
例2:
struct S //類型聲明{ char name;//姓名 int age;//年齡 double d;//身高 char sex;//性別};int main(){ struct S s = {"c", 20, 182.5, "boy"};//初始化 return 0;}
?例3:
#include struct T{ double weight;//體重 double height;//身高}p;struct S //類型聲明{ char name;//姓名 struct T p; short age;//年齡 char sex[5];//性別};int main(){ struct S s = {"A", {63.5, 182.5}, 20, "boy"};//結(jié)構(gòu)體嵌套初始化 printf("%c %lf %lf %d %s/n", s.name, s.p.weight, s.p.height, s.age, s.sex); return 0;}
關(guān)于結(jié)構(gòu)體的基本使用到這里我們就差不多掌握了。
現(xiàn)在我們深入討論一個(gè)問題:我們都知道任何的數(shù)據(jù)類型都應(yīng)有其對應(yīng)的內(nèi)存空間大小。如?char大小為1個(gè)字節(jié),int 為4個(gè)字節(jié),double 為8個(gè)字節(jié)等,如果無法確定大小,就無法在創(chuàng)建的時(shí)候知道該分配給該類型變量的內(nèi)存空間是多少。
那么,結(jié)構(gòu)體的大小是多少?又該如計(jì)算結(jié)構(gòu)體的大小呢??
讓我們來研究一下熱門考點(diǎn):結(jié)構(gòu)體內(nèi)存對齊
1. 第一個(gè)成員在與結(jié)構(gòu)體變量偏移量為0的地址處。
2. 其他成員變量要對齊到某個(gè)數(shù)字(對齊數(shù))的整數(shù)倍的地址處。
對齊數(shù) = 編譯器默認(rèn)的一個(gè)對齊數(shù)與該成員大小的較小值。?
- VS中默認(rèn)的值為8
- Linux中的默認(rèn)值為4
3. 結(jié)構(gòu)體總大小為最大對齊數(shù)(每個(gè)成員變量都有一個(gè)對齊數(shù))的整數(shù)倍。
4. 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對齊到自己的最大對齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對齊數(shù)(含嵌套結(jié)構(gòu)體的對齊數(shù))的整數(shù)倍。(注:不是整體,是自身最大對齊數(shù))
練習(xí)1:
#include struct s1{ char c1; int i; char c2;};int main(){ printf("%d/n", sizeof(struct s1)); return 0;}
執(zhí)行結(jié)果:
分析:
練習(xí)2:
#includestruct s2{ char c1; char c2; int i;};int main(){ printf("%d/n", sizeof(struct s2)); return 0;}
執(zhí)行結(jié)果:
分析:
?練習(xí)3:
#includestruct s3{ double d; char c; int i;};int main(){ printf("%d/n", sizeof(struct s3)); return 0;}
執(zhí)行結(jié)果:
分析:
?練習(xí)4:
#includestruct s3{ double d; char c; int i;};struct s4{ char c1; struct s3 s3; double d;};int main(){ printf("%d/n", sizeof(struct s4)); return 0;}
?執(zhí)行結(jié)果:
分析:
1. 平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址 處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2. 性能原因: 數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。 原因在于,為了訪問未對齊的內(nèi)存,處理器 需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。
?總體來說: 結(jié)構(gòu)體的內(nèi)存對齊是拿空間來換取時(shí)間的做法。
當(dāng)我們在設(shè)計(jì)結(jié)構(gòu)體的時(shí)候,既要滿足對齊,又要節(jié)省空間時(shí):
我們需要讓占用空間小的成員盡量集中在一起。
例:
struct s1{ char c1; int i; char c2;};struct s2{ char c1; char c2; int i;};
此時(shí)s1和s2類型的成員一模一樣,但是s1和s2所占用的空間大小是有區(qū)別的,前者大小為12個(gè)字節(jié),后者為8個(gè)字節(jié),顯然后者這種方式空間利用的效率更高。
之前我們見過了 #pragma 這個(gè)預(yù)處理指令,這里我們再次使用,可以改變我們的默認(rèn)對齊數(shù)。
#include #pragma pack(8)//設(shè)置默認(rèn)對齊數(shù)為8struct s1{ char c1; int i; char c2;};#pragma pack()//取消設(shè)置的默認(rèn)對齊數(shù),還原為默認(rèn)#pragma pack(1)//設(shè)置默認(rèn)對齊數(shù)為1struct s2{ char c1; int i; char c2;};#pragma pack()//取消設(shè)置的默認(rèn)對齊數(shù),還原為默認(rèn)int main(){ printf("%d/n", sizeof(struct s1)); printf("%d/n", sizeof(struct s2)); return 0;}
執(zhí)行結(jié)果:
分析:
由于s1的值我們之前已經(jīng)知道了大小為12,那么我們來分析一下s2的值為什么是6?
?結(jié)論:結(jié)構(gòu)在對齊方式不合適的時(shí)候,我么可以自己更改默認(rèn)對齊數(shù)。
寫一個(gè)宏,計(jì)算結(jié)構(gòu)體中某變量相對于首地址的偏移,并給出說明
我們這邊首先要用到宏,即 offsetof
#include#includestruct s2{ char c1; int i; char c2;};int main(){ printf("%d/n", offsetof(struct s2, c1)); printf("%d/n", offsetof(struct s2, i)); printf("%d/n", offsetof(struct s2, c2)); return 0;}
執(zhí)行結(jié)果:
直接舉例:
#include struct S{ int a; char c; double d;};struct S s = { {1, 2, 3, 4}, 1000 };void Init(struct S tmp){ tmp.a = 100; tmp.c = "w"; tmp.d = 3.14;}//結(jié)構(gòu)體傳參void Print1(struct S tmp){ printf("%d %c %lf/n", tmp.a, tmp.c, tmp.d);}//結(jié)構(gòu)體地址傳參void Print2(struct S* ps){ printf("%d %c %lf/n", ps->a, ps->c, ps->d);}int main(){ struct S s = {0}; Init(s); Print1(s);//傳結(jié)構(gòu)體 print2(&s);//傳結(jié)構(gòu)體地址 return 0;}
結(jié)構(gòu)體傳參有兩種方式:
- 傳遞結(jié)構(gòu)體對象(傳值),對應(yīng)的就是print1函數(shù)的方式
- 傳遞結(jié)構(gòu)體地址(傳址),對應(yīng)的就是print2函數(shù)的方式
思考:上面的print1和print2函數(shù)哪個(gè)好些 ?
答案:首選print2函數(shù)。
原因:
函數(shù)傳參的時(shí)候,參數(shù)是需要壓棧,會有時(shí)間和空間上的系統(tǒng)開銷。
如果傳遞一個(gè)結(jié)構(gòu)體對象的時(shí)候,結(jié)構(gòu)體過大,參數(shù)壓棧的的系統(tǒng)開銷比較大,所以會導(dǎo)致性能的下降。結(jié)論 : 結(jié)構(gòu)體傳參的時(shí)候,要傳結(jié)構(gòu)體的地址。
結(jié)構(gòu)體講完就得講講結(jié)構(gòu)體實(shí)現(xiàn) 位段?的能力。
位段的聲明和結(jié)構(gòu)是類似的,有兩個(gè)不同:
- 位段的成員必須是 int、unsigned int 或signed int 。
- 位段的成員名后邊有一個(gè)冒號和一個(gè)數(shù)字。?
例:?
#includestruct A{ int _a : 2; int _b : 5; int _c : 10; int _d : 30;};int main(){ printf("%d/n", sizeof(struct A)); return 0;}
A就是一個(gè)位段類型。 那位段A的大小是多少呢??
執(zhí)行結(jié)果:
1.位段的成員可以是int unsigned intsigned int或者是char(屬于整形家族)類型
2.位段的空間上是按照需要以4個(gè)字節(jié)([int)或者1個(gè)字節(jié)(char)的方式來開辟的。
3.位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應(yīng)該避免使用位段。
而這些不確定因素體現(xiàn)在:
(1)空間是否要被浪費(fèi)?
(2)空間是從左向右使用還是從右向左使用?
比如說VS2019:先開辟1 / 4個(gè)字節(jié), 從右向左使用時(shí),空間會被浪費(fèi)
例:
#include struct S{ char a : 3; char b : 4; char c : 5; char d : 4;};int main(){ struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0;}
思考:空間是如何開辟的?
執(zhí)行結(jié)果(前):
?執(zhí)行結(jié)果(后):
分析:
1)先開辟一個(gè)char類型大小的空間,也就是占用了1個(gè)字節(jié)。
2)將a的3個(gè)bit內(nèi)容從右往左放入該字節(jié)中,此時(shí)還剩下5bit大小的空間
3)再將b的4個(gè)bit內(nèi)容放到a的后面,此時(shí)還剩下1bit大小的空間
4)1bit空間內(nèi)部不夠c中5bit的內(nèi)容存放,于是重新開辟了一個(gè)字節(jié)空間,從右往左后此時(shí)剩下了3bit大小的空間
5)剩下的空間(3bit)不夠d的4bit內(nèi)容存放,于是又重新開辟了一個(gè)字節(jié)空間,從右往左將d的4bit內(nèi)容放進(jìn)去
1. int 位段被當(dāng)成有符號數(shù)還是無符號數(shù)是不確定的。
2. 位段中最大位的數(shù)目不能確定。(16位機(jī)器最大16,32位機(jī)器最大32,寫成27,在16位機(jī)器會出問題。
3. 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配標(biāo)準(zhǔn)尚未定義。
4. 當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)位段,第二個(gè)位段成員比較大,無法容納于第一個(gè)位段剩余的位時(shí),是舍棄剩余的位 還是利用,這是不確定的。
總結(jié):?跟結(jié)構(gòu)相比,位段可以達(dá)到同樣的效果,但是可以很好的節(jié)省空間,但是有跨平臺的問題存在。
網(wǎng)絡(luò)傳輸協(xié)議包(計(jì)算機(jī)網(wǎng)絡(luò))
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/119016.html
目錄 一、枚舉 (一)枚舉類型的定義 (二)使用枚舉的原因? (三)枚舉的優(yōu)點(diǎn)? (四)枚舉的大小 (五)枚舉的使用 二、聯(lián)合(共用體) (一)聯(lián)合類型的定義 (二)聯(lián)合的特點(diǎn) (三)面試題 (四)聯(lián)合大小的計(jì)算 一、枚舉 枚舉顧名思義就是:列舉?。? ?即把可能的取值一一列舉出來。 比如我們現(xiàn)實(shí)生活中: 一周當(dāng)中從周一至周日的7天,可以一一列舉;性別有:男、女、保密,可以一一列舉;月份有...
摘要:結(jié)構(gòu)體類型的特殊聲明在初階結(jié)構(gòu)體中,我們已經(jīng)將了結(jié)構(gòu)體類型是如何進(jìn)行聲明的,那么在這里,我們將講一些特殊的結(jié)構(gòu)體聲明不完全的聲明。所以我們應(yīng)該這樣寫通過指針來找到下一個(gè)同類型結(jié)構(gòu)體的寫法,我們就稱之為結(jié)構(gòu)體的自引用。 ...
摘要:故使用無具體類型,又稱通用類型,即可以接收任意類型的指針,但是無法進(jìn)行指針運(yùn)算解引用,整數(shù)等。求指針?biāo)甲止?jié)而不是解引用訪問權(quán)限大小。數(shù)組就是整個(gè)數(shù)組的大小,數(shù)組元素則是數(shù)組元素的大小,指針大小都為。 ...
摘要:我們常用的結(jié)構(gòu),就是小端模式,什么則為大端模式?jīng)]學(xué)我也不知道是個(gè)啥,但還是擺出來。 目錄 傳統(tǒng)藝能?過渡區(qū)?正片開始?共用體原理?字節(jié)順序?大小端存儲?共用體判斷...
目錄 ? ?一、數(shù)據(jù)類型介紹 二、類型的意義 三、類型的基本歸類 整型家族 浮點(diǎn)數(shù)家族 構(gòu)造類型(自定義類型) 指針類型 空類型 四、整形在內(nèi)存中的存儲 原碼、反碼、補(bǔ)碼 大小端字節(jié)序 為什么有大端和小端? 一道經(jīng)典筆試題 ?一、數(shù)據(jù)類型介紹 數(shù)據(jù)從大的方向分為兩類: 內(nèi)置類型自定義類型內(nèi)置類型我們前面已經(jīng)學(xué)習(xí)過,如下: char? ? ? ? ? ? //字符數(shù)據(jù)類型 short? ? ? ...
閱讀 3012·2021-11-22 12:06
閱讀 599·2021-09-03 10:29
閱讀 6526·2021-09-02 09:52
閱讀 2013·2019-08-30 15:52
閱讀 3411·2019-08-29 16:39
閱讀 1190·2019-08-29 15:35
閱讀 2061·2019-08-29 15:17
閱讀 1416·2019-08-29 11:17