结构体(C保姆级讲解)

前言:

        为什么会有结构体,结构体可以用来面熟一个复杂对象,我们知道C语言中有哪些数据类型,有整型,有浮点型,有字符型,但是在生活中,我们需要描述一些比较复杂的东西,比如说一本书,有书名,有价格,有出版社等等,所以我们可以用结构体去描述。

结构体的声明

        一般结构体声明时,形式如图所示:

struct book
{int price;char name[20];
}p1,p2;

解析:

        struct为关键字,在声明结构体的时候,必须有关键字struct。

        book表示类型,就类似于int、char、float,只不过book是我们自定义的类型而已。

        {}里面的是描述book这个类型里面的成员,称为成员变量

        {}外边的p1,p2是两个定义的变量名,这里可写也可以不用写,在主函数中也可以定义

        类似于:
 

struct book
{int price;char name[20];
};
int main()
{struct book p1;struct book p2;return 0;
}

放在主函数中定义!

特殊的声明:

        在声明结构的时候,可以不完全的声明;

//匿名结构体类型
struct  //省略了结构体类型
{int a;char b;float c;
}x;

这样声明结构体是可以的

但是,我如果一个源文件中有两个匿名结构体可不可以呢?

答案是不行的,不然初始化的时候不知带是对哪一个结构体进行初始化的。

类似于这样是不可取的:

//匿名结构体类型
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;

这里声明了两个匿名结构体。

注:这里两个匿名结构体是相互独立的,谁有谁的地址,当我定义*p后,我用*p = &x是不行滴!!

初始化结构体

        声明好结构体后,接下来就是初始化结构体

struct book
{int price;char name[20];
};
int main()
{struct book a1 = { 3,"lisi" };//一般初始化struct book a2[2] = {{4,"zhangsan"},{"10","wangwu"}};//结构体数组初始化return 0;
}

  结构体也可以嵌套初始化:

struct Stu        //类型声明
{char name[15];//名字int age;      //年龄
};
struct Node
{int data;struct Stu p;//嵌套声明struct Node* next;
}n1 = { 10, {"lisi",5}, &n1};//嵌套初始化

结构体成员访问:
        

        结构体成员的访问有两种方式,第一种:

#include<stdio.h>
struct book
{int price;char name[20];
};
int main()
{struct book p1 = {10,"lisi"};printf("%d\n", p1.price);printf("%s\n", p1.name);return 0;
}

访问结构体中的成员变量用.去访问。

第二种用指针访问:

#include<stdio.h>
struct book
{int price;char name[20];
};
int main()
{struct book p1 = {10,"lisi"};struct book *p = &p1;printf("%d\n", p->price);printf("%s\n", p->name);return 0;
}

访问结构体中的成员变量用->去访问。

结构体传参:

代码如下:

struct S
{int data[1000];int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s);  //传结构体print2(&s); //传地址return 0;
}

有两种方式,在这里通过代码去分析。

可以传地址进去,用指针接收。

也可以传结构体进去,用结构体接收。

结构体大小(内存对齐):
        

        由于结构体是由问我们自己定义的,那么结构体大小该如如何计算?

计算如下结构体大小:
        

struct arr
{int a;char b;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}

如果直接想,答案应该是4+1+1 = 6;

但是:

答案是8!!

再来看一组:

        

struct arr
{char b;int a;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}

和第一组相比,我交换了一下int a和char b的位置,答案是多少?

是6?还是8?
答案是:

竟然是12!

为什么呢?

按照结构体的对齐规则:

首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

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

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

了解了对齐规则,我们再来看第一个案例,答案为什么是8?

struct arr
{int a;char b;char c;
};
int main()
{printf("%d\n", sizeof(struct arr));return 0;
}

通过画图讲解:

        

所以答案是8,可以自己画图分析。

继续分析第二个,交换顺序后大小为什么会变?

可以自己练习一个:

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

答案应该是多少呢?

答案是16!!

嵌套结构体大小:

        

struct arr
{char b;char c;int a;
};
struct S3
{double d;char c;int i;struct arr a;
};
int main()
{printf("%d\n", sizeof(struct S3));return 0;
}

它的大小是多少呢?

根据内存对齐规则第4条:

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

画图解释:

最后的大小是最大对齐数的整数倍,最大对齐数是8,所以总大小是24。

为什么会存在内存对齐

1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。

2、性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

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

修改默认对齐数

        在vs中,默认对齐数是8,当然我们也可以进行修改,用到#pragma预处理指令。

#pragma pack(1)//设置默认对齐数是1
struct arr
{int a;char b;char c;
};
#pragma pack();//取消默认对齐数

可以设置默认对齐数为1,然后计算结构体大小。

#pragma pack(1)
struct arr
{char a;char b;int c;
};
int main()
{printf("%d\n", sizeof(struct arr));
}

答案是多少呢?

答案应该是6;

位段:

2.1 什么是位段

     

        位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int 和char(整型家族)。

2.位段的成员名后边有一个冒号和一个数字,单位是比特位。

代码如下:

struct arr
{char a : 3;char b : 2;char c : 3;
};

此时,arr就是一个位段类型。

那么位段类型的大小是多少呢?

此时只占用了1个字节,答案是1;

如果是这样呢

struct arr
{char a : 3;char b : 6;char c : 4;
};
int main()
{printf("%d\n", sizeof(struct arr));
}

错误示例:

答案应该是3,不是2!!

枚举

枚举顾名思义就是一一列举。

把可能的取值一一列举。

枚举的定义:
 

关键字是enum!

enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性别
{MALE,FEMALE,SECRET
};
enum Color//颜色
{RED,GREEN,BLUE
};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。

        我们用%d打印一下,看看每个枚举类型中的变量的值。

enum SEX
{MALE,FEMALE,SECRET
};
int main()
{printf("%d\n", MALE);printf("%d\n", FEMALE);printf("%d\n", SECRET);
}

那么这个对应的值可不可以修改呢?

答案是不可以的,因为是枚举常量,常量的值是不可以修改的。

但是在枚举{}内是可以修改的:

enum SEX
{MALE = 10,FEMALE = 9,SECRET = 2
};
int main()
{printf("%d\n", MALE);printf("%d\n", FEMALE);printf("%d\n", SECRET);}

枚举的使用:


enum Color//颜色
{RED = 1,GREEN = 2,BLUE = 4
};
int main()
{enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。}

只能拿枚举常量给枚举常量赋值,才不会出现差异!

枚举的优点:

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

联合(共用体)

   联合体用关键字:union

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。 比如:

        

//联合体的声明
union arr
{int a;char b;
};
int main()
{union arr a1;//联合体定义return 0;
}

 联合体共用一块空间,可以看看联合体内这两个成员的地址是否相同?

union arr
{int a;char b;
};
int main()
{union arr a1;//联合体定义printf("%p\n", &a1.a);printf("%p\n", &a1.b);return 0;
}

地址是一样的。

联合体的特点:

联合的成员是共用同一块内存空间的,

这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。

联合体大小的计算

        联合的大小至少是最大成员的大小。

        当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

例如:

union Un1
{char c[5];int i;
};
union Un2
{short c[7];int i;
};
int main()
{printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));
}

这两个的打印结果是多少呢?

联合体的应用

利用联合体判断大小端。

之前判断大小端的方法:

int Print(int *a)
{return *(char*)a;
}
int main()
{int a = 1;int b = Print(&a);if (b == 1){printf("小端存储");}elseprintf("大端存储\n");return 0;
}

利用联合体判断:

union arr
{int a;char b;
};
int main()
{union arr a1 ;a1.a = 1;printf("%d\n", a1.b);if (a1.b == 1){printf("小端存储");}elseprintf("大端存储\n");return 0;
}

将1存入int型变量中,之后用char类型变量将其取出。

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

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

相关文章

如何理解央行买卖国债?

浙商证券覃汉认为&#xff0c;央行对长债的风险持续关注&#xff0c;30年国债收益率较难突破2.5%&#xff0c;区间底部已经多次印证&#xff0c;在学习效应影响下&#xff0c;长端利率预计继续以震荡调整为主。 1、央行买卖国债的政策要求、历史经验、优势 2023年中央金融工作…

语音助手拦截,拦截小秘书

呼叫中心业务场景下会遇到很多的语音助手和语音小秘书&#xff0c;还有一些漏话提醒、语音信箱等&#xff1b;大部分原因是由于主叫号码标记问题导致的局端和终端拦截策略&#xff0c;电话没有真实有效的触达并产生了通信费&#xff0c;这让很多业务场景下通信成本上涨据不完全…

常用中间件各版本下载

常用中间件下载地址 前言分布式中间件负载均衡中间件缓存中间件数据库中间件其他中间件1、Maven下载地址2、Git下载地址2、JDK下载地址3、MySQL下载地址4、Redis下载地址5、Nacos下载地址6、Tomcat下载地址7、Nginx下载地址8、RocketMQ下载地址8、RabbitMQ下载地址8、Erlang下载…

Amazon云计算AWS(三)

目录 五、关系数据库服务RDS&#xff08;一&#xff09;RDS的基本原理&#xff08;二&#xff09;RDS的使用 六、简单队列服务SQS&#xff08;一&#xff09;SQS的基本模型&#xff08;二&#xff09;SQS的消息 七、内容推送服务CloudFront&#xff08;一&#xff09;CDN&#…

Vue3-路由详解

文章目录 路由对路由的理解安装 Vue Router基本切换效果两个注意点路由器工作模式to的两种写法命名路由嵌套路由路由传参query参数params参数 路由的props配置replace属性编程式导航重定向 更多相关内容可查看 路由 附git地址&#xff1a;https://gitee.com/its-a-little-bad/…

项目纪实 | 版本升级操作get!GreatDB分布式升级过程详解

某客户项目现场&#xff0c;因其业务系统要用到数据库新版本中的功能特性&#xff0c;因此考虑升级现有数据库版本。在升级之前&#xff0c;万里数据库项目团队帮助客户在本地测试环境构造了相同的基础版本&#xff0c;导入部分生产数据&#xff0c;尽量复刻生产环境进行升级&a…

电脑缺少运行库,无法启动程序

在我们使用一些软件的时候&#xff0c;由于电脑缺少一些运行库&#xff0c;导致无法启动应用软件&#xff0c;此时需要我们安装缺少的运行库。 比如当电脑提示&#xff1a; Cannot load library Qt5Xlsx.dll 我们就需要下载C得运行库&#xff0c;以满足软件运行需要。 下载链…

某三甲医院智能化系统建设项目施工组织设计(516页)

第十四节、ICU重症监护探视系统设计方案 1、系统总体概述 某市市第一人民医院为一个集医疗、研究、医学教学为一体现代化医院建筑群&#xff0c;不仅在医学界的学术地位和声誉&#xff0c;还应拥有赋予人性的医疗环境&#xff0c;为病人创造最舒适的医疗条件。 探视系统帮助…

如何查看谁连接到了你的Wi-Fi网络?这里提供几种方法或工具

序言 你知道谁连接到你路由器的Wi-Fi网络吗?查看从路由器或计算机连接到Wi-Fi网络的设备列表,找出答案。 请记住,现在很多设备都可以连接到了你的Wi-Fi,该名单包括笔记本电脑、智能手机、平板电脑、智能电视、机顶盒、游戏机、Wi-Fi打印机等。 使用GlassWire Pro查看连接…

chatMed开发日志博客(持续更新中)

目录 1. 项目概述 2. 开发人员团队 3. 大致需求 4. 开发内容 4.1. 前端开发 4.1.1: 前端页面开发 4.1.2: 登录机制以及路由守卫的开发 4.1.3: 文件上传机制和保存机制 4.1.4: 消息传递机制 4.2. 线程池开发 4.3. 在线调试 1. 项目概述 搭建一个基于深度学习的分析平台…

SpringBoot 七牛云 OSS 私有模式 获取访问链接

目录 一、问题引出 二、在SpringBoot中获取私有访问路径的操作 一、问题引出 由于七牛云OSS的公有模式存在被盗刷的风险&#xff0c;可能导致服务器额外的费用&#xff0c;于是我选择私有模式进行操作。私有模式的访问路径是一个问题&#xff0c;因为需要对应着token和e这两…

Linux系统监控

文章目录 一、系统监控基本介绍二、内存监控2.1、内存监控字段解析2.2、windows下查看内存2.2.1、通过cmd中命令查看内存条信息&#xff1a;2.2.2、通过cmd中命令查看物理内存信息&#xff1a;2.2.3、使用任务管理器查看内存2.2.4、使用资源监视器查看内存2.2.5、使用系统信息工…

【Springboot】——项目的创建与请求参数应用

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

element-plus中在表格校验输入的值

element-plus中在表格校验输入的值 效果&#xff1a; 注意事项&#xff1a;需要在表单套一个表格的字段 代码&#xff1a; <el-form :model"tableFrom" ref"tableDataRef" :rules"rules" style"margin: 0px !important;">&…

vue中大屏可视化适配所有屏幕大小

1. 外部盒子 .screenBox {width: 100vw;height: 100vh;background: url("/assets/images/bg.png") no-repeat;background-size: cover; }2.比例盒子 外层盒子css定义 .boxScale {width: 1920px;height: 1080px;background-color: orange;transform-origin: left top;…

5.29工效学-人因工程人机交互

对于工效学这门课&#xff0c;一直都感觉很有意思&#xff0c;是一个值得再认真一点的课。可惜上课的时候效率不高&#xff0c;有感兴趣的东西课后也没有自行去拓展开来&#xff0c;前面的课我感觉还讲了比较重要的东西&#xff0c;但是&#xff0c;全忘了呢&#xff08;真的对…

Mac OS 用户开启 80 端口

开启端口 sudo vim /etc/pf.conf # 开放对应端口 pass out proto tcp from any to any port 8080 # 刷新配置文件 sudo pfctl -f /etc/pf.conf sudo pfctl -e获取本机ip地址 ifconfig en0 | grep inet | grep -v inet6 | awk {print $2}访问指定端口

C语言:深入了解(联合体和枚举)

目录 联合体 联合体的类型的声明 联合体的特点 相同成员的结构体和联合体对比 联合体大小的计算 联合体的使用举例 联合体的类型&#xff1a;判断联合体是大端还是小端 枚举类型 枚举类型声明 枚举类型的优点 枚举类型的使用 联合体 联合体的类型的声明 像结构体⼀…

一个浏览器插件,绕过限制,登录微信网页版!

摘要 早在2017年开始&#xff0c;微信网页版就已经住逐渐开始停止登录&#xff0c;以为了保障你的账号安全为由引导你使用电脑版微信。具体如下&#xff1a; 当然这个影响并不是所有账号&#xff0c;还是有一些账号不明觉厉地没有被影响到&#xff0c;我自己有2个号都还是可以…

【机器学习】集成语音与大型语音模型等安全边界探索

探索集成语音与大型语言模型&#xff08;SLMs&#xff09;的安全边界 一、引言二、SLMs的潜在安全风险三、对抗性攻击与越狱实验四、提高SLMs安全性的对策五、总结与展望 一、引言 近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;集成语音与大型语言模型&#xff08…