原始数据类型及相互转化

第二课

本文为本系列笔记的第一篇,因为第一课为整体课程介绍,无实质内容,但为了与课程保持一致,本系列笔记从第二课开始

c/c++ 原始数据类型

bool

char

short

int

long

float

double

1 字节

2 字节

4 字节

4 字节

4 字节

8 字节

举例

char ch = 'A'; // 65 -> 01000001
short s = 519; // 519 -> 00000010 | 00000111

负数的表示形式

以 short 为例,1个 short 类型有 2 字节,共包含 65536 种状态。如果都表示自然数,那么可以表示 0~65535 之间的所有自然数。但我们希望计算机能区别正负数,此时最合乎逻辑的做法就是:

拿出这里面一半的状态来表示负数,即 -32767 ~ 32768

因此,取最高位比特 (most significant bit) 表示符号,最高位是 1 表示负数,最高位是 0 表示正数:

7: 00000000 00000111
-7: 10000000 00000111

计算机中最基本的运算除了位运算就是加减法,如果用当前的表示法计算 -7 + 7:

00000000 00000111
+ 10000000 00000111
-------------------
= 10000000 00001000

那么,我们得到的数字是 -8,我们希望计算计在运行时能更优雅地处理加减法,使得 -7 + 7 得到的数字是 0

00000000 00000111 00000000 00000111 00000000 00000111
+ xxxxxxxx xxxxxxxx => + 11111111 11111000 => + 11111111 11111001
------------------- ------------------- -------------------
= 00000000 00000000 = 11111111 11111111 = 00000000 00000000

将一个二进制正数转化为对应的计算机二进制负数:(同理)

11111111 11111001 =翻转所有比特位=> 00000000 00000110 =加1=> 00000000 00000111

这种表示法的学名是 2's complement

浮点数的表示形式

以 float 类型 (4 bytes) 来举例:为了表示浮点数,我们势必需要拿出一些位来表示小数部分 (fractions),如:

// 1 位小数位
00000000 00000000 00000000 0000000[1] 表示 0.5
// 2 为小数位
00000000 00000000 00000000 000000[11] 表示 0.75

但通过观察可以知道:4 个字节一共只能表示 4294967296 种数字,而实数空间有无穷多个数,因此我们能准确表示的数非常有限。为了满足日常需求,我们希望更好地利用这 4 个字节来表示的数字>范围:

  1. 数量级范围较广

  2. 对于量级靠近 0 的数能尽量准确表示

因此,计算机科学家将 4 个字节拆成了 符号位,指数位和小数位来表示浮点数:

float:
sign exponent fractions
| +/- | <-------8-------> | <---------------23-------------------> |
(-1)^(sign) * 1.xxxx... * 2^(exp - 127) 其中 1.xxxx... 来自于 fractions
double:
sign exponent fractions
| +/- | <--------11-------> | <----------------52-------------------> |

举例如下:

float: -7.0 = -1.75 * 2^2 因此,符号位是 1,指数位是2,小数位是 0.75
=> |1|10000001|11000000000000000000000|

小端 (Little Endian) 和 大端 (Big Endian)

Endianness 指的是在多个字节表示的数值类型 (short, long, float, double) 中,字节的排列顺序。最高位所在的字节称为大端,最低位所在的字节称为小端。那么可以按如下方式记住小端存储和大端存储:

  • Little Endian -> Little End -> 从 Little End 开始存储

  • Big Endian -> Big End -> 从 Big End 开始存储

举例如下:

short s = 7;
// Big Endian: => 00000000 00000111
// Little Endian: => 00000111 00000000
float f = -7.0;
// Big Endian: => 11000000 11100000 00000000 00000000
// Little Endian: => 00000000 00000000 11100000 11000000

原始数据类型之间的转化

char 转 short

char ch = 'A';
short s = ch;
// Big Endian:
// ch: => 01000000
// s: => 00000000 01000001
// Little Endian:
// ch: => 01000001
// s: => 01000001 00000000

short 转 int

short s = 1033;
int i = s;
// Big Endian:
// s: => 00000100 00001001
// i: => 00000000 00000000 00000100 00001001
// Little Endian:
// s: => 00001001 00000100
// i: => 00001001 00000100 00000000 00000000
short s = -1;
int i = s;
// Big Endian: (符号位延续)
// s: => 11111111 11111111
// i: => 11111111 11111111 11111111 11111111
// Little Endian:
// s: => 11111111 11111111
// i: => 11111111 11111111 11111111 11111111

int 转 short

int i = 10502151;
short s = i;
// Big Endian:
// s: => 01000000 00000111
// i: => 00000000 10100000 01000000 00000111
// Little Endian:
// s: => 00000111 01000000
// i: => 00000111 01000000 10100000 00000000
int i = -65536;
short s = i;
// Big Endian:
// s: => 00000000 00000000
// i: => 11111111 11111111 00000000 00000000
// Little Endian:
// s: => 00000000 00000000
// i: => 00000000 00000000 11111111 11111111

int 转 float

int i = 5;
float f = i;
// 神奇地转化,非截断,而是保持原数值大小,这也是方便平时计算需求
// f: => 01000001 00100000 00000000 00000000
// i: => 00000000 00000000 00000000 00000101

参考资料