进阶C(结构体)

进阶C(结构体),第1张

进阶C(结构体)

这章我们主要了解结构体,枚举,联合这三点,话不多说,上正文:

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

举个例子,我要表达一个人的信息,那有几方面是我们要描述的:

姓名、性别、年龄、电话号码等,这些类型都不会一样,比如说姓名和性别、电话号码就是char类型,而年龄则是int形,但这些我们联系起来才可以描述一个人。

所以在这里,结构体就可以很好的描述这个复杂成分的对象,在这里我先写一个结构体示范:

struct Stu

{

char name[20];//名字

int age;//年龄

char sex[5];//性别

char id[20];//学号

}s1;

struct告诉电脑这是一个结构体,Stu是你的结构体名,其中的变量是结构体的成员列表,最后的s1则是创建的一个结构体变量,当我们需要引用结构体其中一个值的时候,我们只需结构体名->对象,即:

stu->name

结构体的赋值

当我们创建了结构体的变量时,我们调用的时候就可以往里面赋值,在赋值前,我们先创建一个结构体:

struct print

{

int x;

int y;

char z[15];

};

struct point s2;

注意这里,我们创建变量的时候要将结构体表示和结构体名一起拿过来再空格写变量名,赋值也很简单:

struct point s3 = {3,5,"Knous"};

结构体是可以包含另一个结构体的,称为结构体的嵌套使用。

struct Node

{ int data;

struct Point p;

}

}; //结构体嵌套初始化

struct Node n2 = {20, {3,5,"Knous"}, NULL};//结构体嵌套初始化

像这里,我在Node结构体里面了我Point结构体并初始化,格式为花括号里再花括号即可。

结构体的内存对齐

我们来看一个结构体:

struct S1

{

char c1;

int i ;

char c2;

};

printf("%d",sizeof(struct s1));

我们想看看这个结构体到此占用多少内存,按我们所想,char一个字节,int四个字节,加起来就是六个字节,那结果是不是这样呢?

 出乎我们意料,我们想的是6,结果是12,整整大了一倍,那为什么会这样呢?

 这是一块空间,为了方便看,我将它画成竖条,当程序在储存我们结构体的信息的时候,它会先将我第一个元素放到起始位置为0的位置,但当放我int型的时候,这里就涉及到结构对齐了,char类型占一个字节,对齐数位1,它会与系统默认对齐数比较,但因为它是第一个元素,所以它不比,但int型作为第二个元素,它就要比较了,它占4个字节,对齐数为4,默认8,取4,所以它会放到下标为4的倍数的位置上,从4往后数四个字节给了int,再是char c2,它占一个字节,放到1的倍数,就放到int的后面,如图:

红色是我第一个char类型,蓝色的int形,最后的绿色是c2,可以看到数据占用了下标0-8的空间,即占用9个字节,这里还要说一句,对齐的另一个条件,即最后所占用的空间必须是结构体最大元素的整数倍,这里结构体最大的是int,4个字节,而这里占用9个字节,我们再浪费掉三个字节的空间,所以最后打印的12. 

如果我们换一个排序:

struct S1

{

char c1;

char c2;

int i;

};

那么这样呢?

你想的是不是跟我下面这幅图一样呢?

c2在c1的后面,而int在最后,总体占用8个字节,刚刚好,不用再浪费多余空间。

那么,为什么会有内存对齐这个概念呢?

看图:

这里是八个字节,我在第一个字节放char类型,在放一个int类型进去,结果如下:

当我们电脑访问的时候,它第一次读取4个数据,而我的int类型还有一位放到了后一个字节,它就得再读取一次,而如果我们内存对齐,就是:

 它只需读一次即可将我int类型读取完,这样效率上,是不是就更高一些。

我们可以理解为:结构体的内存对齐是拿空间来换取时间的做法。

修改默认对齐数:

#pragma pack(8)

在括号内改变数字即可,通常我们改为2的n次方,但不改也行。

结构体传参:

#include
#include
struct S{
    int data[1000];
    int num;
};
//结构体初始化
struct S s = { { 1, 2, 3, 4 }, 1000};
//结构体传参
void print1(struct S s){
    printf("%dn", s.num);
}
//结构体地址传参
void print2(struct S* ps){
    printf("%dn", ps->num);
}
int main(){
    print1(s);
    print2(&s);
    system("pause");
    return 0;
}
 

以上两种,让我们想想,当我们调用的时候,哪种更方便一些?

是第二种,因为它直接指向我的结构体地址,而传值过去的话,电脑必须先开辟空间赋值再调用,效率上无疑慢了许多。

所以我们传参的时候,传结构体地址过去比较好。

位段

位段的声明和结构体相似,但有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int 。

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

这里我示范一下位段:

struct A

{

int _a:2;

int _b:5;

int _c:10;

int _d:30;

};

这里_a :2的意思是,开辟两个比特位的空间给a,那_b就只要五个比特位,以此类推。

那这个结构体的大小是多少呢?

光看代码的话,2+5+10+30,这样一共才47个比特位,那实际是不是这样呢,我们打印出来看看。

出乎意料,结构体大小是8,为什么会这样呢,8*8就是64个比特位,64-47,这剩下的17个比特位是怎么用掉的呢?

这里我们就得说到:位段的内存分配

位段的内存分配 

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

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

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

这里,结构体分配的时候,上来开辟4个字节,32个比特位位,a拿去2个,b拿去5个,c拿去10个,那此时,32-17=15,剩下的空间已经不够d的30个了,那就再开辟4个字节的空间给d用,所以才是4+4=8个字节。

那这里就有一个问题,最后d的30个比特位,是用前面剩下的15比特位加新开辟的15个合起来还是只用了新开辟的30个比特位,这一点是没有规定的,完全看编译器版本,也就是说这个编译器可能用了前面的15个,那个编译器只用了后面的30个,所以位段的可移植性不高。

我们如何查看编译器有没有浪费呢,这里有一段代码可以看:

struct S

{

char a:3;

char b:4;

char c:5;

char d:4;

};

这里上来开辟一个字节,给了a和b,再给b开辟一个字节,此时位置不够d,所以又开辟一个字节,加起来是三个字节,浪费了比特位。

如果没有浪费,那它应该是3+4+5+4 = 16个比特位,两个字节,那编译器到底有没有浪费呢,我们程序走起来:

可以看到编译器(vs2013)浪费掉比特位,开辟了3个字节。

 位段的特点比较明显,它是根据需要开辟的空间,比如我的a只要4种可能:00 01 10 11,那给它一个字节是不是就太浪费了一点,所以我们给两个比特位显然节省了空间。

位段的应用

 这里我们看一张表,这个是一个数据包,可以看到它里面有一堆东西,我们发送信息的时候,就是在发送这样一个数据包,我们想象网络是一个高速公路,你数据越多,传输的是不是越慢,在这里,像一些小的数据我就可以以比特位的方式开辟,这样传输数据就快了很多。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5714341.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存