目錄
一、結構體的聲明和使用
1.1 結構體正常聲明和創建
1.2 結構體特殊聲明
1.3 結構體的自引用
二、結構體內存對齊
2.1 對齊規則
2.2 #pragma修改
三、結構體傳參
四、結構體位段
4.1 位段內存分配
4.2 位段內存應用
五、結構體中的柔性數組概念
六、union聯合體
6.1 union聯合體聲明、使用和特點
6.2 union聯合體在內存的存儲
七、枚舉類型
7.1 枚舉類型的聲明
7.2 枚舉類型的優點
一、結構體的聲明和使用
1.1 結構體正常聲明和創建
????????struct用來聲明結構體變量,格式為struct 名字{內部成員};(這里有分號)后面還能直接設置全局變量,直接寫變量名,比如struct Stu{};然后初始化一個變量名struct Stu s1,這個s1就是結構變量(struct Stu是一個結構體的類型是一個整體,而s1是這個變量名)。使用時需要使用{}里面用來設置初始化的,如果struct里面還有一個結構體,那么初始化時{}這個括號內也要再加一個括號給他初始化,引用時可以不按順序來設置初始化,這時候就需要一個操作符“.”點這個操作符,格式為.內部變量名=初始化。當然還可以修改初始化內容就是結構體名.內部變量名=修改內容,但是數組就需要一個庫函數strcpy(string.h),strcpy(結構體名.內部變量,修改內容);對于內部成員為指針,引用時可以用“->”指針取向操作符。
1.2 結構體特殊聲明
? ? ? ? 對于特殊聲明就是沒有名字(指的是結構體名字),而如果沒有名字的結構體內成員是一模一樣的兩個結構體,兩個結構體中設置指針,將一個指針賦給另一個結構體的指針時,編譯器會報警告(編譯器默認這兩個結構體指針類型是不一樣的)。
#include<stdio.h>
struct
{char str[20];int a;char b;
}*s1,a;
struct
{char str[20];int a;char b;
}*s2;
int main()
{s1 = &a;s2 = s1;return 0;
}
?報警告!!!
匿名的結構體類型,如果沒有對結構體類型重命名的話,基本上只能使??次。
1.3 結構體的自引用
? ? ? ? 結構體的自引用就是鏈表,通過指針引用結構體進行內容訪問尋找數據。
? ? ? ? 需要定義鏈表的節點
struct Stu
{char name[20];//數據域struct Stu* nest;//地址域
};
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ↑↑↑
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????????????????????正確方式?
struct Stu
{char name[20];struct Stu nest;
};
????????????????????????????????????????????????????????????????? ↑↑↑
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 錯誤方式
擴展:數據結構,是描述數據在內存中的存儲結構。結構體鏈表(結構體自引用),通過相同的節點進行連接,上一個節點有下一個節點的地址,而下個節點有下下個節點的地址。
????????這里夾雜了 typedef 對匿名結構體類型重命名?,在函數中自引用如果使用不到位會報錯,不能只寫重命名的名字到里面,而是要寫全,才能設置為指針域。
#include<stdio.h>
typedef struct node
{int a;//node* next;//因為上面typedef重命名還沒命名完,所以這里需要寫全struct node* nest;//所以還需要自己來寫
}node;//這里typedef已經重定義完了int main()
{return 0;
}
?
二、結構體內存對齊
2.1 對齊規則
? ? ? ? 第一條:結構體內部的第一個任何成員是在偏移量為0位置
? ? ? ? 第二條:結構體要對齊到最小對齊數的整數倍(最近的整數倍);最小對齊數為成員類型的字節數與編譯器默認的對齊數的最小對齊數。這里以我的編譯器vs來舉例,該編譯器的默認為8,我在結構體內部用了一個char一個int類型成員(按左到右按順序)按照第一條char位于偏移量為0,而int類型的話,那就是要位于偏移量為4的位置了(int類型4個字節,默認8,最小為4)。
? ? ? ? 第三條:結構體總大小要為最大對齊數(這里指的是成員內最大對齊數,char最小是1,int最小是4,所以最大對齊數為4)的整數倍(最近)。同樣的舉一個例子來說明 內部有三個成員(按順序)char、int和char類型的成員
? ? ? ? 第四條:嵌套結構體時,計算結構體總大小時,最大對齊數要和嵌套結構體的內部成員一起計算,嵌套結構體時可以將嵌套的那個結構體當作結構體內的一個成員,規則都和前面3條符號,就是計算總大小時的最大對齊數要配合嵌套結構體的內部成員。
#include<stdio.h>
struct test1
{char a;double b;char c;int d;
};
struct test2
{int e;struct test1;//默認8,這個成員字節為24,最小為8
};
int main()
{printf("%zd\n", sizeof(struct test2));return 0;
}
?
?對于結構體的內存對齊一般是為了用空間換取時間的手段,方便了內存的讀取(如果不對齊,可能需要讀取兩次,對齊只需讀一次),提高讀取效率,不過在對于這個規則,我們不能一味的用大量的空間去換取時間,可以將一些小的類型寫在一起(相同類型),避免了大量空間的浪費,也確保了讀取效率。
計算偏移量,一般用offsetof來表示宏,可以計算結構體成員相較于結構體起始位置的偏移量,他需要頭文件stddef.h。用法為offsetof(結構體類型,結構體內成員)?。
2.2 #pragma修改
? ? ? ? 這個定義是用于改變默認對齊數的(改變的是編譯器),一般編譯器默認是8,改變為4或者2,也會影響結構體大小。
這里pragma要配合pack使用。不過一般修改都是以2的次方數進行修改的。?
三、結構體傳參
? ? ? ? 結構體傳參有兩種方式,一種是傳值(結構體變量),一種是傳址(傳地址)。
#include<stdio.h>
struct Stu
{char name[20];int age;
};
void print2(struct Stu* s1)//傳址
{printf("%s\n", s1->name);
}
void print1(struct Stu s1)//傳值
{printf("%s\n", s1.name);
}
int main()
{struct Stu s1 = { "zhangsan",20 };print1(s1);print2(&s1);return 0;
}
????????一般我們寫結構體的時候會選擇傳址調用,傳值調用消耗內存太大了,如果結構體內部有一個很大的數組,那么這個結構體開辟空間會很大,不僅如此形參和實參的調用都會開辟這么多空間。傳值調用,因為指針字節要么是4個字節要么是8個字節,就算開辟也不會占很大空間,所以一般會使用傳址調用。
四、結構體位段
4.1 位段內存分配
? ? ? ? 結構體位段,用于節省空間,方便傳輸等。結構體內的成員必須是int、unsigned int、signed int類型,不過在c99中其他成員類型也是可以。
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};
????????這就是一個位段,后面接的是比特數?。
? ? ? ? 內存中的位段是
這里如果以上空間都正常使用的話,一共開辟2個字節空間,但是實際不是這樣,因為他們不夠連續在一起,就會再開辟(因為不是從左到右,這里是從右到左)?
4.2 位段內存應用
? ? ? ? ????????位段在網絡傳輸過程中用到很多位段,因為我們在傳輸的時候會考慮到傳輸速度和簡單方面考慮可以類比卡車。
像第二張,這種小車就能夠進行超車,效率會很快。
位段是沒有跨平臺性的,所以我們可以為一個平臺專門設置位段進行節約空間。,不過位段也有缺點,就是不能取地址嗎,所以就不能通過scanf來修改內的內容,只能先賦值在位段。
五、結構體中的柔性數組概念
? ? ? ? 柔性數組是位于結構體內最后的一位成員的數組,可以沒有大小。特點就是計算結構體總大小時,不會計算數組大小,這個柔性數組是可以通過動態內存來確定數組大小(更加靈活),這個我后面會和動態內存一起講。
六、union聯合體
6.1 union聯合體聲明、使用和特點
? ? ? ? union聯合體聲明和時候方法是和struct結構體是一樣的,也有匿名聯合體,唯一的區別就是在計算時總大小時是不一樣的,union聯合體大小是去內部成員內最大字節,其他的會重疊內存,改變一個字節就會影響另一個數。聯合體相比結構體會更加節約空間。
//在表示商品共同屬性和特殊屬性時也可以使用union聯合體
#include<stdio.h>
struct gift_list
{int stock_number;//庫存double price;//定價int item_type;//商品類型union{struct{char tille[20];//書名char author[20];//作者名int num_pages;//頁數}book;struct{char design[20];//設計}mug;struct{char design[20];//設計int colors;//顏色int sizes;//尺寸}shirt;};//這里都用匿名,是因為都只使用一次
}item;
//這樣的設計就能節約空間(相比較全部用struct結構體)
int main()
{return 0;
}
6.2 union聯合體在內存的存儲
? ? ? ? union聯合體在內存中是內存重疊。
????????這里用判斷大小端來舉例
#include<stdio.h>
union A
{char a;int b;
};
int check_sys()
{union A n = { 0 };n.b = 1;if (n.a == 1){return 1;}else{return 0;}
}
int main()
{int ret =check_sys();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}
????????????????????????????????????????????????????????????????這里再來一個例子
#include<stdio.h>
union Un
{char c;int i;
};
int main()
{//聯合變量的定義union Un un = { 0 };un.i = 0x11223344;un.c = 0x55;printf("%x\n", un.i);return 0;
}
七、枚舉類型
7.1 枚舉類型的聲明
????????枚舉一般用于列舉,比如生活中的星期、性別、月份和三原色等。聲明和使用和結構體和聯合體類似。
enum sex
{boy,girl,secret
};//格式和結構體和
內部的值都是枚舉常量,只能在內部改變。內部不賦值,那就默認從0開始遞增。
7.2 枚舉類型的優點
? ? ? ? 枚舉的優點1.方便代碼可讀性,可維護性。2.相較于用#define定義的常量,枚舉有類型的會更加嚴謹。3.使用方便,可以一次性列舉多個常量。4.枚舉是遵循定義域的,在函數中定義,就只能在函數中使用。