结构体(含位段)

文章目录

  • 1结构体的声明
    • 1.1结构体的一般声明
    • 1.2结构体的特殊声明
  • 2.结构体的自引用
  • 3.结构体的定义和初始化
  • 4.结构体的内存齐
    • 4.1内存对其的规则
    • 4.2为什么要进行内存对其呢
  • 6.修改默认对齐数
  • 7.结构体传参
  • 8.位段
    • 8.1什么是位段
    • 8.2位段的内存分配
    • 8.3位段的跨平台问题
    • 8.4位段的应用

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
#include
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
#include
struct S1
{
	char a;
	char b;
	int c;
};

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

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

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

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

4.1内存对其的规则

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

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

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

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

    VS中默认的值为8

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

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

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

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

结构体(含位段)_第1张图片

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

结构体(含位段)_第2张图片

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

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

结构体(含位段)_第3张图片

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

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)

结构体(含位段)_第4张图片

结论:

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

7.结构体传参

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

话不多说看代码

#include
#include
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;
};

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

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

结构体(含位段)_第5张图片

8.2位段的内存分配

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

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

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

结构体(含位段)_第6张图片

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

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

8.3位段的跨平台问题

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

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

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

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

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

8.4位段的应用

结构体(含位段)_第7张图片

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

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

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

你可能感兴趣的:(c语言,c语言,c++)