结构体内存对齐:原来我写的程序一直在偷偷浪费内存!
枚举顾名思义是一一列举,在现实生活中比如星期、性别、月份,都可以一一列举出来enum SexMale,Female,Secretenum Colorred,green,blueint main()return 0;
一、结构体
1.1 结构体变量的创建和初始化
struct Book
{
char name[20];
int price;
char author[10];
};
int main()
{
struct Book s1 = { "C语言",18.8,"lishi" };
struct Book s2 = { .name = "python",.price = 19,.author = "zhangsan" };
}
1.2 匿名结构体
唯一用法
struct
{
char name[10];
int age;
}s1 = { "zhangsan",18 };
int main()
{
printf("%d %s", s1.age, s1.name);
return 0;
}
另一个问题(指针)
struct
{
char name[10];
int age;
}s1;
struct
{
char name[10];
int age;
}* p1;
int main()
{
p1 = &s1;
return 0;
}
原因:因为结构体是匿名的,编译器不认识,编译会报错
1.3 自引用
自己包含与自己相关的信息,但是不能直接包含自己类型(sizeof()没法算大小),可以包含指针
比如说链表节点的定义

typedef struct Node
{
int data;
struct Node* p;
}Node;
int main()
{
Node n1;
}
补充:匿名结构体不能自引用
1.4 重命名
正确示例
错误示例
1.5 结构体内存对齐
结构体大小怎么算
需要知道偏移量和对齐数的概念
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为零的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
- 对齐数是编译器默认的对齐数(VS上是8)与该成员变量大小的较小值
- 结构体的总大小是最大对齐数(每一个成员都有对齐数,所以对齐数中最大的)的整数倍


嵌套结构体:
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
1.6 对齐的原理


设计结构体时尽量要让占空间小的成员挨在一起
1.6 修改默认对齐数
#pragma pack(1)
struct Stu
{
char s1;
int a;
char s2;
};
#pragma pack()
int main()
{
printf("%zd", sizeof(struct Stu));
return 0;
}
不适用于Linux底下,在Linux下 对齐数默认是成员自身大小
1.7 结构体传参
struct Stu
{
int age[100];
char name[100];
};
void Print(struct Stu s2)
{
for (int i = 0; i < 3; i++)
{
printf("%d ", s2.age[i]);
}
printf("%s ", s2.name);
}
int main()
{
struct Stu s1 = { .age = {18,19,21},.name = "zhangsan" };
Print(s1);
return 0;
}
但这样会导致形参和实参占用空间,传数据也耗时间
而传地址恰恰可以解决这个问题(压栈压的是一个指针变量的大小4/8)
struct Stu
{
int age[100];
char name[100];
};
void Print(struct Stu *p)
{
for (int i = 0; i < 3; i++)
{
printf("%d ", p->age[i]);
}
printf("%s ", p->name);
}
int main()
{
struct Stu s1 = { .age = {18,19,21},.name = "zhangsan" };
Print(&s1);
return 0;
}
结论:
结构体穿时,尽量传结构体的地址
传地址可能会有被改掉的风险,在前面加上形参前加上const就行了
1.8结构体实现位段
- 位段的成员可以是intunsignedintsignedint或者是char等类型
- 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

位段的内存分配:
打开内存进行验证:

位段的应用场景:
使用位段的注意事项:
内存中每个字节分配一个地址,由内存分配知,内部的成员的起始位置不是字节的起始位置,字节内部的bit位是没有地址的
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

二、联合体
2.1 联合体类型特点
union S
{
char a;
int b;
};
int main()
{
union S s1;
printf("%zd", sizeof(s1));
return 0;
}
联合体成员是共用一
块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能保存最大的那个成员)
因此改一个,全都改掉了(先改低地址处的地址)
联合体一定是最大成员的大小吗?这句话一定正确吗?
答案是:不一定
- 联合体的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

2.2 应用场景

设计时如果用结构体的话,会浪费空间
可以用联合体来优化
2.3 大小端问题
int Check()
{
union Un
{
char a;
int b;
}s;
s.b = 1;
return s.a;
}
int main()
{
int ret = Check();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
三、枚举类型
3.1 介绍
枚举顾名思义是一一列举,在现实生活中比如星期、性别、月份,都可以一一列举出来
enum Sex
{
Male,
Female,
Secret
};
enum Color
{
red,
green,
blue
};
int main()
{
enum Color col = red;
return 0;
}

3.2 枚举类型的优点
可以使用#define,为啥要使用枚举呢?

- 可读性和维护性增强,可以让人联想到
比如之前写过的计算器
- 枚举是有类型的,而#define是没有类型可言的(直接替换掉常量)
- 便于调试,预处理阶段会删除#define定义的符号
- 使用方便,一次可以定义多个常量
- 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用
更多推荐



所有评论(0)