【C++】结构体、enum、union回顾
文章目录
- 结构体
- 结构体的内存对齐
- 位段
- 位段的内存分配
- 枚举类型 enum
- 强枚举类型 enum class
- enum的内存对齐
- 联合体 union
- 联合体的内存对齐
- union的使用场景
结构体
c语音的结构体在c++中升级为类,但属性和类有点区别:
访问权限:结构体中的成员默认是公有的(public)。
继承控制:结构体也可以继承,但继承默认是公有的,且一般在设计上较少用于复杂继承结构。
构造函数与析构函数:结构体也可以有构造函数和析构函数,但通常用于更简单的数据类型,主要是数据聚合。
成员函数:结构体同样可以包含成员函数,虽然通常使用时主要存储数据。
应用场景:结构体一般用于简单的数据聚合,而不需要复杂的行为或封装
结构体的内存对齐
结构体(struct)的数据成员, 第一个数据成员存放的地址为结构体变量偏移量为0的地址处.
其他结构体成员自身对齐时, 存放的地址为有效对齐值的最小整数倍的地址处.
- 自身对齐值 : 结构体变量里每个成员的自身大小
- 指定对齐值 : 有宏 #pragma pack(N)指定的值, 这里面的 N一定是2的幂次方.如1, 2, 4, 8, 16等.
- 有效对齐值 : min{ 自身对齐值, 指定对齐值 }
- 总的对齐值 : min{ 所有成员中自身对齐值最大的, 指定对齐值 } 的整数倍.
- 在32位Linux主机上默认指定对齐值为4, 64位的默认对齐值为8,
- AMR CPU默认指定对齐值为8;
- vs-8
可以用宏改变默认对齐数 #pragma pack(N)
内存对齐的意义:
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,
它们会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,
注:k被称为该数据类型的对齐模数(alignment modulus)。
这种强制的要求
- 简化了处理器与内存之间传输系统的设计
- 可以提升读取数据的速度。
总结:结构体内存对齐是以 空间 换 时间
怎样即节省空间又节省时间呢? - 调整变量顺序/修改默认对齐数
位段
位段(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)的方式来开辟的
-
位段涉及很多不确定因素,位段是不跨平台的,注意可移植程序一个避免使用位段。
位段的跨平台问题
- int位段被看作有符号还是无符号是不确定的
- 位段中最大位的数码不确定(16位 - int - 2字节,32位-int-32,如int b:17,16位机器超了)
- 位段中成员在内存从左向右还是从右向左,不确定
- 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位是,是舍弃剩余的位还是利用,不确定
枚举类型 enum
enum
(枚举)是一种用户定义的数据类型,用于定义一组具有命名的整型常量。它使代码更具可读性,便于维护。基本的enum
定义如下:
enum Color
{Red,Green,Blue
};
在这个例子中,Color
是一个枚举类型,包含三个可能的值:Red
、Green
和Blue
。默认情况下,第一个值的整数值为0,后续值依次递增(Red
为0,Green
为1,Blue
为2)。
强枚举类型 enum class
c++11对enum进行了升级, 提供了强枚举类型:enum class
enum class Color
{Red,Green,Blue
};
强枚举类型可以显式指定枚举的基础类型,默认为int,但可以使用其他整型类型(如unsigned int、char等):
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; //4cout << sizeof(e2) << endl; //8return 0;
}
联合体 union
C++ 中,联合体(union
)是一种特殊的数据结构,它允许在同一块内存中存储多种不同类型的数据,但每次只能存储其中的一种。也就是说,联合体的所有成员共享同一块内存,因此它的大小>=最大成员的大小。
union Data
{int intValue;float floatValue;char charValue;
};
联合体的内存对齐
#include <stdio.h>
union Un1
{char c[5]; //5字节 有效对齐值 : min{ 1, 8 } = 1int 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 } = 2int i; // 4字节 有效对齐值 : min{ 4, 8 } = 4//取最大,故总的为14字节//总的对齐值 = min{ max{2, 4}, 8 } = 4//14不是4的倍数,扩大为16字节
};
int main()
{// 下面输出的结果是什么?printf("%d\n", sizeof(union Un1)); //8printf("%d\n", sizeof(union Un2)); //16return 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 ArraysType 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 newunion 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;// 使用 stringValuestd::cout << "String value: " << example.stringValue << std::endl;// 切换到 intValue 前,手动析构 stringValueexample.stringValue.~std::string();example.intValue = 42;std::cout << "Integer value: " << example.intValue << std::endl;return 0;
}
如果使用指针来代替,虽然也需要在外部构造和析构,但是更灵活,更轻松。