C语言学习回顾(3)

今天学习了语言操作符,双目操作符与单目操作符,包含一些例子以及编写代码的注意事项

包括加减、位移、赋值,逗号表达式之类

因为是很简单的内容,就不把所有的知识点复制过来了,重点讲几个容易让我们初学者迷惑的

/*移位操作符*/
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。就如注释上所描述那样,完成全部功能。

以上就是今天所学的本人以为的重要内容,这些操作符还有一半没讲,争取在晚上听完所有该章节内容,明天复盘。趁热打铁,以便自己回顾! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值