1.结构体类型的声明
1.1结构的声明
struct tag
{
member-list;
}variable-list;
例如描述一个学生的信息
struct Stu
{
char name[20];//姓名
char sex[3];//性别
int age;//年龄
int id[20];//学号
float score;//分数
}s1,s2,s3;//创建了3个变量,也可以没有,注意分号不能省略
1.1.1 结构体变量的创建和初始化
1.1.2结构特成员访问操作符
结构体成员的直接访问
#include <stdio.h>
struct Point
{
int x;
int y;
}p = {1,2};
int main()
{
printf("x: %d y: %d\n", p.x, p.y);
return 0;
}
#include <stdio.h>
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = {3, 4};
struct Point *ptr = &p;
ptr->x = 10;
ptr->y = 20;
printf("x = %d y = %d\n", ptr->x, ptr->y);
return 0;
}
1.2结构体的特殊声明
在声明结构的时候,可以不完全的声明。 比如
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}* p;
int main()
{
p = &x;
return 0;
}
上面的结构体没有名称,上图所示的 p=&x 其实是违法的,编译器会报警告
所以
1.3结构体的自引用
⽐如,定义⼀个链表的节点:
struct Node
{
int data;
struct Node next;
};
这样的代码是不正确的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大
struct Node
{
int data;
struct Node* next;
};
typedef struct
{
int data;
Node* next;
}Node;
typedef struct Node
{
int data;
struct Node* next;
}Node;
2.结构体内存对齐
2.1对齐规则
首先我们先来学习一下结构体的对齐规则,方便我们计算结构体的大小
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
//练习1
struct S1
{
char c1;
int i;
char c2;
};
//练习2
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%zd\n", sizeof(struct S1));
printf("%zd\n", sizeof(struct S2));
return 0;
}
各位小伙伴们猜对了吗?没猜对的话,可以跟着我一起分析。我将用画图的方式给大家解释
这样解释的话,大家应该能够清楚了,我们再做几个练习加深印象
//练习3
struct S3
{
double d;
char c;
int i;
}
接下来我们做一个结构体嵌套的练习(规则4)
//练习4
struct S4
{
char c1;
struct S3 s3;
double d;
};
各位小伙伴只要知道第一个成员从偏移量为0的地方开始往下走就能算出结构体的大小了
如果小伙伴们不相信的话,我们可以测试一下,C语言中有个宏,叫做offestof,它可以计算结构体成员相较于起始位置的偏移量,如下图所示
struct S1
{
char c1;
int i;
char c2;
};
#include<stddef.h>
int main()
{
printf("%zd\n", offsetof(struct S1, c1));
printf("%zd\n", offsetof(struct S1, i));
printf("%zd\n", offsetof(struct S1, c2));
return 0;
}
offsetof函数第一个参数是结构体类型,第二个参数是成员名,要引用头文件#include<stddef.h>
2.2 为什么要内存对齐
2.3修改默认对齐数
#pragma pack(1)//设置默认对齐数为1,参数尽量设置为2的次方数
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
3.结构体传参
struct S
{
int data[1000];
int num;
};
//结构体传参
void print1(struct S t)
{
printf("%d %d\n", t.data[0], t.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d %d\n", ps->data[0], ps->num);
}
int main()
{
struct S s = { {1,2,3,4,5},100 };
print1(s);
print2(&s);
return 0;
}
在上面的2个传参中,print2要比print1好。
4.结构体实现位段
4.1什么是位段
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
A就是一个位段类型。那位段A所占内存的大小是多少?
冒号后面的数字指的是所占的比特位,2+5+10+30=47个比特位,1个整形是4个字节,32个比特位,小于47,所以需要2个整形,8个字节。
因此,位段的出现就是为了节省空间的。要使用位段,一定是有特殊场景的,如下举例
4.2位段的内存分配
struct S
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
struct S s = { 0 };
s._a = 10;
s._b = 12;
s._c = 3;
s._d = 4;
return 0;
}
调试一下看看
由此可以断定vs2019就是从右向左使用的,假设正确。
4.3位段的跨平台问题
4.4位段使用的注意事项
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}
好了,终于写完了,感谢大家的观看,自己整理的还是有些不足之处,有问题的话欢迎在评论区留言我们一起探讨。