一、結構體
結構體是一種數據類型,它的形式是這樣的:
struct 結構體名
{????????
? ? ? ? 結構體成員語句1;
? ? ? ? 結構體成員語句2;
? ? ? ? 結構體成員語句3;
};
舉個例子:
struct Student
{int id;char name[20];float score;
};
我們創建了一個數據類型Student,在這個聲明里面,創建了三個變量: int型 id? ,? ?字符數組name[20] ,float型score;在聲明的花括號? {? }? 里面,要注意添加分號 “ ; ”,不然系統編譯時會報錯。
在主函數使用這個數據類型的時候:
struct Student
{int id;char name[20];float score;
};int main(void)
{s.id = 1;strcpy(s.name, "zhangsan");s.score = 97.5;printf("%d, %s, %f,%d-%d-%d \n", s.id, srname, s.score);return 0;
}
我們在主函數里定義了一個變量s,t它的數據類型Student,在給它賦值的時候,要按如上方式,依次賦值;
在使用結構體這種數據類型時,可以進行結構體嵌套:
struct Student
{int id;char name[20];float score;struct Date birthday;
};int main(void)
{s.id = 1;strcpy(s.name, "zhangsan");s.score = 97.5;s.birthday.year = 2002;s.birthday.month = 3;s.birthday.day = 12;printf("%d, %s, %f,%d-%d-%d \n", s.id, srname, s.score, s.birthday.year, s.birthday.month, s.birthday.day);return 0;
結構體初始化
在進行設定變量s時,可以直接進行初始化:
struct Student
{int id;char name[20];float score;struct Date birthday;
};int main(void)
{struct Student s = {1, "zhangsan", 97.5, {2002, 3, 12}};printf("%d, %s, %f,%d-%d-%d \n", s.id, srname, s.score, s.birthday.year, s.birthday.month, s.birthday.day);return 0;
結構體嵌套的部分用花括號 { } 隔開;
也可以對部分結構體成員進行初始化:
struct Student
{int id;char name[20];float score;struct Date birthday;
};int main(void)
{struct Student s = {.id = 1,.score = 97.5,.birthday = {.year = 2002,}};
}
其余未初始化的結構體成員變量為0;
為了在調用函數的時候,改變主調函數里struct 里結構體成員的值,我們可以通過指針來進行傳參:
void printStudent(struct Student *p)
{printf("%d, %s, %f\n", p->id, p->name, p->score);
}
int main(void)
{struct Student a[3] = {{1, "zhangsan", 97.5},{2, "lisi", 98},{3, "wanghu", 95}};int len = sizeof(a) / sizeof(*a);printStudents(a, len);return 0;
}
需要注意的是,結構體成員指針不能寫為(*p).id 或(*p) . name等,正確的編程規范應該為:p -> id
p -> name的格式;
使用指針傳遞的方式,占用字節較少,cpu運行速率較快;而普通的變量值傳遞會消耗很多空間,所以我們基本不用。
結構體數組
void printStudent(struct Student *p)
{printf("%d, %s, %f\n", p->id, p->name, p->score);
}void printStudents(struct Student a[], int len)
{int i;for(i = 0;i < len;++i){printStudent(a + i);}
}
int main(void)
{struct Student a[3] = {{1, "zhangsan", 97.5},{2, "lisi", 98},{3, "wanghu", 95}};int len = sizeof(a) / sizeof(*a);printStudents(a, len);return 0;
}
結構體成員按從前往后的順序存儲到數組里面;
同時,結構體數組元素之間是不能進行比較的,如a[0]和a[1],但是a[0].id 與 a[1].id之間是可以比較的。
結構體字節大小
在計算結構體大小前,我們要先厘清一個概念:
內存對齊:
在存儲數據的時候,為了防止cpu為了訪問一個數據,要讀取兩次內存空間的情況發生,在存儲數據的時候,偏移量要可以整除存儲的數據類型大小;
為了清晰易懂,請大家記住這三條準則,就可以又快又準確的計算出結構體所占的字節大小:
1.默認按CPU位數對齊(64位系統按8字節對齊),即最終地址為8的整數倍。
2.找結構體里最長字節的成員,以它的字節大小為基準對齊。
3.按照結構體的聲明順序,依次將成員保存在結構體內存中,最終保存的偏移量 / sizeof(成員) == 0。
如果是數組的話,就按其基類型的字節長度進行對齊;舉個例子:
struct Demo
{char c;short d;int i ;
};int main(void)
{struct Demo s;printf("%d\n",sizeof(s));
}
sizeof (s) = 8字節;
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
char? c | short? d | int i |
上圖分別為存儲變量的首地址,其中int 型變量占4個字節,加上前面的一共占8個字節;
這種方法勢必會浪費一些空間,但是這種方式可以換取CPU的讀寫速率。
共用體
舉個例子:
union Demo
{int i;short s;char c;
};
這是共用一段內存空間的數據類型,其共用體成員語句的內存空間相互彼此覆蓋,打印值的話,系統會打印出最后一個覆蓋的成員變量的值。
需要注意的是,共用體所有成員的地址都頂頭寫,我們可以利用這種特性來判斷系統存儲的方式是打斷還是小端:
union Demo
{int i;short s;char c;
};int main(void)
{union Demo d;d.i = 1;union Demo *p;p = &d;fn(&d);if(d.c == 1){puts("little");}else{puts("big");}
枚舉類型
這是一種自定義數據類型,并設定取值范圍的數據類型:
enum Week
{Sun, Mon, Tue , Wes, Thu, Fri, Sat
};int main(void)
{enum Week w;w = Sun;printf("%d\n", w + 1);
在聲明語句的時候,寫入week所有的可能性,在調用時,使用這種數據類型的變量在枚舉常量里任取其一:其實枚舉常量里列舉的取值可能性,實際上就是給整型常量0,1,2,3……等依次賦值;如果重置第一個常量Sun 賦值為2時,接下來的常量會依次賦為3,4,5,6……,如果從Mon開始重置賦值的話,Sun的值為0;
由此可以看出,它和整型是兼容的。
typedef定義類型
這種數據類型的實際上是給已有的數據類型換一個名字:
typedef int INT;typedef struct Demo
{int i;short s;char c;
}Demo, *PDEMO;typedef void (*pfn)(void);
typedef pfn ARRAY[10];int main(void)
{ARRAY a;INT s;printf("%lu\n" ,sizeof(a));return 0;
}
在主函數里,s是int型變量;a是一個由函數指針構成的元素個數為10的數組,其sizeof(a)= 80