? ? ?在C語言中,我們已經學了好幾種類型的數據。比如整型int、char、short等,浮點型double、float等。但是這些都是基本數據類型,而這些數據類型應用在實際編程里顯然是不夠用的。比如我們沒有辦法用一旦數據類型來定義一個”人“的屬性。因此這里我們需要一個框,將不同的數據類型放在一起,這就是結構體,接下來我們將深入學習結構體的知識。
結構體
結構體的創建與初始化
? ? ? ? 結構體是一些值的集合,這些值被稱之為成員變量。結構的每個成員都是不同類型的變量。
? ? ? ? 語法結構:
struct name//名字自定義
{member-list;//一個或者多個成員變量
}variable-list//可以省略。是結構體變量列表
舉例說明
struct person
{char name[20];int age;float height;
};
int main()
{struct person p1 = {"詩華",18,1.72};return 0;
}
結構體的特殊聲明
? ? ? ? 聲明結構體的時候可以不完全聲明。
? ? ? ? 但是匿名結構體的聲明其后必須創建變量
struct//匿名結構體類型
{char name[20];int age;float height;
}s1,s2;
????????匿名結構體類型只能用一次。
? ? ? ? 但是切記這種方法不行
拓展:數據結構簡單科普
????????鏈表是一種數據結構。數據結構是描述數據在內存當中的存儲結構,對數據的操作就是對數據結構的操作。
? ? ? ? 大體上數據結構分為以下幾種
結構的自引用
????????在結構體中包含一個類型為該結構本身的成員是否可以呢?
上圖報錯的原因:結構體內不能包含同類型的自己。?
如果想正確表達,需要這樣
struct Node
{int data;struct Node *next;
};int main()
{printf("%zd\n",sizeof(struct Node));/*struct person p1 = {"詩華",18,1.72};*/return 0;
}
結果為:?
而在上圖中,int data為數據域,struct Node * next為指針域。
? ? ? ? 也可以簡化為:
typedef struct Node
{int data;struct Node *next;
}Node;
之后如果使用結構體定義變量可以直接寫為
Node n;
但是這么些是絕對錯誤的
typedef struct
{int data;Node *next;
}Node;
結構體內存對齊
? ? ? ? 我們學會結構體的基本使用后,那么問題來了,結構體的大小是怎么計算的呢?我們舉例分析。
#include<stdio.h>
struct s1
{char c1;//1char c2;//1int n;//4
};
struct s2
{char c1;//1int n;//4char c2;//1
};
struct s3
{double d1;//8int n;//1char c1;//4
};
struct s4
{double d1;//8struct s3 S;//16char c2;//1
};
int main()
{printf("%zd\n",sizeof(struct s1));printf("%zd\n", sizeof(struct s2));printf("%zd\n",sizeof(struct s3));printf("%zd\n",sizeof(struct s4));return 0;
}
結果為:
????????
? ? ? ? 為什么是這樣呢?
? ? ? ? 結構體成員在存儲的時候有內存對齊的現象。
? ? ? ? 這里介紹一個新概念:offsetof——一個宏,可以計算出一個結構體成員相較于結構體起始位置的偏移量。(其實之前博主的博客預處理那節講解過:C語言學習之預處理指令-CSDN博客)
? ? ? ? 內存對齊的規則
1.結構體的第一個成員對齊到和結構體變量起始位置偏移量為0的地址處。
2.其他成員變量要對齊到某個數字(對齊數)的整數被的地址處
對齊數=編譯器默認的一個對齊數與該成員變量大小的較小值。
VS中默認的值為8
Linux中gcc編譯器沒有默認對齊數,對齊數就是成員變量自身的大小
3.結構體總大小為最大對齊數(結構體中每個成員變量都有一個對齊數,所有對齊數中最大值)的整數倍
4.如果嵌套了結構體的情況下,嵌套的結構體成員對齊到自己成員中最大對其數的整數倍,結構體的整體大小就是所有最大對其數(含嵌套結構體中成員的對齊數)的整數倍。
????????
?
為什么會存在內存對齊?
1.不是所有的硬件平臺都能訪問任意地址的任意數據;某些硬件平臺只能在某些地址處去某些特定類型的數據。
2.數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要做兩次內存訪問;而對齊的內存訪問僅僅需要一次訪問。
總結來說:結構體的內存對齊是拿空間換取時間的做法
那在設計結構體的時候,我們既要滿足對齊,又要節省空間如何做到呢“
讓占用空間小的成員盡量集中在一起。
struct A
{char c1;char c2;int n;
};
struct B
{char c1;int n;char c2;
};
int main()
{printf("%zd\n",sizeof(struct A));printf("%zd\n",sizeof(struct B));return 0;
}
修改默認對齊數
? ? ? ? #pragma這個預處理指令可以改變編譯器默認對齊數量。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#pragma pack(1)//設置結構體默認對齊數為1
struct S
{char c1;int n;char c2;
};
#pragma pack()//恢復默認對齊數int main()
{printf("%d\n",sizeof(struct S));return 0;
}
結構體傳參
?????????
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4,5} ,1000 };
//結構體傳參
void print1(struct S s)
{printf("%d\n",s.num);
}
void print2(struct S *ps)
{printf("%p\n",ps->num);
}
int main()
{print1(s );print2(&s);return 0;
}
? ? ? ? 上面的print1和print 2哪個更好呢?答案是print2
原因:
函數傳參的時候,參數是需要壓棧的,會有時間和空間上系統的開銷。
如果傳遞一個結構體對象時,結構體體積過大,參數壓棧的系統開銷過大,會導致性能下降。
結論:結構體傳參的時候,要穿結構體的地址
結構體實現位段??
? ? ? ? 位段
? ? ? ??位段的聲明和結構必須要類似,有兩個不同。
1.位段的成員必須是int,unsigned int或者signed int 。在C99中位段成員類型也可以是其他類型
2.位段的成員名之后有一個冒號和一個數字
#include<stdio.h>
//結構體
struct A
{int a;int b;int c;int d;
};
//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 5;
};
int main()
{printf("%zd\n", sizeof(struct A));printf("%zd\n", sizeof(struct B));return 0;
}
結構體和位段的大小分別為:?
????????
位段的空間是如何開辟的呢?
位段的內存分配
1.位段的成員可以是:int,unsigned int或者signed int或者char類型
2.位段的空間上按照需要以4個字節和1個字節的方式開辟
3.位段涉及很多不確定因素,位段是不跨平臺的,注重可移植程序的程序避免使用。
//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 30;
};
int main()
{/*printf("%zd\n", sizeof(struct A));*/printf("%zd\n", sizeof(struct B));return 0;
}
1.int位段被當成有符號還是無符號不確定
2.位段最大位的數目不確定(16位機器最大16,32位機器最大32.寫成27的話16 位機器會出問題)
3.位段成員在內存中從左向右分配還是右向左分配是不確定的
4.當一個結構體包含兩個位段的時候,第二個位段成員比較大,無法榮達第一個剩余的位時是舍棄剩余位置還是保留不確定
總結:與結構相比,位段可以達到同樣的效果,更加節省空間不過會有跨平臺的問題。
位段的應用
在網絡協議的IP數據報中,用位段可以更節省空間的達到想要的效果,對網絡暢通很有幫助。
?位段使用的注意事項
? ? ? ??位段的幾個成員共同用一個字節,這樣有i協成員起始位置并不是某個字節起始位置,那么這些位置只是沒有地址的。內存中每個字節分配一個地址,一個字節內部的比特位是沒有地址的。
? ? ? ? 所以不能對位段的成員使用&*操作符,這樣就不能直接使用scanf直接給位段的成員輸入值。只能先輸入放在一個變量里然后賦值給位段的成員
struct B
{int _a:2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct B s = {0};scanf("%d",&sa.b);//這是錯誤的//正確示范int b = 0;scanf("%d",b);sa._b = b;return 0;
}
感謝看到這里的讀者朋友,求一個贊謝謝。