目錄
🚀結構體
🔥結構體類型的聲明
🔥結構的自引用
🔥結構體變量的定義和初始化
🔥結構體內存對齊
🔥結構體傳參
🔥結構體實現位段(位段的填充&可移植性)
🚀枚舉
🔥枚舉類型的定義
🔥枚舉的優點
🔥枚舉的使用
🚀聯合(共用體)
🔥聯合聯合類型的定義
🔥聯合的特點
🔥聯合大小的計算
🚀結構體
🔥結構體類型的聲明
結構是一些值的集合,這些值成為成員變量。結構的每個成員可以是不同類型的變量。
結構的聲明:
struct Stu
{
?? ?char name[20];//姓名
?? ?int age;//年齡
?? ?char sex[5];//性別
?? ?char id[20];//學號
};//分號不能省略//struct——>結構體關鍵字;Stu——>結構體標簽這里表示的是學生屬性
特殊聲明:
//匿名結構體類型
struct
{
?? ?int a;
?? ?char b;
}x;//匿名結構體類型只能使用一次
struct
{
?? ?int a;
?? ?char b;
}a[20],* p;//p指向的是這個結構體指針的地址
int main()
{
?? ?p = &x;
?? ?return 0;
}?//編譯器會把上面兩個聲明當成兩個完全不同的類型
🔥結構的自引用
在結構體中包含一個類型為該結構本身的成員
struct Node
{
?? ?int data;
?? ?struct Node next;
};
這種類型的結構自引用是非法的,成員next中又會包含一個struct Node的結構,如此遞歸下去永無止境。在計算sizeof(struct Node)時無法求出。合法聲明:
struct Node
{
?? ?int data;
?? ?struct Node* next;
};//也就是說線性結構中鏈表,每個節點包括了這個節點的數據和指向下一節點的地址的指針的信息。即數據域和指針域。
?當使用typedef類型定義和自引用時要注意下面這種陷阱:
typedef struct
{
?? ?int data;
?? ?Node* next
}Node;//匿名結構體類型,typedef重定義一個名字Node
這種寫法是非法的,因為類型名知道整個定義結束才遇到,在結構體內部的Node是未定義的
//解決方案:
typedef struct Node
{
?? ?int data;
?? ?struct Node* next
}Node;?
🔥結構體變量的定義和初始化
struct Point
{
?? ?int x;
?? ?int y;
}s1; ? ? ? ? //聲明類型的同時定義變量s1
struct Point s2;//定義結構體變量s2
//結構體嵌套初始化
struct Score
{
?? ?int n;
?? ?char ch
};
struct Stu
{
?? ?char name[20];
?? ?int age;
?? ?struct Score s
};
int main()
{
?? ?struct Stu s1 = { "zhangsan",20,{20,'q'} };
}?
🔥結構體內存對齊
如何來計算結構體的大小
#include<stdio.h>
struct S1
{char a;int i;char b;
};
struct S2
{char a;char b;int i;
};
int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0;
}
明明只是調換了一下位置,為什么所占字節大小會不同呢?
結構體的對齊規則:
1、第一個成員在與結構體變量偏移量為0的地址處
2、其他成員變量要對齊到某個數字(對其數)的整數倍的地址處。
? ? ? 對其數=編譯器默認的一個對其數與該成員大小的較小值。
? ? ? ? ? ?vs中默認的值是8
3、結構體總體大小為最大對齊數(每個成員變量都有一個對齊數)的整數倍。
4、如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。?
為什么會存在結構體內存?
1、平臺原因:
不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則會拋出硬件異常。
2、性能原因:
數據結構(尤其是棧)應盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要做兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
結構體的內存對齊是拿空間來換取時間的做法。?
設計結構體的時候,我們既要滿足對齊又要滿足節省空間,盡量把小的類型集中在前面,從而減少空間的浪費
修改默認對其數
#pragma pack(8)//設置默認對齊數為8
struct S1
{
?? ?char a;
?? ?int i;
?? ?char b;
};
#pragma pack()//取消設置的默認對齊數,還原為默認#pragma pack(1)//設置默認對齊數為1
struct S2
{
?? ?char a;
?? ?int i;
?? ?char b; ?
};
建議不要隨意修改,當我們不去追求效率,而是追求空間浪費最少時可以考慮修改默認對齊數。
🔥結構體傳參
#include<stdio.h>
struct S
{int data[100];int num;
};
void print1(struct S ss)
{int i = 0;for (i = 0; i < 2; i++){printf("%d ", ss.data[i]);}printf("%d", ss.num);
}
void print2(struct S* ss)
{int i = 0;for (i = 0; i < 2; i++){printf("%d ", ss->data[i]);}printf("%d", ss->num);
}
int main()
{struct S s = { {1,2},100 };print1(s);//傳值調用print2(&s);//傳址調用return 0;
}
?在進行結構體傳參時我們傳地址是更好的,這是因為函數傳參的時候,參數是需要壓棧的,會有時間和空間上的系統開銷。如果傳遞一個結構體對象的時候,結構以過大,參數壓棧的系統開銷比較大,會導致性能的下降。
🔥結構體實現位段(位段的填充&可移植性)
位段(位指的是比特位)的聲明和結構是類似的,有兩個不同:
1、位段的成員只能是整型:int、unsigned int,signed int或者char類型的
2、位段的成員后面有一個冒號和數字
#include<stdio.h>
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};
位段的內存分配
1、位段成員可以是int 、unsigned int、signed int或者char類型
2、位段的空間上是按照需要以4(int)個字節或 1(char)個字節的方式來開辟的
3、位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應避免使用位段
//舉個栗子:
#include<stdio.h>
struct S
{
?? ?//先開辟一個字節的空間,8個比特位
?? ?char a : 3;
?? ?//用了3個還剩5個比特位
?? ?char b : 4;
?? ?//用了4個還剩1個比特位
?? ?char c : 5;
?? ?//不夠用了,再開辟一個字節,8個比特位,用了5個,剩下3個
?? ?char d : 4;
?? ?//又不夠用了,再開辟一個字節,8個比特位用了4個,剩下4個
};
int main()
{
?? ?struct S s = { 0 };
?? ?printf("%d\n", sizeof(struct S));//3
?? ?s.a = 10;
?? ?s.b = 12;
?? ?s.c = 3;
?? ?s.d = 4;
?? ?return 0;
}
?
?調試一走,我們發現vs的規則和我們計算的是沒有差別的
但我們在使用位段的時候會有很多問題:
1、int位段被當成有符號數還是無符號數是不確定的
2、位段中最大位的數目不能確定。(16位機器最大16,32位機器最大32,寫成27,在16位機器會出問題。
3、位段中的成員在內存中從左向右分配,還是從右向左分配標準未定義。
4、當一個結構中包含兩個位段,第二個位段的成員比較大,無法容納容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這是不確定的
總結:與結構相比,位段可以達到同樣效果,但是可以很好的節省空間,但是有跨平臺的問題存在。?當然位段在計算機網絡中有其獨有的作用,能節省不少空間浪費(數據越少,狀態越好),從而達到網絡環境較優的狀態
🚀枚舉
枚舉顧名思義就是一一列舉,比如:一年12個月可以一一列舉、一周7天可以一一列舉。
🔥枚舉類型的定義
enum Day//星期
{
?? ?Mon,
?? ?Tues,
?? ?Wed
?? ?Thur,
?? ?Fri,
?? ?Sat,
?? ?Sun
};enum Sex//性別
{
?? ?MALE,
?? ?FEMALE,
?? ?SECRET
};//與結構體是非常相似的,但其內部是用逗號分隔開的,且內部只包含符號
{ }里面的內容是枚舉類型的可能取值,也叫枚舉常量。
這些可能取值都是有值的,默認從0開始,一次遞增一,當然在定義的時候也可以賦初值。
?enum Color//顏色
{
?? ?RED = 1,
?? ?GREEN=2,
?? ?BLUE=3
};
🔥枚舉的優點
我們可以使用 #define 定義常量,為什么要用枚舉呢?
1、增加代碼的可讀性和可維護性
2、和#define定義的標識符比較枚舉有類型檢查,更加嚴謹
3、防止命名污染(封裝)
4、便于調試
5、使用方便,一次可以定義多個常量
🔥枚舉的使用
enum Color//顏色
{RED = 1,GREEN=2,BLUE=4
};
int main()
{enum Color clr = GREEN;//只能拿枚舉常量給枚舉變量賦值,才不會出現類型的差異clr = 5;return 0;
}
🚀聯合(共用體)
🔥聯合聯合類型的定義
聯合也是一種特殊的自定義類型,這種類型定義的變量也包含一系列的成員,特征是這些成員共用同一塊空間(所以聯合也叫共用體)
//舉例:
union Un
{
?? ?int a;
?? ?char c;
};
int main()
{
?? ?union Un u;
?? ?printf("%d\n", sizeof(u));//4,說明共用了一份空間
?? ?return 0;
}
?從這里我們就能看出來a與c共用一份空間
🔥聯合的特點
聯合的成員是共用同一塊內存空間的,這樣一個聯合變量的大小,至少是最大成員的大小(因為聯合體至少得有能力保存最大的那個成員)
union Un
{int a;char c;
};
int main()
{union Un u;u.a = 0x11223344;u.c = 0x55;printf("%x\n", u.a);return 0;
}
?判斷機器是大端存儲還是小端存儲(用聯合的方法)
int check_sys()
{union Un{int a;char b;}u;u.a = 1;return u.b;
}
int main()
{int ret = check_sys();if (ret == 1){printf("小端\n");}elseprintf("大端\n");return 0;
}
🔥聯合大小的計算
·聯合的大小至少是最大成員的大小。
·當最大成員大小不是最大對齊數的整數倍時,就要對齊到最大對齊數的整數倍。
union Un1
{
?? ?char c[5];
?? ?int i;
};//本來應該是5,最大對齊數為4,所以大小為4的倍數即8