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

鏈表

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


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

4.5 位段使用的注意事項
**位段的?個成員共有同?個字節(jié),這樣有些成員的起始位置并不是某個字節(jié)的起始位置,那么這些位置處是沒有地址的。內存中每個字節(jié)分配?個地址,?個字節(jié)內部的bit位是沒有地址的。
所以不能對位段的成員使?&操作符,這樣就不能使?scanf直接給位段的成員輸?值,**只能是先輸?放在?個變量中,然后賦值給位段的成員。
一個字節(jié)一個地址
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;
}到此這篇關于C/C++自定義類型:結構體的文章就介紹到這了,更多相關C++結構體內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Qt利用QState狀態(tài)機實現(xiàn)控件互斥操作詳解
這篇文章主要為大家詳細介紹了Qt如何利用QState狀態(tài)機實現(xiàn)控件互斥操作,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2022-12-12
淺談C語言中的sizeof()和strlen()的區(qū)別
本文主要介紹了C語言中的sizeof()和strlen()的區(qū)別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05

