iT邦幫忙

0

Java學習之路05---運算子

架構圖

前言

表達式是程式進行算術運算中的表示方式,我們可以簡單地把表達式拆解為表達式 = 運算子 + 操作變數,也就是說任何運算子都不能脫離變數單獨存在

在java中依據使用功能可以區分成多種不同的運算子,該小節主要聚焦介紹幾種常見的運算子,並探討表達式中若存在多個不同運算子時該如何判斷優先順序

重點運算子

  1. 算術運算子(Arithmetic Operators)
  2. 賦值運算子(Assignment Operators)
  3. 關係運算子(Relational Operators)
  4. *三目運算子(Ternary Operators)
  5. 邏輯運算子(Logical Operators)

算數運算子

實現最基本的算術運算。與算術相關的運算子有+, -, *, /, %(加、減、乘、除、取餘),整數類型與浮點類型多使用算術運算子進行數學運算,字串與字元則可以使用+進行拼接

案例

int num1 = 10;
int num2 = 20
int num3 = 0;	

num3 = 12 + num2;
num3 = num1 * 2;
num3 = num1 / 2;
num3 = num2 % 2;

使用括號
在進行運算時請銘記先乘除後加減法則,如果想要更改運算順序可以透過括號來實現優先運算(優先級部分將會提及)

int num1 = 5;
int num2 = 2;
int num3 = 0;

num3 = num1 + num2 * 2; // 9
num3 = (num1 + num2) * 2; // 14

除法與取餘結果
注意除法運算後的變數類型,例如以下例子,當運算結果為整數類型時,小數點會被無條件捨去,所以在進行相關運算時請特別注意。可以透過將轉型使輸出結果轉為浮點類型

int num = 17;
int op1 = 3;
float op2 = 3.0f;

System.out.println("test 1 = " +num/op1); // 5
System.out.println("test 2 = " +num/op2); // 5.6666665

取餘數運算相信大家一定不陌生,當除數具有小數點時,取餘結果也會擁有小數點,當然浮點數運算多少會存在一些誤差值,例如下面例子

float num1 = 17.0f;
float num2 = 17.9f;
float op1 = 3.0f;

System.out.println("test 1 = " +num1%op1); // 2.0
System.out.println("test 2 = " +num2%op1); // 2.8999996

可以在運算子左右兩側留下空白增加易讀性

單目運算子

與上述我們介紹的表達式不同,加減乘除運算均需要兩個或以上的變數參與,而單目運算子僅需要一個變數與一個運算子就可以進行運算

常見的單目算術運算子包括變數正負號表示、遞增與遞減運算:

/* 正負號表示 */
int num1 = -10;
int num2 = +15;

/* 自增與自減運算 */
System.out.println(num1++); // -10
System.out.println(--num2); // 14

常見的遞增運算可以分為前置與後置兩種,在操作上稍微有些不同
前置遞增、遞減
先進行遞增與遞減運算,然後回傳變數值:

int num = 10;
int result = 0;

result = ++num;

System.out.println("result = " + result);
System.out.println("num = " + num);

運算結果:

result = 11
num = 11

後置遞增、遞減
先回傳變數值,在進行遞增或遞減運算:

int num = 10;
int result = 0;

result = num--;

System.out.println("result = " + result);
System.out.println("num = " + num);

運算結果:

result = 10
num = 9

我們再來舉幾個小例子來練練手:

int a = 4;

a = (++a) + 4; // a = 5 + 4
System.out.println("a = "+a);

a = (a++) + 4; // a = 9 + 4
System.out.println("a = "+a);

a += (++a); // a = 13 + 14
System.out.println("a = "+a);

a -= (a--); // a = 27 - 27
System.out.println("a = "+a);

運算結果:

a = 9
a = 13
a = 27
a = 0

實際編寫程式時應當盡量避免以下這種風格寫法,也就是把大量賦值運算、算術運算、遞增遞減運算混合在一起。除了降低可讀性外還可以造成賦值錯誤

int num = 10;
int i = 5;

/* 盡量避免使用這種風格的寫法 */
num += i++;
num += ++i;
num = (num++) * (num++);
num = (++num) + (num++);

指定運算子

最常見的指定運算子莫過於=,它將等號右側的數值賦值給等號左側的變數

int num1;
char num2;
String num3;

num1 = 15;
num2 = 'a';
num3 = "string...";

特別注意賦值方向又由右向左,等號只有一個,且左值必須為一個變數,下面的例子是一些錯誤的寫法:

100.0d = d;
num1 == 0;
num1 + num2 = 5;

複合指定運算子

很多時候賦值運算子會結合算術運算子使用,為了節省語句長度,我們可以將表達式簡寫成特殊的表達形式,例如:

  1. +=
  2. -=
  3. *=
  4. /=
  5. %=

這種表達式稱作賦和指定運算子( Compound Statement),除了算術運算子之外,還可以結合位元運算子(之後的章節會提到)

int num1 = 10;
float num2 = 122.6.f;
double num3 = 1.0;

num3 += num1; // 1.0 + 10
num3 = 1.0;

num3 -= num1; // 1.0 - 10
num3 = 1.0;

num3 *= num2; // 1.0 * 122.6
num3 = 1.0;

num3 /= num2; // 1.0 / 122.6
num3 = 1.0;

num3 %= num1; // 1.0 % 10
num3 = 1.0;

雙目運算子的賦值方向

在雙目運算子的操作運算中,操作順序始終是由左至右的,也就是說等號右側的一連串運算一定是由左側發起

int num1 = 3;
int num2 = (num1=4) * num1; // num1已經被改為4,所以執行運算為4 * 4

System.out.println(num2);

輸出結果:

num2 = 16

同理複合指定運算子也適用這個規則

int num = 9;
num += (num=10); // 9 + 10
System.out.println("num = " + num);

num = (num=3) + num; // 3 + 3
System.out.println("num = " + num);

num -= (num=1); // 6 - 1
System.out.println("num = " + num);

輸出結果:

num = 19
num = 6
num = 5

因此每次進行表達式運算時,都從等號右側開始運算,運算規則始終是左至右,了解這個規則後,我們把將多個複合指定運算子結合起來看看輸出效果如何

int num = 10;
num += num -= num *= num /= num;

/* 相當於 */
num = num + (num - (num * (num / num)));

輸出結果:

num = 10

關係運算子

關係運算子多用在條件判斷與循環語句中,例如if, while等,為了要使判斷語句執行,必須判斷執行或不執行,因此可以得知關係運算子的輸出結果不是true就是false

int num = 10;

if(num < 100)
	System.out.println(num < 100);
else
	System.out.println(num < 100);

輸出結果:

true

java提供多組關係運算子給條件語句進行判斷

  1. >
  2. <
  3. >=
  4. <=
  5. ==
  6. !=

另外關係運算子的判斷不限於整數型態,例如下面例子就是整數與浮點數關係運算子範例,只要兩個變數值相同就會返回true

int num1 = 10;
float num2 = 10.0f;

if(num1 == num2){
	System.out.println(num1 == num2);
}
else{
	System.out.println(num1 == num2);
}

輸出結果:

true

除此之外字元也是一個常見的判斷變數,字元形式主要利用ASCII碼進判斷

char ch = 'a'; // a ASCII碼為97

if(ch >= 100)
	System.out.println(ch > 100);
else
	System.out.println(ch > 100);

輸出結果:

false

另外布林、字串字面值也可以進行判斷,不過僅限於==與!=

System.out.println(10 <= 100);

System.out.println(true == false);
System.out.println("123" != "124");

輸出結果:

true
false
true

三目運算子

所謂三目運算子,是指條件式與計算返回值共有三個不同的區塊。java提供的三目運算子解析如下:
條件判斷 ? 成立返回值 : 失敗返回值
舉幾個例子比較好懂:

int num1=6, num2=9;
boolean b = num1 > num2 ? (13 > 6) : (true == false);

System.out.println(b);

輸出結果:

false

首先判斷條件語句num1 > num2是否成立,若成立則返回第一個區塊的值,也就是(13 > 6),若不成立則返回第二個區塊的值(true == false)

假如我們把三目運算子寫成if-else判斷式的話,就可以看出三目運算子的優勢在於簡潔性了。不過若是牽涉到多條判斷語句(if-else if-else)還是要乖乖地使用判斷式

int num1=6, num2=9;
boolean b=false;

if(num1 > num2)
	b = (13 > 6);
else
	b = (true == false);

System.out.println(b);

接下來我們把三目運算子的返回值雙雙填入另一個三目運算子,讓判斷式多加一層

int num1 = 18, num2 = 44, num3 = 90;
int max;

max = ((num1 > num2) ? (num1 > num3) ? num1 : num3 : (num2 > num3) ? num2 : num3);

System.out.println("最大值是: " + max);

輸出結果:

最大值是: 90

首先判斷num1 > num2是否成立,若成立則只需要再判斷num1 > num3就可以;相反的若是不成立,則需要考慮num2 > num3是否成立

邏輯運算子

前一小節中我們有提到關係運算子與判斷語句的關係,如果關係運算成立就執行該條語句,但假如需要進行判斷的關係運算語句超過一條,就需要使用邏輯運算子

java主要提供三種邏輯運算子,分別是AND, OR, NOT運算:

  1. &
  2. |
  3. !
int num1 = 10;
int num2 = 101;

if(num1 > 5 & num2 > 100)
	System.out.println(num1 >5 & num2 > 100); // true AND ture = true

if(num1 > 10 | num2 > 100) // false OR true = true
	System.out.println(num1 > 10 | num2 > 100);

if(!(num1 > 10) && !(num2 < 100)) // NOT(false) AND NOT(false) = true
	System.out.println(!(num1 > 10) && !(num2 < 100));

輸出結果:

true
true
true

使用短路運算子提高效率

短路運算子的功能與普通的邏輯運算子相同,它的表示方式為&&, ||,都是AND, OR操作,差別在於它不用執行所有的判斷語句。

舉例來說,判斷式A在執行判斷時,遇到第一個關係判斷式為false後就不會再執行(7 > 3)的判斷了,因為不管無論false如何進行AND運算,結果始終為false

判斷式B也是相同的道理,當(4 < 5)為true後,不管接下來為false或為true返回值始終為true,所以不需要做額外的判斷

int num = 0;

if((4 > 5) && (7 > 3)){ // A
	num++;
}

if((4 < 5) || (5 < 6)){ // B
	num++;
}

為了驗證這個運算結果,我們使用普通的邏輯運算子與短路運算子進行比對,發現使用短路運算子確實省略掉判斷式

int i = 5;

if((2*i > 5) | (++i > 2))
	System.out.println("i = " + i);

i = 5;

if((2*i > 5) || (++i > 2))
	System.out.println("i = " + i);

輸出結果:

i = 6
i = 5

運算子優先級

假如有多條運算子存在一條表達式中,編譯器必須要有一個規則可以區分出誰先做誰後做,例如我們舉一個簡單的運算表達式為例子,很明顯的奉行先成除後加減的規定

double x=1,y=2,z=3;
double d = 2 * x * y + z - 1 / x + z % 2; // 4 + 3 - 1 + 1

System.out.println("d = " + d);

輸出結果:

d = 7.0

但除了普通的算術運算子之外,java還規定了我們上述提及的運算子優先順序,例如常見的指定運算子與邏輯運算子等,下表介紹常見運算子的執行優先順序

優先級 結合順序 類型
++, -- 右到左 算術運算子
+(正號), -(負號), ! 右到左 算術運算子
*, % 左到右 算術運算子
+, - 左到右 算術運算子
>, <, >=, <= 左到右 關係運算子
==, != 左到右 關係運算子
& 左到右 邏輯運算子
| 左到右 邏輯運算子
&& 左到右 邏輯運算子
|| 左到右 邏輯運算子
?: 右到左 三目運算子(條件運算子)
=, +=, -=, *=, /=, %= 右到左 複合指定運算子

結合順序
所謂結合順序就是運算子會優先向左或右與操作變數結合的特性。例如負號具有由左到右的結合特性a = 4 + - 5,會先向右側尋找可結合的操作數(常數5)。同理a++也是先向右側尋找操作數,沒有的話向左側尋找(因為單目運算子的特性)

養成使用小括號習慣
雖然我們知道x + y * 2 - 1的執行順序,但若加上小括號x + (y * 2) - 1可以讓其他開發者更快讀懂程式碼

運算運算與優先級問題

優先級讓人頭痛的地方就是該如何在一連串運算式抓出誰該先算,誰又該慢點。尤其是遞增遞減運算子的前後置最容易搞混

其實根據官方文件遞增遞減運算子的優先級,後置(post-fix)是高於前置(pre-fix)。下面我們舉個例子:

int num = 5;

System.out.println(num++ * ++num * num++);

輸出結果

245

其實很多人會將優先級高的運算子解釋成先行運算,也就是說他們認為運算順序應該是:
num++ -> num++ -> ++num
其實先行運算這個觀念沒有錯,不過前提是該操作變數的前後範圍內,而不會直接就找最高優先級的操作數進行運算。這間接帶出一個觀念,在選擇優先級之前首先要確認整個運算式由左至右的,可以從官方文件中查到這個規則。

說白了就是你要先從左到右進行運算操作,遇到前後均有運算子時才可以進行優先級判斷,而不是直接跳去執行優先級最高的運算式,你可以想像優先級主要是為了處理下面這種場景

int num1 = 5;
int num2 = 6;
int num3 = 1;
int result = 0;

result = num1 * ++num2 * num3++;
result = ((num1 * (++num2)) * (num3++)); // 解釋成這樣

跳回剛剛的例子,若原本的假設成立,編譯器真的會一開始就直接執行優先級高的運算子,而忽略由左到右的這條規則的話,下面這個例子的輸出應該會相等

int num = 5;

System.out.println("test1 = " + (num++ * ++num));

num = 5;

System.out.println("test2 = " + (++num * num++));

輸出結果:

test1 = 35
test2 = 36

看到了吧,輸出結果證明原本的假設不成立,但假如我們考慮到由左到右運算的這條規則時,一切就說得通了

test1的運算順序:

  1. num++得到返回值5
  2. ++num得到返回值7
  3. 兩個操作變數相乘得到35

test2的運算順序

  1. ++num得到返回值6
  2. num++得到返回值6
  3. 兩個操作變數相乘得到36

最後回到原先的例子: num++ * ++num * num++

  1. num++得到返回值5
  2. ++num得到返回值7
  3. 兩個操作變數相乘得到35
  4. num++得到返回值7
  5. 兩個操作變數相乘得到245

尚未有邦友留言

立即登入留言