【C语言】自定义类型:结构体,枚举,联合

目录

前言

一、结构体

1.结构体的定义

2.结构体的声明

3.结构体成员类型

4.结构体成员的访问

5.结构体传参

6.结构体的特殊声明

7.结构体内存对齐

8.内存对齐及原因

9.修改默认对齐数

二、位段

1.位段的定义

2.演示

3.位段的应用

三、枚举

1.枚举的定义

2。举例

3.枚举的优点

四、联合(共用体)

1.联合的定义

2.联合体的具体应用


前言

我们终于来到了C语言最自由,最方便的类型----自定义类型


一、结构体

1.结构体的定义

C语言中,结构体(struct)指的是一种数据结构,是C语言中复合数据类型(aggregate data type)的一类。结构体可以被声明为变量指针数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。

2.结构体的声明

struct tag

{

     member-list;

}variable-list;

struct tag整个是类型,结构体是全局变量。其中struct是结构体的关键字。

例如描述一个学生
struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //不能缺少分号

3.结构体成员类型

结构的成员可以是标量、数组、指针,甚至是其他结构体。

4.结构体成员的访问

结构体变量访问成员
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数
例如:
#include<stdio.h>
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};

int main()
{
	struct Stu stu;
	struct Stu* p = &stu;
	stu.age = 10;
}

我们通过(.)访问到了结构体Stu中的年龄,并将其赋值为10;我们还定义了一个指向stu的结构体指针,结构体指针当然也可以访问到结构体中的元素

下面是通过指针访问结构体的两种方式

#include<stdio.h>
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};

int main()
{
	struct Stu stu = { {"张三"},{20},{"男"},{1234567} };
	struct Stu* p = &stu;
	stu.age = 10;
	*(*p).id = 12345678;
	printf("%s\n", p->name);
	return 0;
}

我们这样就通过指针将学号修改,并且将学生张三的姓名打印出来了。

5.结构体传参

结构体传参的时候,要传结构体的地址。因为 函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的 下降。

6.结构体的特殊声明

在声明结构的时候,可以不完全的声明。
这样的结构体被叫做匿名结构体,因为省略了结构体标签

当我们省略了结构体标签时,虽然编译器没有报错但这是不合法的用法,虽然能定义但不能使用

编译器让我们输入标识符,而当我们在main()函数中补充struct Stu stu;时 编译器提醒我们不完整的定义。

我们设想这样一种情况,我们定义两个匿名结构体,其中这两个结构体成员变量都是一模一样的,那么编译器会把它们当成一个结构体吗?

答案是,编译器会把它们当成两个结构体,当你要给其中一个结构体赋值时编译器不知道你要给哪个结构体赋值,所以这是一种不合法的用法。

7.结构体内存对齐

结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处.

其他结构体成员自身对齐时,存放的地址为min{有效对齐值为自身对齐值, 指定对齐值} 的最小整数倍的地址处.
注:自身对齐值:结构体变量里每个成员的自身大小
注:指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8;
注:有效对齐值:结构体成员自身对齐时有效对齐值为自身对齐值与指定对齐值中 较小的一个.

总体对齐时,字节大小是min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍.

#pragma pack(N) 每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。--------以上摘自百度

我们知道了结构体的对齐规则,那我们看几道题吧

struct S1
{
 char c1;
 char c2;
 int i;
};
printf("%d\n", sizeof(struct S1));

求s1在内存中所占的字节大小是多少

我们以VS2022环境为例,VS2022默认对齐数是8

Linux没有默认对齐数自身大小就是其对齐数

首先c1是char类型 占1个字节与8比,8大,对齐数是1,放在偏移量为0的位置

c2也是char 类型对其数是1放在1的整数倍处也就是放在偏移量为1的位置

i是int型占4个字节,最大对其数是4,放在偏移量为4的位置

最后s1偏移量为7一共占用了8个字节,8是最大偏移量4的整数倍,所以s1在内存中占用了8个字节

计算结构体成员相对起始位置的偏移量用offsetof-宏

需要引用头文件#include<stddef.h>

8.内存对齐及原因

 元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
  • 假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这需要做很多工作。
  • 现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

因此我们要尽量将小的成员结合在一起

9.修改默认对齐数

#pragma pack( )(括号内部写用户自己定义的对齐数)

#pragma pack()括号内部什么都不加表示恢复默认对齐数

二、位段

1.位段的定义

位段(或称“位域”,Bit field)为一种数据结构,可以把数据以的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:

  • 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
  • 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。

而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的

2.演示

代码如下(示例):

struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

冒号后面的是设置成员占用的内存大小,单位是比特

普通的int型占用32个比特位,但设置后只占2个比热位,这也导致a只能表示(0,1,2,3)

因此位段可以节省内存,同时它还没有内存对齐

但是它却不能跨平台,因为C语言没有规定位段实现的具体细节,导致每个编译器对位段的实现各有不同。

3.位段的应用

三、枚举

1.枚举的定义

数学计算机科学理论中,一个枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。

枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。

通俗来说,枚举就是一个对象的所有可能取值的集合

2。举例

#include<stdio.h>
enum sex
{
   male,
   female,
   secret,
};

枚举的关键字是enum 在括号内部注意每个成员名后面要加逗号,同时别忘了括号外面的分号

枚举内部的成员被依次赋值为0,1,2

我们假如将female赋值为1,secret就被赋值为2,male赋值为0

3.枚举的优点

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

注意:

枚举是应用于具有相关信息的数据,枚举没有限定枚举常量的范围的功能的,所以不能定义几个相同的常量名字。

四、联合(共用体)

1.联合的定义

计算机科学中,联合体(英语:union)又名共用体,是一种具有多个类型或格式的值,或者把它定义为一种由具有这样的值的变量形成的数据结构。一些编程语言可以支持被称为“联合体”的特殊的资料类型,来表示上述的变量。换句话说,一个联合体的定义(definition)会指定一些允许的可以存储在实例内的原始数据类型(例如整型,浮点)。和记录(record)(或结构,structure)那些可以被定义去包含一个浮点数或整型不同的是,在一个联合体任何时候只有一个值。

C语言中,一个典型的例子如下:

union name1
{
   int     a;
   float   b;
   char    c;
} uvar;

联合体的关键字是union ,其中联合体中的各个成员变量在同一时间只能使用1个。

看到这里我们会有几个疑问?

1.联合体的大小是多少

2.联合体内部会有内存对齐吗

首先,联合体大小至少是最大成员的大小,联合体的成员是共用同一块内存空间。

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

2.联合体的具体应用

我们知道数据在存储时是分大小端的,我们在前面的博客中已经写过了判断大小端的代码,这里我们可以用联合体来判断机器是什么存储类型

#include<stdio.h>
int judge()
{
	union J
	{
		char m;
		int n;
	};
	J s1;
	s1.n = 1;
	return s1.m;
}
int main()
{
	if (judge())
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}
	return 0;
}

因为联合体共用的是低地址处的空间,所以我们只要看01是否被存入低地址就可以判断,当前环境是什么存储方式了。 


总结
以上就是今天要讲的内容,希望大家多多支持。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值