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

news2024/11/24 22:56:58

目录

前言

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直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker harbor.v2.9.2搭建镜像无法下载问题解决

在通过部署docker harbor时&#xff0c;采用的是离线包的方式&#xff0c;当解压压缩包后&#xff0c;执行prepare脚本步骤中有一步是要获取prepare:v2.9.2版本镜像 结果执行脚本时报如下错误&#xff1a; Unable to find image goharbor/prepare:v2.9.2 locally 这时候我们就…

网络: DHCP 协议简介

文章目录 1. 前言2. DHCP 协议简介2.1 DHCP 客户端广播 DHCPDISCOVER 消息2.2 DHCP 服务器回复 DHCPOFFER 消息2.3 DHCP 客户端广播 DHCPREQUEST 消息2.4 DHCP 服务器回复 DHCPACK 消息2.5 剩余的工作 3. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&…

【Linux】shell命令运行原理---认识Linux基本指令

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.shell命令以及运行原理 1.1 shell命令 1.2 Linux内核权限 1.3 图示Linux shell和bash的区别 2.认识Linux基本指令 2.1 指令的…

Vue.js前端开发零基础教学(一)

目录 第一章 初识Vue.js 前言 开发的好处 一.前端技术的发展 什么是单页Web应用&#xff1f; 二. Vue的简介 三. Vue的特性 四. Vue的版本 五.常见的包管理 六.安装node环境 第一章 初识Vue.js 学习目标&#xff1a; 了解前端技术的发展 了解什么是Vue掌握使用方…

Zero-Shot Learning with Joint Generative Adversarial Networks 中文版

目录 摘要介绍1.研究背景和意义2.先前的模型提出了什么方法&#xff1f;解决了什么问题&#xff1f;有什么不足&#xff1f;3.最近的研究提出了什么方法&#xff1f;解决了什么问题&#xff1f;4.最新的研究提出了什么方法&#xff1f;解决了什么问题&#xff1f;有什么不足&am…

关于自己Nginx的使用(ant design pro 部署)

一 原因 工作需要部署 ant design pro 框架开发的前端程序&#xff0c;并且需要有用到代理。就选择了nginx部署。 二 使用nginx部署 ant design pro 框架程序 1. 前端项目打包 &#xff08;1&#xff09;打包命令&#xff1a;npm run build 或者 yarn bulid &#…

SAP STMS请求重复传输

STMS 在接请求的导入的时候&#xff0c;第一次发生了错误&#xff0c;在修复了错误之后&#xff0c; 该请求二次导入显示已经该请求已全部导入 可以按如下操作进行再次导入 附加--》其他请求--》添加 输入请求号并勾选再次导入 然后点选需要重复导入的请求号即可再次导入

Redisinsight默认端口改成5540了!网上的8001都是错误的

Redisinsight 打开白屏解决方法 最近发现一个很讨厌的bug&#xff0c;就是redisinsight运行之后&#xff0c;不行了&#xff0c;在网上找到的所有资料里面&#xff0c;redis insight都是运行在8001端口&#xff0c;但是我现在发现&#xff0c;变成了5540 所以对应的docker-com…

华为ensp中rip动态路由协议原理及配置命令(详解)

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言————— RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;是一种距离矢…

什么样才叫计算机?

我和小宇早恋了&#xff0c;我们家住隔壁。 一、编码与电路——信号的转换 晚上父母会把手机没收&#xff0c;但我们还想继续聊天&#xff0c;又不敢发出声音&#xff0c;于是我们想到了这个办法... 我们把所有的中文都用灯泡的亮灭组合来表示&#xff0c;同时约定好每隔一秒读…

IDA反汇编工具详解之工程和窗口

文章目录 什么是反汇编反汇编的目的ID介绍打开创建工程IDA的基本规则窗口介绍反汇编窗口Names窗口Strings窗口十六进制窗口导出窗口导入窗口函数窗口结构体窗口枚举窗口段窗口签名窗口类型库窗口函数调用窗口问题窗口 什么是反汇编 程序员使用编译器、汇编器和链接器中的一个或…

位图与布隆过滤器

目录 一、位图 1、问题用位图来解决&#xff1a; 二、 布隆过滤器 1、将哈希与位图结合&#xff0c;即布隆过滤器 2.布隆过滤器的查找 3.布隆过滤器的删除 4.布隆过滤器优点 5、布隆过滤器缺陷 三、海量数据处理问题&#xff1a; 一、位图 问题1&#xff1a;给40亿个不…

【C++】详解 INT_MAX 和 INT_MIN(INT_MAX 和 INT_MIN是什么?它们的用途是什么?如何防止溢出?)

目录 一、前言 二、什么是 INT_MAX 和 INT_MIN &#xff1f; 三、INT_MAX 和 INT_MIN 的用途 四、如何避免溢出问题出现 &#xff1f; 五、 INT_MAX 和 INT_MIN 的运算 六、leetcode 常考面试题 七、共勉 一、前言 大家在平时刷 leetcode 的时候&#xff0c;肯定会碰到 溢出…

谷歌seo网络营销哪家好?

对于一个好的服务商的评判标准其实不难&#xff0c;保证结果&#xff0c;服务透明化&#xff0c;专业的服务&#xff0c;专业的指导&#xff0c;但怕就怕在你什么都不懂&#xff0c;只看重短期的结果&#xff0c;不懂谷歌seo的基础 一些做谷歌seo的反面例子也是需要了解的&…

上位机图像处理和嵌入式模块部署(qmacvisual三维测量)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在qmacvisual软件里面&#xff0c;关于三维测量方面的内容讲的比较少。目前来说只有一个插件完成这个功能。这可能也和作者自己当时的开发环境有关…

springcloud-Nacos 更强大的注册中心组件

Nacos 实际上从设计思想来说 Eureka 和 nacos 是一样的。 后者是Alibaba推出的 一款更强大 功能更丰富的注册中心 你可以理解为Eureka的高配版 技多不压身既然了解了 Eureka, nacos也来学习一下吧&#xff01; 安装 首先nacos不像eureka 直接pom里面引个依赖就搞定了&#…

查看angular版本的问题The Angular CLI requires a minimum Node.js version of v18.13.

angular版本与node.js版本不匹配的问题 下载安装angular 查看版本&#xff0c;发现不匹配 安装指定版本即可 查看版本并运行

diandian数据聚合平台参数分析(水)

diandian数据聚合平台参数分析&#xff08;水&#xff09; 链接地址&#xff1a;‘暂无’&#xff08;懂的都懂&#xff09; 1. 打开网页链接&#xff0c;f12 打开控制台&#xff0c;任意搜索。 2 经过对比分析 需要分析参数key 3 通过debugger分析回溯 发现以下参数生成位置 …

许战海战略文库|向宗老致敬!祝娃哈哈未来三十年行稳致远

摘要&#xff1a;许战海咨询对宗老先生的崇高敬意与对民族品牌的坚定支持,许战海咨询运用其独特的战略视角深入剖析产品战略&#xff0c;旨在帮助娃哈哈有效利用自身的竞争优势,打造爆品,实现进一步的高速增长。 娃哈哈品牌当前所面临的种种挑战,其根源在于缺乏明确和有力的主…