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了兄弟们结构体到这就完一段落了。请兄弟们多提问题共同进步。