深入理解结构体

  • 一、结构体类型的声明
  • 二、结构体变量的初始化
  • 三、结构体的自引用
  • 四、特殊结构体声明
  • 五、结构体内存对齐
  • 六、结构体传参
  • 七、结构体实现位段

文章目录

  • 前言

    c语言中允许我们自己创造一些类型,即自定义类型,如结构体、联合体、枚举类型

    今天要讲的是结构体。

  • 一、结构体类型的声明
  • 二、结构体变量的初始化
  • 三、结构体的自引用
  • 四、特殊结构体声明
  • 五、结构体内存对齐
  • 六、结构体传参
  • 七、结构体实现位段
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

c语言中允许我们自己创造一些类型,即自定义类型,如结构体、联合体、枚举类型

今天要讲的是结构体。


提示:以下是本篇文章正文内容,下面案例可供参考

一、结构体的声明和变量定义

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

类比于数组,数组是一组相同类型的值的集合,而结构体是一组不同类型的值的集合,每个成员变量类型可以不同。

结构体关键字是struct。

下面是一个结构体的声明

struct tag
	{
		int a;
		char arr[10];
	    //member_list;
	}variable_list;

注意结构体最后的分号,这是不能缺少的。

此结构体类型为struct tag。

大括号内部为member_list,即成员变量。

代码最后的variable_list是创建的变量名,此处变量可创建也可不创建,不做要求。同时,此方法创建的变量是全局变量。

此外还有另一种创建变量的方式,这是在main函数内部创建变量,所创建的变量也当然是局部变量。

int main()
{
	struct tag s1;
	struct tag s2;
	return 0;
}

同时,为了简便书写,可以利用typedef来重命名结构体类型。

typedef struct student
{
	char arr[20];
	int age;
}stu;
int main()
{
	stu s;
	return 0;
}

此时,struct student类型的结构体就被重命名成为stu,以后使用时直接用stu创建结构体变量即可

二、结构体变量的初始化

结构体变量的初始化需要使用大括号。如下:

typedef struct student
{
	char arr[20];
	int age;
}stu;
int main()
{
	stu s = {"zhangsan",18};

	return 0;
}

此外,还有结构体嵌套初始化,在大括号里用大括号来初始化嵌套的结构体。


struct Point
{
	int x;
	int y;
};
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
};
int main()
{
	struct Node n = { 20,{5,6},NULL };//结构体嵌套初始化
	return 0;
}

三、结构体的自引用

结构体的自引用类似于函数的递归,不过结构体的自引用需要使用结构体指针,否则便会如同死递归一样,一直循环,却没有结果。

下面是正确用例。

struct Node
{
	int data;
	struct Node* next;
};

四、特殊结构体声明

匿名结构体类型

在声明结构的时候,可以不完全声明

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

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

但是要注意的是,上面两个结构体是完全不同的类型,即编译器会把上面两个声明当成完全不同的类型。

五、结构体内存对齐

了解结构体在内存中的存储方式,以便我们计算结构体的大小。

看下面的代码,思考为什么是这个结果

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

int main()
{
	
	printf("%d\n", sizeof(S));
	return 0;
}

 这是由于结构体内存对齐的原因。

1.结构体的第一个成员永远放在0偏移处

2.从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍处,这个对齐数是:成员自身大小和默认对齐数的较小值。

备注:

VS环境下默认对齐数是8

gcc环境下 没有默认对齐数,此时对齐数就是成员自身大小

3.当成员全部存放后

结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。

如果不够,则浪费空间对齐。

值得一提的是,不止是最后浪费空间对齐,而是每次对齐的时候,如果不满足规则都要浪费空间对齐。

这个是上面例题的内存解释。

a占四个字节,从0到3,吧,b占一个字节,c由于要对齐,且本身大小为4个字节,故要从8开始,到12的前面,故最终内存大小为12。

此外,如果嵌套了结构体,则为另一种情况。

4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍

整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员中的对齐数。

如下所示

 此外,c语言中有一个宏叫offsetof是用来计算偏移量的。

头文件是#include<stddef.h>

struct S
{
	char c;
	int a;
};

int main()
{
	struct S s = { 0 };
	printf("%d\n", offsetof(struct S, c));//0
	printf("%d\n", offsetof(struct S, a));//4
	return 0;
}

为什么存在内存对齐呢?

 总的来说,内存对齐是拿空间换取时间的做法。

为了既要满足对齐,又要节省空间,我们应该:

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

例如下面的两段代码

struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

S1和S2类型的成员一模一样,但是前者所占空间更大。

 我们还可以使用#pragma这个预处理指令,改变我们的默认对齐数。

#pragma pack(8)//设置默认对齐数为8

struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原成默认

六、结构体传参

结构体传参有两种情况,一种是直接传结构体,另一种是传结构体地址。

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4},1000 };

//结构体传参
void printf1(struct S s)
{
	printf("%d\n", s.num);
};

//结构体地址传参
void printf2(struct S* ps)
{
	printf("%d\n", ps->num);
}

int main()
{
	printf1(s);//传结构体
	printf2(&s);//传地址
	return 0;
}

实际应用中使用第二个函数,函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。

即结构体传参时要传结构体地址。

七、结构体实现位段

位段是使结构体所占内存减少的一种结构。

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

1.位段的成员必须是int、unsigned int、signed int等整形家族的成员。

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

struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

位段的位指的是二进制位,冒号后面的表示所占的比特位。

比如说一个int类型的数据很小,只需要2个比特位就可以完成存储,这个时候为了节约空间,我们就会用到位段。

位段的内存分配:

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

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

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

上面是VS处理位段的情况,别的编译器不一定。

 位段的跨平台问题

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

总结

以上便是结构体的全部知识,希望对大家有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

north-shaddock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值