自定义类型:结构体

自定义类型:结构体

结构体类型的声明

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

1.结构体的特殊声明

声明结构体的时候也可以不完全声明
匿名结构体类型只能使用一次,后期不能使用这个类型在创建变量

c 复制代码
#include<stdio.h>
//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x, y, z;
//编译器会把这两个类型当成完全不同的类型,会报错
// struct
//{
//	int a;
//	char b;
//	float c;
//}*ps;
int main()
{
	ps = &x;
	return 0;
}

2.结构体的自引用

c 复制代码
struct Node
{
	int data;
	struct Node* next;
};

定义结构体的时候不要使用匿名结构体。

对结构体重命名,简化后续创建变量:

c 复制代码
#include<stdio.h>
typedef struct Node
{
	int data;
	struct Node* next;
}Node;
int main()
{
	Node n;
	return 0;
}

结构体内存对齐

首先我们来看一段程序

这里引入C语言库函数提供的一个宏,需要包含头文件<stddef.h>,用于返回偏移量。

结合上面的偏移量,我们画一个图来理解:

那么剩下的3个字节位置是不是就白白浪费了,这就涉及到了结构体的内存对齐

1.对齐规则

  1. 结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处。
  2. 从第2个成员变量开始,都要对齐到某个对齐数 的整数倍的地址处
    对齐数=编译器默认的一个对齐数与该成员变量大小的较小值
    VS默认的对齐数为8;Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。
  3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对其书的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

现在重新拿出刚刚那个例子就比较好理解了:

2.为什么存在内存对齐

由于某种平台原因以及性能原因,结构体的对齐是拿空间 来换取时间 的做法。那在设计结构体的时候,既要满足对齐又要节省空间又该如何做呢------如果没有特别要求成员变量顺序的话,尽量让占用空间小的成员尽量集中在一起

3.修改默认对齐数

**# pragma pack()**这个预处理指令,可以改变编译器的默认对齐数------设置的一般都是2的次方数.

结构体传参

c 复制代码
#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4},1000 };
//结构体传参
void print1(struct S t)
{
	printf("%s\n", t.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s);//传结构体
	//传值调用
	//浪费了时间和空间
	print2(&s);//传地址
	//传址调用
	//效率高
	return 0;
}

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销;如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

因此:结构体传参的时候,要传结构体的地址

结构体实现位段

1.什么是位段

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

  • 1.位段的成员必须是int,unsigned int或者signed int,在C99中位段成员的类型也可以选择其他整型家族类型,比如char。
  • 2.位段的成员名后边有一个冒号和一个数字。
    例:下面的A就是一个位段类型
c 复制代码
struct A
{
	int a:2;//这个数字表示该成员变量所占bit位
	int b:5;
	int c:10;
	int d:30;
};
struct B
{
	int a;
	int b;
	int c;
	int d;
};
int main()
{
	printf("%zu\n",sizeof(struct A));//8
	//明显节省了空间
	printf("%zu\n",sizeof(struct B));//16
	return 0;
}

上面的结构体A共占47个bit位,6个字节就够了,但最终打印结果其占8个字节,这就涉及到位段的内存分配了

2.位段的内存分配

一个字节(整型)的内存中,到底是从左向右使用,还是从右向左使用不确定;

剩余的空间不能满足下一个成员时,是否浪费也不确定。

由上可知,在VS上从右向左使用是正确的;且在VS上浪费也是正确的。

  1. 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
  2. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植性的程序应该避免使用位段

3.位段的跨平台问题

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

4.位段的应用

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

5.位段使用的注意事项

位段的几个成员共有同一个字节,这些有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。

所以不能对位段的成员使用&操作符 ,这样就不能使用scanf直接给位段的成员输入值,只能是先输入一个变量中,然后赋值给位段的成员
本次内容到这里就结束了,谢谢观看!