练习开胃菜

曾经有一道面试题,要求不能创建临时变量(第三个变量),实现两个数的交换。

这道题如果没有前半句的修饰,就只是简单的一道基础题。

法一:

#include int main(){int a = 0;int b = 10;printf("交换前:a=%d,b=%d\n", a, b);int tmp = a;a = b;b = tmp;printf("交换后:a=%d,b=%d\n", a, b);return 0;}

但是如果加上了前半句的修饰,就需要更换思路了。

法二:

#include int main(){int a = 0;int b = 10;printf("交换前:a=%2d,b=%2d\n", a, b);a = a + b;b = a - b; //a + b - b = aa = a - b; //a + b - a = bprintf("交换后:a=%2d,b=%2d\n", a, b);return 0;}

第二种方法比第一种方法更考验思维,但是中方法也有弊端,如果a和b两个数无限接近计算机能够储存的最大值,那结果将不再准确。

法三:

#include int main(){ int a = 10; int b = 20; a = a ^ b; //a 变成了 a^b b = a ^ b; //a ^ b ^ b = a 相同为0,b变成了a a = a ^ b; //a ^ b ^ a = bprintf("a = %d b = %d\n", a, b); return 0;}

这就是我想讲述的重点:^ 按位异或操作符,二进制位相同为0,相异为1.

技巧1:a ^ 0= a;

技巧2:a ^ a = 0;

练习1:

编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数。

#include 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;}

这种方法不能计算负数,所以引入下面的方法

#include int main(){ int num = -1; int i = 0; int count = 0;//计数 for(i=0; i<32; i++) { if( num & (1 << i) ) count++;} printf("⼆进制中1的个数 = %d\n",count); return 0;}//优化版#include int main(){ int num = -1; int i = 0; int count = 0;//计数 while(num) { count++; num = num&(num-1); } printf("⼆进制中1的个数 = %d\n",count); return 0;}

优化版的方法是很难想到的,所以我们要多见,多去思考别人的代码,这样有助于我们的提升。

练习2:

⼆进制位置0或者置1 ,编写代码将13⼆进制序列的第5位修改为1,然后再改回0。

132进制序列: 00000000000000000000000000001101 将第5位置为1后:00000000000000000000000000011101 将第5位再置为000000000000000000000000000001101

#include int main(){ int a = 13; a = a | (1<<4); printf("a = %d\n", a); a = a & ~(1<<4); printf("a = %d\n", a); return 0;}

这里巧妙运用了位操作符,各位可以总结一下。

问题表达式举例

1、a*b + c*d + e*f

//表达式的求值部分由操作符的优先级决定。 表达式1在计算的时候,由于 * + 的优先级⾼,只能保证, * 的计算是⽐ + 早,但是优先级并不能决定第三个 * ⽐第⼀个 + 早执⾏,因此表达式的计算顺序不一。

2、c + –c;

同上,操作符的优先级只能决定⾃减 — 的运算在 + 的运算的前⾯,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

3、

int i = 10 ; i = i– – –i * ( i = -3 ) * i++ + ++i; printf ( “i = %d\n” , i); 这道题再不同编译器中的结果也不同

4、

int fun(){ static int count = 1; return ++count;}int main(){ int answer; answer = fun() - fun() * fun(); printf( "%d\n", answer);//输出多少? return 0;}

这个代码实际上有问题,虽然在大多数的编译器上求得的结果都是相同的。

但是上述代码 answer = fun() – fun() * fun(); 中我们只能通过操作符的优先级得知:先 算乘法,再算减法。而函数的调用先后顺序我们无法通过操作符的优先级确定。

5、

#include int main(){ int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0;}

这道题在gcc上的编译结果为 10 、4,在vs2022上的编译结果为 12、4.

这段代码中的第⼀个 + 在执⾏的时候,第三个++是否执⾏,这个是不确定的,因为依靠操作符的优先级和结合性是⽆法决定第⼀个 + 和第三个前置 ++ 的先后顺序。所以在不同的编译器上的优先级影响了结果。

结合本期与上期内容,我们要知道:

即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别复杂的表达式。