一:基本运算符
+
,+=
-
,-=
*
,*=
/
,/=
%
,%=
-
(取反)
基本运算符非常简单,涉及到的无非是加,减,乘,除,取模运算。
值得一提的是取模运算是带符号运算,初学者很容易踩到这个坑。
示例:
// 判断一个整数是不是奇数
public static boolean isOdd(int num){
return num % 2 == 1;
}
初学者很有可能写出这样的代码,对于判断一个整数是否为基数,我们还要考虑输入为负数的情况
如果 num 为负数,譬如 -3 ,那么 -3 % 2
的结果为 -1 !
所以,判断一个整数是否为奇数的代码应该改为:
// 判断一个整数是不是奇数
public static boolean isOdd(int num){
return num % 2 != 0;
}
/
和 %
运算符比较经典的应用是求一个数字的数位和
什么叫一个数字的数位和?譬如,有数字 921 ,该数字的数位和为:9 + 2 + 1 = 12
获取一个数字的数位和代码:
int getSum(int num){
int res = 0;
while(num != 0){
res += num % 10;
num = num / 10;
}
return num;
}
自增自减运算符
i++
++i
i--
--i
i++
和 ++i
有什么区别呢?答案是:赋值的顺序不同。
i++
是先赋值后加 1;++i
则是先加 1 再赋值
我们来看一个程序:
public class Main {
public static void main(String[] args) {
int a = 0;
int b = 0;
System.out.println(a++);
System.out.println(++b);
}
}
该程序输出的结果为:
0
1
原因就在于,System.out.println(a++);
会先将表达式的结果打印,然后再执行 a + 1
这个操作,所以打印输出的结果为 0;System.out.println(++b);
则会先执行 b + 1
,然后打印结果,所以打印的结果值为 1 。
来看一道比较经典题目:
public class Main {
public static void main(String[] args) {
int i = 0;
i = i++ + ++i;
int j = 0;
j = ++j + j++ + j++ + j++;
System.out.println(i);
System.out.println(j);
}
}
请说出该程序输出的结果?
该程序输出的结果为:
2
7
没有做对的小伙伴不如仔细思考下,一定要明确的是,+
运算符两侧连接的是两个表达式,我们只需要搞清楚表达式的值这个概念,本题就可以迎刃而解。
比较运算符
>
<
==
>=
<=
!=
比较运算符非常简单,使用比较运算符进行两个变量的比较操作,其结果返回的是一个布尔值。
初学者一定要明确的是 =
和 ==
,前者是赋值操作,后者才是比较变量值是否相等。
二:逻辑运算符与短路特性及断路特性
&&
||
!
逻辑与:&&
逻辑或:||
非:!
逻辑运算符两侧连接的是两个布尔变量,返回的结果仍是布尔值。
当两个值均为真时,逻辑与的结果才返回 true,否则返回 false;当两个值有一个为真,逻辑或的结果就返回 true,只有两个值均为假,逻辑或才返回 false。
逻辑运算符的短路特性
我们先来看两个示例程序
程序一:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (trueThing() || falseThing()) {
}
}
}
该程序输出的结果为:
this is true!
我们看到,在 if 判断中,只执行了 trueThing()
的代码,并没有执行 falseThing()
的代码,这是因为 ||
运算具有 短路特性,当判断出第一条语句为真时,后面的部分就没有执行的意义了,因为结果必然返回 true!
程序二:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (falseThing() && trueThing()) {
}
}
}
该程序输出的结果为:
this is false!
我们看到,在 if 判断中,又是只执行了前半段,没有执行后半段,其原因在于,&&
运算具有断路特性,当判断出第一条语句为假时,后面的部分就没有执行的意义了,因为结果必然返回 false !
三:三元运算符
三原运算符是软件编程中的一个固定格式,语法为:
条件表达式 ? 表达式1:表达式2;
如果条件表达式为 true 那么则调用表达式 1,否则就调用表达式 2
示例程序:
对 res 进行赋值
如果满足 a > b
,则 res 复制为 1
如果满足 a < b
,则 res 赋值为 -1
如果a == b
,则 res 赋值为 0
上述需求可以用三元运算符的方式简洁地实现:
int res = a > b ? 1 : (a < b ? -1 : 0);
四:位运算详解与实战
位运算符
~
,按位取反&
,按位与|
,按位或^
,异或<<
,左移>>
,带符号右移>>>
,无符号右移(总是补 0)
用最有效率的方法计算2
乘以8
最有效率的方法即位运算。
<<
左移;左移一位相当于乘以2
>>
带符号右移;正数右移高位补0
,负数右移高位补1
- 右移一位相当于除以
2
4 >> 1
结果为2
-4 >> 1
结果为-2
- 右移一位相当于除以
>>>
无符号右移;无论正数负数,高位均补0
- 对于正数而言
>>
和>>>
无区别 - 对于负数而言,举例:
-2 >>> 1
结果为:2147483647(Integer.MAX_VALUE)
- 对于正数而言
所以,本题要求使用最有效率的方法来计算2 * 8
我们可以:
int res = 8 << 1
即可。
&
和 &&
的区别
- 首先
&
和&&
都可以作为逻辑与的运算符;区别是&&
具有断路特性,如果&&
符号左边的表达式为false
,则不再计算第二个表达式 &
可以用作位运算符,当&
两边的表达式不是boolean
类型时,&
表示按位与的操作1 & 1 = 1
1 & 0 = 0
0 & 0 = 0
如示例程序:
public class Test {
public static void main(String[] args) {
// 0x7A : 0111 1010
// 0x53 : 0101 0011
// 0x7A & 0x53 = 0101 0010 = 0x52
System.out.println(Integer.toHexString(0x7A & 0x53));
}
}
结果输出:
52
我们在上面也提到了,&
也可以作为逻辑与,和 &&
不同的是,&
是不具有断路特性的。
如示例程序:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (falseThing() & trueThing()) {
}
}
}
该程序输出的结果为:
this is false!
this is true!
如我们所想,即使第一个表达式为假,使用 &
运算符还是会执行第二个表达式。
|
和 ||
也是一样的;|
也可以用作逻辑或运算,其也不具有 ||
的短路特性。
使用最快的方式,交换整型数组中任意两个位置的数字
我们可以使用异或运算快速交换整型数组中任意两个位置的数字。
异或运算是一种基于二进制的位运算,对于运算符^
两侧数的每一个二进制位,同值取0
,异值取1
,形象地说就是不进位加法。
异或运算满足以下的几种性质:
- 交换律
- 结合律
- 对于任何数
x
,都满足x ^ x = 0
;x ^ 0 = x
所以,通过异或运算可以快速交换数组中任意两个位置的数字,代码如下:
public class Main {
public static void swap(int[] nums,int i,int j){
if(i == j || nums[i] == nums[j]){
return;
}
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
}
一个数组中,只有一个数出现了奇数次,其他数则均出现偶数次,如何找到这个数
利用异或^
的运算性质可以轻松求解
异或(XOR)运算性质:
- 两个
bit
位数字相同,异或结果为0
- 两个
bit
位数字不同,异或结果为1
也就是说:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
并且,异或运算满足交换律和结合律
a ^ b = b ^ a
(a ^ b) ^ c = a ^ (b ^ c)
回到本问题:
已知数组中,只有一个数出现了奇数次,其余数均出现偶数次
思路:
将数组中所有数异或起来,偶数次的数字异或起来结果必为0
最后就是出现奇数次的那个数和0
异或,结果为这个数字
参考代码:
public class XORDemo {
public int findOddTimesNum(int[] nums){
int xor = 0;
for(int n : nums){
xor ^= n;
}
return xor;
}
}
位运算中的几个小技巧
n & (n - 1)
n & (n - 1)
是位运算中非常重要的一种技巧,它是将二进制数n
最右边的1
变成0
的操作
示例:n = 10101000
n = 10101000
n - 1 = 10100111
n & (n - 1) = 10100000 // 将二进制数n最右边的那个1变成了0,其余不变
n & (~n + 1)
n & (n - 1)
可以找到一个二进制数 n
最右边的那个 1
代表的数
来看示例:
num = 10101000
我们知道num
最右边的那个1
代表数为00001000
~num = 01010111
~num + 1 = 01011000
num & (~num + 1) = 00001000
这也是一个涉及位运算的算法题中常用的小技巧,它可以找到一个二进制数 n
最右边的那个 1
代表的数
五:运算符优先级与字符串加法
关于运算符的优先级,我们只需要明确两点即可:
- 乘除高于加减
- 其他全部假括号,括号的优先级最高!
字符串加法
- 字符串拼接调用 toString 方法或者原生类型对应的表示
- JDK 偷偷把字符串连接转换成 StringBuilder 调用
来看一个程序:
我们在 StringBuilder 类的构造器上打一个断点
使用 IDEA 的 debug 模式运行程序,我们会发现,程序最终会停留在断点处。
这是因为:
JDK 会偷偷把字符串连接转换成 StringBuilder 类的调用,进行字符串拼接,以节省内存空间。
为什么要这样做呢?
因为字符串本身是一个不可变类。
String
类中使用final
关键字字符数组保存字符串
private final char value[];
所以String
对象是不可变的,这就导致了每次对String
对象添加,更改或删除字符的操作都会产生一个新的String
对象。不仅效率低下,而且浪费了空间。
所以,JDK 偷偷帮我们调用了 StringBuilder 类,在我们对 StringBuilder 类的对象进操作时,不会像 String 类那样频繁创建新的对象,而是都在同一块内存上进行字符串的增删改操作,减少了零碎的对象带来的内存压力,大大节省了空间。