1.结构体类型的声明
C语言提供了char、short、int、long等内置类型,但这些内置类型是不够用的,如我想要描述一本书,想要描述一本书单一的内置类型是不够用的,描述这本书需要作者、出版社、定价等。因此,C语言为了解决这一问题,就引入了结构体这种自定义的数据类型。
1.1结构的声明
struct tag
{
member - list;
}variable-list;
如描述一个学生:
struct student
{
char name[20];//名字
int age;//年龄
int sex[5]//性别
};//结尾分号不能少
1.2结构体变量的定义和初始化
//变量的定义
struct Point
{
int x;
int y;
}p1;//声明类型的同时定义变量p1
//初始化
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
//按照结构体成员的顺序进行初始化
struct Stu s = { "张三",20,"男","20230815648" };
printf("name:%s\n", s.name);
printf("age:%d\n", s.age);
printf("sex:%s\n", s.sex);
printf("id:%s\n", s.id);
printf("\n");
//按照指定的顺序初始化
struct Stu s2 = { .age = 18,.name = "李四",.id = "21015120",.sex = "男" };
printf("name:%s\n", s2.name);
printf("age:%d\n", s2.age);
printf("sex:%s\n", s2.sex);
printf("id:%s\n", s2.id);
}
1.3结构的特殊声明
在声明结构的时候,可以不完全声明
如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
上面两个结构在声明时省略了结构体的标签(tag)
注意:编译器会把上面的两个声明当成完全不相同的两个类型,故非法。
匿名的结构体类型,如果没有对结构体类型进行重命名的话,基本上只能使用一次。
2.结构成员访问操作符
2.1结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。如:
struct Point
{
int x;
int y;
}p = { 1,2 };
int main()
{
printf("x=%d y=%d\n", p.x, p.y);
return 0;
}
输出结果如下:
故使用方式:结构体变量.成员名
2.2结构体成员的间接访问
如果我们得到的不是一个结构体变量,而是得到一个指向结构体的指针,则我们就要用到结构体成员的间接访问。如下:
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = { 1,2 };
struct Point* p1 = &p;
printf("x=%d y=%d\n", p1->x, p1->y);
p1->x = 10;
p1->y = 20;
printf("x=%d y=%d\n", p1->x, p1->y);
return 0;
}
输出结果如下:
使用方式:结构体指针->成员名
3.结构的自引用
错误示范:
struct po
{
int a;
struct po next;
};
在上述代码中,由于一个结构体再包含一个同类型的结构体变量,这样的结构体变量的大小是无穷大的,不合理。
正确的自引用方式:
struct po
{
int a;
struct po* next;
};
需要用指针进行结构体的自引用。
4.结构体内存对齐
结构体的内存对齐就是计算结构体的大小。
4.1对齐规则
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员变量大小的较小值
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
4.对于嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例:
struct s1
{
char c1; //1字节
int i; //4字节
char c2; //1字节
};
int main()
{
printf("%zd\n", sizeof(struct s1));
return 0;
}
输出结果:
首先,根据对齐规则第一条我们可以知道,结构体第一个成员的起始位置是偏移量为0的地址处 ,故第一个成员char c1放在偏移量为0的地址处。根据对齐规则第二条我们可以知道,其他成员变量要对齐到对齐数的整数倍的地址处,且对齐数=编译器默认的一个对齐数与该成员变量大小的较小值,由于vs中默认的对齐数是8,第二个成员的大小为4,小于8,因此对齐数是4,故第二个成员起始位置偏移量为4的倍数的地址处。由于第三个成员的对齐数是1,放在1的倍数处,故可以直接放在第二个成员的后面。因此这三个变量在内存中的存放如图:
我们可以看到,内存只占用了9个字节,但输出结果却是12。这是因为结构体总大小为最大对齐数的整数倍。在这个结构体中三个变量的对齐数依次是1、4、1,故最大对齐数是4,因此该结构体的大小是4的整数倍12。
结构体的内存对齐是拿空间来换取时间的做法。
4.2修改默认对齐数
在编译器中,默认的对齐数是可以通过#pragma这个指令进行修改。
代码:
#pragma pack(1)//设置默认对齐数为1
struct s
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd\n", sizeof(struct s));
return 0;
}
输出结构:
我们可以发现,改代码与上述例题相同,但输出的结构却不同,这是由于默认的对齐数被修改的缘故。当使用#pragma pack()这一语句时,可以取消设置的对齐数,还原为默认。
5.结构体实现位段
5.1什么是位段
1.位段的成员必须是int、unsigned int、signed int,在C99中位段的成员的类型也可以选择其他类型(常用的是char)
2.位段的成员名后面由一个冒号和一个数字
如:
struct A
{
int a : 2; //后面的数字表示占多大比特位
int b : 5;
int c : 10;
int d : 30;
};
int main()
{
printf("%zd\n", sizeof(struct A));
}
输出结果:
5.2位段的内存分配
1.位段的成员可以是int、unsigned int、signed int、char等类型
2.位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
3.位段涉及很多不确定的因素,位段是不跨平台的,注重可移植的程序应避免使用位段