C语言——自定义类型——结构体(从零到一的跨越)

目录

前言

1.什么是结构体

2.结构体类型的声明

2.1结构体的声明

2.2结构体的创建和初始化

2.3结构成员访问操作符

2.3.1结构体成员直接访问

2.3.2结构体成员的间接访问

2.4结构体变量的重命名

2.5结构体的特殊声明

2.6结构的自引用

3.结构体内存对齐

3.1对齐规则

3.2为什么存在内存对齐

3.3修改默认对齐数

4.结构体传参

5.结构体实现位段

5.1位段的声明

5.2位段的内存分配

5.3位段的注意事项



前言

          在学习结构体之前,我们还学习了char ,int,short,float,double等内置类型,他们可以描述一些事物的某一项属性,但是如果我想描述一本书的某些属性,而不是单单的一种属性,该怎么办呢?这时候就会用到自定义类型——结构体,我们就可以创造出属于我们自己的类型。

1.什么是结构体

           结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。也就是说结构体是由一些内置类型构成的,这些类型表示事物的某些属性。

2.结构体类型的声明

2.1结构体的声明

struct book
{
	char name[100];//书名
	int price;//价格
	char author[100];//作者
};//分号不能丢

struct是结构体的标志,book是我们自定义的结构体的名字,{}里面的是成员变量

2.2结构体的创建和初始化

按照顺序初始化

struct book b1 = { "红心照耀中国",100,"埃德加·斯诺" };

指定顺序初始化

struct book b2 = { .author = "卡尔·马克思、弗里德里希·恩格斯",.name = "共产党宣言",.price = 50 };

struct book b1和struct book b2是结构体的创建,后面的{}是对这个结构体进行初始化,struct book相当于之前学习的char和int,b1和b2就是变量名。

以上两种是在声明结构体之后进行的初始化,除此之外还可以在声明结构体的时候进行初始化 ,就像这两种方法这样

	struct book
{
	char name[100];
	int price;
	char author[100];
}b1 = { "红心照耀中国",100,"埃德加·斯诺" };



	struct book
{
	char name[100];
	int price;
	char author[100];
}b2 = { .author = "卡尔·马克思、弗里德里希·恩格斯",.name = "共产党宣言",.price = 50 };

结构体变量的创建和初始化还可以分开进行

比如这个样子:

#include<stdio.h>
struct book
{
	char name[100];
	int price;
	char author[100];
}x;//创建变量

int main()
{
	struct book x = { "红星照耀中国",100,"埃德加·斯诺" };//初始化
	return 0;
}

2.3结构成员访问操作符

结构体变量创建出来是为了使用的,那他会不会像int,double等类型的使用方法一样呢,关于结构体的使用,这里鱼哥给大家介绍两个操作符(.)结构体成员直接访问,(->)结构体成员简介访问

2.3.1结构体成员直接访问

#include<stdio.h>
struct book
{
	char name[100];
	int price;
	char author[100];
};
int main()
{
	struct book b1 = { "红心照耀中国",100,"埃德加·斯诺" };
	printf("%s\n", b1.name);
	printf("%d\n", b1.price);
	printf("%s\n", b1.author);
}

使用方法:变量名.成员名

2.3.2结构体成员的间接访问

#include<stdio.h>
struct book
{
	char name[100];
	int price;
	char author[100];
}b1 = { "红心照耀中国",100,"埃德加·斯诺" };
int main()
{
struct book *ptr = &b1;
ptr->price = 10;
	printf("%d\n", b1.price);
	return 0;
}

使用方法:结构体指针->成员名

2.4结构体变量的重命名

typedef struct book
{
	char name[100];
	int price;
	char author[100];
}Book;
使用typedef对struct book进行重命名,Book就相当于原来的struct book,也就是他们两个的效果是一样的

2.5结构体的特殊声明

struct 
{
	char name[100];
	int price;
	char author[100];
}x;

上面这种声明属于匿名结构体声明,也就是在结构体声明的时候省略了标签(book)

注意:匿名结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次

2.6结构的自引用

结构体出来能装内置类型,可不可以装结构体呢?

struct book2
{
	int x;
	int y;
};
 struct book
{
	char name[100];
	int price;
	char author[100];
	struct book2 x1;
};

这样写是OK的,我们可以算出他所占内存空间的大小

能装别的结构体不算nb,如果他能装自己才叫nb

 struct book
{
	char name[100];
	int price;
	char author[100];
	struct book x1;
};

那么这样写是正确的吗?如果正确,那么他所占内存空间的大小是多少?

经过分析,这样写结构体会无限嵌套下去,这样是无法计算出结构体的大小,他的大小就会无穷大,所以这样的写法是错误的

那么有没有正确的写法呢?答案是有的

 struct book
{
	char name[100];
	int price;
	char author[100];
	struct book *next;
};

我们可以这样写,既然结构体不能嵌套结构体,那我结构体装结构体指针,通过结构体指针找到结构体


在结构体自引用使用的过程中,掺杂着typedef对匿名结构体类型的重命名,也是容易出现问题的

typedef struct
{
int x;
Book*next;
}Book;

这样的写法也是不行的,因为Book是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Book来创建成员变量,这是不行的

3.结构体内存对齐

3.1对齐规则

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

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

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

    --VS中默认的值为8

    --Linux中gcc没有默认的对齐数,对齐数就是成员自身的大小

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

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

光看规则肯定是学不明白的,接下来鱼哥给大家举一下例子帮助大家理解

例1.

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

这个结构体的大小是多少?大家可以自己先算一算。

答案是12,怎么算出来的呢

有人会想,c1一个字节,i四个字节,c2两个字节,应该6个字节才对啊

数字代表偏移量

c1是一个字节,并且要对齐到偏移量为0的地址处

i是四个字节,4小于8,所以对齐数是4,所以i要存储到4的整数倍处

c2是一个字节,对齐数是1,所以c2存到偏移量为8的地址处

最大对齐数是4,所以结构体的总大小是4的倍数,而c1,i,c2占9个字节,所以结构体的大小要大于等于9并且是4的倍数,所以这个结构体的大小是12个字节

 例2.

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

 这个结构体的大小是8,你会想,这个结构体和例1的成员变量都一样,只是位置不同,凭什么这个的内存就要小一点呢?

c1从偏移量为0的地址处开始存储

c2的对齐数是1,偏移量1是1的倍数,所以1处存c2

i的对齐数是4,所以i从4的位置开始存储

c1,c2,i占8个字节,而最大对齐数是4,8是对齐数的整数倍,所以这个结构体的大小是8个字节

例3.

struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));

这个结构体的大小是16,有了前面两个例子,鱼哥相信你应该可以算出结果

d从0开始存储,字节大小是8

c的对齐数是1,存储在偏移量为8的位置

i的对齐数是4,存储在偏移量为12的位置

d,c,i占的字节数为16,16又是最大对齐数8的整数倍,所以结构体的大小为16个字节

例4.

struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));

s3是例3的结构体,这个s4结构体的大小是32

c1从偏移量为0开始

s3的最大对齐数是8,所以s3从偏移量为8的位置开始存储,因为例3已经算出了s3的大小,所以s3存16个字节

d的对齐数是8,24是8的倍数,所以d从24开始存储

c1,s3,d占32个字节,32又是8的整数倍,所以s4的大小是32个字节

3.2为什么存在内存对齐

1.平台原因(移植原因):

     不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

2.性能原因:

      数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中

总的来说:结构体的内存对齐是拿空间换时间 

对例1和例2分析,我们发现如果让占用空间小的成员尽量集中在一起可以起到节省空间的效果

3.3修改默认对齐数

VS的编译器的默认对齐数是8,在某些情况下,我们觉得默认对齐数不合适,想要修改,就会用到#pragma这个预处理指令

#include<stdio.h>
#pragma pack(2)
struct S
{
	char c1;
	int a2;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct S));
	return 0;
}

这里将默认对齐数改为2,大家可以通过前面的学习自己算一下答案

学会修改也要学会恢复,怎么做呢?看接下来的代码

#include<stdio.h>
#pragma pack(1)
struct S
{
	char c1;
	int a2;
	char c2;
};
#pragma pack()
int main()
{
	printf("%d\n", sizeof(struct S));
	return 0;
}

4.结构体传参

#include<stdio.h>
struct S
{
	int arr[100];
	int x;
};

struct S s = { {1,2,3,4,5},7 };
//结构体传参
void print1(struct S n)
{
	printf("%d\n", n.x);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->x);
}

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

上面这个代码进行了两个结构体的传参,那么哪一种传参更好一点呢?

答案是print2的传参更好一点

原因是:

函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。

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

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

5.结构体实现位段

5.1位段的声明

struct S
{
int a:2;
int c:3;
int f:24;
};

上述代码就是一个简单的位段声明,是不是感觉和结构体比较像

那么他和结构体有什么区别呢?

1.位段的成员必须是int,unsigned int,signed int (整形家族的),在C99中也可以是其他类型

2.位段的成员名后面是一个冒号加一个数字(这个数字代表比特位数)

5.2位段的内存分配

位段的空间上是按照4个字节或者1个字节的方式来开辟的

#include<stdio.h>

struct S
{
	char a : 2;
	char b : 4;
	char c : 5;
	char d : 6;
};
struct S s = { 0 };
int main()
{
	s.a = 10;
	s.b = 12;
	s.c = 13;
	s.d = 14;
	printf("%d", sizeof(struct S));
	return 0;
}

结果为3,来看看数据是怎么在内存中存储的吧

·首先申请一个字节的空间也就是8个比特位,a:两个比特位,将a的值转换位二进制,然后存低位的两个存进去,b是4个比特位,因为申请了8个比特位还剩6个,够用,就接着存,c是5个比特位,不够了,就舍去,重新申请一个字节,存放5个比特位,剩三个,不够d的6个bit位,就有申请一个字节存放d,所以总的申请了3个字节

总结:跟结构体相比,位段可以达到同样的效果,并且可以很好的节省空间

5.3位段的注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/472353.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

保护王国的钥匙:探索特权访问管理 (PAM) 的深度

在零信任架构的范例中&#xff0c;特权访问管理&#xff08;PAM&#xff09;正在成为网络安全策略的关键组成部分&#xff0c;旨在控制和监控组织内的特权访问。本文深入探讨了 PAM 在现代网络安全中的关键作用&#xff0c;探讨了其原理、实施策略以及特权访问的演变格局。 什么…

3.20作业

1、思维导图 2、 1> 创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;、姓名、年龄、薪资。 2> 添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff09; 3> 修改某一个工人的薪资&#xff08;确定的一个&am…

机器学习_聚类(Clustering)

文章目录 简介K-均值算法(K_Means) 简介 你经常跟哪些人联系&#xff0c;而这些人又经常给哪些人发邮件&#xff0c;由此找到关系密切的人群。因此&#xff0c;这可能需要另一个聚类算法&#xff0c;你希望用它发现社交网络中关系密切的朋友。 K-均值算法(K_Means) K-均值是…

Cesium新版修改源码后,编译不生效问题

最新版本的cesium源码在编译时默认使用node_models下的cesium/engine&#xff0c;从而导致咱们修改项目中的源码并不会生效 解决方式 &#xff1a; 进入到实际的源码位置&#xff0c;执行npm link 在返回到源码的根目录下执行 npm link ./packages/engine

​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结

接上次博客&#xff1a;Redis&#xff08;四&#xff09;&#xff1a;持久化和事务&#xff1a;RDB&#xff08;定期备份&#xff09;【触发机制、流程说明、文件的处理、优缺点】、AOF&#xff08;实时备份&#xff09;【使用AOF、命令写入、文件同步、重写机制、启动时数据恢…

DEiT中如何处理mask数据的?与MAE的不同

在DeiT里面&#xff0c;是通过mask的方式&#xff0c;将maskunmasked的patches输出进ViT中&#xff0c;但其实在下游任务输入的patches还是和训练时patches的数量N是一致的&#xff08;encoder所有的patches&#xff09;。 而MAE是在encoder中只encoder未被mask的patches 通过…

蓝桥杯java组 螺旋折线

题目描述 如图所示的螺旋折线经过平面上所有整点恰好一次。 对于整点(X, Y)&#xff0c;我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。 例如dis(0, 1)3, dis(-2, -1)9 给出整点坐标(X, Y)&#xff0c;你能计算出dis(X, Y)吗&#xff1f; 【输入格…

直播预告|Sora 会怎样驱动视频编解码领域的突破与革新

在数字化时代&#xff0c;视频内容的传播与消费已成为日常生活的一部分。视频编解码技术是数字媒体领域的一项核心技术&#xff0c;它影响着视频质量&#xff0c;传输速度以及观看体验。与此同时&#xff0c;视频产业正在经历一场由技术驱动的变革&#xff0c;Sora、AIGC 等相关…

通用组件封装——iconfont 封装图标组件

文章目录 背景一、iconfont 处理1. 一键添加入库功能2. 图标项目配置 二、代码实现 背景 在项目中会使用到大量的图标&#xff0c;而 element 等组件库现有的 icon 图标可能无法满足项目的需要&#xff0c;比如很多图标没有可以替代的&#xff0c;或者项目中有彩色图标的需求都…

前端VUE笔记整理

一&#xff1a;PDA H5 1、对于PDA用到的三个命令说明: npm install: 根据package.json安装依赖文件到node_modules文件夹下(如果是第一次可以删除此文件夹下的文件&#xff0c;这个目录不会上传) ​ npm run serve: 运行PDA程序在本地做客户端 ​ npm run build: 打包文件到d…

【CSP】2020-12-2 期末预测之最佳阈值 排序+差分+前缀和

2020-12-2 期末预测之最佳阈值 排序差分前缀和 索引2020-12-2 期末预测之最佳阈值 排序差分前缀和思路遇到的问题完整代码 索引 历年CSP认证考试真题题解总汇持续更新 2020-12-2 期末预测之最佳阈值 排序差分前缀和 这题并不算难&#xff0c;但也不是直接套公式那么简单&…

SpringBoot3框架,事件和监听器、SPI

事件和监听器 生命周期监听 自定义监听器的步骤&#xff1a; 编写SpringApplicationRunListener实现类&#xff08;各个实现方法的功能写在其sout内&#xff09; public class MyAppListener implements SpringApplicationRunListener {Overridepublic void starting(Configu…

git 安装、创建仓库、常用命令、克隆下载、上传项目、删除分支 -- 一篇文章总结

一、git安装 1、git安装地址&#xff1a;https://git-scm.com/downloads 2、选择操作系统 3、安装自己系统对应的操作位数 4、等待下载完&#xff0c;一路next安装就可以了 5、安装完成后&#xff0c;在任意文件夹点击右键&#xff0c;看到下图说明安装成功 二、创建仓库 1…

法语「奶奶」明明是阴性,为什么不用配合?柯桥法语口语学习小语种学校

咦&#xff0c;法语中“奶奶”到底怎么写&#xff1f;是Grande-mre还是Grand mre&#xff1f;又或者 Grand-mre ? 先写下你的回答&#xff0c;法语君再公布答案哦&#xff01; 面对这个问题&#xff0c;你已经开始犹豫了对不对&#xff1f; 那么在法语中&#xff0c;到底哪一个…

蓝桥杯之动态规划冲刺

文章目录 动态规划01背包小练一下01背包网格图上的DP完全背包 最长公共字符串最长递增子序列 动态规划 动态规划&#xff1a;确定好状态方程&#xff0c;我们常常是确定前 当状态来到 i 时&#xff0c;前 i 个物体的状态是怎么样的&#xff0c;我们并不是从一个点去考虑&#x…

Docker部署JumpServer3.9.0

简介 JumpServer 是什么&#xff1f; JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpServer 帮助企业以更安全的方式管控和登录所有类型的资产&#xff0c;实现事前授权、事中监察、事后审计&#xff0c;满足等保合规要求。 Jump…

C/C++炸弹人游戏

参考书籍《啊哈&#xff0c;算法》&#xff0c;很有意思的一本算法书&#xff0c;小白也可以看懂&#xff0c;详细见书&#xff0c;这里只提供代码和运行结果。 这里用到的是枚举思想&#xff0c;还有更好地搜索做法。 如果大家有看不懂的地方或提出建议&#xff0c;欢迎评论区…

如何将Excel两列数据转换为统计图、曲线图、折线图?如何自定义某一列作为Excel的统计图横纵坐标?

这样&#xff0c;横坐标就更换为指定选中的数据了 我们还可以修改统计图的样式 也可以修改统计图的类型

cordova安装安卓版本,遇到的各种坑。折腾了两天才弄好

cordova官网地址 https://cordova.apache.org/docs/en/12.x/guide/cli/index.html 1. 输入命令 npm install -g cordova 全局安装cordova 2. 创建文件和项目以及app的应用名称 cordova create hello com.example.hello HelloWorld 我写的是这个 cordova create myApp 3.co…

深入浅出Hive性能优化策略

我们将从基础的HiveQL优化讲起&#xff0c;涵盖数据存储格式选择、数据模型设计、查询执行计划优化等多个方面。会的直接滑到最后看代码和语法。 目录 引言 Hive架构概览 示例1&#xff1a;创建表并加载数据 示例2&#xff1a;优化查询 Hive查询优化 1. 选择适当的文件格…