【C语言】结构体

本文详细介绍了C语言中结构体的概念、声明方式,以及内存对齐规则、结构体传参策略和位段的使用,包括其声明须知、作用、内存分配和应用场景,特别强调了在实际编程中的注意事项。
摘要由CSDN通过智能技术生成

 

目录

概念

声明

举例

特殊声明

自引用

内存

内存对齐规则

结构体传参

结构体实现位段

声明须知

作用

位段的内存分配

应用场景

使用须知


概念

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

声明

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

member-list是结构体成员清单

variable-list是变量清单

举例

一个有关图书的结构体,包含名字,编号,价格这三个成员

struct Book
{
	char name[20];
	char number[20];
	double price;
};

那么我们在主函数要怎么引用呢?

下面进行一个具体的使用,大家通过代码来参考

#include<stdio.h>
struct Book
{
	char name[20];
	char number[20];
	double price;
};
int main()
{
	struct Book b1 = { "ABCD","A0001",9.9 };
	struct Book b2 = { "EFGH","A0002",19.9 };
	printf("%s %s %f\n", b1.name, b1.number, b1.price);
	printf("%s %s %f", b2.name, b2.number, b2.price);
	return 0;
}

我们可以在主函数中声明,也可以在所有函数外声明成一个全局变量

声明过后要跟上变量名称,即上面的“b1”和“b2”,此时通过b1和b2就能找到两者分别代表的结构体及其内容

我们同样可以把结构体的变量名称写在结构体的声明后面,如下

#include<stdio.h>
struct Book
{
	char name[20];
	char number[20];
	double price;
}b1,b2;
int main()
{
	b1 = { "ABCD","A0001",9.9 };
	b2 = { "EFGH","A0002",19.9 };
	printf("%s %s %f\n", b1.name, b1.number, b1.price);
	printf("%s %s %f", b2.name, b2.number, b2.price);
	return 0;
}

此时的输出的结果和上面是一样的


特殊声明

在声明结构体时,可以不完全的声明,如下

struct
{
	char name[20];
	char number[20];
	double price;
};

我们把结构体的标签(Book)省去了

在这种情况下,若没有对匿名的结构体类型重命名,通常情况下只能使用一次

也就造成了下面的代码会报错

#include<stdio.h>
struct 
{
	char name[20];
	char number[20];
	double price;
}b1;
struct 
{
	char name[20];
	char number[20];
	double price;
}*p;
int main()
{
	p = &b1;
	return 0;
}

p=&b1这一步出错,因为编译器会把上面两个声明当成2个不同的类型


自引用

常用于一个链表的节点,在一个结构体中写上下一个结构体的指针,如下

struct Book
{
	char name[20];
	char number[20];
	double price;
	struct Book* next;
};

注意:自引用这一步不能写成struct Book next,这样写会导致该结构体的大小无穷大,编译器会报错

同时在使用了typedef重命名的匿名结构体中也不能进行自引用,如下:

typedef struct 
{
	char name[20];
	char number[20];
	double price;
	book* next;
}book;

因为book是在结构体后才重命名的,但在匿名结构体中却已经用到了

这种情况就像命令一个人去找他的儿子,给的命令是去找张三的儿子,但是这个人此时还不叫张三,他就不能找到儿子

解决办法:此时定义结构体不要用匿名结构体,更正如下

typedef struct book
{
	char name[20];
	char number[20];
	double price;
	struct book* next;
}book;

内存

内存对齐规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处

2.其他成员变量要对⻬到对⻬数的整数倍的地址处。

        对⻬数=编译器默认的⼀个对⻬数与该成员变量大小的较小值

        VS 中默认的值为8,Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的大小

3. 结构体总大小为最大对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对齐数中最大

    的)的整数倍

4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最⼤对齐数的整数倍处,结构体的整体大小就是所有最⼤对齐数(含嵌套结构体中成员的对齐数)的整数倍

 下面我们举一个例子来具体说明(VS环境)

struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

上面的输出结果为:12

我们用一张图来说明为什么是12而不是6

首先我们的第一个成员c1是char型,占一个字节,第0位就是c1的位置

第二个成员i是int型,我们计算一下它的对齐数:int型占4个字节,VS对齐数默认为8,4<8,所以i的对齐数就是4,我们就需要对齐到偏移量为4的整数倍的地址(即4),所以4,5,6,7这四个字节放的就是i

第三个成员c2是char型,占一个字节,1<8,接下来我们就需要对齐到偏移量为1的整数倍的地址(即接下来的第8位)

所以三个成员存放的地方分别是:c1->0     i->4,5,6,7     c2->8

此时我们占了0-8的地址,一共9个字节,但是由于规则:结构体的大小为最大对齐数的整数倍

此时的对齐数分别是4和1,那就必须是4的倍数,2*4<9<3*4

所以该结构体所占的空间是12字节(1,2,3,9,10,11位置为空)

修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对⻬数

#pragma pack(1)

在VS中上面这个代码可以将默认的对齐数(8)改成1,下面的代码可以恢复默认

#pragma pack()

注意:更改后对结构体使用strlen时,数值有可能出现变化

结构体传参

我们在需要传递结构体的内容时,优先使用传址调用,尽量少使用传值调用

原因:函数传参时,参数需要压栈,有时间和空间上的系统开销,若结构体过大,则会浪费时间和空间,导致性能的下降

结构体实现位段

声明须知

1.位段的成员可以是int , unsigned int , signed int 或者是char 等类型

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

作用

位段可以修改成员所占的空间,专门用来节省内存

比如在结构体中我把int a;修改成int a:2,此时a只占2个bit位,a存的数据超过2个bit位时会省略前面的数据只取2个bit位的内容

在实际使用中,要根据具体情况决定位段的大小

位段的内存分配

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

2.位段存在许多不确定因素,在跨平台开发(使用不同编译器)时要避免使用位段

部分不确定因素:

1.int位段被当做有符号数还是无符号数是未知的

2.位段中最大位的数目不能确定(32位机器和64位机器就有差别)

3.申请的一块内存中,从前向后使用还是从后向前使用,方向取决于编译器

4.当一块内存的空间不足下一位成员使用时,另外开辟空间还是继续使用?

应用场景

在某些网络协议中,某些IP传输数据只需几个bit,此时使用位段就能节省空间,从而提升了网速

使用须知

位段的几个成员共有同⼀个字节,因此有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。

内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输⼊值,只能是先输入放在⼀个变量中,然后赋值给位段的成员

比如这样一个结构体

struct S1
{
	char c : 2;
}b1;

直接使用scanf("%c",&b1.c); 就是错误的

需要先写 char a;    然后输入给它    scanf("%c",&a);   最后把a的值赋值给目标   b1.c = a;

花了好久才把结构体部分的知识整理清楚,对齐规则部分写的稍微有点差劲,还望大家海涵

有不足之处欢迎指出

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值