目录
1. 背景
关于union的知识点,教材和网上的资料都比较少,经过不断的查阅各种资料和测试,对union有了一些基本了解,这里进行整理,方便以后需要时查看
2. union存在的必要性
我们先来看下面的两段对比的代码,体验以下union的用处
#include <iostream>
using namespace std;
int main() {
string name;
int score;
char degree;
bool is_pass;
name = "Li Ming";
score = 90;
cout << name << "---math: " << score << endl; // Li Ming---math: 90
degree = 'B';
cout << name << "---english: " << degree << endl; // Li Ming---english: B
is_pass=true;
cout << name << "---politics: " << is_pass << endl; // Li Ming---politics: 1
return 0;
}
#include <iostream>
using namespace std;
union {
int score;
char degree;
bool is_pass;
} u;
int main() {
string name;
name = "Li Ming";
u.score = 90;
cout << name << "---math: " << u.score << endl; // Li Ming---math: 90
u.degree = 'B';
cout << name << "---english: " << u.degree << endl; // Li Ming---english: B
u.is_pass=true;
cout << name << "---politics: " << u.is_pass << endl; // Li Ming---politics: 1
return 0;
}
说明:
- 如上面所示,科目得分这一数据字段,有几种不同的数据形式;在代码片段1中,我们定义了3个变量来表示3种不同的科目得分,所有需要在内存中分配3块内存,有没有办法只分配1块内存,就能完成这个任务呢,这个办法就是
union
- 在代码片段2中,我们定义了一个
union
类型的数据,union中的3个数据成员共享1块内存,且3个数据成员的内存起始地址一样,这在内存紧张的情况下非常有用,该内存的长度计算方法如下:- 此长度能容纳最大长度的数据成员
- 此长度是构成所有数据成员的基本数据类型长度的整数倍,如
char a[5]
的基本数据类型长度为1
- 下面我们通过一段代码来验证一下:
#include <iostream>
using namespace std;
union {
int i;
double d;
char c[10];
} u;
int main() {
cout << &u << endl; // 0x407040
cout << &u.i << endl; // 0x407040
cout << &u.d << endl; // 0x407040
cout << &u.c << endl; // 0x407040
cout << sizeof(u) << endl; // 16
return 0;
}
因为int的字节数为4,double的字节数为8,char的字节数为1,所有数据成员的最长长度为10,但10不是4和8的倍数,所有该union的长度为16
3. union的几种定义方式
3. 1 只定义类型
#include <iostream>
using namespace std;
union u_type1 {
int i;
double d;
char c[10];
};
int main() {
u_type1 u1;
u1.i = 1;
cout << u1.i << endl; // 1
return 0;
}
3.2 定义类型的同时定义变量**
#include <iostream>
using namespace std;
union u_type2 {
int i;
double d;
char c[10];
} u2;
int main() {
u2.i = 2;
cout << u2.i << endl; // 2
return 0;
}
3.3 定义无类型名的union变量
#include <iostream>
using namespace std;
union {
int i;
double d;
char c[10];
} u3;
int main() {
u3.i = 3;
cout << u3.i << endl; // 3
return 0;
}
3.4 定义无类型名的union
此方式定义的union和union中数据成员的使用需要在同一作用域中
#include <iostream>
using namespace std;
int main() {
union {
int i4;
double d4;
char c4[10];
};
i4 = 4;
cout << i4 << endl; // 4
return 0;
}
4. 数据成员内存覆盖的问题
4.1 十六进制的一个字节几位
我们都知道数据在内存中是以二进制的方式存在的,一个字节有8位
但我们debug的时候都喜欢用十六进制,那是因为十六进制的更加便于我们理解数据,那十六进制的一个字节有几位?
这里我们只讨论正数,方便理解,一个二进制字节能表示的数据范围为00000000-11111111,即0-255,对应的十六进制为00-ff,也是2位十六进制能表示的数据范围,所以十六进制一个字节有2位
4.2 多字节的高位与低位
#include <iostream>
using namespace std;
int main() {
int a = 5456511841;
cout << hex << a << endl; // 453bc361
short *p;
p = (short *)&a;
cout << hex << *p << endl; // c361
return 0;
}
对于上面的a,有4个字节,那在内存中是以453bc361,还是以61c33b45的方式存在?
正确的答案是,不管是存放还是读取,都是数据和内存的高低位一一对应,即数据的低位对应内存的低位,数据的高位对应内存的高位
所有在内存中以61c33b45的方式存在;指针p读取前两个字节,即61c3,对应的数据就是c361
4.3 union数据成员内存覆盖
我们先看下面的例子:
#include <iostream>
using namespace std;
union {
char c;
short s;
int i;
} u1, u2;
int main() {
// =================u1====================
u1.i = 2147483489;
cout << hex << u1.c << endl; // a
cout << hex << u1.s << endl; // ff61
cout << hex << u1.i << endl; // 7fffff61
cout << endl;
u1.s = 32609;
cout << hex << u1.c << endl; // a
cout << hex << u1.s << endl; // 7f61
cout << hex << u1.i << endl; // 7fff7f61
cout << endl;
u1.c = 'A';
cout << hex << u1.c << endl; // A
cout << hex << u1.s << endl; // 7f41
cout << hex << u1.i << endl; // 7fff7f41
cout << endl;
// =================u2====================
u2.c = 'A';
cout << hex << u2.c << endl; // A
cout << hex << u2.s << endl; // 41
cout << hex << u2.i << endl; // 41
cout << endl;
u2.s = 32609;
cout << hex << u2.c << endl; // a
cout << hex << u2.s << endl; // 7f61
cout << hex << u2.i << endl; // 7f61
cout << endl;
u2.i = 2147483489;
cout << hex << u2.c << endl; // a
cout << hex << u2.s << endl; // ff61
cout << hex << u2.i << endl; // 7fffff61
cout << endl;
return 0;
}
说明:
- char为1字节数,short为2字节数,int为4字节数,所以union的字节长度为4
- u1的内存中的数据变化如下:
赋值操作 | 十进制 | 十六进制 | 内存第1字节 | 内存第2字节 | 内存第3字节 | 内存第4字节 |
---|---|---|---|---|---|---|
u1.i = 2147483489; | 2147483489 | 7fffff61 | 61 | ff | ff | 7f |
u1.s = 32609; | 32609 | 7f61 | 61 | 7f | ff | 7f |
u1.c = ‘A’; | 65 | 41 | 41 | 7f | ff | 7f |
- u2的内存中的数据变化如下:
赋值操作 | 十进制 | 十六进制 | 内存第1字节 | 内存第2字节 | 内存第3字节 | 内存第4字节 |
---|---|---|---|---|---|---|
u2.c = ‘A’; | 65 | 41 | 41 | 00 | 00 | 00 |
u2.s = 32609; | 32609 | 7f61 | 61 | 7f | 00 | 00 |
u2.i = 2147483489; | 2147483489 | 7fffff61 | 61 | ff | ff | 7f |
- union中的各个数据成员都可以取值和赋值,取值和赋值都只操作从起始地址开始的各自字节数,但union中最多只有一个数据成员是有意义的
5. union的函数成员、构造函数、析构函数
#include <iostream>
using namespace std;
union u_type {
u_type() {
cout << "constructor" << endl;
}
char c;
short s;
int i;
void print() {
cout << "hello world" << endl;
}
~u_type() {
cout << "destructor" << endl;
}
} u;
int main() {
u.print();
return 0;
}
结果如下:
constructor
hello world
destructor
6. 控制访问权限
默认访问权限是public
#include <iostream>
using namespace std;
union {
public:
char c;
protected:
short s;
private:
int i;
} u;
int main() {
u.c = 'a';
cout << u.c << endl;
return 0;
}
7. union的局限性
- union中的对象成员,不能有构造函数、析构函数、重载的复制赋值运算符;对象成员的对象成员也不能有,依此类推
- union不能继承
8. union实战例子
使用union保存成绩信息,并输出
#include <iostream>
using namespace std;
class ExamInfo {
public:
ExamInfo(string n, char g):name(n),mode(GRADE),grade(g){}
ExamInfo(string n, bool p):name(n),mode(PASS),grade(p){}
ExamInfo(string n, int p):name(n),mode(PERCENTAGE),grade(p){}
void show();
private:
string name;
enum {GRADE, PASS, PERCENTAGE} mode;
union {
char grade;
bool pass;
int percent;
};
};
void ExamInfo::show() {
cout << name << ": ";
switch(mode) {
case GRADE: cout << grade;break;
case PASS: cout << pass ? "PASS" : "FAIL";break;
case PERCENTAGE: cout << percent;break;
}
cout << endl;
}
int main() {
ExamInfo examInfo1("English", 'B');
ExamInfo examInfo2("Calculus", true);
ExamInfo examInfo3("C++ Programming", 85);
examInfo1.show(); // English: B
examInfo2.show(); // Calculus: 1
examInfo3.show(); // C++ Programming: 85
return 0;
}