一、位操作符的运用
我之前的文章《C语言——操作符》中有详细讲解位操作符。
例1:写一个函数计算参数二进制中的1的个数
方法一:
例如5的二进制补码是00000000 00000000 00000000 00000101,这之中的1的个数为2
#include int Counter(int x){int i = 0,k = 1,counter = 0;for (i = 0; i < 32; i++){if ((x & k) != 0){counter++;}k <<= 1;}return counter;}int main(){int n = 0;scanf("%d", &n);printf("%d\n",Counter(n));return 0;}
因为位操作符&的规则是两个二进制位都是1结果才能是1,所以我们,可以对参数的二进制每一位进行按位与操作,只要按位与后的结果不是0,则这一位是1,计数器就加一,这样就可以计算1的个数,可以将1不断向左移位,实现每一位都被用1按位与操作。
0101 & 0001结果不是0,则第一位是1,0101 & 0010结果是0,则第二位是0。
从0001到0010就需要用到左移操作符。
运行结果:
方法二:
#include int Counter(int x){int i = 0,counter = 0;for (i = 0; i > i) & 1) == 1){counter++;}}return counter;}int main(){int n = 0;scanf("%d", &n);printf("%d\n",Counter(n));return 0;}
对于这个方法是直接将参数右移然后按位与1,参数右移32位后会将参数的每一位都按位与1,只要按位与1后等于一,则这一位是1,等于零的话,则这一位是0,这样就可以计算1的个数。
0101 & 0001结果为1,则第一位是1,然后将参数右移一位得到0010,0010 & 0001的结果为0,则第二位为0。
右移32位后就能将参数的每一位都按位与1,则可以计算1的个数。
运行结果:
方法三:
对于这个题目,不用位操作符一样能实现,类比十进制中将一个数的每一位相加,这里也能用取余和除实现。
#include int Counter(unsigned int x)//这里用无符整型是为了适用于负数{int counter = 0;while (x)//只要x不为0,则循环进行{if (x % 2 == 1){counter++;}x /= 2;}return counter;}int main(){int n = 0;scanf("%d", &n);printf("%d\n",Counter(n));return 0;}
对于十进制数字5,它的二进制补码为0101,两个1。
5 % 2 = 1,counter++
5 / 2 = 2 … 1
2 % 2 = 0
2 / 2 = 1
1 % 2 = 1,counter++
1 / 2 = 0 … 1
得到结果为2,所以有两个1。
对于无符整型为了适用于负数,如果输入-1,-1的补码为11111111111111111111111111111111,由于函数参数用的是无符整型,则这个补码会被识别为一个很大的整数,即4294967295,最终会得到结果为32个1,这是正确结果,-1的补码就是32个1。
这里如果不使用无符整型而是用int类型,则-1的补码不会被识别成一个很大的正数,而是被识别成-1,而-1的流程为:
-1 % 2 = -1
-1 / 2 = 0 … -1
结果为0,这个结果是错误的,因为-1的补码有32个1。
方法四:
一个很神奇的方法。
#include int Counter(int x){int counter = 0;while (x){x = x & (x - 1);counter++;}return counter;}int main(){int n = 0;scanf("%d", &n);printf("%d\n",Counter(n));return 0;}
一个十进制数字7,他的二进制补码为0111。
0111 & 0110 = 0110
0110 & 0101 = 0100
0100 & 0011 = 0000
每次进行x = x & (x – 1)操作后,x最右侧的1就会变成0,这样只要每一轮加一,直到x变成0再停止,这样就可以计算1的个数。
运行结果:
例2:计算两个数的二进制补码有多少位是不同的
例如0100和0101有一位不同。
方法一:
直接比较两个数的每一位是否相同。
#include int Func(int x,int y){int i = 0,counter = 0;for (i = 0; i > i) & 1) != ((y >> i) & 1)){counter++;}}return counter;}int main(){int m = 0,n = 0;scanf("%d %d", &m, &n);printf("%d\n",Func(m,n));return 0;}
用右移操作符将两个数的每一位都按位与1,如果按位与1的结果相同,则两个数的这一位是相同的,如果按位与1的结果不相同,则两个数的这一位是不相同的,不相同则加一,这样可以计算不同的位有多少个。
运行结果:
方法二:
#include int Counter(int x){int counter = 0;while (x){x = x & (x - 1);counter++;}return counter;}int Func(int m,int n){return Counter(m ^ n);}int main(){int m = 0,n = 0;scanf("%d %d", &m, &n);printf("%d\n",Func(m,n));return 0;}
按位异或的规则是相同为0,不同为1,只用按位异或两个数然后统计1的个数,就可以实现要求,这又与上面的计算1的个数联系起来了。
运行结果:
例3、获取一个二进制序列的奇数位和偶数位,并打印,假设最低位为第一位
例如0101,奇数位为第一和第三位,打印出即11,偶数位为第二位和第四位,打印出来即00。
将每位都按位与1,按位与1会得到这一位上的数字,再通过右移操作符即可实现打印每一位的数字,再进行一些调节则可实现打印奇数和偶数位上的数字。
#include int main(){int num = 0;int i = 0;scanf("%d", &num);//右移30位是直接将31位放到第一位,i -= 2实现后面的奇数位移到第一位,i >= 0则是最小只移动0位,则是将第一位移到第一位,就是没移动for (i = 30; i >= 0; i -= 2)//这里反向生成是为了从高位向低位打印,方便阅读{printf("%d", (num >> i) & 1);}printf("\n");//右移31位是直接将32位放到第一位,i -= 2实现后面的偶数位移到第一位,i >= 1则是最小只移动1位,则是将第二位移到第一位for (i = 31; i >= 1; i -= 2)//这里反向生成是为了从高位向低位打印,方便阅读{printf("%d", (num >> i) & 1);}printf("\n");return 0;}
十进制数字5的二进制补码是000000000000000000000000 00000101
运行结果:
二、全局变量
全局变量和静态变量都在静态区,如果全局变量或静态变量不初始化的话,则被默认初始化为0。
局部变量在栈区,而局部变量不初始化则里面放的是随机值。
例:程序运行结果
#include int i;int main(){i--;if (i > sizeof(i)){printf("大于!\n");}else{printf("不大于!\n");}return 0;}
这里的运行结果为:
因为全局变量不初始化,则被默认初始化为0,在通过i–时,i变成了-1,那sizeof(i)应该为4啊!为什么会输出大于呢?这是因为sizeof输出的结果为size_t类型,即无符整型,而且当有符号和无符号整型混合运算时,如果无符号类型不小于有符号类型,则有符号类型会转换为无符号类型。这个在我之前的文章《C语言——表达式的求值》中提到过。
-1的二进制补码为11111111111111111111111111111111,这里i被转换为无符号整型,则-1的二进制补码被识别成了很大的正数,即4294967295,4294967295 > 4所以会输出大于的信息。