1. 操作符的分类
操作符
操作符的分类想必大家都知道,这里就不赘述了。本文只是介绍其中的一部分操作符。
2. 算术操作符
算术操作符主要有:
+ - * / %
其中除法(/)分为两类:
- 只含整数的除法:运算结果为整数
- 含有浮点数的除法:运算结果为浮点数
注意:%(取模)的结果是余数。并且取模操作符的两个操作数必须都是整数!
3. 移位操作符
<< 左移操作符
>>右移操作符
注意:移位操作符的操作数只能是整数。
【右移】:
补充:整数的二进制表示形式有三种:原码,反码,补码。
- 正数的原码反码补码相同。
- 负数的原码反码补码要进行计算,不能直接判断。
原码反码补码的计算方法:一个整数,无论正负,直接将其翻译为二进制序列,该二进制序列就是原码。
int a = 15 ;
因为15是被存放在int型中的,而int型有32个比特位,其最高位表示符号位,将其翻译成二进制位就是:
00000000000000000000000000001111 //(最高位是0,表示该数为正数)
就得到了a的原码。因为a是15,为正数,所以该原码同时也是a的反码和补码。
int b = -15;
接下来我们将b翻译成二进制序列:
10000000000000000000000000001111 //原码 (最高位是1,表示该数是负数) 11111111111111111111111111110000 //反码 符号位不变,其他位按位取反得到b的反码 11111111111111111111111111110001 //补码 反码+1得到补码
该二进制序列是b的原码,由于b是负数,所以其反码和补码要根据上面的规则进行计算。
注意:数据在内存中是以补码的形式进行存储的。
那么移位操作就是对数据的补码进行操作的。例如:
int a = 15; 00000000000000000000000000001111 //a的原码 同时也是反码和补码,这里我们将其看作补码 int b = a >> 1; //将a右移一位 00000000000000000000000000000111 //a右移之后得到的二进制序列,得到的是补码,但其是正数,所以其原反补相同 printf("%d",b); //打印7 printf("%d",a); //a的值不变,还是15
注意:C语言中的右移操作分位两种:(一般编译器采用的是算术右移)
- 算术右移(右边丢弃,最高位补原来的符号位)
- 逻辑右移(有边丢弃,最高位一律补0)
在内存中存放的是补码,那么操作之后得到的结果也应该是补码,所以我们要想知道操作的值是多少,必须将操作结果转换成原码,这里看到代码第4行,其最高位是0,代表运算结果是负数,所以其原码反码补码相同。所以直接将该二进制序列的值赋值给b变量,b变量的值就是a右移两位的结果。
接下来在讲一个负数右移:
int main() { int a = -15; 10000000000000000000000000001111 //a的原码 11111111111111111111111111110000 //原码除了符号位不变,其他按位取反得到反码。 11111111111111111111111111110001 //反码+1得到补码 int b = a >> 1; 11111111111111111111111111111000 //右移一位,最高位补符号位,也就是补1,得到补码 11111111111111111111111111110111 //除了符号位之外,其他按位取反,得到反码 10000000000000000000000000001000 //反码+1,得到原码,翻译成10进制,也就是-8 printf("%d\n", b);//打印 -8 printf("%d\n", a);//打印 15 return 0; }
得到操作结果之后,再将该结果转换为其原码,再翻译为十进制,就是-8。所以执行结果是打印-8
【左移】:左移就是将数据的补码向左移动,左边被移出的数据直接丢弃,右边补0。得到的操作结果仍然位补码,需要将其转换成原码。举个例子:
int main() { int a = 15; 00000000000000000000000000001111 //a的原码,a为正数,原反补相同 int b = a << 1; 00000000000000000000000000011110 //左移一位得到补码,由符号位可知,该二进制序列为正数,原反补相同 printf("%d ", b);//打印30 printf("%d ", a);//打印15 return 0; }
特别注意:移位操作不能移动负数位,例如:
int a = 15 >> -1;
这种操作是错误的,是C语言标准未定义的。
4. 位操作符
这里要先知道位操作符都是针对数据的二进制补码进行运算的,所以位操作符的操作数必须是整数。
【分类】
- &(按位与)
- |(按位或)
- ^(按位异或)
【按位与】
运算规则:二进制位有0则为0,全是1才为1。例如:
int main() { int a = 2; //00000000000000000000000000000010 int b = 1; //00000000000000000000000000000001 int c = a & b; //00000000000000000000000000000000 printf("%d ", c); //打印0 return 0; }
这里分别写出a和b的二进制补码,将其进行按位与操作,得到全0的二进制序列,结果也就是0。
【按位或】
运算规则:二进制位有1则为1,全为0才是0。例如:
int main() { int a = 2; //00000000000000000000000000000010 int b = 1; //00000000000000000000000000000001 int c = a | b; //00000000000000000000000000000011 printf("%d ", c); //打印3 return 0; }
分别写出a和b的二进制补码,将其进行按位或操作,得到的二进制序列翻译成10进制也就是3。
【按位异或】
运算规则:二进制位相同为0,相异为1。例如:
int main() { int a = 5; //00000000000000000000000000000101 int b = 1; //00000000000000000000000000000001 int c = a ^ b; //00000000000000000000000000000100 printf("%d ", c); //打印4 return 0; }
分别写出a和b的二进制补码,将其进行按位异或操作,得到的二进制序列就是4。
【按位异或的性质】
- 一个数和0异或得到的结果仍是其本身:我们将一个整数a和0进行异或操作,得到的一定是a本身。
- 两个相同的数异或得到的结果一定是0。
- 异或运算满足交换律和结合律。
【按位异或的应用】
Q1:如何交换两个整数?
交换两个整数的值可以有三种方法:
【Answer1】:
int main() { int a = 10; int b = 20; printf("before: a = %d b = %d\n", a, b); int tmp = a; a = b; b = tmp; printf("after : a = %d b = %d\n", a, b); return 0; }
这种方法会创建新的变量,但是总体问题不大。
【Answere2】:
int main() { int a = 10; int b = 20; printf("before: a = %d b = %d\n", a, b); a = a + b; b = a - b; a = a - b; printf("after : a = %d b = %d\n", a, b); return 0; }
这种方法有个弊端:当a和b的值很大时,将其加在一起可能就会超过int数据类型所能存储的最大数据范围。
【Answer3】:
int main() { int a = 10; int b = 20; printf("before: a = %d b = %d\n", a, b); a = a ^ b; b = a ^ b; a = a ^ b; printf("after : a = %d b = %d\n", a, b); return 0; }
这种方法就比较巧妙了。
这里要结合异或运算的性质进行分析。
【位操作符的应用】
Q2:求一个整数存储在内存中的二进制中1的个数。
【Answer1】:
int main() { int num = 10; int count= 0;//计数 while(num) { if(num%2 == 1) count++; num = num/2; } printf("二进制中1的个数 = %d\n", count); return 0; }
首先强调:**该方法只适用于正数!**该方法先判断该非0数是不是奇数,若是奇数,其最低的二进制位必定是1,计数器加1。接着再将该数除以2,(这里要注意,将一个正整数除以2,等效于将其二进制序列右移一位),再通判断该数的奇偶性间接判断其二进制位的最低位是否为1。知道num为0则停止。
该方法的缺陷是只适用于正整数。
【Answer2】:
我们首先知道,1与0进行按位与操作得到的是0,1与1按位与操作得到的1。
所以可以将该整数的二进制位的最低位和1进行按位与操作,其他高位与0进行按位与操作。此时整个表达式的执行结果就只与该整数的最低二进制位有关了,若最低二进制位是1,则结果为1,若最低二进制位为0,则结果为0。将该最低二进制位判断完毕之后,将整个二进制序列右移1位。将该操作进行32次,因为一个int类型的数据占用32个二进制位,我们需要将每一位都判断一次。
int main() { int num = -1; int i = 0; int a = 1; int count = 0;//计数 for (i = 0; i < 32; i++) { if (num & a) count++; num >>= 1; } printf("二进制中1的个数 = %d\n", count); return 0; }
【Answer3】:
int main() { int num = 9; int i = 0; int count = 0;//计数 while(num) { count++; num = num&(num-1); //第一次执行该语句时: //00000000000000000000000000001001 //00000000000000000000000000001000 //00000000000000000000000000001000 } printf("二进制中1的个数 = %d\n",count); return 0; }
这种方法难以想到。每执行一次num = num&(num-1);语句都能除去二进制序列中权值位最小的1。