结构体冷知识——位段和柔性数组`(*>﹏<*)′

  🎄博客主页:🎐大明超听话

    🎋欢迎关注:👍点赞🙌关注✍评论

    🎍系列专栏:🎑从零开始C语言

                           🎊从0开始数据结构与算法详解

                           🎆计算机考研——从零开始计算机网络

                           🎃C语言游戏制作详解

                           🎉葵花宝典之C语言冷知识

    🎪伙伴们,你们的支持对我真的很重要,欢迎👍点赞✍评论✨关注✨,欢迎各位朋友私信留言随时骚扰,私信必回。感谢你们的支持与转发!

    🎠关注我, 一同分享更优秀的内容吧!ヾ(≧▽≦*)o


🍱🍱本文重点🍱🍱:

    🍣结构体位段🍣&&🧆结构体柔性数组🧆

🦚目录🦚:

    🍜思维导图:🍜

     🍝引言:🍝

    对于我们结构体的学习还有很多重要的部分和未知领域。结构体又是我们编写程序过程中不可或缺的一部分,就比如说我们数据结构中链表的设计,顺序表的设计等等,很多方面都离不开我们的自定义类型——结构体。那么在今天我们就再向大家介绍两个关于结构体大家可能不知道或者不太熟悉的知识——位段与柔性数组。

     🍛PART 1:位段🍛

    🍩1.1什么是位段🍩

    所谓的位段是用来节省空间的工具,只有在我们的结构体中才可以进行位段的表示。但是我们位段又和我们的结构体有着些许的差别,比如:

     1.位段的成员必须是 int、unsigned int 或signed int 。(也可以为char类型的数据因为char类型的数据实质上属于我们的整形家族)

    2.位段的成员名后边有一个冒号和一个数字。

//位段的表示形式
struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};
//位段中的数据只能是我们的整形家族的数据
//位段的成员名后边有一个冒号和一个数字。

     在知道了我们位段的书写形式之后,但是我们位段可以节省空间的说法又是从何而起的呢?那么我们就先来探究一下我们上面的结构体所占的空间的大小为多少字节。由于我们之前已经学过结构体内存大小的判断了,所以我们先按照我们结构体内存的判断方法进行初步的预测:每个元素都是一样的所以不存在内存偏移的情况,所以一共应该为16个字节。但是我们的实际情况却是:

     就像是我们上图中看到的8个字节。我们的内存却是比以前的结构体用的少了,但是我们的内存为什么会少呢?又是怎么样少的呢?让我们再下一个环节中进行探究。 

    🥞1.2位段的内存分配🥞

    那我们就先来介绍一下我们的位段到底是怎样分配我们的内存的。首先有几条规律:

    1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
    2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。简单一点来说就是我们在内存中开辟的字节的大小必须和前面所定义的变量类型相等。
    3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

    4.位段只适合相同的数据类型进行划分,否则在系统会自动将内存化为具有偏移量的结构体类型而不是位段。

    由于我们的语言总是抽象的,那么我们就将抽象化为具体,利用图形的方式来解析一下我们位段的各部分究竟是如何存储在内存中的。

    经过上面我们的三个步骤之后我们的四个数据的内存也就分配完毕了,由于在存储前三个数据的时候只用了四个字节,加上在存储第四个数据的时候存储空间不够又分配的四个字节,一共八个字节和我们程序中求得的内存的大小刚好相等,如果感觉有点不可思议的话我们还可以再重新创建一个结构体位段进行我们方法的求证。

    按照我们上面的说法来梳理一下思路:第一个数据为 char 类型的数据向内存申请一个字节的空间,之后拿走4个比特位的空间,还剩下4个比特位,之后第二个数据拿走1个比特位,还剩下3个比特位的空间,第三个数据又拿走2个比特位的空间,还剩下1个比特位的空间,但是我们的第四个元素想要的空间为6个比特位,内存大小不够,所以就需要重新申请空间。char 类型重新申请一个字节,拿走6个比特位。最后所计算得到的大小就为2各种字节。和我们频幕上输出的结果一模一样。 再来测试一组数据。

    假如我们把最后一个数据想要的空间改为1个比特位,那么我们剩下的空间刚好够我们第四个数据的内存量。所以我们就不需要重新分配数据了。那么求得的内存大小就是一个字节。 

    假如我们定义的数据类型不统一,那么系统就会按照结构体的内存大小来进行计算,自动忽略我们的内存要求。利用偏移量的规则可以求得我们的内存的大小为12个字节。 

    🎂1.3位段的跨平台问题 🎂

    上面介绍了位段的内存分配的知识之后再来谈谈我们上面提及到的位段的跨平台问题。由于我们的机器字长的大小不同,我们的数据所在内存中的大小也不同,这一系列的不同就会造成位段的跨平台性很差。就比如,我们计划中分配的8个比特位刚好用完,不会存在浪费问题,但是我们要是放到我们的64位机器上面就会存在浪费,还可能造成我们之前的排版到后面完全错乱的情况。总结下来位段的跨平台问题如下:

    1. int 位段被当成有符号数还是无符号数是不确定的。
    2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。)
    3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
    4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。

    所以我们在使用位段的时候应该小心,要是需要在多个平台上面进行运行的程序最好少用甚至不适用位段。但是我们的位段也是有很大的优点的,我们位段的优点就是节省内存空间,比较我们的结构体所占用的空间来说我们的位段所占用的空间简直微乎其微。唯一的不足就是存在跨平台方面的问题。

     🥘1.4位段的应用 🥘

    经过上面的介绍之后大家肯定会有疑问——那么位段在什么样的场景底下可以使用呢?只是节省内存这么简单吗?那么我们通过一个示例来向大家介绍一下位段的重要的作用。

    

    相信学过计算机网络的朋友们对上面的图片一定不陌生,计算机网络让人头疼的就是那些乱七八糟的协议,以及数据传输。我们可以看到上面的分割好像都不规律,总共表示的一行位四个字节的大小,但是有的部分却表示的连一个字节的位置的不到,那么我们在这里要用到的部分就是我们的位段的知识了,这样可以有效的节省我们想要传输数据的大小。 

    🍣柔性数组🍣

    🍘2.1柔性数组的特征🍘

    下面我们再来介绍一下我们的柔性数组:也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。在C99标准中,结构体中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。换一句话来说所谓的柔性数组就是“大小可变”的数组。我们的柔性数组要求:我们结构体的最后一个成员假如是数组的话,位于结构体末尾的数组可以不表明数组的大小,最后在使用的时候在分配给数组想要使用的空间。这样就会节约很大的空间并且完善了数组大小不够的缺陷。总结起来柔性数组的特点如下:

    🍕NUM 1. 结构中的柔性数组成员前面必须至少一个其他成员。
    🍕NUM 2.sizeof 返回的这种结构大小不包括柔性数组的内存。
    🍕NUM 3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

    简单一点来说,我们的柔性数组最大的作用就是开辟一个可以动态管理的空间。这个动态开辟的空间定义在结构体当中,但是却不被算成结构体的大小。

    在我们的生活当中肯定有许多情况在描述对象的时候会需要用到未知长度的变量,但是这个特性又属于我们的一个数据类型的所以在这个时候使用我们的柔性数组最为合适,和我们普通的结构体定义的变量相同的是我们的柔性数组同样支持结构体的点引用找到目标对象。下面我们使用一个简单的代码来向大家展示一下我们的柔性数组的使用特点。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct S
{
	int n;
	char c;
	int arr[0];//柔性数组成员
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10*sizeof(int));
	if (ps == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	ps->n = 100;
	ps->c = 'w';
	int i = 0;
	//向数组输入一定的数据
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//打印输入的数据
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
	//调整arr数组的大小
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		ps = ptr;
	}
	//使用
	//向数组输入一定的数据
	for (i = 0; i < 20; i++)
	{
		ps->arr[i] = i;
	}
	//打印输入的数据
	for (i = 0; i < 20; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
	//释放
	free(ps);
	ps = NULL;
	return 0;
}

     来分析一下上面的代码,我们首先创建了根据柔性数组的定义在一个结构体的末尾定义了一个未标明长度的数组,之后再利用malloc函数进行对结构体内存大小的完善和补充,在内存开辟完之后我们就可以使用这段内存了,就比如我们上面的代码。我们向数组空间内存入0-9的数字,之后就可以进行通过ps指针进行查找相应的数据。在我们内存不够的时候我们还可以向我们的堆区重新调整一段空间。再次使用。

    对比于我们在主函数中使用malloc开辟内存使用数组的情况,我们的柔性数组的优点就在于我们可以对一类数据进行统一的管理。这样会使我们代码程序的逻辑更加的清晰。

    相同的我们不仅仅可以使用柔性数组的方式来定义一个可变长的变量。我们还可以使用在结构体中定义一个指针的方式进行变量空间的开辟以及使用。就比如我们下面的一段代码:

#include<stdio.h>
#include<stdlib.h>

//定义一个结构体
struct Stu
{
	int age;
	char ch;
	int* Pname;
};
int main()
{
	int*pa=(int*)malloc(10 * sizeof(int));
	if (pa == NULL)
	{
		perror("malloc");
		return 0;
	}
	struct Stu s;
	s.age = 12;
	s.ch = 'M';
	s.Pname = pa;
	int i = 0;
	//输入0-9的数据
	for (i = 0; i < 10; i++)
	{
		*(s.Pname + i) = i;
	}
	//打印我们输入的数据
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(s.Pname + i));
	}
	printf("\n");
	//如果内存大小不够我们就重新申请内存
	int*ptr=(int*)realloc(s.Pname, 20 * sizeof(int));
	//输入0-9的数据
	for (i = 0; i < 20; i++)
	{
		*(s.Pname + i) = i;
	}
	//打印我们输入的数据
	for (i = 0; i < 20; i++)
	{
		printf("%d ", *(s.Pname + i));
	}
	return 0;
}

    通过上面的一段代码我们可以发现,当我们将我们的变长数组更改为指针的形式,同样可以达到我们的使用效果,在这里我们比较建议使用指针的方式进行代码的编写。代码运行效果如下:

    那么以上就是我们本次博客的全部内容了,感谢您的观看。 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿白逆袭记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值