C语言-关于自定义类型

目录

前言:

结构体

结构体的初始化

 结构体内存对齐

为什么会存在内存对齐呢?

修改默认对齐数

枚举

枚举的优点

改变枚举的初始值

联合(共用体)

联合类型的定义和特点

联合大小的计算

 结尾


前言:

C语言中的内置有char、shor、int、float、double等。

C语言也允许自己创建一些类型,这就是我们要说的自定义类型了。

自定义类型有:结构体枚举联合体

有了这些自定义类型,我们在使用C语言就更加方便,高效了。

  • 结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

下面我们可以定义一个学生的类型,比如他的名字,年龄,性别,学号。

//定义学生类型
struct Stu
{
	//成员变量
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};

当然我们还可以通过结构体的类型来创建变量。就拿上边的代码来创建就好了。

我们也可以在结构体最后分号的前边来定义变量,不过在这里定义的变量是全局变量

如果是在大括号内部定义的结构体变量就是局部变量了。

//定义学生类型
struct Stu
{
	//成员变量
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s4, s5, s6;//全局的结构体变量


int main()
{
	//局部的结构体变量
	struct Stu s1;
	struct Stu s2;
	struct Stu s3;
	return 0;
}
  • 结构体的初始化

当我们有了结构体后,我们就可以对结构体的变量进行初始化了。

就拿上边学生的成员变量来进行初始化好了。

初始化可以根据成员变量的顺序来,当然也可以不根据顺序来。

先来看一下顺序的吧。

//定义学生类型
struct Stu
{
	//成员变量
	char name[20];//名字
	int age;//年龄
	char sex[10];//性别
	char id[20];//学号
};


int main()
{
	//局部的结构体变量初始化
	struct Stu s1 = { "张三", 18, "男", "1232323"};
	printf("%s %d %s %s\n", s1.name, s1.age, s1.sex, s1.id);
	return 0;
}

printf打印可以根据顺序来,也可以不用跟着顺序。上边的是根据顺序来的。可以看一下打印的结果。

 不按照顺序的初始化

//定义学生类型
struct Stu
{
	//成员变量
	char name[20];//名字
	int age;//年龄
	char sex[10];//性别
	char id[20];//学号
};


int main()
{
	//局部的结构体变量初始化
	struct Stu s1 = { .sex = "男", .age = 18, .name = "李四", .id = "233233"};
	printf("%s %d %s %s\n", s1.sex, s1.age, s1.name, s1.id);
	return 0;
}

结果:


  •  结构体内存对齐

当我们了解完结构体的基本情况之后,我们再来看看计算结构体的大小。

我们先来定义三个结构体,然后分别计算他们的大小。

struct s1
{
	char s1a;
	int s1b;
};

struct s2
{
	char s2a;
	int s2b;
	char s2c;
};

struct s3
{
	char s3a;
	int s3b;
	char s3c;
	char s3d;
};


int main()
{
	//打印结构体大小
	printf("%d\n", sizeof(struct s1));
	printf("%d\n", sizeof(struct s2));
	printf("%d\n", sizeof(struct s3));
	return 0;
}

看完代码,我们根据结构体成员类型来猜一下,s1里有一个char 和 int ,char是一个字节,int是4个字节,那么这样来看的话s1一共就是5个字节咯。按照这样来算,s2,就是6个字节,s3就是7个字节。

当然我们也可以看一下打印的结果。

以上的代码我都是用Visual Studio2019环境来进行打印的,其他环境就可能不一样了。

看完结果在vs环境下和我们猜想的完全不一样。

为什么不一样我们慢慢来看。

先来了解下结构体的对齐规则:

  1. 在结构体变量中的第一个成员,永远放在偏移量为0的地址处。
  2. 在第二个成员开始,后面的每个成员都要对齐到某个对齐数的整数倍处。这个对齐数是:成员的自身大小和默认对齐数的较小值。
  3. 当全部成员放进去之后,结构体的总大小必须是,所有成员对齐数中最大对齐数的整数倍。如果空间不够,那么就要浪费空间去对齐。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

在vs环境下的默认对齐数是8

在gcc环境下是没有默认对齐数的,没有默认对齐数的情况下,成员本身就是对齐数。

了解完我们就来画图了解一下。

 s2的解释图

 s3的解释图就不画了,和s2的没有太大区别,就是少浪费一个空间。

这里介绍一个库函数offsetof,它可以计算结构体的偏移量。

它的头文件是<stddef.h>,用的时候记得写上。

这里我使用一下这个库函数。计算一下上面结构体s2的偏移量。

#include <stdio.h>
#include <stddef.h>

struct s2
{
	char s2a;
	int s2b;
	char s2c;
};


int main()
{
	struct s2 s;
	printf("%d\n", offsetof(struct s2, s2a));
	printf("%d\n", offsetof(struct s2, s2b));
	printf("%d\n", offsetof(struct s2, s2c));
	return 0;
}

可以看见结果和上边计算的一样。


  • 为什么会存在内存对齐呢?

在大部分参考资料是这样说的:

1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特
定类型的数据,否则就抛出硬件异常。

2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。

下面就用一个结构体来画图解释下对齐和未对齐的情况。

struct S1
{
	char c;
	int a;
};

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。比如说:我打游戏每次进游戏和进游戏读图花费的时间,比较长,可能是1分钟两分钟,但是游戏占用的硬盘空间比较小。我倒是愿意用大空间来换取时间短的。游戏玩起来流畅才是比较爽的。

当然我们在设计结构体的时候,我们也可以自己设计出一些满足对齐和节省空间的结构体。

如何做到:

那就是让空间小的成员尽量集中在一起。

#include <stdio.h>

struct S1
{
	char c;
	char b;
	int a;
};

struct S2
{
	char c;
	int a;
	char b;
};


int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

两个同样类型的变量,位置不一样所占的空间是大不一样的。所以能省则省,不能省就随便咯。
所以让空间小的成员挤一挤,空间大的往后边靠呗。


  • 修改默认对齐数

当然如果对Visual Studio的默认对齐数不满意,我们还可以进行修改。

#pragma pack(1)//设置默认对齐数为1
#pragma pack()//取消设置的默认对齐数,还原为默认

下面来看一下该如何使用

#pragma pack(8)//设置默认对齐数为8
struct S1
{
	int i;
	char c;
	int a;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
	int i;
	char c;
	int a;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

可以看见修改为1之后就是按照变量自身大小来计算了。

当然我们在修改对齐数的时候尽量设置为2的n次方。


  • 枚举

枚举顾名思义就是一 一列举。
把可能的取值一 一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一 一列举。
性别有:男、女、保密,也可以一 一列举。
月份有12个月,也可以一 一列举
这里给一个性别的枚举,来使用一下

enum Sex//性别
{
	//这里是枚举的可能取值,从第一个开始默认是0,递增1的
	MALE,
	FEMALE,
	SECRET
};

int main()
{
	//把值FEMALE赋给s
	enum Sex s = FEMALE;
	printf("%d\n", s);

	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

可以看见打印的结果是从0开始的,第一个是把FEMALE赋值给s打印出来的。

以上定义的  enum Sex 是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量

  • 枚举的优点

为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量

  • 改变枚举的初始值

枚举的初始值也是可以改变的。

enum Sex//性别
{
	MALE = 2,
	FEMALE = 5,
	SECRET = 7
};

int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	return 0;
}

 打印结果也是根据我们给的值来定的。

 如果只给第一个赋值的话,后边的只会根据1递增下去。


  • 联合(共用体)

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

  • 联合类型的定义和特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)

这里在使用的时候计算下这个联合体的空间大小

union un
{
	char c;
	int i;
};

int main()
{
	union un u;
	printf("%d\n", sizeof(u));
	return 0;
}

 这里可以看见int 和 char大小一个是4个字节。

当然我们也可以取出它们的地址看一下是不是真的共用的一块空间。还是这段联合体

union un
{
	char c;
	int i;
};

int main()
{
	union un u;
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

 不出所料就是使用的同一块空间

  • 联合大小的计算

联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

下面我们就来看一下到底是不是这样的。

union un
{
	char arr[5];//5个字节
	int i;//4个字节
};

int main()
{
	printf("%d\n", sizeof(union un));
	return 0;
}

 char的对齐大小是1,int的对齐大小是4。根据上边来看就要对齐4的整数倍咯,所以最后是8。

再来看一个把。

union un2
{
	short s[7];//大小是14
	int i;//大小是4
};

int main()
{
	printf("%d\n", sizeof(union un2));
	return 0;
}

如果按照最大成员的大小来对齐的话那么最后就是14咯,但是要按照最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。short s要占14个字节的,为了对齐。所以最后是16,是4的整数倍。

  •  结尾

结构体、枚举、联合体就介绍到这里了。

写的哪里有不对的地方,还请指出,我这里会进行更改,谢谢大家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值