【C++】结构体、enum、union回顾


结构体

c语音的结构体在c++中升级为类,但属性和类有点区别:

访问权限:结构体中的成员默认是公有的(public)。

继承控制:结构体也可以继承,但继承默认是公有的,且一般在设计上较少用于复杂继承结构。

构造函数与析构函数:结构体也可以有构造函数和析构函数,但通常用于更简单的数据类型,主要是数据聚合。

成员函数:结构体同样可以包含成员函数,虽然通常使用时主要存储数据。

应用场景:结构体一般用于简单的数据聚合,而不需要复杂的行为或封装

结构体的内存对齐

结构体(struct)的数据成员, 第一个数据成员存放的地址为结构体变量偏移量为0的地址处.
其他结构体成员自身对齐时, 存放的地址为有效对齐值的最小整数倍的地址处.

  • 自身对齐值 : 结构体变量里每个成员的自身大小
  • 指定对齐值 : 有宏 #pragma pack(N)指定的值, 这里面的 N一定是2的幂次方.如1, 2, 4, 8, 16等.
  • 有效对齐值 : min{ 自身对齐值, 指定对齐值 }
  • 总的对齐值 : min{ 所有成员中自身对齐值最大的, 指定对齐值 } 的整数倍.
  1. 在32位Linux主机上默认指定对齐值为4, 64位的默认对齐值为8,
  2. AMR CPU默认指定对齐值为8;
  3. vs-8

在这里插入图片描述

可以用改变默认对齐数 #pragma pack(N)

在这里插入图片描述

内存对齐的意义:
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,
它们会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,
注:k被称为该数据类型的对齐模数(alignment modulus)。
这种强制的要求

  1. 简化了处理器与内存之间传输系统的设计
  2. 可以提升读取数据的速度。

总结:结构体内存对齐是以 空间 换 时间
怎样即节省空间又节省时间呢? - 调整变量顺序/修改默认对齐数

位段

位段(bit field)是指在数据结构中用来表示一组位的方式。它允许在结构体中定义占用特定位数的字段,从而有效地使用内存。例如,在C/C++中,可以通过定义一个结构体中的位段来压缩数据的存储。

struct Flags 
{
    unsigned int flag1 : 1; // 占用1位
    unsigned int flag2 : 1; // 占用1位
    unsigned int flag3 : 1; // 占用1位
    unsigned int reserved : 5; // 占用5位
};

位段的内存分配

  • 位段的成员可以是int , unsigned int, signed int, char(属于整型家族)

  • 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的

  • 位段涉及很多不确定因素,位段是不跨平台的,注意可移植程序一个避免使用位段。

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

位段的跨平台问题

  1. int位段被看作有符号还是无符号是不确定的
  2. 位段中最大位的数码不确定(16位 - int - 2字节,32位-int-32,如int b:17,16位机器超了)
  3. 位段中成员在内存从左向右还是从右向左,不确定
  4. 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位是,是舍弃剩余的位还是利用,不确定

枚举类型 enum

enum(枚举)是一种用户定义的数据类型,用于定义一组具有命名的整型常量。它使代码更具可读性,便于维护。基本的enum定义如下:

enum Color 
{
    Red,
    Green,
    Blue
};

在这个例子中,Color是一个枚举类型,包含三个可能的值:RedGreenBlue。默认情况下,第一个值的整数值为0,后续值依次递增(Red为0,Green为1,Blue为2)。

强枚举类型 enum class

c++11对enum进行了升级, 提供了强枚举类型:enum class

enum class Color
{
    Red,
    Green,
    Blue
};
强枚举类型可以显式指定枚举的基础类型,默认为int,但可以使用其他整型类型(如unsigned intchar等):
enum class Color : unsigned char 
{
    Red,
    Green,
    Blue
};

强枚举类型相比普通的枚举有什么使用上的区别呢?

作用域控制

  • 普通枚举enum)中的枚举值在定义的枚举类型外部是可见的,容易导致命名冲突。
  • 强类型枚举enum class)的枚举值则必须使用枚举类型的作用域访问,例如 EnumType::Value,避免了在不同枚举类型之间产生名称冲突。
cpp复制代码enum Status { Ok, Error };         // Ok 和 Error 直接暴露在全局作用域中
enum class State { Ok, Error };    // Ok 和 Error 限制在 State 作用域中

Status s = Ok;                     // 普通枚举直接使用 Ok
State st = State::Ok;              // 强枚举需要使用 State::Ok

类型安全

  • 普通枚举可以隐式转换为整数,这在某些情况下会导致不安全的行为,比如无意中将枚举值与整数进行比较。
  • 强类型枚举不会隐式转换为整数,需要使用 static_cast,从而避免了隐式转换带来的不安全性,并提高了代码的可读性。
cpp复制代码enum Color { Red, Green, Blue };
enum class Shade { Light, Dark };

int colorValue = Red;              // 普通枚举可以直接转换为整数
int shadeValue = Shade::Light;     // 强枚举会报错,不能隐式转换
int safeShadeValue = static_cast<int>(Shade::Light); // 必须显式转换

enum的内存对齐

在 C++ 中,enum 默认的底层类型通常是 int,因此默认的内存对齐与 int 类型相同。这通常是 4 字节对齐,但具体对齐要求依赖于编译器和系统架构。

enum的大小 = 底层类型大小

#include <iostream>
using namespace std;
enum e1
{
    a
};
enum class e2 : long long
{
    b
};

int main()
{
    cout << sizeof(e1) << endl; //4
    cout << sizeof(e2) << endl; //8
    return 0;
}

联合体 union

C++ 中,联合体union)是一种特殊的数据结构,它允许在同一块内存中存储多种不同类型的数据,但每次只能存储其中的一种。也就是说,联合体的所有成员共享同一块内存,因此它的大小>=最大成员的大小。

union Data 
{
    int intValue;
    float floatValue;
    char charValue;
};

联合体的内存对齐

#include <stdio.h>
union Un1
{
    char c[5]; //5字节 有效对齐值 : min{ 1, 8 } = 1
    int i;     //4字节 有效对齐值 : min{ 4, 8 } = 4
    //取最大,故总的为5字节
    //总的对齐值 = min{ max{1, 4}, 8 } = 4
    //5不是4的倍数,扩大为8字节
};
union Un2
{
    short c[7]; //14字节 有效对齐值 : min{ 2, 8 } = 2
    int i;      // 4字节 有效对齐值 : min{ 4, 8 } = 4
    //取最大,故总的为14字节
    //总的对齐值 = min{ max{2, 4}, 8 } = 4
    //14不是4的倍数,扩大为16字节
};
int main()
{

    // 下面输出的结果是什么?
    printf("%d\n", sizeof(union Un1)); //8
    printf("%d\n", sizeof(union Un2)); //16

    return 0;
}

union的使用场景

自学习union到现在, 我还从没使用过一次,一直觉得它没什么用, 直到遇到一个任务: c++实现Json类

如果读者使用过Json相关的库, 如jsoncpp、Qt里的QJson,应该直到JsonValue这个类。我以QJsonValue为例子:

QJsonValue 是 Qt 提供的 JSON 数据类型之一,用于表示 JSON 文档中的一个值。它可以表示 JSON 的基本数据类型,包括:

  • Null(空值)
  • Bool(布尔值)
  • Double(双精度浮点数)
  • String(字符串)
  • Array(数组)
  • Object(对象)
QJsonValue nullValue;               // 默认构造为 Null 类型
QJsonValue boolValue(true);         // 布尔类型
QJsonValue doubleValue(42.0);       // 数值类型
QJsonValue stringValue("Hello Qt"); // 字符串类型

问题:它是如何存储不同的类型的?

最初,我以为是为不同的类型都设置一个成员,后来看了源码后,才发现是使用了union

QJsonValue源码

    union {
        quint64 ui;
        bool b;
        double dbl;
        QStringData *stringData;
        QJsonPrivate::Base *base;
    };
    QJsonPrivate::Data *d; // needed for Objects and Arrays
    Type t

我模拟的KJsonValue

    union Value
    {
        int intValue;
        double doubleValue;
        bool boolValue;
        std::string* stringValue;
        KJsonObject* kjsonObjectValue;
        KJsonArray* kjsonArrayValue;
    } m_value;

问题:为什么这里存储都是对象的指针?

在 C++ 中,union 里的成员不会自动调用构造和析构函数。这是因为 union 的所有成员共享同一块内存,编译器无法判断该调用哪个成员的构造或析构函数。这种特性给 union 带来了更灵活但也更危险的管理方式。

因此如果成员中有类对象,必须要显示调用构造和析构。

#include <iostream>
#include <string>
#include <new>  // for placement new

union ExampleUnion {
    int intValue;
    double doubleValue;
    std::string stringValue;

    // 手动管理构造和析构
    ExampleUnion() { new (&stringValue) std::string("Hello, union!"); } // 构造 stringValue
    ~ExampleUnion() { stringValue.~std::string(); }                     // 析构 stringValue
};

int main() {
    ExampleUnion example;

    // 使用 stringValue
    std::cout << "String value: " << example.stringValue << std::endl;

    // 切换到 intValue 前,手动析构 stringValue
    example.stringValue.~std::string();
    example.intValue = 42;

    std::cout << "Integer value: " << example.intValue << std::endl;

    return 0;
}

如果使用指针来代替,虽然也需要在外部构造和析构,但是更灵活,更轻松。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值