【C语言】新类型,结构体篇-----深入理解结构体,结构体成员的访问,结构体的大小和新概念偏移量!【图文详解】

欢迎来CILMY23的博客喔,本篇为【C语言】新类型,结构体篇-----深入理解结构体,结构体成员的访问,结构体的大小和新概念偏移量!【图文详解】,感谢观看,支持的可以给个一键三连,点赞关注+收藏。

 前言

C语言中的数据类型有内置类型,和用户自定义类型,内置类型包括char ,short,int,double,float,long long……,用户自定义类型包括,结构体,枚举,联合体,数组……本篇博客将会带大家了解用户自定义类型中的结构体。

目录

一、结构体的声明和使用

结构体的声明:

结构体的使用:

特殊的结构体声明: 

二、结构体的访问

 三、结构体大小

结构体嵌套的大小

为什么会有内存对齐?

四、offsetof宏和#pragma预处理指令

 五、结构体的自引用(涉及数据结构)


一、结构体的声明和使用

在过去我们经常使用数组存储同一类型的数据,但我们不仅仅只有同一类型的数据,于是C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它被称为结构体

结构体的声明:

struct Student
{
	int num;			//学号为整型
	char name[20];		//姓名为字符串
	char sex;		   //性别为字符型
	int age;		  //年龄为整型
	float score;      //成绩为浮点型
};

结构体类型的声明是用一个关键字struct +结构体名字,结构体名字是用户指定的。

 

typedef也可以对结构体重命名:

typedef struct Student
{
	int num;
}Stu;

int main()
{
	Stu n;
	return 0;
}

结构体的使用:

int main()
{
	struct Student s = {23,"zhangsan",'m',21,88.9};
	return 0;
}

这样我们就创建了一个结构体变量,这个变量的信息就如{}中所示。

我们也可以在声明结构体类型的时候声明变量

struct Student
{
	int num;			//学号为整型
	char name[20];		//姓名为字符串
	char sex;		   //性别为字符型
	int age;		  //年龄为整型
	float score;      //成绩为浮点型
}s1, s2;

这两种变量还是有差别的,s1,s2是全局变量,s只是局部变量 

 我们创建变量就相当于在内存中开辟了空间,这么多数据类型就构成一个s结构体变量,其中的分布如下:

特殊的结构体声明: 

 我们可以把结构体类型名称省去,这种情况叫做匿名结构体,用匿名结构体类型创建了一个s对象

struct
{
	int num;			
}s;

struct
{
	int num;			
}*ps;

int main()
{
	ps = &s;
	return 0;
}

 但是同样要注意,在vs编译器看来这两个匿名结构体每个都是单独的个体,我们并不能把s的地址给到指针ps。

二、结构体的访问

我们有两种访问方式,一种是.,一种是->

 我们把.和->叫做结构成员访问操作符

那如果我们得到的是s 的地址呢,就可以用到->操作符

int main()
{
	struct Student s = {23,"zhangsan",'m',21,88.9};
	printf("%d %s\n", s.num, s.name);
	struct Student* ps = &s;
	printf("%d %s\n", (*ps).num, (*ps).name);
	printf("%d %s\n", ps->num, ps->name);
	return 0;
}

结果如下:

 三、结构体大小

 首先我们来看以下代码:

struct s1
{
	char c1;
	char c2;
	int i1;
};

struct s2
{
	char c1;
	int i1;
	char c2;
};

struct s3
{
	double d;
	char c;
	int i;
};

int main()
{
	struct s1 st1;
	struct s2 st2;
	struct s3 st3;

	printf("%d\n", sizeof(st1));
	printf("%d\n", sizeof(st2));
	printf("%d\n", sizeof(st3));

	return 0;
}

结果如下:

那为什么会这样呢?这就涉及结构体的大小规则了-----内存对齐 

 内存对齐的规则:

1.结构体的第⼀个成员对齐到相对结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到对齐数的整数倍的地址处。
        对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
        VS中默认的值为8
        Linux中没有默认对齐数,对齐数就是成员自身的大小

3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

st1 

偏移量是什么?假设我们有一个内存,偏移量为1个字节就是指向1这个地方了,所以偏移量为0的话就是指从起始位置开始.(可以理解为距离起始位置有多远)

 所以第一个成员c1就存放在第一个字节里

其余成员要按照对齐数来对齐, 根据下面图片来看,c2只需要对齐到1的倍数即可,而i1需要对应到地址字节数4的倍数即可,也就是从第四个字节开始

如下所示: 

结构体总大小为最大对齐数,所有成员最大对齐数是4,st1的大小刚好是4的倍数,所以st1的结构体大小就为8 

 st2

所以我们看st2就容易多了

首先第一个成员要对齐偏移量为0的位置,c1的位置就确定了

其余成员要按照对齐数来对齐, 根据下面图片来看,c2只需要对齐到1的倍数即可,而i1需要对应到地址字节数4的倍数即可,也就是从第四个字节开始,

总的大小为9,结构体总大小为最大对齐数,所有成员最大对齐数是4,所以str2的大小为12.

 st3

首先第一个成员要对齐偏移量为0的位置,d的位置就确定了,其余成员要按照对齐数来对齐, 根据下面图片来看,c只需要对齐到1的倍数即可,而i需要对应到地址字节数4的倍数即可,也就是从第12字节开始。

总的大小为16,结构体总大小为最大对齐数,所有成员最大对齐数是8,所以str2的大小为16.

结构体嵌套的大小

struct S3
{
	double d;
	char c;
	int i;
};

struct s4
{
	char c1; 
	struct S3 s3; 
	double d;
};


int main()
{
	struct s4 st4;

	printf("%d\n", sizeof(st4));

	return 0;
}

结果如下:

 st4

如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数,也就是对齐到s3当中所有最大成员的对齐数,也就是十六的对齐数。

总的大小为16,结构体总大小为最大对齐数,所有成员最大对齐数是16,所以str2的大小为32.

为什么会有内存对齐?

1.    平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.    性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。 

最后,尽量将字节数小的成员安排在前面,防止空间浪费

四、offsetof宏和#pragma预处理指令

 offsetof宏是用来计算偏移量的,在cplusplus网站可以查询

offsetof - C++ Reference (cplusplus.com)

offsetof的使用如下: 

#include<stdio.h>
#include<stddef.h>

struct S3
{
	double d;
	char c;
	int i;
};

struct s4
{
	char c1; 
	struct S3 s3; 
	double d;
};

int main()
{
	printf("%zd\n", offsetof(struct s4, c1));
	printf("%zd\n", offsetof(struct s4, s3));
	printf("%zd\n", offsetof(struct s4, d));

	return 0;
}

结果如下:

 #pragma预处理指令

#pragma pack()//取消设置的默认对⻬数,还原为默认
struct S3
{
	double d;
	char c;
	int i;
};

#pragma pack(1)//设置默认对⻬数为1
struct s4
{
	char c1; 
	struct S3 s3; 
	double d;
};

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

一般使用都会将其设置成2,4,6,8,这样的数值。 

 五、结构体的自引用(涉及数据结构)

可以看以下这篇补充一下链表的结构知识点 ,然后再看结构体的自引用

http://t.csdnimg.cn/BDRBY

假设我们这里有个链表,定义⼀个链表的节点如下所示:

struct Node
{
	int num;
	struct Node next;
};

int main()
{
	struct Node n;
	return 0;
}

 仔细分析,其实是不行的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的,一个结构体中有一个Node,Node中又有一个Node如此无限循环下去,就会造成结构体变量的大小无穷大了。

正确的自引用应该如下所示:

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

int main()
{
	struct Node n;
	return 0;
}

 同样对tepedef我们仍然要注意以下情况是不能使用的

看以下两个代码对比例子:

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

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

我们得先有结构体类型重命名后,得到的名字,然后再去命名next。 

正确引用如下:

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

int main()
{
	Node n = {0};
	return 0;
}

感谢各位同伴的支持,本期结构体篇就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞关注+收藏,若有不足,欢迎各位在评论区讨论。  

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值