移位操作符
左移
0000 1011 << 1 在右边统一补0 11
0001 0110 << 2 11*2^1
0010 1100 11*2^2
右移
0000 1011 >>1 补当前数字的最高位 11
0000 0101 >>2 11/2^1
0000 0010 11/2^2
特殊情况,最高位为1时
最高位为符号位,1为负数,0为正数
负数在内存当中存储的是补码
原码 --》反码 --》补码
原码 1000 0000 0000 0000 0000 0000 0000 0001 此为-1的源码
反码 1111 1111 1111 1111 1111 1111 1111 1110 符号位不变,其他位取反
补码 1111 1111 1111 1111 1111 1111 1111 1111 对反码+1得到补码
-1
在内存中是存放了32个1
在定义时使用unsigned int a = -1
定义出来的是无符号量,此时当我们以printf("%d\n",a)
时,打印出来的为-1
,但当使用printf("%u\n",a)
时,打印出来将是一个很大的数。
位操作符
& 按位与
0000 1010
0100 1001
0000 1000 对应为上相同,就是那个位上的值
| 按位或
0000 1010
0100 1001
0100 1001 只要对应位上是1,结果就是1
^ 按位异或(不一样的位上或,一样的都是0)
0000 1010
0100 1001
0100 0011 ————》 0异或任何数字,都是那个数字本身
两个相同的数字,因为对应位2进制一样,所以结果就是0
例题1:对于一个数组[1,2,3,2,1]找到这个数组中只出现一次的数
解析:
只要是运算符,都满足交换律
1^2^3^2^1 = (1^1)^(2^2)^3 = 0^0^3 = 3
对例题1进行升级:在一个数组中,有两个数字是出现一次的,其他数字都是出现两次的,找到出现一次的那两个数字
以[1,2,3,2,1,4]数组为例
对上述数组进行异或,其结果就是3^4的结果
0000 0011 3 0000 0001 1
0000 0100 4 0000 0010 2
0000 0111
得到3和4在最后三位位置上,对应位不一样
我们设定一个规则,只要数组中从右往左第二个位是1的,放在一类,不是1的放在一类
第二位上为1的 第二位上不为0的
2,2,3 1,1,4
对两类分别进行异或,就可以找到两个数字
例题2:不创建临时变量实现,实现两个数的交换
一般情况下,想要交换两个数,使用的是以下代码
int main(){
int a = 10;
int b = 20;
int tmp = a;
a = b;
b = tmp;
printf("%d,%d",a,b);
}
不添加临时变量
int main(){
int a = 10;
int b = 20;
a = a + b; // a=30
b = a - b; //30-20=10
a = a - b; //30-10=20
}
使用异或的方法
int main(){
int a = 3;
int b = 2;
a = a^b; // 0011 ^ 0010 = 0001
b = a^b; // 0001 ^ 0010 = 0011 3
a = a^b; // 0001 ^ 0011 = 0010 2
}
例题3:计算一个数字的二进制有多少个1
#include<stdio.h>
int main(){
int n = -1;
int count = 0;
while(n ! = 0){
count++;
n = n & (n-1);
}
printf("%d\n",count);
}
练习1:关于表达式求值的相关概念:
①表达式求值先看是否存在整形提升或算数转换,在进行计算。
②表达式真正计算的时候先看相邻操作符的优先级决定先算谁
③相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序
④有了优先级和结核性,表达式也有可能有不同的计算路径,导致计算结果的差异。
练习2:下面代码结果:
#include<stdio.h>
int main(){
int i = 1;
int ret = (++i) + (++I) + (++i);
printf("ret = %d\n",ret);
return 0;
}
解析:表达式(++i) + (++I) + (++i)
只有操作符的优先级和结合性,无法计算唯一计算路径。在不同的编译器上结果可能有差异。所以此表达式为错误的表达式
练习3:下面代码的结果是:
#include<stdio.h>
int i;
int main(){
i--;
if(i > sizeof(i)){
printf(">\n");
}
else{
printf("<\n");
}
return 0;
}
解析:在C语言中,0为假,非0为真;这里定义了i
为全局变量,没有给初始值时,编译会默认将其初始化为0。则i--
结果为-1
,i
为整形,sizeof(i)
求i
类型的大小,在32系统下为4;理论上结果应该时输出“<”
,但是sizeof
的返回值类型实际为无符号整型,因此编译器会自动将左侧的i
自动转换为无符号整形数据,因此-1
将会被转化为一个很大的数。
练习4:下面代码的运行结果:
#include <stdio.h>
int main()
{
int a, b, c;
a = 5;
c = ++a; // ++a:加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6 c = 6
b = ++c, c++, ++a, a++; // ++c:c为7 c++:c值不变 ++a:a的值为7,a++:a值不变,b取a的值:7
// 表达式结束时,c++和a++会给a和c分别加1,此时c:8,a:8,b:7
b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
return 0;
}
结果为:a=9,b=23,c=8
练习5:求两个数二进制中不同位的个数
思路:
1. 先将m和n进行按位异或,此时m和n相同的二进制比特位清零,不同的二进制比特位为1
2. 统计异或完成后结果的二进制比特位中有多少个1即可
#include<stdio.h>
int calc_diff_bit(int m, int n){
int tmp = m^n;
int count = 0;
while(tmp){
tmp = tmp & (tmp - 1);
count++;
}
return count;
}
int main(){
int m,n;
while(scanf("%d %d", &m, &n) == 2){
printf("%d\n", calc_diff_bit(m, n))
}
}
练习6:打印整数二进制的奇数位和偶数位,获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
思路
1. 提取所有的奇数位,如果该位是1,输出1,是0则输出0
2. 以同样的方式提取偶数位置
检测num中某一位是0还是1的方式:
1. 将num向右移动i位
2. 将移完位之后的结果与1按位与,如果:
结果是0,则第i个比特位是0
结果是非0,则第i个比特位是1
#include<stdio.h>
#include<windows.h>
void my_print(int n)
{
int i = 0;
printf(" 打印奇数位\n");
for (i = 30; i >= 0; i = i - 2)
{
printf("%d ", (n >> i) & 1);
}
printf("\n");
printf("打印偶数位\n");
for (i = 31; i >= 1; i = i - 2)
{
printf("%d ", (n >> i) & 1);
}
}
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
my_print(num);
system("pause");
}
练习7:统计二进制中1的个数
作业内容
写一个函数返回参数二进制中 1 的个数。
比如: 15 0000 1111 4 个 1
方法一;循环进行以下操作,直到n被缩减为0:
1. 用该数据模2,检测其是否能够被2整除
2. 可以:则该数据对应二进制比特位的最低位一定是0,否则是1,如果是1给计数加1
3. 如果n不等于0时,继续1
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n){
int count = 0;
while (n){
if (n % 2 == 1){
count++;
}
n = n / 2;
}
return count;
}
int main(){
int n = 0;
scanf("%d", &n);
printf("%d\n",count_one_bit(n));
system("pause");
}
上述方法缺陷:进行了大量的取模以及除法运算,取模和除法运算的效率本来就比较低。
方法二:
一个int类型的数据,对应的二进制一共有32个比特位,可以采用位运算的方式一位一位的检测。
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n){
int count = 0;
for (int i = 0; i < 32; i++){
if (((n >> i) & 1) == 1){
count++;
}
}
return count;
}
int main(){
int n = 25;
//scanf("%d ", &n);
//printf("%d\n", count_one_bit(n));
count_one_bit(n);
system("pause");
}
//方法二优点:用位操作代替取模和除法运算,效率稍微比较高,缺陷:不论是什么数据,循环都要执行32次
方法三:
思路:采用相邻的两个数据进行按位与运算
举例:
9999:10 0111 0000 1111
第一次循环:n=9999 n=n&(n-1)=9999&9998= 9998
第二次循环:n=9998 n=n&(n-1)=9998&9997= 9996
第三次循环:n=9996 n=n&(n-1)=9996&9995= 9992
第四次循环:n=9992 n=n&(n-1)=9992&9991= 9984
第五次循环:n=9984 n=n&(n-1)=9984&9983= 9728
第六次循环:n=9728 n=n&(n-1)=9728&9727= 9216
第七次循环:n=9216 n=n&(n-1)=9216&9215= 8192
第八次循环:n=8192 n=n&(n-1)=8192&8191= 0
可以观察下:此种方式,数据的二进制比特位中有几个1,循环就循环几次,而且中间采用了位运算,处理起来比较高效
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n)
{
int count = 0;
while(n)
{
n = n&(n-1);
count++;
}
return count;
}
int main(){
int n = 0;
scanf("%d", &n);
printf("%d\n",count_one_bit(n));
system("pause");
}
练习8:交换两个变量,不允许创建临时变量,交换两个整数的内容
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d\n", a,b);
a = a^b;
b = a^b;
a = a^b;
printf("交换后:a = %d b = %d\n", a,b);
return 0;
}