前言:
其实之前学这块的时候那老师就说别去瞎搞,但是好奇心重,加上当时确实没认真听。就不能理解为什么行内大佬那么多不能够在一个小小的四则运算上做出一个万能公式呢 ?后面深入学习了才发现自己的想法过于天真。
1.操作符的优先级和结合顺序
优先级的记忆我个人感觉是不必要的,写的多了对常用的有印象即可,而至于结合性有兴趣记忆的可以通过下图记忆
2.表达式不能通过操作符的属性确定唯一计算路径的情况
操作符看似简单,但其中也有一些标准尚未定义的情况,一些不规范的写法很容易让我们陷入为难
2.1复杂表达式的求值有三个影响的因素。
1. 操作符的优先级 2. 操作符的结合性 3. 是否控制求值顺序。
2.2表达式的计算路径和料想有偏差的原因
2.2.1优先级只是规定在两个“相邻”的运算符引领的“表达式运算”在“逻辑上”的先后顺序
比如a+b+c*d,乘法比加法优先级高,但是,这里我们完全可以先算a+b,后算c*d,因为第一个+和后面的*不相邻,不相干,这是一个条件
2.2.2规定的顺序仅是“表达式求值”
int b=1,a=1;
b=a++;
这个表达式是有两个运算,从优先级来说++是要先算,然后=后算,实际也是这么执行的:
第一步:对a++表达式求值,即做++运算,结果假设存在tmp临时变量,根据定义值则为a自增前的值
第二步:对c=tmp做表达式求值,根据赋值表达式的定义,其结果为tmp的值,如果这个表达式是一个子表达式(比如你写的是printf("%d", c=d++);),则这个值就被上层表达式求值继续用到
诶?你是不是觉得漏了两个地方,一个是d的自增,一个是c的赋值?
嗯这就是关键了,自增和赋值的操作是“副作用”,而不是表达式求值的一部分,其实你完全可以看做是和表达式求值分离的,之所以为“副”作用,说的就是“表达式求值”才是主业,改变内存的副作用只是“兼职”,C语言对副作用起作用时机的定义是,在下一个顺序点到来之前的任何时候,比如
再比如
a+(c=d++);
按照C的规定,这里,顺序点是在“全表达式”结束后,而c和d的值的改变可以在此之前任何时候起作用,展开伪码:
tmp = d //表达式求值:++运算
tmp = tmp //表达式求值:=运算
a + tmp //表达式求值,+运算
可以看到,是严格按照优先级来的,++先算,=后算,+最后算
d=d+1和c=tmp在什么时候执行呢,可以分别插在上面三步的任意位置,这个跟优先级一点关系都没有
2.2.3逻辑上的顺序,
比如a&&b+c,虽然+比&&高,这里逻辑上也的确是得先算+再&&,但是由于&&有短路特性,所以确切说是只有“有必要计算b+c”时候,我们才计算加法,然后&&,这是从表达式角度来看,实际上&&并不是一个计算操作,而是一段类似if else的逻辑代码,这实际上有可能缺失一些运算,
2.3.一些例子
2.3.1 操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得 知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义 的
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
2.3.2 函数的调用先后顺序无法通过操作符的优先级确定。
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);
return 0;
}
2.3.3
下面这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级 和结合性是无法决定第一个 + 和第 三个前置 ++ 的先后顺序。
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
2.4小结
如果你多个副作用(赋值)是改了同一块内存,或对这块内存同时有读写,由于副作用(赋值)时机的不确定性,执行结果也是不确定的。
3.位操作符
3.1移位操作符
<<左移操作符,左边抛弃,右边补0,左移n位就是把原数值(*2的n次方)
>>右移操作符
右移有两种:
算数右移:右边丢弃,左边补原符号位
逻辑右移:右边丢弃,左边补0 ,右移n位就是把原数值/(2的n次方)
是算数右移还是逻辑右移取决于编译器
注意,位操作符都是针对补码进行运算,对负数使用时需要注意
3.2 按位操作符
3.2.1 & 按(二进制)位与
a和b的补码相运算(补码:取反 ,加一)
对应二进制位上,a和b中只有一个有0就为0,两个都为1时才为1
3.2.2 | 按(二进制)位或
对应二进制位上,a和b中有1就为1
3.2.3 ^ 按(二进制)位异或
对应二进制位上,相同为0,相异为1
任何数与0异或仍是它本身 a^0=a
任何数与1异或等于将其取反 a
任何数与自己异或,等于把自己置0 a^a=0
异或满足交换律、结合律 a^a^b=a^b^a
3.3一些常用
找到数字a二进制中最低位1的位置:a&(-a)
去掉数字a二进制中最低位的1:a=a&(a-1)
把数字a二进制中第n位改成1:a=a|(1<<(n-1))
把数字a二进制中第n位改成0:a=a|(~1<<(n-1))
3.4一些题目
1.数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。
思路:
利用异或的特性,对同一个值异或两次,那么结果等于它本身,所以我们先把ret 从0-nums进行异或,再对nums数组中的值进行异或,出现重复的会消失,所以最后ret的值是只出现一次的数字,也就是nums数组中缺失的那个数字
2.不用中间变量交换两个数字
int a=3,b=4;
a=a^b;//a=a^b,b=b
b=a^b;//b=a,a=a^b
a=a^b;//a=b,b=a完成交换
3.寻找单身狗,
一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。