#C语言——学习攻略:自定义类型路线--结构体--结构体类型,结构体变量的创建和初始化,结构体内存对齐,结构体传参,结构体实现位段

🌟菜鸟主页:@晨非辰的主页

👀学习专栏:《C语言学习》

💪学习阶段:C语言方向初学者

⏳名言欣赏:“人理解迭代,神理解递归。”


目录

1.  结构体类型

1.1  旧知识回顾

1.1.1  结构体声明

1.1.2  结构体的创建和初始化

1.2  结构体的特殊声明

1.3  结构体的自引用

2.  结构体内存对齐(热门考点)

2.1  对齐规则

习题1

习题2

习题3

习题4--嵌套结构体大小

2.2  为什么存在内存对齐

2.3  修改默认对齐数

3.  结构体传参

4.  结构体实现位段

4.1  什么是位段

4.2  位段的内存分配

4.3  位段的跨平台问题

4.4  位段的应用

4.5  位段使用注意事项


1.  结构体类型

1.1  旧知识回顾

-- 在前面操作符部分有过初步的了解: 结构体是一些值的集合,这些值称为成员变量。结构体的每个成员都可以是不同类型的变量;比如:数组、指针、其他结构题、体等等。

        --博客跳转链接:结构成员访问操作符

1.1.1  结构体声明

struct tag        struct表明是结构体
{member-list;    一个或多个成员变量
}variable - list    变量列表, 可有可无,在声明变量类型是可同时定义的变量,且为全局变量

--比如,当我们想描述一个学生时,就要包括;姓名、成绩、年龄、学号等等,这时候单一的内置类型就显得力不从心; 哎~~,结构体就上场了:

struct Stu
{char name[20];  名字拼音int age;        年龄char id[20];    学号float score;    成绩//……
};    分号绝对不能丢

1.1.2  结构体的创建和初始化

struct Stu
{char name[20];int age;char sex[5];char id[20];
};int main()
{//初始化--按成员顺序struct Stu s1 = { "liming", 18, "man", "2023319829" };//进行访问-printf("name:%s\n", s1.name);printf("age:%d\n", s1.age);printf("sex:%s\n", s1.sex);printf("id:%s\n", s1.id);printf("\n");//初始化--按指定顺序struct Stu s2 = { .age = 20, .sex = "man", .name = "zhangsan", .id = "2023393839" };printf("name:%s\n", s1.name);printf("age:%d\n", s1.age);printf("sex:%s\n", s1.sex);printf("id:%s\n", s1.id);return 0;
}

1.2  结构体的特殊声明

--在声明时,结构体也存在着不完全声明:

        --匿名结构体:

struct
{int a;char b;float c;
}x;//全局变量struct
{int a;char b;float c;
}* p;

--可以发现,上面的结构体省略了标签-tag;

--那如果在上面代码的基础,那下面的合理吗?

p = &x;

警告:

--虽然上面两个结构体成员相同,但是编译器会将上面两个声明当作不同的类型(类似两个同名但不同地址的房屋),会导致类型不兼容错误;

--匿名结构体(无标签)只能通过原始定义使用(同时声明结构体、变量),无法在其他地方引用相同的类型匿名结构体无法复用-改进:

        --使用结构体标签、用 typedef 创建类型别名;

1.3  结构体的自引用

--在对结构体进行定义后,那是否可以将结构体本身当作结构体的一个成员呢?

        --比如定义一个链表的结点:

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

--这样其实是错误的!!

        --因为当结构体里面在包含一个同类型的结构体,会导致结构体内存无限大,显然是错误的。

--但是可以这样操作(可以包含指向同类型的指针):

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

--在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,容易引入问题,看下面的代码:

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

        --这样也是错误的!!因为Node是重命名来的,在结构体内部提前使用重命名的结果是不可行的。所以最好不要使用匿名结构体!!


2.  结构体内存对齐(热门考点)

--了解了基础知识后,下面来谈一谈它的内存如何计算??

2.1  对齐规则

结构体对齐规则:

  • 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处;
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;

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

                -- VS 中默认的值为 8 、 Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小;

  • 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍;
  • 如果嵌套了结构体,嵌套者对齐到自己成员最大对齐数的整数倍,总的结构体大小由第3条进行判断(包括嵌套者的成员);

--这样干巴得理解还是有点模糊,别急,下面几道例题来救一下!

习题1


struct s1
{char c1;int i;char c2;
};int main()
{printf("%zd\n", sizeof(struct s1));	12return 0;
}

图解演示——

习题2

struct S2
{char c1;char c2;int i;
};int main()
{printf("%zu\n", sizeof(struct S2));//8
}

图解演示——

习题3

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

图解演示——

习题4--嵌套结构体大小

struct S3
{double d;char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zu\n", sizeof(struct S4));  32
}

图解演示——

2.2  为什么存在内存对齐

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

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

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

--那该如何做到是设计结构体是,满足对齐和节省空间呢,对此,我们可以让占用空间小的成员尽量集中在一起。(减少空间浪费)

struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};int main()
{printf("%zu\n", sizeof(struct S1));  12printf("%zu\n", sizeof(struct S2));  8
}

--看上面的代码,成员一样,但是排列顺序不同,明显看到S2更小一点。

2.3  修改默认对齐数

--#pragma 预处理指令,可以改变默认对齐数,但是一般设置为2的次方数。

#pragma pack(2)//设置默认对⻬数为2
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认,下次再到别的结构体中就是默认的了
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S));//8return 0;
}

3.  结构体传参

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;
}

--很显然,通过地址来传参数最好的:

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

4.  结构体实现位段

4.1  什么是位段

--位段的声明和结构十分类似,但有者两个不同:

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

        --比如:

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

补充——

--一般习惯在位段成员加上'-' ;

--冒号后面的数字表示:这个成员要占用的比特位的数量;

--A就是⼀个位段类型。 那位段A所占内存的大小是多少呢?   

4.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;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;//空间是如何开辟的?
}

4.3  位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的;
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32),写成27,在16位机器会出问题;
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义;
  4. 当⼀个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的;

总结——

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

4.4  位段的应用

--图片为网络协议中,IP数据报的格式,可以看到其中大多属性只需要几个bit位就能描述,这里使用位段能够实现想要的结果,也节省了空间;这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。

4.5  位段使用注意事项

--位段的几个成员共有同⼀个字节,导致有些成员的起始位置不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的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;
}

旧知识回顾——

#C语言——学习攻略:数据在内存中的存储--整数在内存中的存储,大小端字节序和字节序判断,浮点数在内存中的存储

#C语言——学习攻略:探索内存函数--memcpy、memmove的使用和模拟实现,memset、memcmp函数的使用

结语:本篇文章到此结束,呈现了自定义--结构体的内容,内涵丰富,大家要多次回顾,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅、这么美给个三连吧~~~

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

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

相关文章

机器学习——TF-IDF算法

TF-IDF(Term Frequency-Inverse Document Frequency)是一种广泛应用于文本挖掘和信息检索领域的经典加权算法,主要用于评估一个词语在文档集合中的重要程度。其核心思想是:一个词语在文档中出现的频率越高,同时在所有文…

区块链技术原理(9)-什么是以太币

文章目录前言什么是加密货币?什么是以太币(ETH)铸造 ETH燃烧 ETHETH 面额传输 ETH查询 ETH以太币的经济模型:发行与流通以太币与其他代币的区别以太币的历史与市场地位总结前言 以太币(Ether,简称 ETH&…

【Oracle APEX开发小技巧16】交互式网格操作内容根据是否启用进行隐藏/展示

在日常开发中,有想要根据某一状态或条件去限制/隐藏对应权限或操作按钮的情况,于是用简报模板列表进行展示,并提供以下功能:显示模板基本信息提供启用/禁用模板的开关提供编辑模板的入口根据模板状态显示不同的操作选项效果展示&a…

AIStarter:全网唯一跨平台桌面AI管理工具,支持Windows、Mac和Linux一键部署

AIStarter作为全网唯一支持Windows、Mac和Linux的桌面AI管理平台,为开发者提供高效的项目管理、模型插件和工作流共享体验。最近,熊哥发布了4.1.0版本更新视频,详细演示了如何在多平台上安装、使用和分享AI项目。本文基于视频内容&#xff0c…

AP模式/ESP32作为TCP服务端,转发串口接收的数据给网络调试助手

此代码为接收STM32的数据然后直接转发到网络调试助手,当有设备连接到esp32软件热点时会通过串口发送字符’a’给STM32,当有设备断开连接时会通过串口发送字符’b’,ESP32的TX:GPIO4, RX:GPIO5ESP32作为TCP服务器地址为192.168.4.1 监听端口为3333#include <string.h> #in…

kafka 中的Broker 是什么?它在集群中起什么作用?

Kafka中的Broker&#xff1a;集群的核心支柱 在分布式消息系统Apache Kafka中&#xff0c;Broker是构成Kafka集群的核心节点或服务器。 简单来说&#xff0c;每一个Broker就是运行着Kafka服务的一个实例&#xff0c;多台Broker共同协作&#xff0c;形成了强大的、可扩展的消息处…

【SOA用于噪声抑制】光纤DFB激光器中弛豫振荡噪声抑制

概述&#xff1a;本章记录了我们在光纤分布式反馈DFB激光器中使用饱和SOA来降低RIN的工作&#xff0c;以用于低频传感器应用。结果表明&#xff0c;放大器的增益动力学允许光纤激光器的弛豫振荡RO噪声分量减少30dB。 1 背景到目前为止&#xff0c;我研究了将饱和半导体光放大器…

神经网络的核心组件解析:从理论到实践

神经网络作为深度学习的核心技术&#xff0c;其复杂性常常令人望而却步。然而&#xff0c;尽管神经网络的结构、参数和计算过程看似繁琐&#xff0c;但其核心组件却是相对简洁且易于理解的。本文将深入探讨神经网络的四大核心组件——层、模型、损失函数与优化器&#xff0c;并…

Spring Boot项目通过Feign调用三方接口的详细教程

目录 一、环境准备 二、启用Feign客户端 三、定义Feign客户端接口 四、定义请求/响应DTO 五、调用Feign客户端 六、高级配置 1. 添加请求头&#xff08;如认证&#xff09; 2. 超时配置&#xff08;application.yml&#xff09; 3. 日志配置 七、错误处理 自定义错误…

ubuntu24.04安装 bpftool 以及生成 vmlinux.h 文件

文章目录前言一、apt安装二、源码安装三、生成vmlinux.h参考资料前言 $ cat /etc/os-release PRETTY_NAME"Ubuntu 24.04.2 LTS"$ uname -r 6.14.0-27-generic一、apt安装 安装bpftool&#xff1a; $ sudo apt install linux-tools-commonThe following NEW packa…

Pytorch FSDP权重分片保存与合并

注&#xff1a;本文章方法只适用Pytorch FSDP1的模型&#xff0c;且切分策略为SHARDED_STATE_DICT场景。 在使用FSDP训练模型时&#xff0c;为了节省显存通常会把模型权重也进行切分&#xff0c;在保存权重时为了加速保存通常每个进程各自保存自己持有的部分权重&#xff0c;避…

IDEA自动生成Mapper、XML和实体文件

1. 引入插件 <build><finalName>demo</finalName><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.5</version><depe…

单例模式的理解

目录单例模式1.饿汉式(线程安全)2.懒汉式(通过synchronized修饰获取实例的方法保证线程安全)3.双重校验锁的方式实现单例模式4.静态内部类方式实现单例模式【推荐】单例模式 1.饿汉式(线程安全) package 并发的例子.单例模式; // 饿汉式单例模式&#xff08;天然线程安全&…

NLP---IF-IDF案例分析

一案例 - 红楼梦1首先准备语料库http://www.dxsxs.com这个网址去下载2 任务一&#xff1a;拆分提取import os import redef split_hongloumeng():# 1. 配置路径&#xff08;关键&#xff1a;根据实际文件位置修改&#xff09; # 脚本所在文件夹&#xff08;自动获取&#xff0…

LaTeX(排版系统)Texlive(环境)Vscode(编辑器)环境配置与安装

LaTeX、Texlive 和 Vscode 三者之间的关系&#xff0c;可以把它们理解成语言、工具链和编辑器的配合关系。 1.下载Texlive 华为镜像网站下载 小编这边下载的是texlive2025.iso最新版的&#xff0c;下载什么版本看自己需求&#xff0c;只要下载后缀未.iso的即可。为避免错误&am…

【深入浅出STM32(1)】 GPIO 深度解析:引脚特性、工作模式、速度选型及上下拉电阻详解

GPIO 深度解析&#xff1a;引脚特性、工作模式、速度选型及上下拉电阻详解一、GPIO概述二、GPIO的工作模式1、简述&#xff08;1&#xff09;4种输入模式&#xff08;2&#xff09;4种输出模式&#xff08;3&#xff09;4种最大输出速度2、引脚速度&#xff08;1&#xff09;输…

第1节 大模型分布式推理基础与技术体系

前言:为什么分布式推理是大模型时代的核心能力? 当我们谈论大模型时,往往首先想到的是训练阶段的千亿参数、千卡集群和数月的训练周期。但对于商业落地而言,推理阶段的技术挑战可能比训练更复杂。 2025年,某头部AI公司推出的130B参数模型在单机推理时面临两个选择:要么…

《软件工程导论》实验报告一 软件工程文档

目 录 一、实验目的 二、实验环境 三、实验内容与步骤 四、实验心得 一、实验目的 1. 理解软件工程的基本概念&#xff0c;熟悉软件&#xff0c;软件生命周期&#xff0c;软件生存周期过程和软件生命周期各阶段的定义和内容。 2. 了解软件工程文档的类别、内容及撰写软件工…

基于elk实现分布式日志

1.基本介绍 1.1 什么是分布式日志 在分布式应用中&#xff0c;日志被分散在储存不同的设备上。如果你管理数十上百台服务器&#xff0c;你还在使用依次登录每台机器的传统方法查阅日志。这样是不是感觉很繁琐和效率低下。所以我们使用集中化的日志管理&#xff0c;分布式日志…

多模态RAG赛题实战之策略优化--Datawhale AI夏令营

科大讯飞AI大赛&#xff08;多模态RAG方向&#xff09; - Datawhale 项目流程图 1、升级数据解析方案&#xff1a;从 fitz 到 MinerU PyMuPDF&#xff08;fitz&#xff09;是基于规则的方式提取pdf里面的数据&#xff1b;MinerU是基于深度学习模型通过把PDF内的页面看成是图片…