当前位置: 首页 > news >正文

C语言:自定义类型:结构体

目录

1. 前言

2. 结构体初识

3. 结构体创建变量

3.1 方法一

3.2 方法二

4. 结构体初始化

5. 结构体自引用

6. 结构体的大小

6.1 结构体对齐规则

6.2 常规结构体

6.3 结构体成员含数组

6.4 结构体嵌套结构体

6.5 为什么存在结构体对齐?

6.6 修改默认对齐数

7. 结构体传参

8. 结构体实现位段

8.1 位段基本讲解

8.2 位段的作用


1. 前言

C语言中有很多种类型,例如:
int ,float ,char ,double, long  ,long long ,long double等等,这些叫C语言的内置类型

但是有的时候描述一个人或物只用某一个类型是明显不够的

这时候C语言提供了自定义类型:结构体

2. 结构体初识

我们可以在结构体里面自己使用C语言的内置类型构造我们想要的自定义类型

比如我们需要描述一个学生

struct student
{char name[20];int num;int age;
};

学生有姓名,学号,年龄等等属性,并不是一个int或者char就能表示的,所以我们可以自定义一个类型,里面有姓名,学号,年龄,这些属于结构体的成员,这个struct student 就是我们自己定义的类型

结构体的最后有个分号一定不能漏掉

有了类型,我们就可以跟其它的内置类型一样,创建变量

3. 结构体创建变量

int a = 10;
char c = 'l';

上面我们就用了int 类型和 char类型创建了两个变量a 和 c,然后给它们赋值

结构体也是如此

3.1 方法一

struct student
{char name[20];int num;int age;
};int main()
{struct student stu;return 0;
}

我们用自己创建的类型后面加上变量名字,这样就创建好了一个变量stu

3.2 方法二

struct student
{char name[20];int num;int age;
}stu;

在创建好的结构体后面分号前面直接加变量名字即可

4. 结构体初始化

#include<stdio.h>
#include<string.h>struct student
{char name[20];int num;int age;
};int main()
{struct student stu;strcpy(stu.name, "xiaoming");stu.age = 18;stu.num = 1;return 0;
}

我们要在结构体变量的后面加个. 才能找到结构体里的成员

然后我们如果给name这个字符数组初始化的话需要使用string.h库里的strcpy来给它赋值

然后下面的age 和 num一样都是用 变量+.操作符找到成员

int 类型就正常赋值即可

然后可以正常打印输出了

printf("名字:%s\n", stu.name);
printf("学号:%d\n", stu.num);
printf("年龄:%d\n", stu.age);

5. 结构体自引用

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

上面的结构体成员里嵌套了一个自身结构体成员next

这个就是结构体嵌套,自引用

但是这样的写法是错误的❌

因为在我们计算结构体大小的时候,还要计算里面的next的大小,但是里面的next又包含了下一个data和下一个next,这样就无限循环下去了导致结构体大小无穷大,是不合理的

正确写法✔:

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

 这也是链表的写法

错误写法❌:

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

 因为这个结构体只有创建完成了才会重命名为Node,所以在创建完成前成员里使用Node是不对的

 正确写法✔:

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

6. 结构体的大小

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

猜猜上面代码的结果是什么?

如果你觉得char占1个字节,int占4字节,char占1个字节总共6个字节那么就掉坑了

结构体其实是有它自己的一个对齐规则的

6.1 结构体对齐规则

规则:

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

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

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

3.结构体大小为最大对齐数的整数倍(选所有对齐数中最大的那一个)

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

先来一个个解释一下

6.2 常规结构体

如果这是我们的内存空间,那么我们在存放的时候会先从结构体的第一个成员所占的空间开始占用第一个0

按照上面我们写的结构体,那么第一个char就会占用一个0

然后根据第二个对齐规则,其他成员要对齐到对齐数的整数倍

那么我们要先算对齐数,下一个是int类型,所以算int类型的对齐数

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

如果用的是VS,编译器默认对齐数就为8,这是固定的

该成员(int类型)大小为4个字节

所以选择小的作为对齐数所以对齐数就是4

所以我们要占用从4的整数倍开始的4个字节

下一个类型是char,char的字节是1,取最小值作为对齐数当然对齐数就是1了

所以下一个8就是1的倍数,所以直接占用即可

再根据第三个规则

我们现在全部的对齐数最大的为int类型的4,所以整个结构体的大小要为4的整数倍

但是我们看到上图中从0-8有9个字节占用了

所以我们还要再次浪费三个字节的空间满足这个规则

所以最终如图:

所以最后从0-11总共是用了12个字节,那么最终输出的结果当然就是12了

6.3 结构体成员含数组

如果是结构体成员里含数组的情况,其实跟常规的结构体一样

struct S1
{char c1;int i;char c2[5];
};
printf("%d\n", sizeof(struct S1));

 这个char数组其实没什么特别的,就把它看成5个char类型的东西即可

然后这5个char类型都是对齐数为1,然后最后再根据最大对齐数int的4对齐结构体总大小即可

如图:

所以是16个字节

下面来看看嵌套:

6.4 结构体嵌套结构体

struct S
{char c1;struct S1 s1;double d;
};

老样子char先占用0偏移量

然后我们上面已经计算过了结构体S1的最大对齐数为4,所以结构体S1对齐数为4

根据第4条对齐规则,可以画出如图:

然后double是8个字节,所以从8的倍数就是16开始到23的这个位置

在最后计算结构体总大小对齐的位置时,嵌套的结构体并不会用总大小参与,而是用它的成员来与其他成员比较,里面明显是double的大小最大,所以按8个字节的整数倍来定义结构体的大小

图中0-23正好是24(8的整数倍),所以最终答案是24

6.5 为什么存在结构体对齐?

我们看到上面的每一个结构体几乎都浪费了很多空间,但这么做其实是有好处的

这是一个以空间换时间的做法

假设一个处理器总是一次性从内存中取8个字节,则拿到的地址对应的都是8的倍数

如果我们能够保证能将double类型的数据都对齐成8的倍数

那么处理器一次扫描就能够将整个double全部取出

而不是扫描两次

这样效率就快了很多

如果我们想要两者兼得,如何做到呢?

目前没什么特别好的方法,但是我们可以尽可能让空间小的成员尽可能的聚在一起

例如:

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

它们两个的成员一模一样,但是

第一个结构体需要12个字节的空间,而第二个只需要8个

6.6 修改默认对齐数

 我们其实是可以自己更改默认对齐数的

#pragma pack(1)//默认对齐数改为1
struct S1
{char c1;int i;char c2;
};
#pragma pack()//取消修改默认对齐数int main()
{printf("%d\n", sizeof(struct S1));
}

结构体成员的对齐数都为1那结构体的总大小就是全部成员的大小相加

所以答案为: 

7. 结构体传参

结构体也是和其他内置类型一样作为参数传递给函数的

我们之前有写到这个代码

#include<stdio.h>
#include<string.h>struct student
{char name[20];int num;int age;
};int main()
{struct student stu;strcpy(stu.name, "xiaoming");stu.age = 18;stu.num = 1;return 0;
}

现在可以用一个Print函数来打印试试看

#include<stdio.h>
#include<string.h>struct student
{char name[20];int num;int age;
};void Print(struct student s)
{printf("%s\n", s.name);printf("%d\n", s.age);printf("%d\n", s.num);
}int main()
{struct student stu;strcpy(stu.name, "xiaoming");stu.age = 18;stu.num = 1;Print(stu);return 0;
}

我们创建了一个 struct student类型的形参s

然后对这个形参进行打印

在传参的时候,我们只需要将我们之前初始化过的 stu传进去即可 

这样一样是可以的

8. 结构体实现位段

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

我们在成员之后加了个:+数字就是位段

什么意思呢?

8.1 位段基本讲解

这些数字表示当前成员所占用的bit的大小

总所周知,一个int类型为4个字节也就是32个bit

所以我们:后面的数字一定不能超过32,因为一个int类型最多32个bit位

8.2 位段的作用

它可以帮我们节省空间,如果我们要开装下成员a和b和c,那么我们只需要开一个int类型即可

32个空间足以装下 2+5+10 = 17的空间

注意事项:
位段尽量不要跨平台使用,因为C语言并未完全的定义位段如何使用,所以不同的编译器都会有它自己的处理方式,所以不要跨平台使用!!!


相关文章:

  • Autosar的前世今生:E2E通信校验
  • elementUI(Vue2)和elementPlus(Vue3)图标icon差异
  • VB.NET 中的异常处理机制是什么?请提供简单的异常处理示例
  • OpenLayers基础教程——使用WebGL加载海量数据(1)
  • Docker学习笔记 - 使用配置脚本来启动image
  • golang常用库之-golang常用库之-ladon包 | 基于策略的访问控制
  • docker入门(十)—— docker-compose详解
  • stm32定时器
  • scDEA一键汇总12种单细胞差异分析方法 DESeq2、edgeR、MAST、monocle、scDD、Wilcoxon
  • K8S Storage
  • PYTorch训练和推理 指定GPU
  • docker swarm 集群创建
  • Go语言学习04~05 函数和面向对象编程
  • c++算法学习笔记 (15) 质数
  • 新手如何入门电子电路
  • 分享一款快速APP功能测试工具
  • 《深入 React 技术栈》
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • CSS盒模型深入
  • E-HPC支持多队列管理和自动伸缩
  • ES10 特性的完整指南
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • Java Agent 学习笔记
  • PHP面试之三:MySQL数据库
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • Transformer-XL: Unleashing the Potential of Attention Models
  • vue-cli在webpack的配置文件探究
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 基于Android乐音识别(2)
  • 聚类分析——Kmeans
  • 那些年我们用过的显示性能指标
  • 前端之React实战:创建跨平台的项目架构
  • 数据仓库的几种建模方法
  • 线性表及其算法(java实现)
  • hi-nginx-1.3.4编译安装
  • #《AI中文版》V3 第 1 章 概述
  • $(function(){})与(function($){....})(jQuery)的区别
  • (145)光线追踪距离场柔和阴影
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (二)PySpark3:SparkSQL编程
  • (附源码)ssm码农论坛 毕业设计 231126
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (学习日记)2024.02.29:UCOSIII第二节
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)编辑寄语:因为爱心,所以美丽
  • .NET MVC 验证码
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • /var/spool/postfix/maildrop 下有大量文件
  • @Bean注解详解
  • [20190416]完善shared latch测试脚本2.txt
  • [android] 请求码和结果码的作用
  • [AR Foundation] 人脸检测的流程
  • [BT]BUUCTF刷题第4天(3.22)