基础知识:各种运算符号
-
算数运算符(9个):+ - * / % ++ –
-
关系运算符(6个):== != > >= < <=
-
逻辑运算符(6个):
&&(逻辑与)、||(逻辑或)、!(逻辑非),还有几个容易弄混淆的位操作符(进行按位操作):&(按位与)、|(按位或)、~(按位非),后面三个既是逻辑运算符又是位运算符 -
位运算符(7个):&(按位与)、|(按位或)、~(按位非) ^(按位异或)、 x >> y(将x的补码右移y位,每次右移一位,相当于原数据乘以2) 、 x << y(将x的补码左移y位,每次左移一位,相当于原数据除以2)
-
赋值运算符用于赋值运算,分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共十一种。
-
条件运算符这是一个三目运算符,用于条件求值(?😃。
-
逗号运算符用于把若干表达式组合成一个表达式(,)。
-
指针运算符用于取内容(*)和取地址(&)二种运算。
-
求字节数运算符用于计算数据类型所占的字节数(sizeof)。
-
特殊运算符有括号(),下标[],成员(→,.)等几种。
关于运算符的具体运算说明和例子,可以参考此链接
1 "异或"的性质与计算实例
1.1 望文生义,助力记忆
“异或”(逻辑运算)(Exclusive OR),通过字面意思也可以知道,如果两个二进制数字不同(对应 "异"字),则执行相加操作(对应"或"字),结果只能为1;那么相反的看,如果两个二进制数字相同,执行与,
1.2 对于两个二进制数相加,对应位上的两个数相同则为0,不同则为1, 可参考如下例子
e.g1 :
1 0 1 1 0 1 0 1 0
+ 0 1 1 1 1 0 0 0 0
--------------------------
= 1 1 0 0 1 1 0 1 0
1.3 采用 “异或” 计算的性质
- 异或计算的实现原理相当于 无进位相加,拿上面的e.g1为例, 1 + 1 = 0且不产生进位, 0 + 1 = 1, 0 + 0 = 0;
- 0 和任意一个二进制数异或,其值不变,即 0 ^ N = N
任何一个二进制数和自己异或,其值都为0,即 N ^ N = 0 - 异或运算满足交换律和结合律, 即
- 交换律: A ^ B ^ C = C ^ B ^ A
- 结合律: (A ^ B) ^ C = A ^ C + B ^ C
- 如果将N个数进行异或操作,则其结果与这个N个数的异或顺序无关,即无论几个数值的异或的顺序如何变,结果总是一样的,这是由其交换律决定的。
2 采用 "异或"实现两个元素的交换
2.1 普通的数组元素交换方法
public static void swap(int i, int j, int arr[])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2.2 只用算数运算符(即加减乘除)或者逻辑运算符(|, &)加减就可以进行交换的方式
2.2.1 代码展示
e.g1: 算数运算符
public static void swap(int i, int j, int arr[])
{
arr[i] = arr[i] + arr[j]; // 可以写成arr[i] += arr[j];
arr[j] = arr[i] - arr[j]; // 可以写成arr[i] -= arr[j];
arr[i] = arr[i] - arr[j]; // 可以写成arr[i] -= arr[j];
}
e.g2: 逻辑运算符
public static void swap(int i, int j, int arr[])
{
arr[i] = arr[i] | arr[j]; // 相当于arr[i] + arr[j];也可以写成arr[i] |= arr[j];
arr[j] = arr[i] & arr[j]; // 相当于arr[i] - arr[j];也可以写成arr[i] &= arr[j];
arr[i] = arr[i] & arr[j]; // 相当于arr[i] - arr[j];也可以写成arr[i] &= arr[j];
}
2.2.2 代码分析
优点:空间上避免申请临时空间
缺点:可能会导致溢出,效率比使用temp变量低一些
2.3 使用 “无进位相加” 方法实现i,j位置上的值交换
2.3.1
public static void swap(int i, int j, int arr[])
{
arr[i] = arr[i] ^ arr[j]; // (1)
arr[j] = arr[i] ^ arr[j]; // (2)
arr[i] = arr[i] ^ arr[j]; // (3)
}
2.3.2
代码分析:
假设赋予值arr[i] = 20,arr[j] = 10,
- 当执行完第一行代码时,arr[i] = 20 ^ 10
- 当执行完第二行代码时,arr[i] = 20 ^ 10,所以 arr[j] = arr[i] ^ 10 = 20 ^ 10 ^ 10 = 20(根据 "任何一个二进制数和自己异或,其值都为0"和"0 和任意一个二进制数异或,其值不变"这两个规律得出 20 ^ 10 ^ 10 = 20)
- 当执行完第三行代码时,arr[j] = 20 , arr[i] = 20 ^ 10 所以 arr[i] = arr[j] ^ arr[i] = 20 ^ 20 ^ 10 = 10
2.3.3
使用时的注意事项:以arr[i]和arr[j]为例
(1) 当arr[i] = arr[j]时,仍然能够交换
(2) 交换的变量在内存中的地址必须不同,即&arr[i] != arr[j] => i != j; 如果在传参的时候,存在i == j的情况,则得到 arr[i] = 0;
我们可以通过模仿2.2.2中的分析步骤来看看当i == j的时候,arr[i]的结果。假设arr[i] = 10;
- 当执行完第一行代码时,arr[i] = 10 ^ 10 = 0
- 当执行完第二行代码时,arr[i] = 0, 因为i和j的下表一样,即&arr[i] == &arr[j],arr[j] = 0所以 arr[j] = arr[i] ^ arr[j] = 0 ^ 0 = 0(根据 "任何一个二进制数和自己异或,其值都为0"和"0 和任意一个二进制数异或,其值不变"这两个规律得出 20 ^ 10 ^ 10 = 20)
- 当执行完第三行代码时,arr[j] = 0 , arr[i] = 0 所以 arr[i] = arr[j] ^ arr[i] = 0
3 性能分析:
空间上:对比2.1中的代码,后面的三种方法不需要额外申请一个temp空间,
时间上:后面三种方法的性能比使用临时变量的方法要低一些,使用异或方法/位运算方法或者算数运算方法需要访问的内存次数总是比使用临时变量的方法少一次(因为一般编译器都会把临时变量放入到寄存器中),到底访问多少次,需要查看相应的汇编代码。,所以一般不推荐使用异或运算或者算术运算或者位运算的方式实现两个数据元素的交换
其他:使用算术运算的方法交换数据元素存在内存溢出风险
因为该方法访问四次内存:(待定)
- 从(1)中可知,第一次和第二次从内存空间中分别取出a和b,
- 运算完(2)后,会将结果结果a写回内存空间,
- 运算完b后,会将结果b写回内存空间
而对于2.1中的方法,会访问三次内存,(待定)
- 从(1)中可知,第一次和第二次从内存空间中分别取出a和b,
- 运算完(2)后,会将结果结果a写回内存空间,
- 生成一个temp变量,将其放入寄存器中
关于具体的性能分析和汇编代码,
可以参考这篇