1、結(jié)構(gòu)體
1.1 結(jié)構(gòu)體基礎(chǔ)知識
1.1.1 結(jié)構(gòu)體類型的定義
struct?Person{
?char?name[64];
?int?age;
};
typedef?struct?_PERSON{
?char?name[64];
?int?age;
}Person;
注意:定義結(jié)構(gòu)體類型時不要直接給成員賦值,結(jié)構(gòu)體只是一個類型,編譯器還沒有為其分配空間,只有根據(jù)其類型定義變量時,才分配空間,有空間后才能賦值。
10W+字C語言硬核總結(jié)(一),值得閱讀收藏!
10W+字C語言硬核總結(jié)(二),值得閱讀收藏!
10W+字C語言硬核總結(jié)(三),值得閱讀收藏!
熬夜整理的C/C++萬字總結(jié)(五),文件操作
程序員必備硬核資源,點擊下載!
程序員書籍資源,值得收藏!
1.1.2 結(jié)構(gòu)體變量的定義
struct?Person{
?char?name[64];
?int?age;
}p1;?//定義類型同時定義變量
struct{
?char?name[64];
?int?age;
}p2;?//定義類型同時定義變量
struct?Person?p3;?//通過類型直接定義
1.1.3 結(jié)構(gòu)體變量的初始化
struct?Person{
?char?name[64];
?int?age;
}p1?=?{"john",10};?//定義類型同時初始化變量
struct{
?char?name[64];
?int?age;
}p2?=?{"Obama",30};?//定義類型同時初始化變量
struct?Person?p3?=?{"Edward",33};?//通過類型直接定義
1.1.4 結(jié)構(gòu)體成員的使用
struct?Person{
?char?name[64];
?int?age;
};
void?test(){
?//在棧上分配空間
?struct?Person?p1;
?strcpy(p1.name,?"John");
?p1.age?=?30;
?//如果是普通變量,通過點運算符操作結(jié)構(gòu)體成員
?printf("Name:%s?Age:%d\n",?p1.name,?p1.age);
?//在堆上分配空間
?struct?Person*?p2?=?(struct?Person*)malloc(sizeof(struct?Person));
?strcpy(p2->name,?"Obama");
?p2->age?=?33;
?//如果是指針變量,通過->操作結(jié)構(gòu)體成員
?printf("Name:%s?Age:%d\n",?p2->name,?p2->age);
}
1.1.5 結(jié)構(gòu)體賦值
1.1.5.1 賦值基本概念
相同的兩個結(jié)構(gòu)體變量可以相互賦值,把一個結(jié)構(gòu)體變量的值拷貝給另一個結(jié)構(gòu)體,這兩個變量還是兩個獨立的變量。
struct?Person{
?char?name[64];
?int?age;
};
void?test(){
?//在棧上分配空間
?struct?Person?p1?=?{?"John"?,?30};
?struct?Person?p2?=?{?"Obama",?33?};
?printf("Name:%s?Age:%d\n",?p1.name,?p1.age);
?printf("Name:%s?Age:%d\n",?p2.name,?p2.age);
?//將p2的值賦值給p1
?p1?=?p2;
?printf("Name:%s?Age:%d\n",?p1.name,?p1.age);
?printf("Name:%s?Age:%d\n",?p2.name,?p2.age);
}
1.1.5.1 深拷貝和淺拷貝
//一個老師有N個學生
typedef?struct?_TEACHER{
?char*?name;
}Teacher;
void?test(){
?
?Teacher?t1;
?t1.name?=?malloc(64);
?strcpy(t1.name?,?"John");
?Teacher?t2;
?t2?=?t1;
?//對手動開辟的內(nèi)存,需要手動拷貝
?t2.name?=?malloc(64);
?strcpy(t2.name,?t1.name);
?if?(t1.name?!=?NULL){
??free(t1.name);
??t1.name?=?NULL;
?}
?if?(t2.name?!=?NULL){
??free(t2.name);
??t1.name?=?NULL;
?}
}
1.1.6 結(jié)構(gòu)體數(shù)組
struct?Person{
?char?name[64];
?int?age;
};
void?test(){
?//在棧上分配空間
?struct?Person?p1[3]?=?{
??{?"John",?30?},
??{?"Obama",?33?},
??{?"Edward",?25}
?};
?struct?Person?p2[3]?=?{?"John",?30,?"Obama",?33,?"Edward",?25?};
?for?(int?i?=?0;?i?<?3;i?++){
??printf("Name:%s?Age:%d\n",p1[i].name,p1[i].age);
?}
?printf("-----------------\n");
?for?(int?i?=?0;?i?<?3;?i++){
??printf("Name:%s?Age:%d\n",?p2[i].name,?p2[i].age);
?}
?printf("-----------------\n");
?//在堆上分配結(jié)構(gòu)體數(shù)組
?struct?Person*?p3?=?(struct?Person*)malloc(sizeof(struct?Person)?*?3);
?for?(int?i?=?0;?i?<?3;i++){
??sprintf(p3[i].name,?"Name_%d",?i?+?1);
??p3[i].age?=?20?+?i;
?}
?for?(int?i?=?0;?i?<?3;?i++){
??printf("Name:%s?Age:%d\n",?p3[i].name,?p3[i].age);
?}
}
1.2 結(jié)構(gòu)體嵌套指針
1.2.1 結(jié)構(gòu)體嵌套一級指針
struct?Person{
?char*?name;
?int?age;
};
void?allocate_memory(struct?Person**?person){
?if?(person?==?NULL){
??return;
?}
?struct?Person*?temp?=?(struct?Person*)malloc(sizeof(struct?Person));
?if?(temp?==?NULL){
??return;
?}
?//給name指針分配內(nèi)存
?temp->name?=?(char*)malloc(sizeof(char)*?64);
?strcpy(temp->name,?"John");
?temp->age?=?100;
?*person?=?temp;
}
void?print_person(struct?Person*?person){
?printf("Name:%s?Age:%d\n",person->name,person->age);
}
void?free_memory(struct?Person**?person){
?if?(person?==?NULL){
??return;
?}
?struct?Person*?temp?=?*person;
?if?(temp->name?!=?NULL){
??free(temp->name);
??temp->name?=?NULL;
?}
?free(temp);
}
void?test(){
?
?struct?Person*?p?=?NULL;
?allocate_memory(&p);
?print_person(p);
?free_memory(&p);
}
1.2.2 結(jié)構(gòu)體嵌套二級指針
//一個老師有N個學生
typedef?struct?_TEACHER{
?char?name[64];
?char**?students;
}Teacher;
void?create_teacher(Teacher**?teacher,int?n,int?m){
?if?(teacher?==?NULL){
??return;
?}
?//創(chuàng)建老師數(shù)組
?Teacher*?teachers?=?(Teacher*)malloc(sizeof(Teacher)*?n);
?if?(teachers?==?NULL){
??return;
?}
?//給每一個老師分配學生
?int?num?=?0;
?for?(int?i?=?0;?i?<?n;?i?++){
??sprintf(teachers[i].name,?"老師_%d",?i?+?1);
??teachers[i].students?=?(char**)malloc(sizeof(char*)?*?m);
??for?(int?j?=?0;?j?<?m;j++){
???teachers[i].students[j]?=?malloc(64);
???sprintf(teachers[i].students[j],?"學生_%d",?num?+?1);
???num++;
??}
?}
?*teacher?=?teachers;?
}
void?print_teacher(Teacher*?teacher,int?n,int?m){
?for?(int?i?=?0;?i?<?n;?i?++){
??printf("%s:\n",?teacher[i].name);
??for?(int?j?=?0;?j?<?m;j++){
???printf("??%s",teacher[i].students[j]);
??}
??printf("\n");
?}
}
void?free_memory(Teacher**?teacher,int?n,int?m){
?if?(teacher?==?NULL){
??return;
?}
?Teacher*?temp?=?*teacher;
?for?(int?i?=?0;?i?<?n;?i?++){
??
??for?(int?j?=?0;?j?<?m;j?++){
???free(temp[i].students[j]);
???temp[i].students[j]?=?NULL;
??}
??free(temp[i].students);
??temp[i].students?=?NULL;
?}
?free(temp);
}
void?test(){
?
?Teacher*?p?=?NULL;
?create_teacher(&p,2,3);
?print_teacher(p,?2,?3);
?free_memory(&p,2,3);
}
1.3 結(jié)構(gòu)體成員偏移量
//一旦結(jié)構(gòu)體定義下來,則結(jié)構(gòu)體中的成員內(nèi)存布局就定下了
#include?<stddef.h>
struct?Teacher
{
?char?a;
?int?b;
};
void?test01(){
?struct?Teacher??t1;
?struct?Teacher*p?=?&t1;
?int?offsize1?=?(int)&(p->b)?-?(int)p;??//成員b?相對于結(jié)構(gòu)體?Teacher的偏移量
?int?offsize2?=?offsetof(struct?Teacher,?b);
?printf("offsize1:%d?\n",?offsize1);?//打印b屬性對于首地址的偏移量
?printf("offsize2:%d?\n",?offsize2);
}
1.4 結(jié)構(gòu)體字節(jié)對齊
程序員必備硬核資源,點擊下載!
在用 sizeof 運算符求算某結(jié)構(gòu)體所占空間時,并不是簡單地將結(jié)構(gòu)體中所有元素各自占的空間相加,這里涉及到內(nèi)存字節(jié)對齊的問題。
從理論上講,對于任何變量的訪問都可以從任何地址開始訪問,但是事實上不是如此,實際上訪問特定類型的變量只能在特定的地址訪問,這就需要各個變量在空間上按一定的規(guī)則排列, 而不是簡單地順序排列,這就是內(nèi)存對齊。
1.4.1.1 內(nèi)存對齊原因
我們知道內(nèi)存的最小單元是一個字節(jié),當 cpu 從內(nèi)存中讀取數(shù)據(jù)的時候,是一個一個字節(jié)讀取,所以內(nèi)存對我們應該是入下圖這樣:

但是實際上 cpu 將內(nèi)存當成多個塊,每次從內(nèi)存中讀取一個塊,這個塊的大小可能是2、4、8、16等,
那么下面,我們來分析下非內(nèi)存對齊和內(nèi)存對齊的優(yōu)缺點在哪?
內(nèi)存對齊是操作系統(tǒng)為了提高訪問內(nèi)存的策略。操作系統(tǒng)在訪問內(nèi)存的時候,每次讀取一定長度(這個長度是操作系統(tǒng)默認的對齊數(shù),或者默認對齊數(shù)的整數(shù)倍)。如果沒有對齊,為了訪問一個變量可能產(chǎn)生二次訪問。
至此大家應該能夠簡單明白,為什么要簡單內(nèi)存對齊?
1.4.1.1 如何內(nèi)存對齊
-
數(shù)組成員對齊規(guī)則。第一個數(shù)組成員應該放在offset為0的地方,以后每個數(shù)組成員應該放在offset為min(當前成員的大小,#pargama pack(n))整數(shù)倍的地方開始(比如int在32位機器為4字節(jié),#pargama pack(2),那么從2的倍數(shù)地方開始存儲)。
-
結(jié)構(gòu)體總的大小,也就是sizeof的結(jié)果,必須是min(結(jié)構(gòu)體內(nèi)部最大成員,#pargama pack(n))的整數(shù)倍,不足要補齊。
-
結(jié)構(gòu)體做為成員的對齊規(guī)則。如果一個結(jié)構(gòu)體B里嵌套另一個結(jié)構(gòu)體A,還是以最大成員類型的大小對齊,但是結(jié)構(gòu)體A的起點為A內(nèi)部最大成員的整數(shù)倍的地方。(struct B里存有struct A,A里有char,int,double等成員,那A應該從8的整數(shù)倍開始存儲。),結(jié)構(gòu)體A中的成員的對齊規(guī)則仍滿足原則1、原則2。
手動設(shè)置對齊模數(shù):
顯示當前packing alignment的字節(jié)數(shù),以warning message的形式被顯示。
將當前指定的packing alignment數(shù)組進行壓棧操作,這里的棧是the internal compiler stack,同事設(shè)置當前的packing alignment為n;如果n沒有指定,則將當前的packing alignment數(shù)組壓棧。
從internal compiler stack中刪除最頂端的reaord; 如果沒有指定n,則當前棧頂record即為新的packing alignement數(shù)值;如果指定了n,則n成為新的packing alignment值
指定packing的數(shù)值,以字節(jié)為單位,缺省數(shù)值是8,合法的數(shù)值分別是1,2,4,8,16。
程序員必備硬核資源,點擊下載!???????
1.4.2 內(nèi)存對齊案例
#pragma?pack(4)
typedef?struct?_STUDENT{
?int?a;
?char?b;
?double?c;
?float?d;
}Student;
typedef?struct?_STUDENT2{
?char?a;
?Student?b;?
?double?c;
}Student2;
void?test01(){
?//Student
?//a從偏移量0位置開始存儲
?//b從4位置開始存儲
?//c從8位置開始存儲
?//d從12位置開存儲
?//所以Student內(nèi)部對齊之后的大小為20?,整體對齊,整體為最大類型的整數(shù)倍?也就是8的整數(shù)倍?為24
?printf("sizeof?Student:%d\n",sizeof(Student));
?//Student2?
?//a從偏移量為0位置開始?
?//b從偏移量為Student內(nèi)部最大成員整數(shù)倍開始,也就是8開始
?//c從8的整數(shù)倍地方開始,也就是32開始
?//所以結(jié)構(gòu)體Sutdnet2內(nèi)部對齊之后的大小為:40?,?由于結(jié)構(gòu)體中最大成員為8,必須為8的整數(shù)倍?所以大小為40
?printf("sizeof?Student2:%d\n",?sizeof(Student2));
}