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-charconst 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 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后的角度出发,专注但不局限于分享电气、嵌入式、机器视觉以及芯片行业的算法、技术文章和最新资讯。如果想查看更多内容,可以关注我的微信公众号。