# Lecture 4 Functions
函式在程式設計中扮演重要的角色,撰寫程式時通常會將重複使用到的運算包裝成函式。函式原型(Function prototype)定義了函式的外觀,其中包含回傳值、函式名稱及傳入參數,三者要素。函式原型通常會定義在獨立的標頭檔,並以另一個 .cpp file 實作函式中的主體運算。而值得注意的是若函式是在 main 之後實作,必須在 main 之前進行宣告,否則會出現編譯錯誤。
Parameters and Passing Arguments
---
當在設計函式時,傳入的參數大致可分為3類:
* 傳值或傳址
* 傳入常數地址(const reference parameters)
* 傳入指標或陣列
傳址是應用在需要被函式改變的任何物件。
傳值是應用於不想被函數改變的小物件。
傳入常數地址則適用在不想被函數改變的大型物件,如 vector、string、自訂義的類別等。
```
#include <iostream>
#include <string>
using namespace std;
// returns the index of the first occurrence of c in s
// the reference parameter occurs counts how often c occurs
string::size_type find_char(const string &s, const char &c, string::size_type& occurs){
auto ret = s.size(); // position of the first occurrence, if any
occurs = 0; // set the occurrence count parameter
for (decltype(ret) i = 0; i != s.size(); ++i) {
if (s[i] == c) {
if (ret == s.size())
ret = i; // remember the first occurrence of c
++occurs; // increment the occurrence count
}
}
return ret; // count is recorded in the occurs
}
int main() {
string s = "Hello world";
string::size_type cnt;
auto index = find_char(s, 'o', cnt);
cout << "Character o first occurs at the index of "
<< index << " in " << s << endl;
cout << "Character o occurs " << cnt << " times"
<< " in " << s << endl;
return 0;
}
```
#### Array Parameters
使用陣列作為參數的宣告方式為Type name[],而作為陣列變數本身也是指向陣列中第一個元素的指標故亦可寫作Type* name。
```
void function(int a[])// void function(int *a)
```
而當呼叫函式時,參數和引數間是透過執行附值的動作將引數引入函式中進行計算。因此當某個函式使用陣列作為參數,在函式中 a 雖然指向傳入的陣列位置但卻非原本的陣列,因此在函式內使用 begin()、end() 等使用到陣列長度的方法將會失去原本的意義。
```
// using begin() & end() inside the fuction
int sum(int a[]){
int summation = 0;
for (auto i=begin(a); i!=end(a); ++i)
summation += i;
return summation;
}// cannot use begin and end in the function since the size of the array is lost in the function.
// using for range inside the fuction
int sum(int a[]){
int summation = 0;
for (int i:a)
summation += i;
return summation;
}// when passing the array into a function, the range-for WILL NOT work since we lose an important information: the size of an array!
```
解決該問題的一種方法是傳遞陣列的參考而非陣列。
```
#include <iostream>
using namespace std;
// passing array reference to function with fixed size 5
void arrAutoTest(int (&ia)[5]) {
for (auto i : ia)
cout << i << " ";
}
int main() {
const size_t array_size = 5;
int ia[array_size] = { 0, 1, 2, 3, 4 };
arrAutoTest(ia);
return 0;
}
```
但上述的方法會綁定陣列的大小,而使函數不便於操作,更進階的方法是使用函式樣版。
```
#include <iostream>
using namespace std;
// passing array reference to function with generic size arr_size
template <std::size_t arr_size>
void arrAutoTest(int(&ia)[arr_size]) {
for (auto i : ia)
cout << i << " ";
}
int main() {
const size_t array_size1 = 7;
int ia[array_size1] = { 0, 1, 2, 3, 4, 5, 6 };
arrAutoTest(ia);
cout << endl;
const size_t array_size2 = 4;
int ib[array_size2] = { 0, 1, 2, 3 };
arrAutoTest(ib);
return 0;
}
```
Return Types
---
在定義函式時,必須定義傳回值型態,如果函式不傳回值,使用 void 表示不傳回任何數值;若定義了傳回值型態不為 void,函式最後要使用 return 傳回數值,否則編譯器失敗。
* 回傳非參考物件 & 回傳參考物件(千萬不能回傳在函式內建立的 local object)
```
// return plural version of word if ctr isn't 1
string make_plural(size_t ctr, const string& word, const string &ending){
return (ctr == 1) ? word : word + ending;
}
// find longer of two strings
// lvalue 參考
const string& shorterString(const string &s1, const string &s2){
return s1.size() < s2.size() ? s1 : s2;
}
```
定義傳回值型態為 rvalue 參考,通常是為了借此避免不必要的複製,藉而提升效率。
```
// rvalue 參考
#include <iostream>
using namespace std;
string&& concat(string &&lhs, string &rhs) {
lhs += rhs;
return std::move(lhs);
}
int main() {
string s = "++";
string result = concat("C", s);
cout << result << endl;
return 0;
}
```
* 回傳指標
代表著記憶體位址在函式執行完後,必須仍是有效的,通常代表著函式內會配置動態記憶體,再將其回傳。
```
int* makeArray(int m, int initial = 0) {
int *a = new int[m];
for(int i = 0; i < m; i++) {
a[i] = initial;
}
return a;
}
```
Overloaded Functions
---
C++支援函式重載(Overload),為類似功能的函式提供了統一名稱,然而根據參數個數或型態的不同,由編譯器選擇要呼叫的函式,函式重載令開發者在設計函式名稱可以簡便一些。
Q:Given
`int gcd(int v1, int v2);`
Are the following overloaded functions valid?
```
int gcd(double v1, double v2);
int gcd(int v1, int v2, int v3);
int gcd(int v1, int v3);
int gcd(double v1, int v3);
double gcd(int v1, int v3);
```
A:
Y, Y, N, Y, N
Functions with Default Arguments
---
在函式中使用預設引數時,在實際操作函式時就無需再輸入該引數。在設計時應注意,預設引數需要放在位預設的參數之後。除此之外,操作函式時輸入的引數會依照順序對應參數。
```
int f(int, int=0, int=0); //ok
int f(int=0, int=0, int); //error
int f(int=0, int, int=0); //error
int f(int, int, int=0); //ok
```