结构体(4)

1.结构体传参

结构体传参第一点我们要知道的是,和函数相同,结构体的传参要在结构体声明前

void print1(struct S tmp) 
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d", tmp.arr[i]);
	}
	printf("\n");
	printf("n=%d\n", tmp.n);
	printf("ch=%c", tmp.ch);
}
struct S
{
	int arr[1000];
	int n;
	char ch;
};
int main(void)
{
	struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };
	print1(s);
	return 0;
}

像上面这个函数,要把类型放到函数前面,要先有类型你再用函数的时候才能使用啊

像下面这样使用就没问题了

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
	int arr[1000];
	int n;
	char ch;
};
void print1(struct S tmp) 
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d", tmp.arr[i]);
	}
	printf("\n");
	printf("n=%d\n", tmp.n);
	printf("ch=%c", tmp.ch);
}
int main(void)
{
	struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };
	print1(s);
	return 0;
}

上面这串代码我是将结构体的值传过去了

那么传指针呢?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
	int arr[1000];
	int n;
	char ch;
};
void print1(struct S* tmp) 
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d", tmp->arr[i]);
	}
	printf("\n");
	printf("n=%d\n", tmp->n);
	printf("ch=%c", tmp->ch);
}
int main(void)
{
	struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };
	print1(&s);
	return 0;
}

如上所示,好像传指针也可以!那么传值和传指针哪个好呢?

我们传值相当于把数据拷贝一份过去,但是这是个结构体,相当于我要拷贝一个完整的结构体

第一很浪费空间(结构体的大小一般比指针大)

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候;结构体过大;

参数压栈的的系统开销比较大,所以会导致性能的下降。

其次你创建了一个结构体,你再把结构体的值传过去,结构体成员的每个值都要传,很浪费时间

因此如果两种方式都可以选择的话,建议优先选择传指针,时间空间上都会更优

而且传值可以做到的传指针基本也都可以做到,但是传指针做到的传值未必可以。

当然也有人说传指针容易把值修改很危险,那这还不简单,直接加一个const就可以了

比如void print1(const struct S* tmp) 

2.位段

我们先直接上代码来看

struct S
{
	int a:3;
	int b : 4;
	int c: 5;
	int d : 4;
};

 int a:3意思就是用3个比特位空间来存放a,

int b :4意思就是用4个比特位空间来存放b

c,d可以类比理解

我们有时候比如说如果a的值是3转换成二进制就是011

两位明显就够了,用32位(一个int大小)完全就是浪费了

因此位段可以很好的帮我们提高内存占用率

按理来说,这个结构体所占的空间不应该是3+4+5+5=16个比特位也就是两个字节的空间吗?

但为啥答案是4呢?

这就涉及到了位段的内存分配了

位段的内存分配
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;
};

我们先看这个结构体的内存分配

首先第一个成员的类型是char,我们就先开辟一个字节(8个比特位)大小的空间

给定了空间后,在空间内部是从左向右使用,还是从右向左使用,这个不确定。

我们假设是从右向左使用

我们可以先把a和b放进去

bbbbaaa

但是这个时候只剩一个比特位空间大小了,肯定是无法放入完整的c了,这个时候是把这最后一个空间也利用,还是说重新开辟一个空间去存放c呢 

当剩下的空间不足以放下一个成员的时候,空间是浪费还是使用不确定

假设浪费

bbbbaaacccccdddd

这样我们最后放完的结果刚好是用了3个字节的空间

我们来用编译器验证一下我们的对不对

刚好和我们的计算的结果一模一样

我们再来看一个有意思的题

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
	char a:3;
	char b : 4;
	char c: 5;
	char d : 4;
};

int main(void)
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%zd", sizeof(struct S));
	return 0;
}

a是10,转换成二进制,就是1010 ,但是这个地方只给a3个比特位大小的空间

因此存入的就是010,b是12,转换成二进制,就是1100,刚好给b的4个比特位大小的空间全部用完,c是3,转换成二进制就是11,但是c被给予了5个空间大小,因此就是00011,d是转换成二进制就是100,但是b被给予4个空间大小也就是0100

1100010000110100

其它位补0 

011000100000001100000100

每四个二进制位可以换算成一个16进制位

620304

我们可以调试看看内存里面是不是这样的呢?

刚好和我们计算的一模一样

 3.位段的跨平台问题


1.int 位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3.位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位,还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

4.位段的应用

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

5.位段使用的注意事项

 像上面的例子,a和b在同一块开辟的一字节的空间,但是内存中每一个字节分配一个地址,一个单独的比特位是没有地址的,因此我们基本无法对位段取地址


位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。

内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。


所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

例子如下 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
	int a : 2;
	int b : 5;
	int c: 10;
	int d : 30;
};

int main(void)
{
	struct S s = { 0 };
	//错误示范
	scanf("%d", &s.b);
	//正确示范
	int b = 0;
	scanf("%d", &b);
	s.b = b;
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值