C/C++自定義類型結(jié)構(gòu)體全解析
前言
集成開發(fā)環(huán)境為vs2022
c語(yǔ)言有內(nèi)置類型(char short int long flaot double long double),也有自定義類型—結(jié)構(gòu)體(struct) 枚舉(enum) 聯(lián)合體(union) 本篇幅介紹結(jié)構(gòu)體
自定義類型:結(jié)構(gòu)體
1.結(jié)構(gòu)體類型的聲明
前?我們?cè)趯W(xué)習(xí)操作符的時(shí)候,已經(jīng)學(xué)習(xí)了結(jié)構(gòu)體的知識(shí),這?稍微復(fù)習(xí)?下。
1.1 結(jié)構(gòu)體回顧
結(jié)構(gòu)是?些值的集合,這些值稱為成員變量。結(jié)構(gòu)的每個(gè)成員可以是不同類型的變量。
1.1.1 結(jié)構(gòu)的聲明
struct tag//標(biāo)簽名
{
member-list;//成員 1個(gè)或多個(gè)
}variable-list;//變量列表例如描述?個(gè)學(xué)?:
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學(xué)號(hào)
}; //分號(hào)不能丟 struct Book b2;//全局變量
int main()
{
struct Book b1;//局部變量
return 0;
}1.1.2 結(jié)構(gòu)體變量的創(chuàng)建和初始化
#include <stdio.h>
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學(xué)號(hào)
};
int main()
{
//按照結(jié)構(gòu)體成員的順序初始化
struct Stu s = { "張三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
//按照指定的順序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
"?" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}1.2 結(jié)構(gòu)的特殊聲明
在聲明結(jié)構(gòu)的時(shí)候,可以不完全的聲明。
?如:
//匿名結(jié)構(gòu)體類型
struct//這里不寫名字
{
int a;
char b;
float c;
}s;//可以在這初始化
//}s={'x',100.3.14};
int main()
{
printf("%c %d %lf",s.c,s.i,s.d);
}匿名結(jié)構(gòu)體也可以重新命名
typedef struct
{
char c;
int i;
double d;
}s;上?的兩個(gè)結(jié)構(gòu)在聲明的時(shí)候省略掉了結(jié)構(gòu)體標(biāo)簽(tag)。 那么問題來(lái)了?
//在上?代碼的基礎(chǔ)上,下?的代碼合法嗎? p = &x;
警告:
編譯器會(huì)把上?的兩個(gè)聲明當(dāng)成完全不同的兩個(gè)類型,所以是非法的。
匿名的結(jié)構(gòu)體類型,如果沒有對(duì)結(jié)構(gòu)體類型重命名的話,基本上只能使??次。
1.3 結(jié)構(gòu)的自引用
在結(jié)構(gòu)中包含?個(gè)類型為該結(jié)構(gòu)本?的成員是否可以呢?
?如,定義?個(gè)鏈表的節(jié)點(diǎn):
在這之前先講一下鏈表
數(shù)據(jù)結(jié)構(gòu)–其實(shí)是數(shù)據(jù)在內(nèi)存中的存儲(chǔ)和組織的結(jié)構(gòu) 數(shù)據(jù)有多種
線性數(shù)據(jù)結(jié)構(gòu):順序表,鏈表,棧,隊(duì)列
順序表–數(shù)組

鏈表

//定義一個(gè)鏈表節(jié)點(diǎn)
struct Node
{
int data;
struct Node next;
};上述代碼正確嗎?如果正確,那 sizeof(struct Node) 是多少?
仔細(xì)分析,其實(shí)是不?的,因?yàn)?個(gè)結(jié)構(gòu)體中再包含?個(gè)同類型的結(jié)構(gòu)體變量,這樣結(jié)構(gòu)體變量的? ?就會(huì)?窮的?,是不合理的。
正確的?引??式:
struct Node{
int data;//數(shù)據(jù)
struct Node* next;//指針
};在結(jié)構(gòu)體?引?使?的過程中,夾雜了 typedef 對(duì)匿名結(jié)構(gòu)體類型重命名,也容易引?問題,看看 下?的代碼,可?嗎?
typedef struct
{
int data;
Node* next;
}Node;答案是不?的,因?yàn)镹ode是對(duì)前?的匿名結(jié)構(gòu)體類型的重命名產(chǎn)?的,但是在匿名結(jié)構(gòu)體內(nèi)部提前使 ?Node類型來(lái)創(chuàng)建成員變量,這是不?的。
匿名結(jié)構(gòu)體類型不能實(shí)現(xiàn)結(jié)構(gòu)體的自引用
解決?案如下:定義結(jié)構(gòu)體不要使用匿名結(jié)構(gòu)體了
typedef struct Node
{
int data;
struct Node* next;
}Node;//上述代碼等價(jià)于下邊代碼
struct Node
{
int data;
struct Node* next;
}
typedef struct Node Node;2.結(jié)構(gòu)體內(nèi)存對(duì)齊
我們已經(jīng)掌握了結(jié)構(gòu)體的基本使?了。
現(xiàn)在我們深?討論?個(gè)問題:計(jì)算結(jié)構(gòu)體的??。
這也是?個(gè)特別熱?的考點(diǎn): 結(jié)構(gòu)體內(nèi)存對(duì)?
2.1 對(duì)?規(guī)則
?先得掌握結(jié)構(gòu)體的對(duì)?規(guī)則:
1.結(jié)構(gòu)體的第1個(gè)成員對(duì)?到和結(jié)構(gòu)體變量起始位置偏移量為0的地址處
2.從第2個(gè)成員變量開始,都要對(duì)?到某個(gè)對(duì)?數(shù)的整數(shù)倍的地址處。
對(duì)?數(shù)=編譯器默認(rèn)的?個(gè)對(duì)?數(shù)與該成員變量??的較?值。
VS 中默認(rèn)的值為 8
Linux中g(shù)cc沒有默認(rèn)對(duì)?數(shù),對(duì)?數(shù)就是成員??的??
3.結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)(結(jié)構(gòu)體中每個(gè)成員變量都有?個(gè)對(duì)?數(shù),所有對(duì)?數(shù)中最?的)的 整數(shù)倍。
4.如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體成員對(duì)?到??的成員中最?對(duì)?數(shù)的整數(shù)
//練習(xí)1
struct S1
{ // 默認(rèn) 對(duì)齊數(shù)
char c1;// 1 8 1
int i;// 4 8 4
char c2;// 1 8 1
};
printf("%d\n", sizeof(struct S1));
//練習(xí)2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//練習(xí)3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//練習(xí)4-結(jié)構(gòu)體嵌套問題
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
2.2 為什么存在內(nèi)存對(duì)齊?
?部分的參考資料都是這樣說的:
1. 平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定 類型的數(shù)據(jù),否則拋出硬件異常。
2.性能原因
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在?然邊界上對(duì)?。原因在于,為了訪問未對(duì)?的內(nèi)存,處理器需要 作兩次內(nèi)存訪問;?對(duì)?的內(nèi)存訪問僅需要?次訪問。假設(shè)?個(gè)處理器總是從內(nèi)存中取8個(gè)字節(jié),則地 址必須是8的倍數(shù)。如果我們能保證將所有的double類型的數(shù)據(jù)的地址都對(duì)?成8的倍數(shù),那么就可以 ??個(gè)內(nèi)存操作來(lái)讀或者寫值了。否則,我們可能需要執(zhí)?兩次內(nèi)存訪問,因?yàn)閷?duì)象可能被分放在兩 個(gè)8字節(jié)內(nèi)存塊中。
總體來(lái)說:結(jié)構(gòu)體的內(nèi)存對(duì)?是拿空間來(lái)?yè)Q取時(shí)間的做法。
例如
struct S
{
char c;//1
int i;//4
};
那在設(shè)計(jì)結(jié)構(gòu)體的時(shí)候,我們既要滿?對(duì)?,?要節(jié)省空間,如何做到:
讓占?空間?的成員盡量集中在?起
//例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};S1 和 S2 類型的成員?模?樣,但是 S1 和 S2 所占空間的??有了?些區(qū)別。
2.3 修改默認(rèn)對(duì)?數(shù)
#pragma 這個(gè)預(yù)處理指令,可以改變編譯器的默認(rèn)對(duì)?數(shù)。
#include <stdio.h>
#pragma pack(1)//設(shè)置默認(rèn)對(duì)?數(shù)為1 一般是2的次方數(shù) linux中不能改
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的對(duì)?數(shù),還原為默認(rèn)
int main()
{
//輸出的結(jié)果是什么? 6
printf("%d\n", sizeof(struct S));
return 0;
}結(jié)構(gòu)體在對(duì)??式不合適的時(shí)候,我們可以??更改默認(rèn)對(duì)?數(shù)。
3. 結(jié)構(gòu)體傳參
struct S
{
int data[1000];//4000字節(jié)
int num;
};
struct S s = {{1,2,3,4}, 1000};
//結(jié)構(gòu)體傳參
void print1(struct S s)//s先拷貝,占用內(nèi)存很大
{
//for循環(huán)打印數(shù)組
printf("%d\n", s.num);
}
//結(jié)構(gòu)體地址傳參
void print2(const struct S* ps)
{
printf("%d\n", ps->num);
printf("%d\n",ps->data[i]);
}
int main()
{
print1(s); //傳結(jié)構(gòu)體
print2(&s); //傳地址
return 0;
}上?的 print1 和 print2 函數(shù)哪個(gè)好些?
答案是:首選print2函數(shù)。
原因:
函數(shù)傳參的時(shí)候,參數(shù)是需要壓棧,會(huì)有時(shí)間和空間上的系統(tǒng)開銷。
如果傳遞?個(gè)結(jié)構(gòu)體對(duì)象的時(shí)候,結(jié)構(gòu)體過?,參數(shù)壓棧的的系統(tǒng)開銷?較?,所以會(huì)導(dǎo)致性能的下降。
結(jié)論:結(jié)構(gòu)體傳參的時(shí)候,要傳結(jié)構(gòu)體的地址。
4.結(jié)構(gòu)體實(shí)現(xiàn)位段
4.1 什么是位段
位段的聲明和結(jié)構(gòu)是類似的,有兩個(gè)不同:
1.位段的成員必須是 int、unsigned int 或signed int ,在C99中位段成員的類型也可以 選擇其他整型家族類型,?如:char。
2.位段的成員名后邊有?個(gè)冒號(hào)和?個(gè)數(shù)字。
?如:
struct A
{
int _a:2;//只占兩個(gè)bit位
int _b:5;
int _c:10;
int _d:30;
};struct s
{
int _a;//4字節(jié) 32bit 可以節(jié)省30個(gè)字節(jié)
int _b;
int _c;
int _d;
//00 0
//01 1
//10 2
//11 3
}A就是?個(gè)位段類型。
位段是專門用來(lái)節(jié)省內(nèi)存的
那位段A所占內(nèi)存的??是多少?
// %zd 8字節(jié)
printf("%d\n", sizeof(struct A));//
4.2 位段的內(nèi)存分配
1.位段的成員可以是 int unsigned int signed int 或者是 char 等類型
2.位段的空間上是按照需要以**4個(gè)字節(jié)( int )或者1個(gè)字節(jié)( char )**的?式來(lái)開辟的。
3.位段涉及很多不確定因素,位段是不跨平臺(tái)的,注重可移植的程序應(yīng)該避免使?位段。
//?個(gè)例?
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;//00001010
s.b = 12;//00001100
s.c = 3;//00000011
s.d = 4;//00000100
//空間是如何開辟的? 在這之前我們先要了解一下內(nèi)存的使用順序
1.申請(qǐng)到的一塊內(nèi)存中,從左向右使用,還是從右向左使用,是不確定的 vs是從右向左
2.剩余空間,不是下一個(gè)成員使用的時(shí)候,是浪費(fèi)呢?還是繼續(xù)使用? vs是浪費(fèi)


4.3 位段的跨平臺(tái)問題
- int位段被當(dāng)成有符號(hào)數(shù)還是?符號(hào)數(shù)是不確定的。
- 位段中最?位的數(shù)?不能確定。(16位機(jī)器最?16,32位機(jī)器最?32,寫成27,在16位機(jī)器會(huì)出問題。
- 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配,標(biāo)準(zhǔn)尚未定義。
- 當(dāng)?個(gè)結(jié)構(gòu)包含兩個(gè)位段,第?個(gè)位段成員?較?,?法容納于第?個(gè)位段剩余的位時(shí),是舍棄剩余的位是利?,這是不確定的。
- 總結(jié):
- 跟結(jié)構(gòu)相?,位段可以達(dá)到同樣的效果,并且可以很好的節(jié)省空間,但是有跨平臺(tái)的問題存在。
4.4 位段的應(yīng)用
下圖是?絡(luò)協(xié)議中,IP數(shù)據(jù)報(bào)的格式,我們可以看到其中很多的屬性只需要?個(gè)bit位就能描述,這? 使?位段,能夠?qū)崿F(xiàn)想要的效果,也節(jié)省了空間,這樣?絡(luò)傳輸?shù)臄?shù)據(jù)報(bào)??也會(huì)較??些,對(duì)?絡(luò) 的暢通是有幫助的。

4.5 位段使用的注意事項(xiàng)
**位段的?個(gè)成員共有同?個(gè)字節(jié),這樣有些成員的起始位置并不是某個(gè)字節(jié)的起始位置,那么這些位置處是沒有地址的。內(nèi)存中每個(gè)字節(jié)分配?個(gè)地址,?個(gè)字節(jié)內(nèi)部的bit位是沒有地址的。
所以不能對(duì)位段的成員使?&操作符,這樣就不能使?scanf直接給位段的成員輸?值,**只能是先輸?放在?個(gè)變量中,然后賦值給位段的成員。
一個(gè)字節(jié)一個(gè)地址
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//這是錯(cuò)誤的
//正確的?范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}到此這篇關(guān)于C/C++自定義類型:結(jié)構(gòu)體的文章就介紹到這了,更多相關(guān)C++結(jié)構(gòu)體內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++產(chǎn)生隨機(jī)數(shù)的實(shí)現(xiàn)代碼
本篇文章是對(duì)C++中產(chǎn)生隨機(jī)數(shù)的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C++ 標(biāo)準(zhǔn)模板庫(kù) STL 順序容器詳解
這篇文章主要介紹了C++ 標(biāo)準(zhǔn)模板庫(kù) STL 順序容器詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
C++中的string庫(kù)函數(shù)常見函數(shù)的作用和使用方法
這篇文章主要介紹了C++中的string庫(kù)函數(shù)常見函數(shù)的作用和使用方法,庫(kù)函數(shù)的靈活應(yīng)用是程序員的一大重要技能,本文通過實(shí)例實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-04-04
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
VSCode無(wú)法打開源文件及無(wú)法打開鏈接庫(kù)文件的解決方法
本文主要介紹了VSCode無(wú)法打開源文件及無(wú)法打開鏈接庫(kù)文件的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
C++ DFS算法實(shí)現(xiàn)走迷宮自動(dòng)尋路
這篇文章主要為大家詳細(xì)介紹了C++ DFS算法實(shí)現(xiàn)走迷宮自動(dòng)尋路,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05

