现代计算机存储和处理的信息以二值信号表示。这些微不足道的二进制数字,或者称为位(bit),奠定了数字革命的基础。孤立的讲,单个的位不是非常有用。然而,当把位组合在一起,再加上某种 解释,即给不同的可能 位模式 赋予含义,我们就能表示任何有限集合的元素。
一、数值的表示
1. 1 数值的编码
数值的类型主要分为2种:整数 和 浮点数。 编码的方式主要分为3种:无符号编码、补码编码 和 浮点数编码。
无符号 编码基于传统的二进制表示法,表示大于等于0的数字。
补码 编码是表示有符号整数的最常见的方式。
浮点数 编码是表示实数的 科学计数法 的以二位基数的版本,所以浮点数不能精确保存,而且由于表示的精度有限,浮点运算是不可结合的。
无符号数采用原码存储,有符号数采用补码存储。正数的补码就是他本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1. (即在反码的基础上+1)
[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
计算机的表示法是用有限数量的位来对一个数字编码,因此,当结果太大以至不能表示时,某些运算就会导致 溢出。例如,大多数计算机(使用32bit表示整数数据int),计算表达式 200*300*400*500 会得出结果 -884 901 888。
整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的;而浮点数虽然可以编码一个较大的数值范围,但是这种表示只是近似的。
1.2 进制转换
编写程序的一个常见任务是在位模式的十进制、二进制、八进制和十六进制表示之间人工进行转换。
1.2.1 二进制,八进制 与 十六进制之间的转换
下面以二进制与十六进制之间的转换为例,其他情况在代码中非另外说明:
二进制 -> 十六进制
二进制向十六进制转换时,四位转换成十六进制的一位,运算的顺序是从低位向高位依次进行,高位不足四位用零补。以“1110011”转换成十六进制为例,如下图所示:
转换的结果为:1001011101 == 0X25D
// 二进制转换为十六进制
string bin2hex(string two)
{
string res; // 转换结果
int i = two.length() - 1; // 从最低位开始转换
int j = 0;
int sum = 0; // 每4位的和
while (i >= 0)
{
j = 0;
sum = 0;
while (j<4 && i >= 0) // 把 “j < 4” 改成 “j < 3” 就变成了“二进制 -> 八进制”
{
sum += (two[i] - '0') * pow(2, j); // 2^j
i--;
j++;
}
if (sum <= 9)
res += sum + '0';
else
res += sum - 10 + 'A';
}
return res;
}
十六进制 -> 二进制
十六进制向二进制转换,就是把十六进制的一位转换成二进制的四位,注意运算的顺序是从低位向高位依次进行。同样以十六进制“0X25D”为例,如下图所示:
// 十六进制 -> 二进制
string hex2bin(string hex)
{
string res; // 转换结果
int i = hex.length() - 1; //从低位开始
int num = 4; //用于保证每个16进制位用4个二进制位来表示
while (i >= 0)
{
char c = hex[i];
int val;
if (c >= '0' && c <= '9')
{
val = c - '0';
}
else if (c >= 'A' && c <= 'F')
{
val = c - 'A' + 10;
}
else
{
return string();
}
num = 4;
while (val)
{
res += val % 2 + '0';
val = val / 2;
num--;
}
while (num != 0) //如果转换过程中,已经占用4位,则不用在高位补0
{
res += '0';
num--;
}
i--;
}
return res;
}
1.2.2 十进制转换为任意进制
十进制转换为其他进制较为简单,只要依次取得余数即可。注意,越先出来的余数是越低位。
string ten2any(int ten, int rd) // rd : 目标进制
{
string res;
int val;
while (ten)
{
val = ten % rd;
if (val >= 10)
res += val - 10 + 'A';
else
res += val + '0';
ten = ten / rd;
}
return res;
}
1.2.3 任意进制转换为十进制
二进制数 1010 转换为十进制的过程是: (0 * 2^0) + ( 1 * 2^1) + (0 * 2 ^ 2) + (1 * 2 ^3) = 10。
// 任意进制 -> 十进制
int any2ten(string any, int rd)
{
int i = any.length() - 1;
int j = 0;
int sum = 0;
while (i>=0)
{
if (any[i] <= '9' && any[i] >= '0')
sum += (any[i] - '0') * pow(rd, j);
else
sum += (any[i] - 'A' + 10) * pow(rd, j);
j++;
i--;
}
return sum;
}
1.3 大端序 & 小端序
小端序指数据的低字节保存在内存的低地址中,而数据的高字节保存在内存的高地址中 。 大端序刚好相反。
如下一段代码:
int a = 4 ; // 0000 0100
int b = 257 ; // 0000 0001 0000 0001
根据 小端模式 的规定,低字节存储在低地址中,所以对于a = 4 , 低8位将放置在a所占用的4个地址中的最低位。如下:
对于以上代码,如果按照 大端序 存储的话,则如下:
1.3.1 判断CPU的?端序
int a = 1;
if ((char)a == 1) //取最低地址的一个字节
cout << "小端序" << endl;
else
cout << "大端序" << endl;
1.3.2 说明
更多关于端序的讨论,可参考 C++ 笔试题集锦(1) - 问题4 。
1. 4 无符号数 & 有符号数
有符号数到无符号的隐式强制类型转换经常导致某些非直观的行为。而这些非直观的行为经常导致程序出错。
如下两个示例:
float sum_elements(float a[], unsigned len)
{
int i ;
float result = 0;
for(i = 0; i <= len - 1; i++)
{
result += a[i];
}
return result;
}
当参数len == 0
时,运行这段代码本应该等于0。但是 len - 1 == 4294967295;
而且int 的表示范围是 -2147483648~2147483647 , 所以当 i 增长到 2147483647 时,继续+1将变为-2147483648,如此循环下去,所以这个for循环本身就是死循环。另一方面,i 是负数或者太大都会造成a[]数组访问越界。
size_t strlen(const char *s); // 注意 : typedef unsigned int size_t
int strlonger(char * s, char * t)
{
return strlen(s) - strlen(t) > 0;
}
这个函数并不能实现比较两个字符串长度的功能。当s的长度更小时,strlen(s) - strlen(t)
将会因为借位得到一个大于0的值。
二、类型转换
当等号两边的类型不一致时,将自动发生隐式类型转换。
2.1 相同字长的整数转换
C语言允许无符号数和有符号数之间的转换。
转换的原则: 底层的位模式保持不变,改变解释这些位的方式。
char v = -1; // 1111 1111 -> -1
unsigned char uv = v; // 1111 1111 -> 255
char v2 = uv; // 1111 1111 -> -1
unsigned char uc = 0; // 0000 0000 -> 0
uc = uc - 1; // 1111 1111 -> 255 借位,重新解读位模式
char c = 127; // 0111 1111 -> 127
c = c + 1; // 1000 0000 -> -128 进位,重新解读位模式
2.2 不同字长的整数转换
2.2.1 短字长 -> 长字长
零扩展
将一个无符号数转换为一个更大的数据类型,我们只需要简单地在表示的开头添加0。
unsigned char -> short
unsigned char uc = 128; // 1000 0000 -> 128
short s = uc; // 0000 0000 1000 0000 -> 128 添加0
符号扩展
将一个有符号数转换为一个更大的数据类型,我们只需要简单地在表示的开头添加符号位。
char -> short
char uc = -128; // 1000 0000 -> -128
short s = uc; // 1111 1111 1000 0000 -> -128 添加符号位 1
char uc2 = 127; // 0111 1111 -> 127
short s2 = us2; // 0000 0000 0111 1111 -> 127 添加符号位 0
2.2.2 长字长 -> 短字长
当把一个更大数据类型的值 赋给 一个较小数据类型的值时,将发生 截断 。
short s = 128; // 0000 0000 1000 0000 -> 128
char c = s; // 1000 0000 -> -128 只获得低8位
short s = 384; // 0000 0001 1000 0000 -> 384
char c = s; // 1000 0000 -> -128 只获得低8位
2.3 说明
更多关于类型转换示例,参考 C++ 类型转换
三、整数运算
TODO
四、浮点数
TODO