無痛入手 C++:基礎系列4 - 常用的運算

2024/04/22閱讀時間約 20 分鐘

電腦只做一件事情: 運算。

我們所看到的任何酷酷的應用: 不論是網頁動畫、遊戲特效、甚至是 AI 說的話,全部都
是由電腦的運算結果組合而成的。

首先我們來梳理一下各個名詞之間的關聯:
1. 運算分成兩個部分: 運算子 (運算的名稱,如: 加法) 和運算元 (運算的對象,可以是常值,如: 8 或變數)。運算就是對資料進行處理,並產生新的資料
2. 資料如同上一篇所說的,可以分各種資料型態。
3. 資料的型態會決定電腦可以對它進行什麼運算,以及到底該怎麼算


資料型態的運算

對多數的 C++ 內建資料型態 (除了少數的特例,以後會提到) 進行運算的時候,需要滿足以下條件:
同個運算的每個運算元都需要有相同的資料型態,產生的結果也會是同樣的資料型態。舉例來說,整數只能跟整數進行加法,產生的結果也會是整數。

需要注意到,上一篇介紹的 string 並不是內建資料型態,所以它不受限於上述的限制,稍後會對於這個特性做進一步的解釋。


算術運算 (Arithmetic operator)

基本上就跟數學計算一樣,用來進行我們熟知的四則運算,共有五種: 加(+)、減(-)、乘(*)、除(/)、取餘數(%)。

它們的優先順序就跟數學一樣是先乘除後加減,取餘數的優先度和乘除是一樣的。假如遇到兩個優先度相同的運算,則由左到右進行運算。被小括號 () 包起來的部分則也會優先計算。

cout << 1 + 2 << '\n';
cout << 0.1 + 2.5 << '\n';
cout << 10 / 2 << '\n';
cout << 3 * (10 - 5) % 2 << '\n';

範例1: 輸入長方形的長和寬,計算出面積

int x, y;
cin >> x >> y;
cout << x * y;

範例2: 輸入圓的半徑,計算出面積

int r;
cin >> r;
cout << 3.14 * r * r;

範例3: 輸入 1 元、5 元、10 元銅板的數量,計算總價值

int num_1, num_5, num_10;
cin >> ​num_1 >> num_5 >> num_10;
cout << num_1 + 5 * num_5 + 10 * num_10;


C++ 的除法有個需要注意的部分: 執行 cout << 3 / 2 << '\n'; 的話會發現運算的結果竟然是 1 而不是 1.5。

先前有提到,對大部分的內建資料型態來說,運算結果的型態必須跟運算元的型態相同,因此在整數的除法中,小數點後的數字會被無條件捨去

那該怎麼保留小數點後的數字呢? 在回答這個問題前,先來思考一下這行程式會印出什麼?

cout << 0.1 + 2;

在這個例子中,加法運算的左邊是浮點數,右邊卻是整數。
為了維持型態要一致的規則,電腦會將資訊量較少的資料轉換成資訊量較多的資料型態。以這個例子來說,浮點數可以紀錄小數點以後的數字,所以相對於整數而言,浮點數是資訊含量較多的資料型態。
因此電腦實際上會執行以下的程式:

cout << 0.1 + 2.0;

答案是 2.1,且兩個運算元和運算結果都具有相同的資料型態。

假如電腦選擇將浮點數轉換成整數,就需要丟棄小數點後的數字: 變成 0 + 2,答案會是 2,顯然是錯誤的。


型態轉換

回到前面 3 / 2 的例子,為了讓運算結果維持浮點數的型態,我們必須將其中一個運算元轉換成浮點數。可以使用一個 C++ 的內建函式做到這件事情:

cout << static_cast<float>(3) / 2;

static_cast 是 C++ 的函式,它會把輸入的值 (小括號中的內容) 轉換成指定的資料型態 (<>裡面的型態)。
經過轉換後,原本的運算就會變成 3.0 / 2, 依照前面提到的資料轉換原則,電腦在進行運算的時候會自動把 2 轉換成 2.0,因此運算的結果就會是 1.5。

注意: 你也可以利用 static_cast 將福點數轉換成整數,但是要記得這麼做可能會遺失小數點後的值,請確定這樣的結果是你預期的。

稍微補充一下,函式 (function) 在概念上跟數學課學到的函數有一點像,給它一組輸入,就能得到對應的輸出: y = F(x1, x2, x3)。我們會在後續的文章中深入介紹函式。

範例: 輸入梯形的上底、下底和高,計算出面積:

int top, bottom, height;
cin >> top >> bottom >> height;
cout << static_cast<float>(top + bottom) * ​height / 2;

上述例子中,top 和 bottom 作為 static_cast 的輸入,會先進行相加再進行進態轉換,也可以寫成:

int top, bottom, height;
cin >> top >> bottom >> height;
cout << (top + bottom) *static_cast<float>(height) / 2;

但這樣寫的話要記得幫 top 和 bottom 加上小括號,才會優先進行計算。


比較運算

其實就是比大小,共有六種: 大於(>)、大於或等於(>=)、小於(<)、等於或等於(<=)、等於 (==)以及不等於(!=)。

比較運算的結果為布林值 (bool),只有兩種可能: 真 (true) 和假 (false)。

以下是一些例子:

cout << (1 >= 3) << '\n';
cout << (0.0 != -1.0) << '\n';
cout << ((0.1 + 5) == 5.1) << '\n';

注意: 等於 (==)是兩個等號組成的,一個等號 (=) 是給值 (assign),要小心不要搞錯了!

範例1: 判斷使用者輸入的數是否為正數

int x;
cin >> x;
cout << (x > 0);

如果是正數的話會印出 1,否則會印出 0。

範例2: 判斷使用者輸入的數是否為偶數

int x;
cin >> x;
cout << (x % 2 == 0);

如果是偶數的話會印出 1,否則會印出 0。


邏輯運算

對布林值進行運算,得到另一個布林值。有三種: 且 (&&)、或 (||)、相反 (!)。
這部分對於非本科的朋友可能會比較陌生,所以講得仔細一點。

(&&): 需要兩個布林運算元。兩個運算元都要為真,運算的結果才會為真。以下列出所有可能的狀況 (假設 x 是布林型態的變數):

x = true && true; // x == true
x = true && false; // x == false
x = false && true; // x == false
x = false && false; // x == false

(||): 需要兩個布林運算元。只要其中一個運算元為真,運算的結果就會為真。以下列出所有可能的狀況:

x = true && true;  // x == true
x = true && false; // x == true
x = false && true; // x == true
x = false && false; // x == false

相反 (!): 只需要一個布林運算元。如果該布林值為真,則運算結果為假,反之如果該布林值為假,則運算結果為真。就是相反過來的意思。

x = !true;  // x == false
x = !false; // x == true

範例1: 判斷使用者輸入的數是否為偶數且為正數

int x;
cin >> x;
cout << (x % 2 == 0) && (x > 0);

如果是的話會印出 1,否則會印出 0。

範例2: 判斷使用者輸入的數是否為小於 10 的質數

int x;
cin >> x;
cout << (x == 2) || (x == 3) || (x == 5) || (x == 7);

上面列舉了所有小於 10 的質數,看使用者輸入的數是否為其中一個。

範例3: 輸入一個二位數,判斷是否可以被 3 整除

int num;
cin >> num;
int x = num % 10;
int y = num - x;
cout << ((x + y)% 3 == 0);

說明: x 是個位數,y 是十位數,兩者相加如果能被 3 整除,表示 num 可以被 3 整除。


運算子的優先順序

細心一點的朋友可能會注意到在比較運算的範例中,所有的運算都被額外的小括號給包起來了。會這麼做的原因在於相對比較運算而言, << 的優先級比較高。

算術運算 (+ - * / %)、比較運算 (> >= < <= == !=) 和 << 都屬於 C++ 的運算子。不同的運算子之間存在優先順序,就跟先乘除後加減一樣。如果在程式中看到奇怪的符號,基本上都是某種運算子。

因此,上述程式需要利用小括號來提升比較運算的優先級,電腦才會先計算 1 >= 3 後,再把結果交給 cout 印在螢幕上。算術運算的話就不需要這麼做,因為它們的優先級比 << 還要高。

此外,需要特別注意邏輯運算的優先級: 相反 (!) 最高,再來是 (&&),最低的是 (||)。和其他運算一樣,你可以使用小括號來把要優先運算的部分包起來,就可以讓本來優先級比較低的運算優先進行運算。猜猜看下列的陳述會印出什麼值:

cout << (true || false) && (true && false);

對 C++ 完整的運算子種類及優先順序有興趣的話,可以參考 C++ Operator Precedence 這個網站。某些運算子會留到之後的文章再介紹。

注意: 如果擔心搞錯優先順序的話,使用小括號也是個不錯的選擇。

其實 = 也是 C++ 定義的運算子,而且 = 的優先順序比任何會產生運算結果的運算子都還低,因此不論變數右邊的運算為何,電腦都會先將它們算完,才會將最終的結果透過 = 運算子存到左邊的變數中。


指派運算子

你也可以把算數運算子跟 =合併起來使用,變成 +=-=*=/=%=
指派運算子的左邊跟右邊各可以放一個運算元,其中左邊那個一定要是變數。它們的意思是將左邊變數的值取出來,跟右邊的運算元進行運算,最後將結果存回左邊的變數
舉幾個例子會比較清楚:

int x = 10;
x += 2;
// x == 12
cout << x << '\n';
x %= 3;
// x == 0
cout << x << '\n';

x += 2 為例,可以把它想像成是:

x = x + 2;

其他的指派運算子也是相同的概念。
這樣的語法可以讓程式碼看起來比較簡潔一點。


遞增、遞減運算子

遞增 (++) 和遞減 (--) 運算子是專門用來對變數進行加一或減一的運算子。
它的行為基本上就是:

x = x + 1;
x = x - 1;

它們可以擺在變數的前面或是後面。

前置遞增、遞減

前置遞增和遞減的樣子是: ++x--x
當其他運算需要使用到它們的結果時,前置遞增和遞減會先完成加一或減一的運算,再將運算後的結果交給其他運算。以下舉個例子說明:

int x = 10, y = 0;
// print 11
cout << ++x << '\n';
// print -1
cout << --y << '\n';

你也可以把 cout << ++x cout << --y 想像成是:

x = x + 1;
cout << x;

y = y - 1;
cout << y;

後置遞增、遞減

後置遞增和遞減的樣子是: x++x--。概念剛好和前置遞增、遞減相反。
當其他運算需要使用到它們的結果時,後置遞增和遞減會先將變數目前的值取出來交給其他運算使用,再將加一或減一的運算結果儲存進變數。以下舉個例子說明:

int x = 10, y = 0;
// print 10
cout << x++ << '\n';
// print 11
cout << x << '\n';

// print 0
cout << y-- << '\n';
// print -1
cout << y << '\n';

你也可以把 cout << x-- cout << y++ 想像成是:

cout << x;
x = x + 1;

cout << y;
y = y - 1;

注意: 也可以對浮點數型態的變數進行遞增、遞減運算。


String 的算術運算

儘管 string 不是內建的資料型態,但它仍然支援了一些好用的運算子。以下舉三個常用的運算。

加法

string 是由一堆字元所組成,它的加法也很直觀: 把兩堆字串接在一起,如:

string str1 = "Hello ";
string str2 = "C++!";
// print: Hello C++!
cout << str1 + str2;

和整數及浮點數的加法一樣,string 的加法也可以串在一起:

string str1 = "Say 1\n";
string str2 = "Say 2\n";
string str3 = "Say 1,2,3,4!";
// print:
// Say 1
// Say 2
// Say 1,2,3,4!
cout << str1 + str2 + str3;

此外,string 也可以和 char 進行加法:

string str = "A question mark ";
char c = '?';
// print: A question mark ?
cout << str + c + '\n';
// print: ?A question mark
cout << c + str;

加法指派

通常可以進行 + 就可以進行 +=
利用 += 可以讓我們用更精簡的語法將更多內容串接在字串後面 (英文是 append):

string str = "Hi!";
// append using + and =
str = str + " I am";
// append using +=
str += " Bob.";
// print:​ Hi! I am Bob.
cout << str;

比較

我們可以用 == 和 != 來比較兩個字串是否有相同的內容:

string foo = "foo";
string bar = "bar";
// print: 0​
cout << (foo == bar) << '\n';
// print: 1
cout << (foo != bar);




總結

這篇文章介紹了一些 C++ 的基本運算子。

  1. 在進行算數運算的時候要注意電腦可能會偷偷把浮點數的資料轉換成整數,我們可以利用 static_cast 來進行轉型,進而得到預期的結果。
  2. 不同的運算子有優先順序,= 的優先級是最低的。此外,我們可以利用小括號把想要先做的運算包起來。
  3. 遞增、遞減運算子放在變數前面或後面會有不一樣的意思 (語意)。
  4. 字串 (string) 可以跟別的字串或字元進行相加,也可以和其他字串進行比較。


習題

1. 算出下列的程式會印出什麼結果,再實際執行看看:

int x = 10;
cout << x / 3 << '\n';

float y = 2 * x;
cout << ++y / 2 << '\n';

bool b = !true || false;
cout << b || true && false;
  1. 寫一個程式,首先請使用者輸入秒數,接著將換算後的結果用 a min b sec 的格式印出。舉例來說,使用者輸入了 70,程式應該要印出:70 sec = 1 min 10 sec.。下面是程式的範本,請寫程式計算出正確的 min 和 sec:
#include <iostream>
using namespace std;

int main() {
int total_sec = 0;
// input how many seconds
cin >> total_sec;
int ​min = 0;
int sec = 0;
cout << total_sec << " sec = "
<< min << " min "
<< sec << " sec.";
return 0;
}
  1. 同上題,但加入小時。舉例來說,使用者輸入了 10000,程式應該要印出:10000 sec = 2 hr 46 min 40 sec.。下面是程式的範本,請寫程式計算出正確的 hour、min 和 sec:
#include <iostream>
using namespace std;

int main() {
int total_sec = 0;
// input how many seconds
cin >> total_sec;
int ​hr = 0;
int ​min = 0;
int sec = 0;
cout << total_sec << " sec = "
<<​ hr << " hr "
<< min << " min "
<< sec << " sec.";
return 0;
}
  1. 假如上述兩題你使用到了 /% 以外的運算,嘗試只用/%寫出來。假如你使用到了 %,嘗試在不使用它的情況下寫出來。 ​
  2. 輸入三個數,判斷是否有任兩個數相加的和等於 10,將結果印出來。
  3. 輸入一個三位數,判斷是否可以被 3 整除。
  4. 輸入一個三位數,印出個位、十位、百位數字的和。舉例來說: 輸入 123 的話,要印出 6 (1 + 2 + 3 = 6)。





1會員
12內容數
程式設計 & 電腦系統 & 系統軟體
留言0
查看全部
發表第一個留言支持創作者!