数制
指二进制、十进制、八进制等不同的数字表示方法。例如十进制里的15,用二进制表示是1111,用八进制表示则是017。这里需要注意,一个数字的每一位代表其“权”。每个数字都可以写成如下的形式:
1234 = 1x103 + 2x102 +3x101 4x100 。而 1111b = 1x23 + 1x22 +1x21 1x20 = 15。其余的基本概念这里不在叙述。
编码
编码是使用特定规则对数据进行编写,例如熟悉的ASCII码就是一种编码方式。这里主要说一下原码、反码、补码,因为这三种编码在计算机运算时时很重要(都以有符号整型为例,暂不涉及浮点型)。
原码
原码就是这个数字的二进制表示。由于一个整型占4字节空间,除过最高位是符号位之外(0正1负),其余位均可以用于表示原码。举个例子:
int a = -5;
-5 的原码:1000 0000 0000 0000 0000 00000 0000 0101
反码
一个数字的原码按位取反就是反码。
-5 的反码:1111 1111 1111 1111 1111 1111 1111 1010
补码
反码+1 = 补码。
-5 的补码:1111 1111 1111 1111 1111 1111 1111 1011
对于正整数,它的原码、反码、补码相同,都是原码。
注意,计算机在存储整型时,一律使用补码。 为什么要这样做呢?愿因有3个:
- 使用补码运算的时候,可以不再额外处理符号位;
- 计算机中只有加法器,在计算减法的时候,例如a- b,实际执行的是a+(-b),使用补码存储可以统一处理;
- 补码和原码之间可以通过同样的步骤进行转换,意思就是补码 = 原码取反后+1,同时,对补码取反后+1得到的就是原码。
移位操作符
移位操作符仅能操作整数。
<< 左移操作符
规则:左边抛弃,右边补0。
从数字上看,相当于给原数字*2(不溢出的情况下)。
>>右移操作符
规则:
- 逻辑右移:左边补0,右边抛弃
- 算术右移:左边用原有的符号位填充,右边抛弃
采用哪个规则是编译器负责的,VS使用的是算术右移。
位操作符
&按位与
这个是将两个数字的二进制表示按位进行与运算。
|按位或
这个是将两个数字的二进制表示按位进行或运算。
^按位异或
这个是将两个数字的二进制表示按位进行异或运算。异或是满足交换律与结合律的。另外,异或还满足:
a^a = 0;
a^0 = a;
~按位取反
这个是将两个数字的二进制表示按位进行取反运算。
位运算的作用
使用起来非常灵活。例如嵌入式系统中,需要给某个寄存器位赋值1,且不影响其它位:
x | (1<<5)
需要将某个寄存器位清零且不影响其它位:
x & ~(1<<5)
不使用中间变量交换两个变量的值:
int a = 5;
int b = 3;
a = a^b;
b = a^b;// b = a^b^b = a^0 = a
a = a^b;//a = a^b^a = a^a^b = 0^b = b
计算一个二进制数中1的个数:
int main()
{
int num = -1;
int count =0;
while(num)
{
count++;
num = num&(num-1)//这个运算每次都会将最低位的1移除,运算了多少次,数字里就有多少个1。
}
return 0;
}
找单身狗数字:
一个数组中所有的数字都会出现两次,仅有1个数字只出现了1次,找出这个数字。
int main()
{
int arr[] = {1,2,3,4,5,1,2,3,4}
int i = 0;
int ret = arr[0];
for(i = 1;i < 9; i++)
{
ret ^=arr[i];
}
printf("%d\n",ret);
return 0;
}
由于a^a = 0,所以出现两次的数字在异或了之后都是0,仅出现1次的数字和0做异或运算,得到的就是这个数字本身。
逗号表达式
逗号表达式的运算规则:从左至右运算,整个表达式的值是最后一个值。某些情况下会让代码更加简洁。