本文主要介绍一些C语言中常见的易错的操作符。
目录
1. 移位操作符
<< 左移操作符
>> 右移操作符
操作符的操作数只能是整数。
1.1 左移操作符
移位规则:左边丢弃,右边补齐。
1.2 右移操作符:
移位规则分俩种。
1.逻辑右移:左边用0补充,右边丢弃。
2.算数右移:左边用该值原符号位填充,右边舍弃。
移位运算符,不要移动负数位,这是标准未定义的。
2. 位操作符
& 按位与
| 按位或
^ 按位异或
~ 按位取反
它们的操作数都是整数。
位,代表的都是二进制位。
& 按位与 同1为1
| 按位或 有1为1
^ 按位异或 相同为0,相异为1
~ 按位取反 取相反,1变0,0变1
举个例子
由于按位异或的特点,相同为0,相异为1,我们可以利用这个特点来处理很多数据。下面有面试题就用到了这个知识。
int a =1;
0 ^ a = a;
a ^ a = 0;
按位异或的考点,相对来说较偏,让我们来分析几道面试题。
1. 不创建临时变量,实现两个数的交换。
#include<stdio.h>
int main()
{
int a = 3;
int b = 4;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d", a, b);
return 0;
}
可以简单的理解 a ^ b ^ b = a
或许有聪明的读者,可能想。a = a+b,b= a-b,a =a-b,这不也可以达到目的。好像是可以完成,但是如果a 和 b 数值都特别大,a+b 就容易过大,超出最大值,容易出错。
2. 找出一只单身狗
在一组数据中,存在唯一一个与其他数不同的数,其他的数都是俩俩相同,现在要求我们找出唯一单身的数字。
#include<stdio.h>
int test(int arr[], int sz)
{
int i = 0;
int ret = 0;
for (i = 0; i < sz; i++)
{
ret ^= arr[i];
}
return ret;
}
int main()
{
int arr[9] = { 1,2,3,4,5,1,2,3,4 };
int sz = (sizeof(arr) / sizeof(arr[0]));
printf("%d",test(arr, sz));
}
由于两两相等,它们异或的结果为0,此时,0异或那个唯一不同的数据,还是那个数据本身,成功找到那个单身的数据
3. 找到两个单身狗
在一组数据中,存在两个与其他数不同的数,其他的数都是俩俩相同,现在要求我们找出两个单身的数字。
#include<stdio.h>
int test(int arr[], int sz)
{
int single1 = 0;
int single2 = 0;
int i = 0;
int ret = 0;
for (i = 0; i < sz; i++)
{
ret ^= arr[i]; //将所有数据异或,得到两个不同的数的异或的结果。
}
int pos = 0;
for (i = 0; i < 32; i++)
{
if (((ret >> i) & 1) == 1) // 相异为1 ,找到两个数不同的那个二进制位并记录
{
pos = i;
break;
}
}
for (i = 0; i < sz; i++)
{
if (((arr[i] >> pos)&1) == 1)
{
single1 ^= arr[i]; // 将数据分为俩组,不同的那个比特位为1的,分为一组,并异或,得到一个单身的数据
}
}
single2 = ret ^ single1;
printf("%d %d", single1, single2);
}
int main()
{
int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
int sz = (sizeof(arr) / sizeof(arr[0]));
test(arr, sz);
return 0;
}
1.将所有数据异或,得到两个不同的数的异或的结果。
2. 相异为1 ,找到两个数不同的那个二进制位并记录
3. 将数据分为俩组,不同的那个比特位为1的,分为一组,并异或,得到一个单身的数据
4.ret 与一个单身的数据异或,得到第二个。
5.注意运算符的属性,不确定时,可以多加(),比如(((ret >> i) & 1) == 1) 这个判断。
3. 逻辑操作符
&& 逻辑与
|| 逻辑或
&& 可以简单理解为并且,即左右都为真,才为真。
|| 可以简单理解为或者,左右有一个为真,即为真。
分析一道360笔试题
#include<stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i = a++ || ++b || d++;
printf("%d %d %d %d\n", a, b, c, d);
}
&&的情况,打印的应该是 1,2,3,4,&& ,当有一个条件为假(0),则后面的表达式不需要在运算,如果为真,则需要一个一个判断。
|| 情况,打印的应该是 1,3,3,4 ,|| 当有一个条件为真时,后续的表达式,不需要再运算,结果为真,如果逐一判断,全部为假时,结果才为假。
4.条件操作符
exp1? exp2:exp3
exp1 判断语句,为真则执行 exp2,为假则执行 exp3.
5.逗号表达式
exp1,exp2,exp3,exp4,...ex
逗号表达式,就是用逗号隔开的多个表达式。
整个表达式的结果为最后一个表达式的结果
6. 隐式类型转换(整型提升)
C的整型算数运算总是至少以整型类型的精度来计算的。
简单的说,计算时,表达式中的字符和短整型操作数在使用前被转换为普通整型。
整型提升规则:
按照变量的数据类型的符号来提升的
有符号的
负数的整型提升:
char c1 = -1; 11111111
char 为有符号的char ,高位补充符号位 为1 。
整型提升的结果为: 1111111 11111111 11111111 11111111
整数的整型提升 :
char c2 = 1; 00000001;
整型提升,高位补 0;
结果: 00000000 00000000 00000000 00000001
无符号的
整型提升,高位直接补 0
7.算数转换
如果某个操作符的各个操作数属于不同类型,那么只有其中的操作数转化为其他操作数相同的类型,否则操作无法进行。
算数转换一定要合理,否则存在问题,比如精度丢失。
8.操作符的属性
复杂的表达式的求值有三个影响因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
我们写出的表达式如果过于复杂不可以通过操作符的属性确定唯一的计算路径,那么这个表达式就是存在问题的。就比如上文的寻找俩个不同的数字的程序,如果不加括号,它们的运算顺序不是我们所认为的从左到右,就会发生错误导致程序失败。
本文结束,欢迎评论区讨论,谢谢观看!