今天学习了语言操作符,双目操作符与单目操作符,包含一些例子以及编写代码的注意事项
包括加减、位移、赋值,逗号表达式之类
因为是很简单的内容,就不把所有的知识点复制过来了,重点讲几个容易让我们初学者迷惑的
/*移位操作符*/
int main()
{
int a = 16;
int b = a >> 1; // 右移操作符
// 移动的是二进制
// 00000000000000000000000000010000 <--- 32个byte位、 4个字节 (int)
// 1. 算术右移:00000000000000000000000000010000 ---> 0000000000000000000000000001000
// 右边丢弃,左边补原符号位 |
// 正数,符号位为0
// 2. 逻辑右移: 00000000000000000000000000010000 ---> 0000000000000000000000000001000
// 右边丢弃,左边补0
printf("a = %d\n", b);
int c = -1;
// 整数的二级制表示有:原码,反码,补码
// 存储到内存的是补码
// 10000000000000000000000000000001 ---------原码
// | |
// 符号位,负数为1 数值1
// 11111111111111111111111111111110 ----------反码
// 符号位不变,其他位按位取反
// 11111111111111111111111111111111 ----------补码
// 反码 + 1 = 补码
int d = c >> 1;
printf("c = %d", d);
return 0;
}
// 15 ----> 1*2^3+1*2^2+1*2^1+1*2^0 = 8+4+2+1 ( 4个1 ) ---- 16进制里一个f表示15,二进制内表示4个1;8个f表示32个1
/*左移*/
int main()
{
int a = 5;
int b = a << 1;
// 00000000000000000000000000000101
//左边丢弃,右边补0
// 00000000000000000000000000001010
printf("a = %d", b);
return 0;
}
// 不能移动负数位,整个是标准未定义的
首先是位移操作符,我们需要明确原码,补码以及反码的相关联系,再进行位移操作,这个内容为后续很多运算都打下基础,应该着重注意
我们要注意,存储到内存的都是补码
补码 = 反码 + 1??
将这两个加粗的内容牢记,并看看上面贴的例子,整个位移的操作应该很好记
获得这个知识之后,再学习与、或、异或就很简单了,笔者也做了注释
/*位操作符*/
int main()
{
// & - 按2进制位与
// 有0为0
int a1 = 3;
int b1 = 5;
int c1 = a1 & b1;
// 00000000000000000000000000000011
// 00000000000000000000000000000101
// 00000000000000000000000000000001
printf("a1 & b1 = %d\n", c1);
// | - 按2进制位或
// 有1为1
int a2 = 3;
int b2 = 5;
int c2 = a2 | b2;
printf("a2 | b2 = %d\n", c2);
// 00000000000000000000000000000011
// 00000000000000000000000000000101
// 00000000000000000000000000000111
// ^ - 按2进制位异或
// 相同为0,相异为1
int a3 = 3;
int b3 = 5;
int c3 = a3 ^ b3;
printf("a3 ^ b3 = %d\n", c3);
// 00000000000000000000000000000011
// 00000000000000000000000000000101
// 00000000000000000000000000000110
return 0 ;
}
通过这两个内容,完成一个测试题,本题是某公司的真实面试题:
交换两个int变量的值,不能使用第三个变量
int main()
{
int a = 3;
int b = 5;
int tmp = 0;
printf("before : a = %d,b = %d\n", a, b);
tmp = a;
a = b;
b = tmp;
printf("after : a = %d,b = %d ", a, b);
/*加减法*/
int a = 3;
int b = 5;
printf("before : a = %d,b = %d\n", a, b);
a = a + b; // a = 8,b = 5
b = a - b; // a = 8,b = 3
a = a - b; // a = 5,b = 3
// 加减运算中,超出int变量标准的最大值,则会有部分数值丢失,方法有缺陷
printf("after : a = %d,b = %d ", a, b);
/*异或的方法*/
int a = 3;
int b = 5;
printf("before : a = %d,b = %d\n", a, b);
a = a ^ b; // 011 ^ 101 = 110 ---> 6
b = a ^ b; // 110 ^ 101 = 011 ---> 3 ( 最终的b )
a = a ^ b; // 110 ^ 011 = 101 ---> 5 ( 最终的a )
printf("after : a = %d,b = %d ", a, b);
// 不会产生数据的丢失,因为异或不会造成进位
return 0 ;
}
?在这里,一共有三种解决方法,第一种是运用了第三个变量,不符合题目要求,但是是最简单易懂的,第二种和第三种是本题的两种解法。
加减法的运用非常巧妙,通过重置两个变量内的内容,进行替换,这其实像是小学数学的益智游戏,但需要注意的一点在代码内也标注了:
加减运算中,超出int变量标准的最大值,则会有部分数值丢失,方法有缺陷
?因此引申出了第二种方法,也就是跟刚刚所学的与、或、异或相关联起来
通过连续的 ^ 操作,将a与b之间的数字进行调换,由于是二进制内进行操作,并且是每byte位的单操作,并不会产生进位,这样就杜绝了数字的溢出,也许这算是程序员的益智游戏。
第二个测试题是老师自出的:
求一个整数存储在内存中的二进制中1的个数
int main()
{
int num = 0;
int cout = 0;
scanf("%d", &num); // 3 ---> 011
// 统计num的补码中有几个1
while (num)
{
if (num % 2 == 1)
cout++;
num = num / 2;
}
//如果num是负数,就会出错
num & 1 == 1
// 00000000000000000000000000000011
// 00000000000000000000000000000001
// 00000000000000000000000000000001
// 如果num按位与1,得到1,则最低位一定是1
// 因此只需要将num的二进制位右移32个单位,就可以得到每个bit中是否存在1
int i = 0;
for (i = 0; i < 32; i++)
{
if (1 == ((num >> i) & 1))
cout++;
}
// 必须循环32次
// 方法三
int i = 0;
while (num)
{
cout++;
num = num & (num-1);
}
printf("1 有%d个\n", cout);
return 0;
}
?在这里需要注意的是,由于上面已经提过了,内存中存储的是补码,因此如果要scanf一个数字,那么统计的也是该数二进制后的补码中1的个数。
在第一种方法中,对需要统计的数进行while循环,如果这个数模2之后为1,则计数加1,再将此数除二后重新进入循环直至跳出。
这个方法的缺点也很明显,无法对负数进行操作。
因此我们引申出第二种方法,由于我们需要得知某数二进制转化后共有几个1,通过上面“按位与”的学习,只要按位与了1,那么就能得到1,因此,将某个数二进制化后按位与1,再将此数往右移动32次,一次一个单位,就可以确定共有多少个1,也就能确定此数共有多少个1了。
但这个方法有一点非常遗憾:如果是32位,就需要移动32次,那么64位就要移动64次了,步骤非常死板冗杂,因此第三种方法应时而生,做到了对第二种方法的优化。
上面对二进制的0和1的计数做了些许文章,接下来的题目就是如何修改二进制中的0和1,也就是如何将0变为1,将1变为0
int main()
{
int a = 0;
// ~ 按(2进制)位取反
// 00000000000000000000000000000000
// 11111111111111111111111111111111 -- 补码 (补码-1再取反得到原码)
// 11111111111111111111111111111110 -- 反码
// 10000000000000000000000000000001 -- 原码
// -1
printf("%d\n", ~a);
int b = 11;
/*将1011中的0改为1*/
// 00000000000000000000000000001011
// 00000000000000000000000000000100 (或上这个数字)
// 00000000000000000000000000000100 就是 1 << 2
// 00000000000000000000000000001111
b = b | (1 << 2);
/*将1111中的改过来的1再改回去*/
// 00000000000000000000000000001111
// 11111111111111111111111111111011 (与上这个数字)
// 00000000000000000000000000000100 (这个数字的取反)
// 00000000000000000000000000001011
int b1 = b & (~(1 << 2));
printf("b1 = %d\n", b1);
printf("b = %d\n", b);
return 0;
}
首先讲第一个“如何将1011中的0变为1,成为1111”
根据代码所示,由于“按位或”的功能是有1为1,因此,如果将第三位或上1,整体就可以变为1111,那么如何或呢,直接打一个代码?
b = b | 4
这样吗?那如果我不是要或第3位呢,要或第20位呢,应该如何计算这个呢,在此我愈来愈理解到程序员的一种思维,就像前几篇文章所写的
如果要求一个字符串的长度,假如自己编写的确切知道长度是10,根据这个长度执行一段循环,不可以直接写 i < 10 ,而是要写 i < sizeof(A),这样才能体现出来严谨;另一篇扫雷游戏的文章中也写过类似的:在头文件确定好雷的数量之后,如果要在雷的数量上做一些功能,不能直接写数字,而是要写相应的确定好后的变量名。?
因此,我们要使第三位变为1,我们就创建一个0001,再将它左移2个单位,变成0100,这样,使它和需要改变的数按位或,得到最后的答案。
相似的,如果我们需要将一个数的1改回0,也要遵循这种思想,而不是凭空造出一个在该位上有0,而其他全为1的数。
代码中,将1改为0的方法中,需要该位为0,其他为全为1,再用按位或的功能,使该位从1变为0,那么如何创建一个确定位为0,其他位为1的数字呢,需要用到一种单目操作符,取反,我们继续使用工具数字:1;也就是0001,将它先左移到固定位,比如1000后,这样,只有第四位为1,其他位全为0,对这数进行取反,也就是将0的位置全置换为1,将1的位置全置换为0,这样就可以得到一个0111。就如注释上所描述那样,完成全部功能。
以上就是今天所学的本人以为的重要内容,这些操作符还有一半没讲,争取在晚上听完所有该章节内容,明天复盘。趁热打铁,以便自己回顾!?
|