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

自定义类型: 结构体 (详解)

本文索引

  • 一. 结构体类型的声明
    • 1. 结构体的声明和初始化
    • 2. 结构体的特殊声明
    • 3. 结构体的自引用
  • 二. 结构体内存对齐
    • 1. 对齐规则
    • 2. 为啥存在对齐?
    • 3. 修改默认对齐值
  • 三. 结构体传参
  • 四. 结构体实现位段
    • 1. 什么是位段?
    • 2. 位段的内存分配
    • 3. 位段的应用
    • 4. 位段的注意事项


前言:

这篇博客将对结构体类型进行详解, 后续我还会对枚举与联合体进行详解

个人主页: 酷酷学!!!


正文开始

一. 结构体类型的声明

1. 结构体的声明和初始化

结构体是⼀些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。

struct tag
{member-list;
}variable-list;

描述一个学生:

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};//分号不能丢

初始化:

#include<stdio.h>
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};//分号不能丢int main()
{//按照结构成员的顺序初始化struct Stu s = { "zhangsan", 20 , "nan","20230818001" };printf("%s\n", s.name);printf("%d\n", s.age);printf("%s\n", s.sex);printf("%s\n", s.id);//按照指定的顺序初始化struct  Stu s2 = { .age = 20,.name = "lisi",.id = "12345678",.sex = "nv" };printf("%s\n", s2.name);printf("%d\n", s2.age);printf("%s\n", s2.sex);printf("%s\n", s2.id);return 0;
}

2. 结构体的特殊声明

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

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

上面的两个结构体在声明的时候省略掉了结构体标签(tag)
那么问题来了?

//在上⾯代码的基础上,下⾯的代码合法吗? 
p = &x;

警告:

编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

3. 结构体的自引用

在结构体中包含⼀个类型为该结构本⾝的成员是否可以呢?
比如,定义⼀个链表的节点:

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

上述代码正确吗?如果正确,那 sizeof(struct Node) 是多少?
仔细分析,其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤
⼩就会⽆穷的⼤,是不合理的。

正确的⾃引⽤⽅式:

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

在结构体⾃引⽤使⽤的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引⼊问题,看看
下⾯的代码,可⾏吗?

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

答案是不⾏的,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使
⽤Node类型来创建成员变量,这是不⾏的

解决⽅案如下:定义结构体不要使⽤匿名结构体了

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

二. 结构体内存对齐

我们已经掌握了结构体的基本使⽤了。
现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩。
这也是⼀个特别热⻔的考点: 结构体内存对⻬

1. 对齐规则

1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。-VS 中默认的值为 8 -Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤
的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,
结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
//练习1 
struct S1
{char c1;int i;char c2;
};int main()
{printf("%zd\n", sizeof(struct S1));return 0;
}

解析: 首先c1存放在偏移量为0的位置处, i的对其数为4, 需要对其到起始位置的4倍,如下图, c2对其数为1, 如下图, 然后结构体的总大小为最大对其数的整数倍,即为12个字节

画图:
在这里插入图片描述

//练习2 
struct S2
{char c1;char c2;int i;
};int main()
{printf("%zd\n", sizeof(struct S2));return 0;
}

解析: c1对齐数1, 对齐到起始位置1倍, c2对齐数1, 对齐到起始位置1倍, i对齐数4,对齐到起始位置4倍, 整体为4的整数倍.

画图:

在这里插入图片描述

//练习3 
struct S3
{double d;char c;int i;
};
int main()
{printf("%zd\n", sizeof(struct S3));return 0;
}

解析: d的对齐数为8, c的对齐数为1,对齐到起始位置的1倍, i 的对齐数为4, 对齐到起始位置的4倍

画图:

在这里插入图片描述

//练习4-结构体嵌套问题 struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S4));return 0;
}

解析: 首先, c1的对齐数为1, s3的对齐数取其成员最大对齐数为8, d对齐数为8,总大小为8的倍数

画图:

在这里插入图片描述

2. 为啥存在对齐?

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

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

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

那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:

那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:
//例如: 
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};

S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的⼤⼩有了⼀些区别。

3. 修改默认对齐值

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1 
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认 
int main()
{//输出的结果是什么? printf("%d\n", sizeof(struct S));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;
}

上⾯的 print1 和 print2 函数哪个好些?
答案是:⾸选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,
所以会导致性性能的下降。

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


四. 结构体实现位段

1. 什么是位段?

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

  1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
  2. 位段的成员名后边有⼀个冒号和⼀个数字。

    ⽐如:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};

A就是⼀个位段类型。
那位段A所占内存的⼤⼩是多少?

接下来我们就来了解位段的内存分配

2. 位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
//⼀个例⼦ 
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};//如果让
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

在这里插入图片描述

但是位段存在几个跨平台的问题:

1. int位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会出问题。
(比如int位数写成int _e : 27在16位机器上是错误的,因为16位机器int占2个字节)
4. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
5. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利⽤,这是不确定的。

跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

3. 位段的应用

下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络的畅通是有帮助的。

在这里插入图片描述

4. 位段的注意事项

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

struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{struct A sa = { 0 };scanf("%d", &sa._b);//这是错误的 //正确的⽰范 int b = 0;scanf("%d", &b);sa._b = b;return 0;
}

本次分享就到这里, 您的点赞收藏就是我的最大动力!

相关文章:

自定义类型: 结构体 (详解)

本文索引 一. 结构体类型的声明1. 结构体的声明和初始化2. 结构体的特殊声明3. 结构体的自引用 二. 结构体内存对齐1. 对齐规则2. 为啥存在对齐?3. 修改默认对齐值 三. 结构体传参四. 结构体实现位段1. 什么是位段?2. 位段的内存分配3. 位段的应用4. 位段的注意事项 ​ 前言:…...

设计模式(23):访问者模式

定义 表示一个作用于某对象结构中的各元素的操作&#xff0c;它使我们可以在不改变元素的类的前提下定义作用与这些元素的新操作。 模式动机 对于存储在一个集合中的对象&#xff0c;他们可能具有不同的类型(即使有一个公共的接口)&#xff0c;对于该集合中的对象&#xff0…...

【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 前言 本篇主要内容&#xff1a;类的6个默认成员函数中的拷贝构造函数…...

掀起区块链开发狂潮!Scaffold-eth带你一键打造震撼DApp

文章目录 前言一、Scaffold-eth是什么&#xff1f;二、安装和配置1.准备工作2.安装3.配置开发环境 三、进阶使用1.放入自己的合约2.部署运行 总结 前言 前面的文章传送&#x1f6aa;&#xff1a;hardhat入门 与 hardhat进阶 在之前的文章中&#xff0c;我们已经探讨了使用Har…...

【Qt 学习笔记】Qt常用控件 | 按钮类控件Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件Check Box的使用及说明 文章编号&#xff1a;…...

android gradle 配置远程仓库

build.gradle buildscript { ext.kotlin_version "1.6.0" // 使用适合你项目的Kotlin版本 repositories { maven { url http://maven.aliyun.com/nexus/content/groups/public/ } maven { url http://maven.aliyun.com/nexus/content/repos…...

第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光

第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影…...

力扣经典150题解析之二十八:盛最多水的容器

目录 力扣经典150题解析之二十八&#xff1a;盛最多水的容器1. 介绍2. 问题描述3. 示例4. 解题思路5. 算法实现6. 复杂度分析7. 测试与验证测试用例设计测试结果分析 8. 总结9. 参考文献感谢阅读 力扣经典150题解析之二十八&#xff1a;盛最多水的容器 1. 介绍 在这篇文章中&…...

Rockchip Android13 Vold(二):Framework层

目录 前言 1、接收VolumeInfo状态 2、通知VolumeInfo状态变化 3、创建StorageVolume...

Oracle数据库故障类别及日常运维规划策略

一、故障类别 1、语句故障 单个数据库操作失败&#xff08;select、insert、update或delete&#xff09;&#xff0c;如&#xff1a; 在表中输入无效的数据&#xff0c;解决方法&#xff1a;可与用户合作来验证并更正数据&#xff1b;执行操作&#xff0c;但权限不足&#x…...

电商技术揭秘九:搜索引擎中的SEO数据分析与效果评估

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台的个性…...

多线程传参以及线程的优缺点

进程是资源分配的基本单位 线程是调度的基本单位 笼统来说&#xff0c;线程有以下优点&#xff1a; 创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比&#xff0c;线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多 能充分利用多…...

keil创建单片机工程

一、创建工程 打开Keil uVision4&#xff0c;依次选择 Project—>New uVision4 Project&#xff0c;选择工程保存路径及填写工程名称&#xff0c;如下图 然后点“保存”。在Select a CPU Data Base File中选择"STC MCU Database"&#xff0c;点 "OK"&am…...

QT 串口助手 学习制作记录

QT 串口助手qt 学习制作记录 参考教程&#xff1a;​​​​​​QT初体验&#xff1a;手把手带你写一个自己的串口助手_qt设计串口助手的流程图-CSDN博客 Qt之串口编程&#xff08;添加QSerialPort模块&#xff09;_如何安装 qt串口模块教程-CSDN博客 串口调试助手&#xff1…...

Github 2024-04-13 Rust开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10CUE项目1Go项目1Tauri: 构建小型、快速和安全的桌面应用程序 创建周期:1673 天开发语言:Rust协议类型:Apache License 2.0Star数量…...

大模型日报|今日必读的10篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.谷歌推出新型 Transformer 架构&#xff1a;反馈注意力就是工作记忆 虽然 Transformer 给深度学习带来了革命性的变化&#xff0c;但二次注意复杂性阻碍了其处理无限长输入的能力。 谷歌研究团队提出了一种新型 T…...

深度学习 Lecture 8 决策树

一、决策树模型&#xff08;Decision Tree Model) 椭圆形代表决策节点&#xff08;decison nodes)&#xff0c;矩形节点代表叶节点&#xff08;leaf nodes)&#xff0c;方向上的值代表属性的值&#xff0c; 构建决策树的学习过程&#xff1a; 第一步&#xff1a;决定在根节点…...

打包 docker 容器镜像到另一台电脑

# 提交容器为镜像 <container_id> 容器id my_migration_image 镜像名称 docker commit <container_id> my_migration_image # 保存镜像为tar文件 docker save my_migration_image > my_migration_image.tar 在另一台电脑上导入上面的镜像&#xff0c;请…...

贪心算法--购买股票

给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获得的 最大 利润 。 示例 1&a…...

在Mac主机上连接Linux虚拟机

前言 最近醉心于研究Linux&#xff0c;于是在PD上安装了一个Debian Linux虚拟机&#xff0c;用来练练手。但是每次在mac和Linux之间切换很是麻烦&#xff0c;有没有一种方法&#xff0c;可以在mac终端直接连接我的虚拟机&#xff0c;这样在mac终端上就可以直接操控我的Linux虚…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...

stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)

这是系统中断服务程序的默认处理汇编函数&#xff0c;如果我们没有定义实现某个中断函数&#xff0c;那么当stm32产生了该中断时&#xff0c;就会默认跑这里来了&#xff0c;所以我们打开了什么中断&#xff0c;一定要记得实现对应的系统中断函数&#xff0c;否则会进来一直循环…...

【QT控件】显示类控件

目录 一、Label 二、LCD Number 三、ProgressBar 四、Calendar Widget QT专栏&#xff1a;QT_uyeonashi的博客-CSDN博客 一、Label QLabel 可以用来显示文本和图片. 核心属性如下 代码示例: 显示不同格式的文本 1) 在界面上创建三个 QLabel 尺寸放大一些. objectName 分别…...

Vue 实例的数据对象详解

Vue 实例的数据对象详解 在 Vue 中,数据对象是响应式系统的核心,也是组件状态的载体。理解数据对象的原理和使用方式是成为 Vue 专家的关键一步。我将从多个维度深入剖析 Vue 实例的数据对象。 一、数据对象的定义方式 1. Options API 中的定义 在 Options API 中,使用 …...