Kevin's Blog

Core of Kevin.

一道有意思的union问题

一道简单的C++ union的问题

#include <iostream>
using namespace std;

union A {
    int t;
    char s[2];
};

int main() {
    A a;
    a.t = 0;
    a.s[0] = 1;
    a.s[1] = 2;
    cout << a.t << endl;
    return 0;
}

这个问题很简单,就是让我们回答输出的a.t是多少。问题虽然很小,但是却反映了C++中的两个重要的知识点:union和数据类型

C++ 中的union

我们知道,union是一种特殊的class,同时也是一种构造类型的数据结构,其中声明的所有的成员共享同一块内存。因此,一个union的变量的长度就是等于其声明的成员中最长的长度。如下面的例子所示:

#include <iostream>
using namespace std;

union M {
    char a;
    int b;
    double c;
};

int main() {
    M m;
    cout << sizeof(m) << endl;
    return 0;
}

我们可以看到,上面的代码执行之后得到的结果是 8(这里的单位是byte),然后我们将注释掉 ‘double c’, 运行之后得到的结果是 4。实验的结果正如上文所说的一个union的变量的长度就是等于其声明的成员中最长的长度。那么原始的问题中定义的union A的大小是多少呢?由于 char s[2]的大小是2,而int的大小是4,那么显然,在main函数的变量 a 的大小就是4.

整数的表示

如果说这道题只是单纯地进行union的分析,那么它的价值就不会很大了,这道题的另一个知识点就是整数的表示。 大家都知道数据在计算机中是按字节来存储的(1byte = 8bits),而计算机只能识别0和1这两个数。这样,1个字节能表示256种不同的信息(28 = 256)。比如我们要定一个1字节的无符号整数

unsigned char a;

那么a就能表示在[0,255]区间内的数,比如说我现在设定a = 17那么在计算机中,a就表示为00010001。而计算机中无符号数就是按照这个书的本身的二进制码来存储的。但是别急,这只是无符号整数的表示,而下面有符号的整数就不是这么简单了。

有符号整数的表示

从上文我们可以知道无符号数的表示直接以原码的形式展现出来,但是一旦一个数是负数的时候该如何表示呢?如果是书面的去写负数,很简 单,正数前面加一个就可以了,但是我们却不能在计算机中这么做(只能识别1和0的计算机君表示根本不知道这是什么玩意 儿),那么就引入了这样的一种表示方法,那就是最高位作为符号位,如果第一位是0则表示正数,第一位是1就表示负数,这样的话,对于一个 8位的二进制来说,表示数值的只有7位了,那么表示有符号整数的正数范围是[1, 127], 负数的范围就是[-127, -1],当然,还有0,但是 我们看到一个问题,那就是当我们表示-127的时候,二进制表示为11111111,这和无符号数里面的255是一样的,那么具体是表示-1还是 255,就需要我们人为来指定了。而在计算机中,真正用来表示有符号数的是补码。这里我们需要了解:原码,反码,补码。 对于正数,原码/反码/补码都是其原码本身;但是负数的反码是除去符号位之外的所有位取反,而补码是反码加一。 上述的内容大家可能觉得知其然,但是为什么有符号整数需要用补码来表示呢? 我们首先看看如果用原码来表示有符号数有什么不合逻辑的地方,让我们来表示+0和-0。 +0: 00000000 -0: 10000000 反码表示+0和-0 +0: 00000000 -0: 11111111 上述的两种表示中,+0和-0的表示不是一样的,这样就有违背我们数学上关于0的意义。下面,我们看看补码是如何表示+0和-0的 +0: 00000000 -0: 00000000 哈,两种表示是一样的,那么自然我们采取补码来表示有符号整数,其实非负数的表示范围是00000000~01111111,而负数的表示范围是 11111111~10000000,记住了,10000000不是表示0的,是表示-128。所以这样一来,补码表示就比原码和反码表示的范围都大,并且 也解释了为什么计算机中有符号整数的范围是非对称的^–^

程序的结果

根据上述的关于union和有符号整数的分析,我们可以知道程序的输出了。简单地演算一下吧


a.t = 0;    //00000000 00000000 00000000 00000000
a.s[0] = 1; //00000000 00000000 00000000 00000001
a.s[1] = 2; //00000000 00000000 00000010 00000001

结果就是 27 + 20 = 513