# 函式 function
---
## 初階班講義
初階班的同學可以參考:[函式 function](/aUvFrVwUReOdb5WoYSOiKQ)
==$by$ **11th** 初階教學 **samsonjaw 趙炫翔**==
---
## 簡介
他的目的在於將重覆使用到的程式碼以一個又一個的小區塊來表示
可以增加程式的 ==可讀性== 以及 ==可維護性==
這個技能非常重要,有時候不用函式寫個 $200$ 行會讓人瘋掉
同時,有函式的配合 `debug` 的過程也會順利一些
當我們在一個函式中呼叫自己時,就稱為遞迴 `recursion`
最常見的函式就是我們的 `main()` 函式
還有許多像 `<cmath>` 標頭檔中的 `sqrt()`、`<cctype>` 標頭檔中的 `toupper()`
都是常見的內建函式使用
而上面提到過像 `vec.push_back()` 這種,`.` 後面的都是成員函式,他也是「函式」的一種,只不過位置不同
成員函式是由 `class` 定義的,但其實這些只是名詞而已,看看即可
---
## 基本使用 call by value
函式就如同變數一般,需要宣告
只是和一般變數不同的是他需要有「參數」
程式是由上讀到下的,因此要在上面有宣告後才能使用
但還有一種寫法 **C++** 也承認:
先宣告再定義
定義和宣告分開也是可以的,但一定要在使用之前宣告完畢
### 宣告
```cpp
T name(T1 a, T2 b, ..., Tn x) {
定義;
}
// T 是回傳型別 type,若為 void 則不需進行 return
// T1 ... Tn 是參數型別,傳入函式的參數一定要照這些順序來
// a, b, x 等都可以自己取
```
### 先宣告再定義
這裡直接打範例程式碼
```cpp
#include <iostream>
using namespace std;
int f(int a); // 先宣告
int main() {
int a;
cin >> a;
cout << f(a) << endl;
}
int f(int a) { // 再定義
return a * (a + 1) / 2;
}
```
### 範例
範例 $1$:
原本內建的 `abs()`,以自定義方式呈現
假設傳入的數字為整數 `int`
`abs()` 原於標頭檔 `<cmath>` 中定義,但現在 `<iostream>` 中就有他的存在了
`abs` 指 $absolute\ value$,中文即絕對值
```cpp=
#include <iostream>
using namespace std;
int abs(int x) {
if (x >= 0)
return x;
return -x; // else 不必要,因為若已經 return 函式就會結束
}
int main() {
int n;
cin >> n;
cout << abs(n) << endl;
return 0;
}
```
範例 $2$:
原本內建的 `toupper()`,以自定義方式呈現
並且定義於主函式 `main()` 下方
假設傳入的必為一無空格字串 `string`
`toupper()` 是將字母轉成大寫的意思
原本也是在 `<cctype>` 標頭檔中定義,現在同時於 `<iostream>` 中被定義
```cpp=
#include <iostream>
using namespace std;
char toupper(char ch);
int main() {
string s;
cin >> s;
for (int i = 0; i < s.length(); i++)
s[i] = toupper(s[i]);
cout << s << endl;
return 0;
}
char toupper(char ch) {
if (ch >= 'a' && ch <= 'z')
return ch - 'a' + 'A';
return ch;
}
```
---
## 遞迴
遞迴就是在函式中呼叫自己,在後面的 `DFS` 中會用到這個技能
大家應該都有碰到過遞迴,舉高一的資訊課教的費氏數列為例:
```cpp=
#include <iostream>
using namespace std;
long long fibonacci(int x) {
if (x == 0 || x == 1) return 1;
return fibonacci(x - 1) + fibonacci(x - 2);
}
int main() {
int t, n;
cin >> t;
while (t--) {
cin >> n;
cout << fibonacci(n) << endl;
}
return 0;
}
```
---
## cmp 自定義函式
`std::sort` 中有自定義比較函式,他位於 `<algorithm>` 中,是十分好用的排序方式,想要看更多介紹可以參考:
[`sort` 章節](/wCGOhmzxQneHPiXFk8KOtg)
內建 `sort` 在 `algorithm` 中如下:
```cpp!
template <class Iterator class Compare>
void sort (Iterator first, Iterator last, Compare comp);
```
而 `cmp` 寫法有很多種,這邊只舉他是函式的寫法
若 `comp` 格沒有值,則預設使用內建的小於運算子
因此直接重載 (`overload`) 小於運算子也可以
### 寫法:
```cpp
bool cmp(T a, T b) {
return ......;
}
// T 是你要比較的型別 type
// return 放入你的比較條件,a 是前,b 是後
```
### 例題:
[dandanjudge a086: 放眼世界 征服宇宙](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a086)
:::spoiler `code`
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
// #include <utility> 和 <algorithm> 重疊,可以不用寫這行
using namespace std;
bool cmp(pair <int, int> a, pair <int, int> b) {
if (a.first == b.first)
return a.second > b.second;
return a.first < b.first;
// return a.first == b.first ? a.second > b.second : a.first < b.first;
// return a.first == b.first && a.second > b.second || a.first < b.first;
}
int main() {
cin.tie(nullptr);
ios::sync_with_stdio(false);
int n, i = 1;
vector < pair<int, int> > vec; // 重量, 編號
while (cin >> n) {
vec.push_back(make_pair(n, i));
i++;
}
sort(vec.begin(), vec.end(), cmp);
for (i = 0; i < vec.size(); i++)
cout << vec[i].second << ' ';
cout << '\n';
return 0;
}
```
:::
---
## 其它事項
### 重名
因為 **C++** 採用區域變數問題,所以重名也是沒問題的
只要同一個區域 (大括號) 中沒有一樣的變數名,那基本上都不會產生衝突
以上面的程式碼為例:
可以改寫為
```cpp=
#include <iostream>
using namespace std;
int abs(int n) {
if (n >= 0)
return n;
return -n;
}
int main() {
int n;
cin >> n;
cout << abs(n) << endl;
return 0;
}
```
### call by value
這邊的參數都是被 ==**「複製」**== 一份到函式中進行運算的,因此在函式中改變其值也不影響原本的值
例:
```cpp=
#include <iostream>
using namespace std;
void f(int a, int b) {
a = b;
b = 8;
cout << a << ' ' << b << endl;
}
int main() {
int a = 10, b = 2;
cout << a << ' ' << b << endl;
f(a, b);
cout << a << ' ' << b << endl;
return 0;
}
```
答案如下:
``` cpp
10 2
2 8
10 2
```
:::success
有全域變數則不一樣,但全域變數有點神奇,這邊先不談他
小知識:全域變數若不初始化,系統會自動設成預設值
例如:
```cpp
int x; // x 的值為 0
char c; // c 的值為 '\0'
bool b; // b 的值為 false
float f; // f 的值為 0.0
int *p; // p 的值為 nullptr
```
:::
### 函數多載 overloading
和前幾章運算子重載一樣,代表函式的名子重複
函式多載只要參數的型別不一樣或數量不相同
那麼編譯器就會自行判斷要使用哪個函式
但若被重複定義則編譯器會報錯:$ambiguous$,意思是模糊不清的
:::danger
小心:**C / C++** 語言屬於弱型別語言,他們的 `long long`、`int`、`double` 有時可以互相轉換
故有時候即使型別不同也會出現編譯錯誤
因此個人不推薦大量使用,因為有時候會不知道自己在寫什麼,等真正熟練後再來會比較恰當
:::
### 預設參數值
函式參數可以有預設值
如果參數沒有輸入的話,就用預設值來運算
但若沒有設預設值
則使用函式時,那個參數一定要有輸入
舉例:
計算給定半徑的圓面積
```cpp=
#include <iostream>
using namespace std;
double area(int r = 1, double pi = 3.14) {
return r * r * pi;
}
int main() {
int r;
cin >> r;
cout << area() << endl; // 單位圓面積
cout << area(r) << endl; // 直接使用預設圓周率 3.14
cout << area(r, 3.14159) << endl; // 使用更精準的圓周率
return 0;
}
```
:::danger
注意:預設參數必須在一般參數的最後面
例如:
```cpp
double area(int r, double pi = 3.14) // 合法
double area(int r = 1, double pi) // 編譯錯誤
double area(int r = 1, double pi = 3.14) // 合法
```
:::
## 補充事項
在上面初階班講義中有提到模板 (或稱樣板),`template` 的用法
感興趣的話也可以學學看,在源代碼中常常出現,學會閱讀可以加強自己的學習能力
---
> 製作者:
>
> > [color=#000000] [name= 編者frankie]
>
> 引用講義:
>
> > [color=#1E1ED2] [name= 第 11 屆初階教學 samsonjaw 趙炫翔]