自定义类型:结构体

1.结构体传参

关于结构体传参我们可以给一串代码来看一下。

#include<stdio.h>

struct S
{
	int arr[1000];
	int n;
	double d;
};

void print1(struct S tmp)//2.如果我这个s传参,传过去给tmp的话,s占多大空间,tmp就占多大空间。
//因为这里的print1是值传递,所以我会把s里面的数据拷贝一份给tmp,这样s有一块空间,但是tmp还要再创建一份空间,这样就会造成空间的浪费。
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d", tmp.arr[i]);
	}
	printf("%d", tmp.n);
	printf("%lf", tmp.d);
}

int main()
{
	struct S s = { {1,2,3,4,5},100,3.14 };//1.我们可以看一下这串代码是怎么完成任务的呢:
//首先我们创建了s这个变量,这个结构体是非常大的,arr里面有1000个元素这一共就占4000个字节,n也占4个字节,d占8个字节,那么这个结构体变量非常大
	print1(s);

	return 0;
}

关于这种传递方法的弊端我已经在代码中进行讲解了,但是有没有更好的方法呢?下面再给出一个·代码来进行讲解:

#include<stdio.h>

struct S
{
	int arr[1000];
	int n;
	double d;
};
//const修饰指针防止指针被修改。
void print2(const struct S* ps)//这里也仅仅创建了一个指针变量,无非就是4或者8个字节,那么消耗就会低很多。
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d", ps->arr[i]);
	}
	printf("%d", ps->n);
	printf("%lf", ps->d);
}

int main()
{
	struct S s = { {1,2,3,4,5},100,3.14 };
    print2(&s);//这里我们传给的是地址,这里的大小也就是4个字节或者是8个字节。

	return 0;
}

上面的print1和pint2函数哪一个会更好一些?

答案就是print2原因就是:

1.函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

2.如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。

结论:

结构体传参的时候,要传结构体的地址。

2.结构体实现位段

结构体讲完就要讲一讲结构体实现位段的能力,首先我们先来说一下什么是位段?

2.1什么是位段 

位段是基于结构体的。

位段的声明和结构是类似的,但是还是有两个不同:

1.位段的成员必须是int ,unsigned int或signed int,在c99中位段成员的类型也可以选择其他类型。

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

就i像这样:

struct A
{
 int _a:2;//位段的成员名后边有一个冒号和一个数字。
 int _b:5;
 int _c:10;
 int _d:30;
};

在C语言中,位段(bit-field)是一种特殊的结构体成员它允许程序员指定结构体中成员所占用的位数。位段通常用于节省空间,尤其是在需要存储大量布尔值或小整数时。

关于位段怎么节省空间?我们可以对比这样的一串代码,并且我会在代码中进行讲解:

//我们对比一下结构体和位段式的结构
struct S
{
//我们可以看出来这里的每一个元素都是占4个字节,32个比特位。假如说我们这个_a中存的是0,1,2,3,4这样的数字,他们用二进制00,01,10,11表示就可以了,这样的表示就占2个字节,但是_a他是包含32个字节的,光这一个浪费了30个字节,所以说浪费空间很严重,所以这时候我们就可以用位段式结构来表示
  int _a;
  int _b;
  int _c;
  int _d; 
};


struct S
{
 int _a:2;//只占2个比特位
 int _b:5;//只占5个比特位
 int _c:10;//只占10个比特位
 int _d:30;//只占30个比特位
};
//看到了没有,位段是专门用来节省内存的。

那这个位段式的结构体的内存是多大呢? 

A是一个位段类型。我们来测试一下:

我一开始认为是把所有的比特位加起来除以8就是字节个数,那样我们算出来是6,但是答案是不是这样呢我们看一下:

#include<stdio.h>

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}

我们发现结果和我们预想中的不一样。 

那怎么知道位段的内存空间是怎么进行分配的?接下来会讲到:

2.2位段的内存分配 

1.位段成员可以是int,unsigned int ,signed int或者是char等类型的

2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的.

3.位段涉及很多不确定的因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

这里我们还是给出一个例子来直观的感受一下位段的内存分配:

struct S
{

 char a:3;
 char b:4;
 char c:5;
 char d:4;
};
int main()
{
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

return 0;
}

我们可以看到这个位段中的每一个元素都是char类型的,他们都是占用一个字节,假设每一个小格就是一个比特位,开始存a的时候在内存中申请了一个字节就是八个比特位,可以理解为八个小格

就是标红的那一部分,但是a就占用3个比特位,所以在这八个比特位中就拿出三个比特位给a,到那时需要注意的一点就是a的内存存储是从左往右还是从右往左?这其实是取决于编译器的。 vs中就是从右往左填充三个。

那么接下来的元素会在上一个元素停止的地方继续填充4个比特位,这样就占用了8个了。就像这样

 

你再接下来的元素呢?接下来的元素又占5个比特位,但是第一个字节就剩下一个比特位了,拿着时候就会再次开辟一个字节的大小,就是上图所示,这里又有一个重要的的点就是:那剩下的格子是使用还是抛弃?在vs上是抛弃。 就一直这样进行分配空间最后就是这样的情况:

所以说我们一共申请(开辟)了3个字节的空间。 答案如下:

其实编译器一运行把这第三个字节的空间开辟出来了。 

 但是我们这串代码不只是计算位段占多大的内存,我们把s全部初始化成了0,还要在里面重新存放数据。

把s初始化为0,说明每一个比特位都是0,这时候往这里面存放数据都是以二进制的形式存放的

10的二进制序列是00001010。12的二进制序列是00001100。3的二进制序列是00000011。4的二进制序列是00000100 。

第一个元素只给申请了3个比特位所以所以要进行截断就是010.第二个就是1100,第三个就是00011,第四个就是0100。结果就是如图所示:

 为了看一下内存我给改成十六进制就是0x620304,这时候我们来调试看一下:

就是这样。

那这个题,也就是可以解决的了。

#include<stdio.h>

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}

所以说这个位段就是占8个·比特位。

2.3位段的跨平台问题

 关于第二条的解释就是:32位和64位的机器上int的长度是4个字节,而在16位的机器上面int的长度是2个字节。这就会导致如果你给int型变量的比特位是30,但是是在16位的机器上面,就不会完成这个空间的开辟。

而三四条在上面空间的申请的讲解中都有说明。

总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题。

2.4位段的应用

这个图表示的就是在网络协议中,ip数据报的格式,我们可以看到其中很多的属性只需要几个bit就能描述,这里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

2.5位段使用的注意事项 

位段的成员会有共有同一个字节的情况,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit是没有地址的所以不能对位段的成员使用&操作符这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

struct A
{
 int _a : 2;
 int _b : 5;
 int _c : 10;
 int _d : 30;
};
int main()
{
 struct A sa = {0};
 scanf("%d", &sa._b);//这是错误的
 
 //正确示范
 int b = 0;
 scanf("%d", &b);
 sa._b = b;
 return 0;
}

OK了兄弟们结构体到这就完一段落了。请兄弟们多提问题共同进步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值