结构体(含位段)

1结构体的声明

结构体是一些不同类型变量的集合

1.1结构体的一般声明

struct tag
struct tag
{
	member - list;
}variable - list;

举个例子

struct Stu
struct Stu
{
	char name[20];//姓名
	int  age;//年龄
	char sex[5];//性别
	char id[15];//学号
};//这个分号不能丢

1.2结构体的特殊声明

在声明结构体时可以不声明结构体的标签(tag)

struct
struct
{
	char a;
	int b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c;
} * p;

如果省略了标签,则必须在定义结构体时就声明变量。否则无法使用该结构体(因为该结构体没有名字所以你在其他地方无法创建该结构体变量)。

对于上面代码,进行下面操作可以吗?

p = &x;

答案是不行的

编译器会把这两结构体当成两种完全不同的类型

2.结构体的自引用

struct Node
struct Node
{
	int dada[20];
	struct Node next;
};

这段代码正确吗?

显然是错误的。

此时我们正在定义结构体(构造)。想要定义struct Node这个变量,至少要等到将结构体创建完成后,才能创建其变量。

定义结构体时要确定其需要多大空间,如果结构体不完整,就无法确定它需要多大内存空间。所以在定义结构体时不能定义自身的普通变量

那么如何做,才可以自引用呢?

struct Node
struct Node
{
	int dada[20];
	struct Node *next;
};

这样就是正确的

肯定有不少人有疑问,为啥变成指针变量就可以了呢?

你在定义自身的指针时分配的内存空间是确定的(32位指针变量大小是4字节,64位是8字节)存放的是struct Node的地址,在分配内存时不需要知道stuct Node 的结构,只需要知道struct Node 中有这个指针就可以了。

3.结构体的定义和初始化

定义

struct Point
struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1   -----第一种

struct Point p2; //定义结构体变量p2 -----第二种

初始化

struct Stu        //类型声明
struct Stu        //类型声明
{
	char name[15];//名字
	int age;      //年龄
}s1 = {"lisi",18};                         -----第一种
struct Stu s2 = { "zhangsan", 20 };//初始化 ----第二种
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化 ----第一种
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化 ----第二种

4.结构体的内存齐

我们已经会定义和初始化结构体了。

那么一个结构体的大小该怎么算呢?

#include<stdio.h>
#include<stdio.h>
struct S1
{
	char a;
	int c;
	char b;
};

int main()
{
	printf("%d", sizeof(struct S1));
	
	return 0;
}

看看上面的代码,大家觉得会显示什么?

有人会说是6 ----这个答案是错误的,可以再往下看看,你就懂了

也会有人说12 ----这个答案是正确的,但你不一定是正确的理解

因为有一次我想知道结构体内存如何算,便去百度了一下,看了一些人是这样说的

先判断结构体中有多少成员,再找出成员中所占空间最大的是多大。

结构体内存大小 = 成员数量*成员中最大的字节数 错误

就比如上面的例子:

3个成员,最大字节是int型(4个字节) 结构体大小 = 3*4 = 12

我当时什么都不知道,觉得挺对,因为当时确实算正确了。

但是我想说:这种方法所错误的,你算对了只能说是巧合

在看一个代码

#include<stdio.h>
#include<stdio.h>
struct S1
{
	char a;
	char b;
	int c;
};

int main()
{
	printf("%d", sizeof(struct S1));
	
	return 0;
}

其实和上面代码差不多,只是换了一下结构体中的顺序,猜猜看输出的还是12吗

肯定不是,不然我还举这个例子干什么

这个输出的是8,好奇就往下看,看完就懂了。

4.1内存对其的规则

首先要掌握内存对其的规则

  • 第一个成员在与结构体变量偏移量为0的地址处。

  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值

    VS中默认的值为8

  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

文字看完便看图理解吧,文字总没有图片直观吧

请添加图片描述

看完这个,再来看看另外一个代码的内存分布吧。

请添加图片描述

图就是这样希望大家自己去好好理解理解

还没有讲规则4,应为上面这些例子没有设计嵌套结构体,下面看看下一例子

请添加图片描述

以上就是结构体的内存对其全部内容,从此你在也不用担心算错了😄。

4.2为什么要进行内存对其呢

  1. 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会出现硬件异常。

  1. 性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

总的来说:

就是以空间换取时间

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。

struct S1
struct S1
{
	char c1;
	int i;
	char c2;
};   //12
struct S2
{
	char c1;
	char c2;
	int i;
};  //8

S1和S2类型的成员一模一样,但一个占8个字节,一个占12字节

6.修改默认对齐数

如何修改默认对齐数呢?

要通过#pragma这个预处理指令,可以改变我们的默认对齐数。

但修改的默认对齐数必须是2 ^ n( n>=0)

请添加图片描述

结论:

结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

7.结构体传参

我们知道的传参方式有两种:1.值传递 2.地址传递 在结构体中也一样有两种。

话不多说看代码

#include<stdio.h>
#include<stdio.h>
struct S
{
	int arr[100];
	int a;
}s ={{0},100};

void test1(struct S s)   //值传递
{
	printf("%d\n", s.a);
}

void test(struct S* s)  // 地址传递
{
	printf("%d\n", s->a);
}
int  main()
{
	test1(s);
	test2(&s);

	return 0;
}

两种方法,都可以达到目的。哪一种更好呢?

第一种,如果用值传递的话,形参是实参的一份临时拷贝,形参会占用很大的空间,在时间和空间上都有很大的 开销

第二种,如果用值传递,不需用进行开辟空间进行临时拷贝,只需要存储地址,不会占用大部分空间,只占用一个指针变量的空间,时间上也会快很多,大大降低了时间和空间的开销

综上所述:结构体传参时,用地址传递更好。

8.位段

8.1什么是位段

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

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

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

数据类型 成员名 :整数(不能大于int的字节数)

看例子

struct A {
struct A 
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

冒号后面的数字代表,占几个比特位

那么位段的大小是多少呢?

请添加图片描述

8.2位段的内存分配

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

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

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

请添加图片描述

从上面这幅图,我们可以看出他是如何存储的。

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

8.3位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。

  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。

  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的

那位段有这么多问题为什么要使用位段呢?

8.4位段的应用

请添加图片描述

位段可以大大更好的利用内存,不造成浪费。

上图是一个数据包的个个部分,这里应用位段,可以大量的节省内存。

比如:四个版号位,完全可以用位段,4个比特位,可以大大减少内存。

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值