🌇个人主页:平凡的小苏
📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情
🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html
小苏希望大家能从这篇文章中收获到许多,如果大家觉得这篇文章对你有帮助,请给小苏点赞+收藏+评论
目录
1 结构体的声明
1.1 结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.2 结构的声明
struct tag
{
member-list;//成员变量
}variable-list;//全局变量
struct Stu
{
char name[20];
int age;
}s1,s2;//这里的s1和s2是全局变量
//在局部创建变量就是全局变量
注意:如果在结构体前面加入typedef,那么在结构体后面加入的就不是变量了,
意思是将结构体重命名
typedef struct Stu
{
char name[20];
}Stu;//这时候就将struct Stu重命名为Stu了
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
1.3 特殊的声明
在声明结构的时候,可以不完全的声明。
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
上面的两个结构在声明的时候省略掉了结构体标签( tag )。那么问题来了?// 在上面代码的基础上,下面的代码合法吗?p = & x ;警告:编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。
1.4 结构的自引用
//代码2
struct Node
{
int data;
struct Node* next;
};
注意:
// 代码 3typedef struct{int data ;Node * next ;} Node ;// 这样写代码,可行否?答案是不可以的,因为程序从上往下执行,编译器还不知道已经重命名为Node了。// 解决方案:typedef struct Node{int data ;struct Node * next ;} Node ;
1.5 结构体变量的定义和初始化
struct Point
{
int x;
int y;
}p1 = {10, 20};//全局变量初始化
struct Point p2 = {0,0};//全局变量初始化
struct S
{
int num;
char ch;
struct Point p;
float d;
};
int main()
{
struct Point p3 = {1,2};//局部变量初始化
struct S s = { 100, 'w', {2,5}, 3.14f};
struct S s2 = {.d=1.2f, .p.x=3,.p.y=5, .ch = 'q', .num=200};//乱序初始化
printf("%d %c %d %d %f\n", s.num, s.ch, s.p.x, s.p.y, s.d);
printf("%d %c %d %d %f\n", s2.num, s2.ch, s2.p.x, s2.p.y, s2.d);
return 0;
}
1.6 结构体内存对齐
1. 第一个成员在与结构体变量偏移量为0的地址处。2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
- VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
练习例子:
//练习1
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
画图演示:
//练习2:结构体嵌套问题
#include<stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
}
画图演示:
为什么存在内存对齐?
大部分的参考资料都是如是说的:
1. 平台原因 ( 移植原因 ) :不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2. 性能原因 :数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。总体来说:结构体的内存对齐是拿空间来换取时间的做法。那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中在一起。
例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
注:S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
1.7 修改默认对齐数
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
结论:结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
2. 位段
2.1 什么是位段
1.位段的成员必须是 int、unsigned int 或signed int 。2.位段的成员名后边有一个冒号和一个数字。
比如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
注:一个int占32个比特位,而位段意思就是第一个int用了2个比特位,还剩30个,第二个int用5个比特位还剩25个,第三个int用10个比特位,还剩15个,而最后一个直接用一个int大小,,那么剩余15个比特位不够存了,他就会重新开辟4个字节来存放,并不会从剩余的开始存放 。(位段是没有结构体对齐的,不然就会冲突了)
我们来看一个样例:看他在内存中是如何存放的
//VS2019上验证:位段的内存开辟和使用
#include<stdio.h>
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;
printf("%d\n", sizeof(s));
return 0;
}
注:vs中以小端字节序存放的,并且以十六进制存放,所以可以看出它用了三字节的空间
2.3 位段的跨平台问题
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
3. 枚举
- 枚举顾名思义就是一一列举。
- 把可能的取值一一列举。
- 比如我们现实生活中:
- 一周的星期一到星期日是有限的7天,可以一一列举。
- 性别有:男、女、保密,也可以一一列举。
- 月份有12个月,也可以一一列举
- 这里就可以使用枚举了。
3.1 枚举类型的定义
例如:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。{} 中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从 0 开始,一次递增 1 ,当然在定义的时候也可以赋初值。
例如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
3.2 枚举的优点
注意:define编译器在预处理阶段就替换掉了define定义的常量,当程序运行起来编译的时候,define已经被替换了,所以想要调试是不能调试define定义的常量的。这里不作详细介绍,小编会在预处理那篇文章做详细讲解
4. 联合(共用体)
4.1 联合类型的定义
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
注:从这里可以看出,联合体的成员是共用一块内存的,共用的是联合体成员中占字节最大的一个。所以计算联合体的大小是4.(注意:联合体也是有内存对齐的)。
4.2 联合的特点
#include<stdio.h>
union Un
{
int i;
char c;
};
union Un un;
int main()
{
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
从这里可以看出:联合体的成员共用一块内存的话,那么它的成员的起始地址是一样的。并且un.i存入的是11223344,un.c存入的是55,因为它共用一块空间,那么当我们以%x打印的时候,打印出来的自然就是11223355了。
4.3 联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
- 比如:
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
注:根据联合体共用的性质和内存对齐的性质,算出来的自然就是8和16了。
好了小编的分享到这里就结束了,如果有什么不足的地方请大佬多多指教!!!