C语言/C++结构体成员遍历进阶版(成员变量不一致的遍历)_遍历结构体成员-程序员宅基地

技术标签: 算法  c++  c语言  指针  数据结构  

C语言/C++结构体成员遍历进阶版(成员变量不一致)

大家好,我是鸟哥

最近很多人留言问我不同长度的结构体成员变量的遍历究竟如何实现,话不多说,直接先上完整代码。

“结构体成员遍历”进阶版:

当结构体里的成员数据类型不一样,可以计算出每个结构体成员的偏移量,并将偏移量放到一个数组中,遍历数组获取偏移量后再根据偏移去获取成员变量。这样取出了不同类型的结构体,直接进行赋值取值即可。

//使用内存对齐让结构体成员紧凑起来
#pragma pack(1)
typedef struct s1{
    
    char Member_0;
    int Member_1;
    float Member_2;
    char Member_3;
 
    //偏移量数组
    const char offsetRecord[4] = {
    0, 1, 4, 4};  //member的sizeof()
    //类型数组
    const char typeRecord[4] = {
    1, 2, 3, 1};    //member的type,与上面定义的需要赋值的变量顺序相对应。1-char;2-int;3-floa;4-char
    //数量
    const char memberSum = 4;   //用来循环赋值
}S1;
//还原内存对齐
#pragma pack()
 
int main(int argc, char *argv[])
{
     
    S1 tS1;
    S1* startP = &tS1;  //先获取指针
    void* p = startP;

    for(int i = 0; i < tS1.memberSum; i++)
    {
    
        p = ((char*)p + startP->offsetRecord[i]);   //转换成单字节指针方便偏移操作
        switch (tS1.typeRecord[i])
        {
    
        case 1: 
            *((char*) p) = i;
            break;
        case 2:
            *((int*) p) = i;
            break;
        case 3:
            *((float*) p) = i;
            break;
        default:
            break;
        }
    }
    return 0;
}

接下来再和大家一起分析这个代码的实现流程。

问题:

假设现在有一个结构体,成员都是类型各异,现在需要对结构体的成员进行赋值或者取值。
将数值0-3分别赋给结构体成员Member_0到Member_3。

解决方法:

根据每个成员变量的类型和所占内存空间的大小,计算出指针偏移量,保存指针偏移量和变量类型,当指针偏移到对应成员变量,根据变量类型,进行指针类型强转,再对指针取值赋值即可。

先对结构体进行分析

//使用内存对齐让结构体成员紧凑起来
#pragma pack(1)
typedef struct s1{
    char Member_0;
    int Member_1;
    float Member_2;
    char Member_3;

    //偏移量数组
    const char offsetRecord[4] = {0, 1, 4, 4};  //member的sizeof()
    
    //类型数组
    //member的type,与上面定义的需要赋值的变量顺序相对应。1-char;2-int;3-floa;4-char
    const char typeRecord[4] = {1, 2, 3, 1};   
     
    //成员变量的数量
    const char memberSum = 4;   //用来循环赋值
}S1;
//还原内存对齐
#pragma pack()
  • 首先是内存对齐。我们都知道,当一个结构体里面存在多个不同类型的成员变量时,结构体整体对齐方式取决于结构体中所有成员的自然边界对齐值最大值的整数倍。通俗点讲,就咱们这个例子来说,不加上内存对齐的预编译语句,结构体中char类型的变量在32位的系统中是占用4个字节,与int和float成整数倍对齐。
    而这里,#pragma pack(1),利用这条语句,我们人为改变对齐的字节是1个字节。尽可能减少结构体的空间占用。
  • 其次是结构体的成员分析
    Member0-3是需要赋值的4个成员变量,有char、int、float三种类型。
    offsetRecord[4]是指针偏移量。这里存放的值我们提前计算好的,结构体指针遍历所有成员,针对相同类型的成员变量,只要保证指针大小与成员变量的类型即可;而不同类型的成员变量,遍历成员时,指针每次偏移量都不一样。关于偏移量的计算规则下面有说。
    typeRecord[4]是指成员变量的类型。这里存放的值是我们假定的,假定char是编号1,int是2,float是3,每次赋值时都会进行类型选择。由于变量类型不一,利用指针进行赋值时,我们需要提前知道赋值的变量的类型,因此需要强转指针类型,确保正确的将数值写入到这个变量类型的内存空间。
    memberSum是需要赋值的成员变量的个数,作为循环的次数,进行循环赋值。

再对main函数进行分析

	S1 tS1;	//结构体命名
    S1* startP = &tS1;  //指向该结构体的指针
    void* p = startP;	//由于需要赋值的结构体成员变量类型不一,因此这里用void指针更好,方便转换。
    for(int i = 0; i < tS1.memberSum; i++)		//根据成员变量的数量来进行循环赋值
    {
        p = ((char*)p + startP->offsetRecord[i]);   //进行偏移量的计算,转换成char型单字节指针方便偏移操作
        switch (tS1.typeRecord[i])		//判断当前需要赋值的成员变量的类型
        {
        case 1: 		//char型成员变量
            *((char*) p) = i;
            break;
        case 2:		//int型成员变量
            *((int*) p) = i;
            break;
        case 3:		//float型成员变量
            *((float*) p) = i;
            break;
        default:
            break;
        }
    }

几个要点:

  • void 类型的指针。因为需要赋值的结构体成员变量类型不一,用void指针进行强转更好。在进行指针偏移时,只要确保偏移单位是1个字节就好了,省去了一些指针的强转。
  • 偏移量的计算。“p = ((char*)p + startP->offsetRecord[i])”,进行char型单字节指针强转,p每次偏移都是一个字节,此时void* 类型针会自动根据=等号右边的值的类型来进行转换。
    offsetRecord[] 是我们提前根据成员变量类型计算好的偏移量的存放数组,打个比方来解释这一过程。p指向结构体的头指针,默认指向是char Member_0的地址,我们想指到int Memer_1,此时就需要"(char*)p + 1",这里的偏移量1就是char Member_0的大小(1个字节),偏移1个字节之后就指向了int Member_1,offsetRecord[0]存放的值0在赋值给Member_0的时候已经用过了。
  • 判断当前需要赋值的成员变量的类型。switch (tS1.typeRecord[i]),此时typeRecord的作用就出来了,上面假定char是编号1,int是2,float是3,在进行赋值时,根据类型进行指针强转,确保值完全写进这个类型的内存空间。打个比方来讲述类型判断的必要性。
    打个比方,int类型在32位系统中占4个字节(假设分配的内存空间地址为0x61fdf0-0x61fdf4),通过switch语句进入到case 2,此时将void* 类型的指针p强转为指向int变量的int* 指针,不同类型的数据在内存中所占的字节数和存放方式是不同,现在p刚好是4个字节,对指针p进行取值操作,则该内存空间存储的数值无论是1还是10000,都是按照int类型的内存存放规则来对4个字节进行写值。
    假如不进行类型判断,默认都用char* p来进行赋值,由于p是1个字节,也许将1写到该内存空间,最终显示出来的值是正确的,但将超过一个字节大小的数值写入时,就会出现错误。

结构体成员遍历输出结果:

在这里插入图片描述
我的微信公众号(ID:00后开发者)从00后的角度出发,专注但不局限于分享电气、嵌入式、机器视觉以及芯片行业的算法、技术文章和最新资讯。如果想查看更多内容,可以关注我的微信公众号。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41820972/article/details/130901145

智能推荐

http隧道 java_使用java语言实现http隧道技术-程序员宅基地

文章浏览阅读119次。该楼层疑似违规已被系统折叠隐藏此楼查看此楼/***Getaparametervalue**@paramkeyString*@paramdefString*@returnString*/publicStringgetParameter(Stringkey,Stringdef){returnisStandalone?System.getProperty(ke..._java http隧道

Keepalived高可用+邮件告警_keepalived sendmail-程序员宅基地

文章浏览阅读913次。IP主机名备注192.168.117.14keepalived-master主节点192.168.117.15keepalived-slaver备节点192.168.117.100VIP1.主备节点均安装keepalived# yum install -y keepalived httpd2.主备节点均修改keepalived日志存放路径..._keepalived sendmail

SPFILE 错误导致数据库无法启动(ORA-01565)_ora01565 ora27046-程序员宅基地

文章浏览阅读469次。--==========================================--SPFILE错误导致数据库无法启动(ORA-01565)--========================================== SPFILE错误导致数据库无法启动 SQL> startup ORA-01078: failurein proce_ora01565 ora27046

功能测试基础知识(1)-程序员宅基地

文章浏览阅读6.1k次,点赞2次,收藏54次。功能测试基础知识总结_功能测试

postgresql 中文排序_pg中文排序-程序员宅基地

文章浏览阅读3.2k次,点赞3次,收藏2次。pg 中文首字母排序_pg中文排序

[Mysql] CONVERT函数_mysql convert-程序员宅基地

文章浏览阅读3.1w次,点赞23次,收藏109次。本文主要讲解CONVERT函数_mysql convert

随便推点

HTML5与微信开发(2)-视频播放事件及API属性_微信开发者工具视频快进-程序员宅基地

文章浏览阅读8.6k次,点赞2次,收藏2次。HTML5 的视频播放事件想必大家已经期待很久了吧,在HTML4.1、4.0之前我们如果在网页上播放视频无外乎两种方法: 第一种:安装FLASH插件或者微软发布的插件 第二种:在本地安装播放器,在线播放组件之类的 因为并不是所有的浏览器都安装了FLASH插件,就算安装也不一定所有的都能安装成功。像苹果系统就是默认禁用FLASH的,安卓虽然一开始的时候支持FLASH,但是在安卓4.0以后也开始不_微信开发者工具视频快进

JedisConnectionException Connection Reset_jedisconnectionexception: java.net.socketexception-程序员宅基地

文章浏览阅读5.4k次,点赞3次,收藏4次。在使用redis的过程常见错误总结1.JedisConnectionException Connection Reset参考这边文章:Connection reset原因分析和解决方案https://blog.csdn.net/cwclw/article/details/527971311.1问题描述Exception in thread "main" redis.clients...._jedisconnectionexception: java.net.socketexception: connection reset

Lua5.3版GC机制理解_lua5.3 gc-程序员宅基地

文章浏览阅读8.3k次,点赞8次,收藏42次。目录1.Lua垃圾回收算法原理简述2.Lua垃圾回收中的三种颜色3.Lua垃圾回收详细过程4.步骤源码详解4.1新建对象阶段4.2触发条件4.3 GC函数状态机4.4标记阶段4.5清除阶段5.总结参考资料lua垃圾回收(Garbage Collect)是lua中一个比较重要的部分。由于lua源码版本变迁,目前大多数有关这个方面的文章都还是基于lua5.1版本,有一定的滞后性。因此本文通过参考当前..._lua5.3 gc

手机能打开的表白代码_能远程打开,各种手机电脑进行监控操作,最新黑科技...-程序员宅基地

文章浏览阅读511次。最近家中的潮人,老妈闲着没事干,开始学玩电脑,引起他的各种好奇心。如看看新闻,上上微信或做做其他的事情。但意料之中的是电脑上会莫名出现各种问题?不翼而飞的图标?照片又不见了?文件被删了,卡机或者黑屏,无声音了,等等问题。常常让她束手无策,求助于我,可惜在电话中说不清,往往只能苦等我回家后才能解决,那种开心乐趣一下子消失了。想想,这样也不是办法啊, 于是,我潜心寻找了两款优秀的远程控制软件。两款软件...

成功Ubuntu18.04 ROS melodic安装Cartograhper+Ceres1.13.0,以及错误总结_ros18.04 安装ca-程序员宅基地

文章浏览阅读1.8k次。二.初始化工作空间三.设置下载地址四.下载功能包此处可能会报错,请看:rosdep update遇到ERROR: error loading sources list: The read operation timed out问题_DD᭄ꦿng的博客-程序员宅基地接下来一次安装所有功能包,注意对应ROS版本 五.编译功能包isolated:单独编译各个功能包,每个功能包之间不产生依赖。编译过程时间比较长,可能需要几分钟时间。此处可能会报错:缺少absl依赖包_ros18.04 安装ca

Harbor2.2.1配置(trivy扫描器、镜像签名)_init error: db error: failed to download vulnerabi-程序员宅基地

文章浏览阅读4.1k次,点赞3次,收藏7次。Haobor2.2.1配置(trivy扫描器、镜像签名)docker-compose下载https://github.com/docker/compose/releases安装cp docker-compose /usr/local/binchmod +x /usr/local/bin/docker-composeharbor下载https://github.com/goharbor/harbor/releases解压tar xf xxx.tgx配置harbor根下建立:mkd_init error: db error: failed to download vulnerability db: database download

推荐文章

热门文章

相关标签