柚子快報激活碼778899分享:C語言?定義類型:結(jié)構(gòu)體
柚子快報激活碼778899分享:C語言?定義類型:結(jié)構(gòu)體
目錄
前言
1. 結(jié)構(gòu)體類型的聲明
1.1 結(jié)構(gòu)的聲明
1.2 結(jié)構(gòu)體變量的創(chuàng)建和初始化
1.3?結(jié)構(gòu)的特殊聲明
1.4?結(jié)構(gòu)的?引?
2.結(jié)構(gòu)體內(nèi)存對齊
2.1 對?規(guī)則
實例講解
2.2 為什么存在內(nèi)存對??
2.3 修改默認對?數(shù)
3. 結(jié)構(gòu)體傳參
4.?結(jié)構(gòu)體實現(xiàn)位段
4.1 什么是位段
?4.2 位段的內(nèi)存分配
4.3 位段的跨平臺問題
4.4 位段的應(yīng)?
4.5 位段使?的注意事項
結(jié)語
?編輯
前言
在C語言中提供了許多內(nèi)置類型,例如:int、short、long、longlong、char、float、double等,但是如果想描述一個學(xué)生時,這些單一的內(nèi)置類型是無法做到的,描述一個學(xué)生需要包含名字、年齡、身高、體重等特征,為了解決這類問題,C語言中引入了結(jié)構(gòu)體的概念,結(jié)構(gòu)體這種自定義的數(shù)據(jù)類型,能讓我們自己創(chuàng)建出合適的類型,那么接下來學(xué)習(xí)一下結(jié)構(gòu)體該如何使用吧?。?!
1. 結(jié)構(gòu)體類型的聲明
1.1 結(jié)構(gòu)的聲明
定義結(jié)構(gòu)體的基本形式:
struct tag? ?//結(jié)構(gòu)體名稱
{
member-list;? //結(jié)構(gòu)體成員列表
}variable-list;? ?//結(jié)構(gòu)體變量列表
例如描述一個學(xué)生的基本信息:
struct stu
{
char name[20]; //學(xué)生姓名
int age; //學(xué)生年齡
int stu_id[10];//學(xué)生學(xué)號
char sex[5]; //學(xué)生性別
}
1.2 結(jié)構(gòu)體變量的創(chuàng)建和初始化
既然已經(jīng)學(xué)習(xí)了結(jié)構(gòu)體是如何聲明的,那么來看看結(jié)構(gòu)體變量是如何創(chuàng)建和初始化的:
1.按照結(jié)構(gòu)體成員順序進行初始化、
?
#include
struct Stu
{
char name[20]; //學(xué)生姓名
int age; //學(xué)生年齡
char stu_id[20];//學(xué)生學(xué)號
char sex[5]; //學(xué)生性別
};
int main()
{
//按照結(jié)構(gòu)體成員的順序初始化
struct Stu s = { "張三", 20, "男", "20230818001" };//s就是結(jié)構(gòu)體變量
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("stu_id : %s\n", s.stu_id);
return 0;
}
?
2.按照指定的順序初始化
#include
struct Stu
{
char name[20]; //學(xué)生姓名
int age; //學(xué)生年齡
char stu_id[20];//學(xué)生學(xué)號
char sex[5]; //學(xué)生性別
};
int main()
{
struct Stu s2 = { .age = 18, .name = "lisi", .sex ="女" ,.stu_id = "20230818002"};
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("stu_id : %s\n", s2.stu_id);
}
1.3?結(jié)構(gòu)的特殊聲明
在聲明結(jié)構(gòu)的時候,可以不完全的聲明,例如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}* p;
int main()
{
p = &x;
return 0;
}
上?的兩個結(jié)構(gòu)在聲明的時候省略掉了結(jié)構(gòu)體標簽(tag),此時是匿名結(jié)構(gòu)體類型,那么此時主函數(shù)中的p=&x 這個代碼是合法的嗎?
注意:編譯器會把上?的兩個聲明(兩個匿名結(jié)構(gòu)體類型)當(dāng)成完全不同的兩個類型,所以是?法的。 匿名的結(jié)構(gòu)體類型,如果沒有對結(jié)構(gòu)體類型重命名的話,基本上只能使??次。
1.4?結(jié)構(gòu)的?引?
結(jié)構(gòu)體自引用指的是在結(jié)構(gòu)體定義中,結(jié)構(gòu)體本身作為該結(jié)構(gòu)體類型中的一個成員出現(xiàn),但是直接進行結(jié)構(gòu)體自引用會引發(fā)無限遞歸的結(jié)構(gòu)體定義,就是結(jié)構(gòu)體試圖包含自己,導(dǎo)致編譯器無法確定結(jié)構(gòu)體的大小。
例如:
struct Node
{
int data;
struct Node next;
};
?個結(jié)構(gòu)體中再包含?個同類型的結(jié)構(gòu)體變量,這樣結(jié)構(gòu)體變量的? ?就會?窮的?,是不合理的,那么正確的自引用方式是怎么樣的呢?
struct Node
{
int data;
struct Node* next;
};
?使用指向自己類型的指針來實現(xiàn)自引用的效果,這種方式不會引發(fā)無限遞歸的問題,并且指針的大小是固定(4/8字節(jié))。
2.結(jié)構(gòu)體內(nèi)存對齊
我們已經(jīng)掌握了結(jié)構(gòu)體的基本使?了。 現(xiàn)在我們深?討論?個問題:計算結(jié)構(gòu)體的??,要計算結(jié)構(gòu)體大小,我們就要明白結(jié)構(gòu)體成員在內(nèi)存中是如何進行存儲的,這就涉及到結(jié)構(gòu)體內(nèi)存對齊的問題。
2.1 對?規(guī)則
1. 結(jié)構(gòu)體的第?個成員對?到和結(jié)構(gòu)體變量起始位置偏移量為0的地址處。
2. 其他成員變量要對?到某個數(shù)字(對?數(shù))的整數(shù)倍的地址處。 對?數(shù) = 編譯器默認的?個對?數(shù) 與 該成員變量??的較?值。?VS 中默認的值為 8 - Linux中 gcc 沒有默認對?數(shù),對?數(shù)就是成員??的??。
3. 結(jié)構(gòu)體總??為最?對?數(shù)(結(jié)構(gòu)體中每個成員變量都有?個對?數(shù),所有對?數(shù)中最?的)的 整數(shù)倍。
4. 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體成員對?到??的成員中最?對?數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體??就是所有最?對?數(shù)(含嵌套結(jié)構(gòu)體中成員的對?數(shù))的整數(shù)倍。
什么意思呢?我們接下來根據(jù)實例進行探索(vs2022環(huán)境)
實例講解
練習(xí)1:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
S1與S2的區(qū)別就在結(jié)構(gòu)體成員的位置的不同,那么里面的成員都是一樣的,為什么S1和S2的大小是不一樣的呢?這就涉及到結(jié)構(gòu)體內(nèi)存對齊的規(guī)則了。
畫圖演示:
所以此時S1的大小為12個字節(jié),S2的大小也是通過對齊規(guī)則進行判斷的,這里就不在進行演示啦。
練習(xí)2:(結(jié)構(gòu)體嵌套問題)
struct S3
{
double d;
char c;
int i;
};
//結(jié)構(gòu)體嵌套問題
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
return 0;
}
接下來我們來計算一下S3和S4的大小,根據(jù)之前講過的對齊規(guī)則,我們可以快速的計算出S3的大小是16個字節(jié),那么我們重點來講一下S4的大小該如何計算。
畫圖演示:
運行結(jié)果:
2.2 為什么存在內(nèi)存對??
1. 平臺原因 (移植原因)
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定 類型的數(shù)據(jù),否則拋出硬件異常。
2. 性能原因
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在?然邊界上對?。原因在于,為了訪問未對?的內(nèi)存,處理器需要 作兩次內(nèi)存訪問;?對?的內(nèi)存訪問僅需要?次訪問。假設(shè)?個處理器總是從內(nèi)存中取8個字節(jié),則地 址必須是8的倍數(shù)。如果我們能保證將所有的double類型的數(shù)據(jù)的地址都對?成8的倍數(shù),那么就可以 ??個內(nèi)存操作來讀或者寫值了。否則,我們可能需要執(zhí)?兩次內(nèi)存訪問,因為對象可能被分放在兩 個8字節(jié)內(nèi)存塊中。
總體來說:結(jié)構(gòu)體的內(nèi)存對?是拿空間來換取時間的做法。
那么在設(shè)計結(jié)構(gòu)體的時候,我們該如何盡可能的讓占用空間更小呢?
解決辦法:讓占?空間?的成員盡量集中在?起,就比如練習(xí)一中的例子,S1 和 S2 類型的成員?模?樣,但是 S1 和 S2 所占空間的??有了?些區(qū)別。
2.3 修改默認對?數(shù)
結(jié)構(gòu)體在對??式不合適的時候,我們可以??更改默認對?數(shù)。
#pragma 這個預(yù)處理指令,可以改變編譯器的默認對?數(shù)。
#include
#pragma pack(1)//設(shè)置默認對?數(shù)為1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的對?數(shù),還原為默認
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
運行結(jié)果:
這里將默認對齊數(shù)從8變成了1,再根據(jù)對齊規(guī)則,得到的大小就是6個字節(jié)。
3. 結(jié)構(gòu)體傳參
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//結(jié)構(gòu)體傳參
void print1(struct S s)
{
printf("%d\n", s.num);
}
//結(jié)構(gòu)體地址傳參
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //傳結(jié)構(gòu)體
print2(&s); //傳地址
return 0;
}
結(jié)構(gòu)體傳參有兩種方式:一種是值傳遞另一種是址傳遞
值傳遞的方式:調(diào)用函數(shù)的時候,系統(tǒng)要為函數(shù)的形參部分分配空間來存放實參的值,相當(dāng)于又開辟了一份相同的空間。
址傳遞的方式:調(diào)用函數(shù)的時候,函數(shù)的形參部分用結(jié)構(gòu)體指針接收實參的地址,指向的就是實參,系統(tǒng)不會再為形參部分開辟一份與實參相同的空間,可以節(jié)約對內(nèi)存空間的占用。
那么print1(值傳遞)和 printf2(址傳遞)哪種方式更好呢?其實 printf2 址傳遞的方式更好
原因:
函數(shù)傳參的時候,參數(shù)是需要壓棧,會有時間和空間上的系統(tǒng)開銷。 如果傳遞?個結(jié)構(gòu)體對象的時候,結(jié)構(gòu)體過?,參數(shù)壓棧的的系統(tǒng)開銷?較?,所以會導(dǎo)致性能的下降。
結(jié)論: 結(jié)構(gòu)體傳參的時候,盡量傳結(jié)構(gòu)體的地址。
4.?結(jié)構(gòu)體實現(xiàn)位段
4.1 什么是位段
位段的聲明和結(jié)構(gòu)是類似的,有兩個不同:
1. 位段的成員必須是 int、unsigned int 或signed int ,在C99中位段成員的類型也可以選擇其他類型。
2. 位段的成員名后邊有?個冒號和?個數(shù)字。(表示成員在內(nèi)存中所占的比特位)
例如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
?4.2 位段的內(nèi)存分配
1. 位段的成員可以是 int unsigned int signed int 或者是 char 等類型
2. 位段的空間上是按照需要以4個字節(jié)( int )或者1個字節(jié)( char )的?式來開辟的。
3. 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應(yīng)該避免使?位段。
?位段成員在內(nèi)存中的分配方式(從左到右,從右到左)是由編譯器決定的
//?個例?
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空間是如何開辟的?
畫圖演示:
4.3 位段的跨平臺問題
1. int 位段被當(dāng)成有符號數(shù)還是?符號數(shù)是不確定的。
2. 位段中最?位的數(shù)?不能確定。(16位機器最?16,32位機器最?32,寫成27,在16位機器會 出問題。
3. 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配,標準尚未定義。
4. 當(dāng)?個結(jié)構(gòu)包含兩個位段,第?個位段成員?較?,?法容納于第?個位段剩余的位時,是舍棄 剩余的位還是利?,這是不確定的。
總結(jié): 跟結(jié)構(gòu)相?,位段可以達到同樣的效果,并且可以很好的節(jié)省空間,但是有跨平臺的問題存在。
4.4 位段的應(yīng)?
下圖是?絡(luò)協(xié)議中,IP數(shù)據(jù)報的格式,我們可以看到其中很多的屬性只需要?個bit位就能描述,這? 使?位段,能夠?qū)崿F(xiàn)想要的效果,也節(jié)省了空間,這樣?絡(luò)傳輸?shù)臄?shù)據(jù)報??也會較??些,對?絡(luò) 的暢通是有幫助的。
4.5 位段使?的注意事項
位段的?個成員共有同?個字節(jié),這樣有些成員的起始位置并不是某個字節(jié)的起始位置,那么這些位 置處是沒有地址的。內(nèi)存中每個字節(jié)分配?個地址,?個字節(jié)內(nèi)部的bit位是沒有地址的。 所以不能對位段的成員使?&操作符,這樣就不能使?scanf直接給位段的成員輸?值,只能是先輸? 放在?個變量中,然后賦值給位段的成員。
例如:
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//這是錯誤的
//正確的?范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}
結(jié)語
以上就是自定義類型—結(jié)構(gòu)體的內(nèi)容啦,希望大家看完后能夠靈活運用結(jié)構(gòu)體,并且明白結(jié)構(gòu)體數(shù)據(jù)在內(nèi)存中是如何存儲的,在此感謝大家的觀看?。?!
柚子快報激活碼778899分享:C語言?定義類型:結(jié)構(gòu)體
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。