首页 > 编程语言 >C/C++ union联合体介绍

C/C++ union联合体介绍

时间:2022-10-10 22:34:56浏览次数:78  
标签:字节 union u1 联合体 C++ bval 0x%

C/C++ union联合体介绍

文章参考:https://blog.csdn.net/mooneve/article/details/92703036


目录


1. 联合体union简介

union是在某种程序上类似结构体struct的一种数据结构,union也可以包含很多种数据类型和变量,区别在于:

  • 结构体中所有变量是共存的,内存空间的分配是粗放的,不管用不用,都会分配;
  • union中个变量是互斥的,任何两个成员不会同时有效,内存使用更精细灵活,节省内存空间。

当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体。

在《C Programming Language》中对联合体union的描述:

  1. 联合体是一个结构体;
  2. 它的所有成员相对于基地址的偏移量是0;
  3. 此结构空间要大到足够容纳最宽的成员;
  4. 其内存对齐方式要适合其中所有的成员;

联合体union定义形式如下:

union 名称{
   public:  // 默认为public,可不写
   	公有成员
   protected:
   	保护成员
   privated:
   	私有成员
};

示例,无名联合体声明和使用

union{
   int i;
   float f;
}
i=1;
f=1.2;

示例:使用联合体管理成绩信息

/**
 * @file 1unioc.cpp
 * @author zoya ([email protected])
 * @brief 联合体使用:使用联合体保存信息并数据
 * @version 0.1
 * @@date: 2022-10-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <iostream>
#include <string>

using namespace std;

class ExamGradeInfo
{
private:
    string name; // 课程名
    enum
    {
        LEVEL,
        PASS,
        GRADE,
    } mode; // 计分方式
    union
    {
        char level; // 等级表示成绩 A/B/C等
        bool pass;  // 只计算是否通过
        int grade;  // 具体的分数
    };

public:
    ExamGradeInfo();
    ExamGradeInfo(string name, char level) : name(name),
                                             mode(ExamGradeInfo::LEVEL),
                                             level(level)
    {
    }
    ExamGradeInfo(string name, bool pass) : name(name),
                                            mode(ExamGradeInfo::PASS),
                                            pass(pass)
    {
    }
    ExamGradeInfo(string name, int grade) : name(name),
                                            mode(ExamGradeInfo::GRADE),
                                            grade(grade)
    {
    }

    void show()
    {
        cout << name << ":";
        switch (mode)
        {
        case ExamGradeInfo::LEVEL:
            cout << level << endl;
            break;
        case ExamGradeInfo::PASS:
            cout << (pass?"PASS":"FAILE") << endl;
            break;
        case ExamGradeInfo::GRADE:
            cout << grade << endl;
            break;
        default:
            break;
        }
    }
};

int main()
{
    ExamGradeInfo course1("english", 'B');
    ExamGradeInfo course2("math", true);
    ExamGradeInfo course3("C", 60);

    course1.show();
    course2.show();
    course3.show();

    return 0;
}

运行显示结果:

english:B
math:PASS
C:60

2. 联合体union内存分配与所占空间

联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,其大小必须满足两个条件:

  1. 大小足够容纳最宽的成员;
  2. 大小能被其包含的所有的基本数据类型的大小整除。

示例:测试如下联合体所占的大小:

/**
 * @file 2union.c
 * @author zoya ([email protected])
 * @brief 测试联合体所占内存大小
 * @version 0.1
 * @@date: 2022-10-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>

int main()
{
    union u1
    {
        int n;
        char s[5];
        double f;
    };
    union u2
    {
        int n;
        char s[9];
        double f;
    };

    u1 tmp1;
    u2 tmp2;
    printf("sizeof(int) = %ld, sizeof(char) = %ld, sizoef(douvle) = %ld\n", sizeof(int), sizeof(char), sizeof(double));
    printf("sizeof(u1) = %ld, sizeof(u2) = %ld\n", sizeof(tmp1), sizeof(tmp2));
    printf("u1各个变量的地址, &u1 : 0x%x, &u1.n : 0x%x, &u1.s : 0x%x, &u1.f : 0x%x\n",
           &tmp1, &tmp1.n, tmp1.s, &tmp1.f);
    printf("u2各个变量的地址, &u2 : 0x%x, &u2.n : 0x%x, &u2.s : 0x%x, &u2.f : 0x%x\n",
           &tmp2, &tmp2.n, tmp2.s, &tmp2.f);

    return 0;
}

运行结果:

sizeof(int) = 4, sizeof(char) = 1, sizoef(douvle) = 8
sizeof(u1) = 8, sizeof(u2) = 16
u1各个变量的地址, &u1 : 0x5ec19748, &u1.n : 0x5ec19748, &u1.s : 0x5ec19748, &u1.f : 0x5ec19748
u2各个变量的地址, &u2 : 0x5ec19750, &u2.n : 0x5ec19750, &u2.s : 0x5ec19750, &u2.f : 0x5ec19750

对联合体u1,n占4字节,f占8字节,s占5字节,u1最多占用8个字节,所以sizoef(u1)=8;

对联合体u2,n占4字节,f占8字节,s占9字节,最多占用了9字节,但是9字节不能被double所占的8字节整除,所以就会扩充到16字节,所以sizeof(u2) = 16;

另外,还可以发现联合体中各个变量的地址都是相同的。

3. 联合体union的优缺点

联合体的优点:多种访问内存的手段可以灵活读取任意部分的数据,也可整体进行赋值。在某些寄存器或通道大小有限制的情况下,可以分多次搬运;

联合体的缺点,由于所有变量都能使用,容易使用错误的变量造成逻辑错误。

示例:通过改变联合体中其中一个变量的值,操作共享到其它变量:

/**
 * @file 3union.c
 * @author zoya ([email protected])
 * @brief 通过改变union中某一个变量的值来操作其它变量的改变
 * @version 0.1
 * @@date: 2022-10-09
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>

int main()
{
    union
    {
        short nval;
        char bval[2];
    };

    nval = 0x0102;
    printf("联合体内存地址:0x%x\n", &nval);
    printf("nval(0x%x) = 0x%04x, bval[0](0x%x) = 0x%02x, bval[1](0x%x) = 0x%02x\n", &nval, nval, &bval[0], bval[0], &bval[1], bval[1]);

    bval[0] = 0x03;
    printf("nval(0x%x) = 0x%04x, bval[0](0x%x) = 0x%02x, bval[1](0x%x) = 0x%02x\n", &nval, nval, &bval[0], bval[0], &bval[1], bval[1]);

    return 0;
}

运行结果:

联合体内存地址:0x6427da16
nval(0x6427da16) = 0x0102, bval[0](0x6427da16) = 0x02, bval[1](0x6427da17) = 0x01
nval(0x6427da16) = 0x0103, bval[0](0x6427da16) = 0x03, bval[1](0x6427da17) = 0x01

因为测试机电脑是小段字节序的(高位数据存储在高位内存地址,低位数据存储在低位内存地址),bval[0]所在的地址是低位内存地址(0x6427da16),bval[1]所在地址是高位内存地址(0x6427da17),所以分别存储的是0x020x01

根据运行结果可以看出来,改变其中一个变量,那么另一个变量也会对应改变。

4. 联合体union的应用

  1. 判断电脑字节序

根据联合体的这一特点,还可以判断电脑是小端字节序还是大端字节序,如下:

/**
 * @file byteorder.c
 * @author your name ([email protected])
 * @brief 通过代码检测当前主机的字节序
 * @version 0.1
 * @date 2022-10-08
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <stdio.h>

int main()
{
    union
    {
        short value;               // 2字节
        char bytes[sizeof(short)]; // 2字节数组
    } test;

    test.value = 0x0102;
    if (0x01 == test.bytes[0] && 0x02 == test.bytes[1])
    {
        printf("大端字节序\n");
    }
    else if (0x02 == test.bytes[0] && 0x01 == test.bytes[1])
    {
        printf("小端字节序\n");
    }
    else
    {
        printf("未知\n");
    }
}
  1. 寄存器读取

    假设有一个I2C的温度控制寄存器,该寄存器是10位有效,需要读取该寄存器的值。但是I2C的数据传输是按照8bit的,每个时序只能接收8bit数据,只有使用char或unsigned char型接收数据,并且要接收2次。

    传统的做法是声明一个char数组,包含2个元素,接收到数据后再通过计算给到一个short或int整数。

    通过使用联合体也可以实现,使用联合体不用考虑各种转换问题,并且如果寄存器的有效位改变,也不用更改太多代码。

    如下示例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        // 传统的做法
        char bval[2] = {0};
        // 通过读取获取到寄存器的值,假设bval[0] = 02,bval[0] = 0x01
        bval[0] = 0x02;
        bval[1] = 0x07;
        // 转换为整型
        int nval = bval[0] | ((bval[1] & 0x03) << 8); // &0x03是因为10位有效
        printf("nval = 0x%x\n", nval);
    
        // 使用联合体
        union u1
        {
            int val;
            char cval[sizeof(int)];
        };
        u1 tmp;
        memset(&tmp, 0, sizeof(tmp));
        tmp.cval[0] = 0x02;
        tmp.cval[1] = 0x07;
    
        printf("val = 0x%x\n", tmp.val & 0x3ff); // &0x3ff是因为10位有效
    
        // 寄存器的有效位改变,17位有效
        tmp.cval[0] = 0x02;
        tmp.cval[1] = 0x07;
        tmp.cval[2] = 0x1f;
        printf("val = 0x%x\n", tmp.val & 0x1ffff); // 0x1ffff是因为17位有效
    }
    

    程序运行:

    nval = 0x302
    val = 0x302
    val = 0x10702
    

标签:字节,union,u1,联合体,C++,bval,0x%
From: https://www.cnblogs.com/Zoya-/p/16777663.html

相关文章

  • C/C++基于数据分析的小区电量扩容推荐系统
    C/C++基于数据分析的小区电量扩容推荐系统程序设计题:基于数据分析的小区电量扩容推荐程序出题人:朱立华面向专业:测绘工程及其他理工科专业难度等级:41问题描述老旧小......
  • C++多线程同步技巧(二) ---事件
    简介Windows在线程控制方面提供了多种信号处理机制,其中一种便是使用CreateEvent()函数创建事件,然后使用信号控制线程运行。其中将事件变为有信号可使用SetEvent()函数,将......
  • vscode——如何在vscode中运行C/C++
    前言mingw-w64:https://sourceforge.net/projects/mingw-w64/files/mingw-w64/内容安装mingw-w64下载地址x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0.7z:x86_64-8.1......
  • C++和Java多维数组声明和初始化时的区别与常见问题
    //C++只有在用{}进行初始化的时候才可以仅仅指定列数而不指定行数,因为可以通过直接//初始化时的元素个数自动计算出行数。而仅声明/创建数组而不初始化时,Cpp要求必须写明//......
  • C++算术表达式求值
    C++算术表达式求值二、实验内容:设计一个程序,用算符优先法对算术表达式求值。三、基本要求:以字符序列的形式从终端输入语法正确的、不含变量的算术表达式,利用算符优先......
  • C++ Primer Plus学习笔记之预备知识
    前言个人觉得学习编程最有效的方法是阅读专业的书籍,通过阅读专业书籍可以构建更加系统化的知识体系。一直以来都很想深入学习一下C++,将其作为自己的主力开发语言。现在为......
  • Microsoft Visual C++ 14.0 免费下载
    ​​https://pan.baidu.com/s/1uGOeqwpAO1RleXmTFt2Hrg ​​提取码:5zyd......
  • GitHub 开源推荐 | 一个轻量级、高性能的 C++ Web 框架
     Github开源推荐​​专注分享GitHub上有趣、好玩的开源项目,​​以帮助大家提高编程技巧,找到编程乐趣。如果你对开源感兴趣,想和大家分享一些优质项目,随时欢迎投稿(微信号:i......
  • C++ 栈和典型迷宫问题
    C++栈和迷宫问题1.前言栈是一种受限的数据结构,要求在存储数据时遵循先进后出(LastInFirstOut)的原则。可以把栈看成只有一个口子的桶子,进和出都是走的这个口子(也称为......
  • 【C\C++】函数指针与指针函数
    函数指针的优点1.灵活调用性设计之初,程序员可能不知道一些方法最后会怎么去具体的实现,就可以使用函数指针预留,后期直接挂接进来。2.更好的封装编写模块时,可以将一些方法......