一、结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以使不同类型的变量。
struct student //标签Tag,可以省略但是尽量不要省略。 { char name[]; //结构体成员,c语言中不能为空 int age; char sex[]; char id[20]; }list; //变量列表,可以省略。 注意切记不要忘记分号。
struct Stu { char name[20]; int age; }; struct Stu s;//定义结构体变量
struct S s; strcpy(s.name,"zhangsan"); s.age = 20;结构体访问指向变量的成员 有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针,那么则应该这样访问:
struct S { char name[20]; int age; }s; void print(struct S* ps) { printf("name=%s age=%d\n", (*ps).name, (*ps).age); printf("name=%s age=%d\n", ps->name, ps->age); }结构的自引用
结构中包含一个类型为该结构本身的成员。
typedef struct Node { int date; struct Node* next; }Node;
若两个结构体互相包含,且要正常使用,则需要这样:
struct B; struct A { int _a; struct B* pb; }; struct B { int _b; struct A* pa; };结构体变量的定义和初始化
定义的时候有两种方式:
struct Point //1 { int x; int y; }p1; struct Point p2; //2
struct Point p3 = { x, y }; struct Stu //类型声明 { char name[15]; int age; }; struct Stu s = { "zhangsan", 20 }; //初始化
struct Node { int date; struct Point p; struct Node* next; }n1 = { 10, { 4, 5 }, NULL }; struct Node n2 = { 20, { 5, 6 }, NULL };
1.第一个成员在与结构体变量偏移量为0的地址处(所以结构体成员的第一个元素你需要内存对齐,默认对齐);
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数:编译器默认的一个对齐数与该成员大小得到较小值; VS中默认为8 Linux中默认为4
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小为所有最大对齐数的整数倍。
//练习1 struct S1 { char c1; //偏移量为1 int i; //大小为4,所以需要对齐,所以总大小为1+3+4 char c2; //大小为1,不需要对齐 1+3+4+1 }; //所以总大小为最大对齐数的倍数: 12 printf("%d\n", sizeof(struct S1)); //练习2 struct S2 { char c1; char c2; int i; }; //这次的结果是8 只是变换了一下成员的位置,结果就不一样,正好说明了结构体的对齐规则 printf("%d\n", sizeof(struct S2)); //练习3 struct S3 { double d; char c; int i; }; //按照规则,结果为16 printf("%d\n", sizeof(struct S3));
//练习4 struct S4 { char c1; //1+3 struct S3 s3; //由上可知 16 double d; //8 }; //∴ 1+3+16+8=28 但要满足总大小为最大对齐数的倍数 ∴结构体总大小为32 printf("%d\n", sizeof(struct S4));
#pragma pack(4) struct S { char c1; double d; char c2; }; #pragma pack()
位段的声明和结构是类似的,但有两个不同:
①位段的成员必须是int,unsigned int,signed int。
struct S { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; //因为它的单位是bit位,所以它的大小是8
位段的内存分配:
①位段的成员可以是int,unsigned int,signed int或者是char(属于整形家族)类型;
②位段的空间上是按照需要以4个字节(int)或者一个字节(char)的方式来开辟的;
③位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序尽量避免使用位段。
位段的跨平台问题:
①int位段被当成是有符号数还是无符号数是不确定的;
②位段中最大位的数目不能确定;
③位段中的成员在内存中从左行右分配,还是从右向左标准尚未定义;
④当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,也不确定。
总结:位段跟结构相比,可以达到同样的效果,还可以节省空间,但是有跨平台的问题存在。
这里就不介绍枚举了,我们来看看它的优点吧:
①增加代码的可读性和可维护性;
②和#define定义的标识符比较枚举有类型检查,更加严谨;
③防止了命名污染(封装)
④便于调试;
⑤使用方便,一次可以定义多个常量。
①联合的大小至少是最大成员的大小;
②当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1); //可以将long类型的IP地址转化为点分十进制的表示形式。
int check_sys() { union { int i; char c; }un; un.i=1; return un.c }