Bootstrap

【C语言进阶】--- 结构体

比如说你要存一个学生的信息,有它的姓名,年龄,电话号,家庭住址,那我们可以用字符串、整型变量存储就行,要是一个班的学生信息呢?难道给每个信息开辟几个数组存吗?那有没有想过这个数组中的这个元素对应哪个数组中的哪个元素呢,是不是管理起来就比较复杂呀。想要解决这类问题就要引入我们这篇【C语言进阶】— 结构体

一、结构体

1.1 什么是结构体?

结构体其实是一个自定义类型,你可以往这个类型中设置一些成员,举个例子,学生、老师属于一类吗?学生有学生的属性,学生有他自己的名字,年龄,爱好,特点等等,那学生是一个人吗,他们的名字、年龄都一样吗?不是的吧,学生是有很多的,而且他们各自的属性各有不同
由此,我定义了一个学生类型的结构体

struct stu
{
	char name[20];//姓名
	int age;//年龄
	char telephone[20];//手机号
};

struct stu就是我为学生这个类型所定义的类型
语法:struct 类型名 {成员1;成员2;…};

1.2 初始化一个结构体变量

  • struct stu s1 = {“程小k”,18,12345678910};
  • struct stu s2 = {0};
  • struct stu
    {
    char name[20];
    int age;
    char telephone[20];
    }s3 = {“cz”,18,12345678910};
    这里的s3是个全局变量

1.3 一个结构体变量占多少内存呢?(内存对齐)

结构体内存规则如下:

内存对齐的规则:

  1. 结构体中第一个成员永远放在相较于结构体变量起始位置,偏移量为0的位置
  2. 从第二个成员开始,往后的成员都要对齐到某个对齐数的整数倍处
    对齐数:结构体成员自身大小和默认对齐数的较小值
    VS的默认对齐数是8,gcc没有默认对齐数
  3. 当最后一个成员对齐结束后,整个结构体的内存与当前最大对齐数的整数倍有关,如果不够整数倍,需补上
  4. 如果遇到结构体嵌套循环,那么将这个成员结构体的最大对齐数当做对齐数,然后对齐这个对齐数的整数倍

在这里插入图片描述

1.4 结构体传参

通常情况下,结构体传参都是传结构体变量的地址

  • 函数传参的时候是需要压栈的(拷贝实参),会有时间和空间上的系统开销
  • 如果传递的对象过大,参数压栈使系统开销比较大,导致性能下降
  • 那传址进去,害怕对象的值被修改,怎莫办?不用怕,用const修饰形参的指针就行

二、位段

2.1 什么是位段?

位段的声明和结构体类似,有两个不同

  1. 位段的成员必须是整形
  2. 位段的成员后面有一个冒号和一个数字

2.2 位段的定义

struct A
{
	int _a:2;
	int _b:5;
	int _c:10;
	int _d:30;
}

A是一个位段,那A的大小是多少呢?答案是8个字节
位段中成员的数字实际是比特位,2就是_a占用两个比特位
2+5+10+30==47bit,那怎么就8个字节呢,这里不做解释,因为每个编译器可能存储方式会不同,位段写出的代码最好不要跨编译器使用,会出错,这里了解有这么个东西就行

三、枚举

3.1 什么是枚举?

把可能得取值列举出来
比如:一周有7天是固定的,星期一到星期日;性别,有男有女,再多也就那么几种吧

3.2 枚举的定义

enum Day
{
    Mon,
    Tues,
    ...
    Sun
};
默认值赋值从0开始,依次加10,1,2,...
也可以自己赋初值
enum Day d = Mon;

自我感觉还是有点鸡肋,就当了解一下吧,应用的很少,也许是我功夫不够深

四、联合(共用体)

4.1 什么是联合?

union un
{
	int i;
	char c;
};
  • 与结构体的语法格式相似,那么先判断一下这个联合体的内存大小是多少?
  • 是5?还是8?
  • 都不是,其实是4,为什么呢?
  • 联合体就是为了节省内存出现的
  • 里面的变量是共用同一块空间,但是,不能同时使用这块空间
  • 试想一下,只有4个字节,怎么存储5个字节的内容,只有你用的时候我不用,我用的时候你别来用才能做到吧,联合也就是这么个道理

4.2 应用联合

这里来一个应用场景体会一下联合吧
用联合体判断当前系统是用大端还是小端存储方式

union un
{
	int i;
	char c;
};

int main()
{
	union un u1;
	u1.i = 1;
	if (u1.c == 1)
		printf("小端");
	else
		printf("大端");

	return 0;
}

这里就用文字解释吧,首先i和c共用一块4个字节的内存,先将1存入i中,如果是小端存储方式,低位存储在低地址中,那么第一个字节存储的就应该是0000 0001,剩下3个字节存储的数据都是0000 0000,现在用c来访问第一个字节中的内容,如果是小端那就访问到1,要是大端就访问的是0

4.3 联合内存的大小计算

  • 是不是以为联合内部成员谁大就占多少个字节,猜对了一半
  • 要是成员是数组呢
  • 实际上联合的大小至少是最大成员的大小
  • 当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
    举例说明:
union U1
{
	char c[5];
	int i;
}
这里字符数组c占5个字节,i占4个,所以当前联合的内存是5,但是最大对齐数是4,离5最近4的整数倍是8,所以该联合的内存占8个字节
这里最大对齐数是4的原因是,数组的对齐数是数组中的单个成员所占内存大小,字符数组的对齐数是1,i的对齐数是4,所以最大对齐数就是4

五、柔性数组

柔性数组涉及的知识其实与上一节动态内存息息相关,我是先学的结构体后学的动态内存,学结构体那天没时间更新,第二天学习动态内存就先更新的动态内存了,结构体就搁置了几天,结果第三天学柔性数组发现和是动态内存的内容,我想,这不乱了,那就乱点吧,人生中不完美才是常态,反正柔性数组也是结构体的成员。

5.1 什么是柔性数组?

实际上就是结构体中最后一个成员

struct S
{
	int a;
	int arr[0];
}
sizeof(struct S);//4  这里不计算柔性数组的空间,因为没分配
struct S* p = (struct S*)malloc(sizeof(struct S*)+40);// 这里的40是专门给柔性数组分配的空间
struct S* newp = (struct S*)realloc(p,80);//  用realloc函数给其扩容

5.2 为什么要使用柔性数组?

  • 通常情况下结构体的一个成员是数组,初始化结构体后,这个数组的大小就不能变化了,如果想改变数组的大小,其实在结构体成员中定义一个指针,用指针维护数组,这时数组不在与结构体有直接联系,给结构体初始化也不会锁死数组的大小,只需要用动态内存函数给指针指向的地方申请相应的空间就行。初始化结构体malloc一下,给数组开辟空间再malloc一下
  • 那柔性数组能干什么?其实用动态内存函数给结构体变量开辟内存时顺便就可以给柔性数组开辟空间,上面代码已经演示过了。只用malloc一次就能分配好空间
  • 动态内存的开辟,会产生内存碎片,次数越多,碎片越多,会造成空间的浪费,所以尽量减少使用次数,而柔性数组相较于第一种方法可以减少内存碎片,有益于提高访问效率
;