# 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 ```