目錄
結構體
結構體聲明
結構體成員的訪問
結構體自引用?
結構體變量定義,初始化,傳參?
結構體內存對齊?
位段
枚舉
聯合(共用體)
結構體
結構體聲明
1. 概念
1. 結構體是一些值的集合,這些值稱為成員變量。
2. 結構體的每個成員可以是不同類型的變量。
3. 數組:一組相同類型元素的集合,結構體:一組不一定相同類型元素的集合。
4.?結構體的成員可以是標量、數組、指針,甚至是其他結構體。
2. 聲明
例子
假設我們要用結構體表示一個學生類型
struct Stu { char name[20]; //名字int age; //年齡char sex[5]; //性別 };
我們還可以這樣寫,這樣表示直接用這個結構體類型創建s1和s2變量。
struct Stu { char name[20]; int age; char sex[5]; }s1, s2;
也可以這樣創建變量。
struct Stu s3;
2. 特殊的聲明?
在聲明結構的時候,可以不完全的聲明。
比如:匿名結構體類型。
接下來我這樣寫。
struct {int a;char b;float c; }x;struct {int a;char b;float c; }*p;
請問我可以寫 p = &x; 嗎?答案是不行。雖然成員是一樣的,但編譯器會把上面的兩個聲明當成完全不同的兩個類型。
結構體成員的訪問
1. 結構體變量訪問成員
結構體變量的成員是通過點操作符(.)訪問的。點操作符接受兩個操作數。
比如:
struct Stu {char name[20];int age; };struct Stu s; strcpy(s.name, "zhangsan");//使用.訪問name成員 s.age = 20;//使用.訪問age成員
2. 結構體指針訪問成員?
有時候我們得到的不是一個結構體變量,而是指向一個結構體的指針,那該如何訪問成員?
struct Stu {char name[20];int age; };void print(struct Stu* ps) {printf("name = %s age = %d\n", (*ps).name, (*ps).age);//使用結構體指針訪問指向對象的成員printf("name = %s age = %d\n", ps->name, ps->age); }int main() {struct Stu s = {"zhangsan", 20};print(&s);//結構體地址傳參return 0; }
結構體自引用?
1.?在結構中包含一個類型為該結構本身的成員是否可以呢?
比如這樣寫可以嗎?
struct Node {int data;struct Node next; };
答案是不行,因為無法計算struct Node有多大,無限套娃。
正確寫法:
//結構體的自引用 struct Node {int data;struct Node* next; };
2.?搭配 typedef 的寫法?
錯誤寫法:
typedef struct {int data;Node* next; }Node;
正確寫法:
typedef struct Node {int data;struct Node* next; }Node;
這里的執行邏輯是我們先對這個類型重命名之后產生Node,也就是說重命名之前的寫法必須是正確的。
結構體變量定義,初始化,傳參?
1. 定義,初始化
有了結構體類型,那如何定義變量呢?如何初始化呢?
有兩個地方可以定義變量。定義變量的同時也能初始化。
struct Point {int x;int y; }p1 = {1, 2}; //第一種方法,這里創建的變量是全局變量,同時也能初始化 int main() {struct Point p2 = {3, 4}; //第二種方法,這里創建的變量是局部變量,同時初始化struct Point p3 = {.y=5, .x=6}; //這里用了結構成員訪問符,可以不按順序初始化return 0; }
2. 嵌套初始化?
我們想一個問題,結構體里面有沒有可能出現結構體類型的數據呢?答案是有可能的。
比如:
struct Point {int x;int y; };struct PP {double d;struct Point pt;int a; }
那么這個怎么初始化呢?
很簡單,嵌套的那個結構體自己加一對大括號就好了。
int main() {struct PP p = {3.14, {1, 2}, 8};printf("%d\n", p.pt.x); //想要訪問嵌套的結構體只需用多一次 . 即可 return 0; }
3. 結構體傳參
下方代碼中傳結構體與傳結構體地址哪個好一些?
struct S {int data[1000];int num; };//結構體傳參 void print1(struct S s) { printf("%d\n", s.num); }//結構體地址傳參 void print2(struct S* ps) { printf("%d\n", ps->num); }int main() {struct S s = {{1,2,3,4}, 1000};print1(s); //傳結構體print2(&s); //傳結構體地址return 0; }
答案:傳結構體地址。
原因:函數傳參的時候,參數是需要壓棧,會有時間和空間上的系統開銷。 如果傳遞一個結構體對象的時候,結構體過大,參數壓棧的的系統開銷比較大,所以會導致性能的下降。
結構體內存對齊?
1.?計算結構體的大小
想了解內存對齊我們得先思考下面這個例子
struct S1 {char c1; //1int i; //4char c2; //1 };struct S2{char c1; //1char c2; //1int i; //4};int main() {printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0; }
經過分析我們認為都是6,因為1+1+4嘛,然而結果是S1大小為12,S2大小為8。為什么?
因為這里面就涉及到結構體內存對齊了。
2.?結構體的對齊規則
1. 結構體的第一個成員永遠放在與結構體變量起始位置偏移量為0的位置。
2. 從第二個成員開始,往后每個成員都要對齊到對齊數的整數倍處。對齊數 = 編譯器默認的一個對齊數與該成員大小的較小值。VS中默認的值為8。Linux中沒有默認對齊數,對齊數就是成員自身的大小。
3. 結構體總大小為最大對齊數的整數倍。最大對齊數是所有成員的對齊數的最大值。
4. 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,
結構體的總大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。
3. 練習?
struct S3 {double d;char c;int i; };printf("%d\n", sizeof(struct S3));
答:16
解析:1. d的對齊數是8,但因為d是第一個成員所以直接放在偏移量為0的位置,d是8個字節占用偏移量0到7的位置。
2. c的對齊數是1,此時偏移量為8是1(對齊數)的整數倍,c是1個字節占用偏移量為8的位置。
3. i的對齊數是4,此時偏移量為9不是4(對齊數)的整數倍,一直往后到偏移量12的位置,此時是4(對齊數)的整數倍,i是4個字節占用12到15的位置。
4. 偏移量從0到15,所以總大小為16,16是8(最大對齊數)的整數倍。
.
下面這個結構體嵌套了結構體,這大小該如何計算呢?
struct S4 {char c1;struct S3 s3;double d; };printf("%d\n", sizeof(struct S4));
答:32
解析:1. c1對齊數是1,因為是第一個成員所以放在偏移量為0的位置,c1是一個字節所以占用一個位置。
2. s3是結構體所以s3的對齊數是自己成員的最大對齊數是8,此時的偏移量是1不是對齊數的整數倍所以一直移到偏移量為8的位置,s3是16個字節占用8到23的位置。
3. d的對齊數是8,此時偏移量為24是對齊數的整數倍,d是8個字節占用24到31的位置。
4. 偏移量從0到31一個32個位置是所有最大對齊數的整數倍。
4. 為什么存在內存對齊??
1. 平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2. 性能原因: 數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。
原因在于為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。 總體來說: 結構體的內存對齊是拿空間來換取時間的做法。
.
那我們如何設計結構體既能滿足對齊又能節省空間呢?
答案:讓占用空間小的成員盡量集中在一起。
比如下面代碼,S2就比S1省空間。
struct S1 {char c1;int i;char c2; };struct S2 {char c1;char c2;int i; };
5. 修改默認對齊數
#pragma pack(8)//設置默認對齊數為8struct S1{char c1;int i;char c2;};#pragma pack()//取消設置的默認對齊數,還原為默認#pragma pack(1)//設置默認對齊數為1struct S2{char c1;int i;char c2;};#pragma pack()//取消設置的默認對齊數,還原為默認
結論:結構體在對齊方式不合適的時候,我們可以自己更改默認對齊數。
位段
1. 什么是位段
位段的成員名后邊有一個冒號和一個數字。
比如:A就是一個位段類型。
struct A {int _a : 2;int _b : 5;int _c : 10;int _d : 30; };
那位段A的大小是多少?
printf("%d\n", sizeof(struct A));
答案:8字節
因為位段的位是二進制位,意味著_a占兩個比特位,_b占5個比特位,_c占10個比特位,_d占30個比特位,一個是47個比特位,一個int32字節不夠,所有給了兩個int。
.
那有同學要問了,為什么要弄位段呢?
其實有時候我們設計結構體成員的時候,它的取值非常有限,可能只會用到幾個比特位,剩下的比特位給其他成員用,這樣可以節省空間。
2. 位段的內存分配?
1. 位段的成員可以是 int,unsigned int,signed int 或者是char (屬于整形家族)類型。
2. 位段的空間上是按照需要以4個字節(int)或者1個字節(char)的方式來開辟的。
3. 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應該避免使用位段。
.
這里的不確定因素包括:
1. 位段中的成員在內存中從左向右分配,還是從右向左分配標準尚未定義。
2. 當一個位段成員占用比特位比較大需要開辟新空間時,前面空間剩余的比特位是使用還是舍棄不確定。
3. int 位段被當成有符號數還是無符號數是不確定的。
4. 位段中最大位的數目不能確定(16位機器最大16,32位機器最大32)。
不確定就會導致不同的平臺有不同的實現。
3. VS環境位段的內存分配?
這個62 03 04是如何來的呢?請聽我細細道來。
第一步,根據每個成員后面的數字分配比特位,在VS中位段的內存分配是從右到左的。并且,當需要開辟新空間時,前面剩余的比特位不使用。
第二步,根據上面代碼將數值轉成二進制初始化,s.a=10,比特位是1010,但a只有三個比特位,所以高位的1會舍去。
第三步,轉成十六進制,四個比特位轉一個十六進制數。
4. 位段的應用?
枚舉
1. 枚舉類型的定義
enum Day//星期 {Mon,Tues,Wed,Thur,Fri,Sat,Sun };enum Sex//性別 {MALE,FEMALE,SECRET };enum Color//顏色 {RED,GREEN,BLUE };
1. 以上定義的 enum Day,enum Sex,enum Color 都是枚舉類型。
2.?{ }中的內容是枚舉類型的可能取值,也叫枚舉常量。
3.?這些可能取值都是有值的,默認從0開始,依次遞增1,在聲明枚舉類型的時候也可以賦初值。比如:
enum Color {RED=1,GREEN=8,BLUE };
這個BLUE的值是在前面的基礎上遞增1,也就是9。
2. 枚舉的使用
只能拿枚舉常量給枚舉變量賦值,才不會出現類型的差異。
enum Color {RED=1,GREEN=2,BLUE=4 };enum Color clr = GREEN;
3.??枚舉的優點
我們可以使用 #define 定義常量,為什么非要使用枚舉?
枚舉的優點:
1. 增加代碼的可讀性和可維護性。
2. 和#define定義的標識符比較枚舉有類型檢查,更加嚴謹。
3. 便于調試。
4. 使用方便,一次可以定義多個常量。
聯合(共用體)
1.?聯合類型的定義
1. 聯合是一種特殊的自定義類型。
2. 這種類型定義的變量包含一系列的成員,特征是這些成員公用同一塊空間,所以聯合也叫共用體。
例子:
//聯合類型的聲明 union Un {char c;int i; };//聯合變量的定義 union Un un;
2.?聯合的特點?
1. 聯合的成員是共用同一塊內存空間的。
2. 一個聯合變量的大小,至少是最大成員的大小,因為聯合至少得有能力保存最大的那個成員。
例子:
我們可以看出,這個聯合類型大小為4且成員的地址都相同。
如圖,i 和 c 共用同一塊空間。
.
所以當我們修改 c 的時候,i 也會跟著變。
3.??面試題:判斷當前計算機的大小端存儲
之前我們學到用指針來判斷,這次我們利用聯合判斷。
思路:i = 1 有兩種存儲字節序,一種是 00 00 00 01,一種是 01 00 00 00,所以我們就對比第一個字節即可。
int main() {union {char c;int i;}un = {.i = 1};//判斷第一個字節是1還是0if ((int)un.c) printf("small\n");else printf("big\n");return 0; }
4.??聯合大小的計算
1. 聯合的大小至少是最大成員的大小。
2. 當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍。
例子1:
union Un1 {char c[5];int i; };
答:Un1大小是8字節。
解析:首先c是char數組大小為5,i大小為4,那么Un1大小至少為5,然后我們看5是不是最大對齊數的整數倍,c的對齊數是1因為c的元素類型是char,i的對齊數是4,所以總大小要從5提升到8。
.
例子2
union Un2 {short c[7];int i; };
答:16字節。
解析:第一步,c的大小為14,i的大小為4,那么可以確定Un2的大小至少為14。第二步,判斷14是不是最大對齊數的整數倍,很明顯14不是4的整數倍,所以14增加到16。