TCIRC C++教學講義 === >[name=張皓凱、沈奕呈][time=Nov 12,2021] ###### tags:`tcirc` `社課` `C++` `台中一中電研社` --- 第五節社課 === [TOC] --- ## 電研 講義都會公告在社網上 [社網:tcirc.tw](https://tcirc.tw/) [IG:tcirc_39th](https://www.instagram.com/tcirc_39th/) ![ig QRcode](https://i.imgur.com/4hyS6GM.png) [online judge:judge.tcirc.tw](https://judge.tcirc.tw/) --- ## 函式(function) 函式是執行某項作業的一段程式碼區塊,常用於需要大量在無法使用迴圈的地方重複使用某一段程式碼時。在我們之前的社課其實已經出現過各種不同的函式,例如:`.size()`、`.append()`、`getline()`。 ---- 函式(function)的概念類似於數學中的函數(function),一樣都是有輸入、輸出,之中的運作也有一定的關係。 ![](https://i.imgur.com/Kz0CPhu.png) (圖片取自維基百科) --- ### 宣告 --- #### 方式 定宣告的方式如下: ```cpp= 輸出值資料型態 函式名稱(輸入值1資料型態 輸入值1名稱, 輸入值2資料型態 輸入值2名稱, ...){ return 回傳值; } ``` 輸入值(input,參數parameter)的部分可以有`0`到無限多個,而回傳值(盡量不要用輸出值因為容易跟`cout`搞混,output)只可以沒有或一個。如果沒有輸出值則「輸出值資料型態」的部分用`void`代替。(<font color='#ff0000'>注意</font>:`void`不是一種資料型態)。輸入值的變數寫在小括號中就是宣告,不需要在宣告函式前先宣告變數,而輸入值變數的有效範圍是整個函式。 ---- 直接看範例 有回傳值: ```cpp= int add(int a, int b){ return a+b; } ``` 函式會在執行完`return`後直接停止。 無回傳值: ```cpp= void printmessage(){//沒有輸入小括號還是要留著 cout << "I'm a function!"; } ``` --- #### 位置 函式宣告的位置分成兩種:在`main()`前面、在`main()`後面,兩者沒有任何功能性上的差異,只是美觀而已。在`main()`之前的宣告方法與上面相同,之後的話只需要在`main()`前面加上這一行: ```cpp= 輸出值資料型態 函式名稱(輸入值1資料型態 輸入值1名稱, 輸入值2資料型態 輸入值2名稱, ...) //必須與宣告函式的部分相同 ``` 這個動作稱之為prototype --- #### 呼叫 使用函式的動作我們稱為呼叫,一個函式被呼叫後電腦會執行函式內的程式碼。呼叫方法如下: ```cpp= 函式名稱(輸入值1,輸入值2,...)//輸入值的數量需要與定義時的輸入值數量相同 ``` 如果是有回傳值的函式,可以將變數值設為函式的回傳值。 ---- ```cpp= #include <iostream> using namespace std; int f(int a, int b){ return a*a+5*b+2; cout << a; } void g(int a,int b){ cout << "a=" << a << " " << "b=" << b << endl; } int main(){ int a=3,b=7; g(a,b); int c=f(a, b); cout << c; } ``` ``` /*---output a=3 b=7 46 ---------*/ ``` --- #### 預設值 如果函式中有某個輸入值常常為同樣的值,我們可以在宣告函式時給予參數一個預設值,則在之後呼叫時就可以不用輸入該參數。但若要給定每一值則必須輸入以前的所有參數。 ---- ```cpp= #include <iostream> using namespace std; int power(int a, int b=2){ int re=1; for(int i=0;i<b;i++){ re*=a; } return re; } int main(){ cout << power(3) << " " << power(2,3); } ``` ``` /*---output 9 8 ---------*/ ``` --- ### 陣列為參數 若要讓一個函式的參數為陣列,在宣告時可不指定大小,若有指定大小,在輸入時也不必給一個相同大小的陣列。另外,在傳入時只需要輸入陣列名稱即可。 ```cpp= #include <iostream> using namespace std; int countsum(int arr[], int n){ int sum=0; for(int i=0;i<n;i++){ sum+=arr[i]; } return sum; } int main(){ int arr[5]={2,5,9,7,3}; cout << countsum(arr,5); } //26 ``` --- ### main() `main()`其實也是一個函式,有沒有發現到沒有`main()`的回傳型態是`int`,但卻沒有`return`,那是因為`C++`會自動讓`main()`回傳0。 --- ## 遞迴 當在一個函式中使用自己就稱為遞迴,遞迴最重要的就是<font color="ff0000">設定停止條件(base case)</font>,否則你會把你的電腦給炸了(其實不會,電腦會有一個最大遞迴上限,超過了會自動終止你的程式)。 ---- 一樣,直接看範例 ---- 沒有base case: ```cpp= #include <iostream> using namespace std; int f(int a){ cout << a; return f(a); } int main(){ cout << f(4); cout << "end"; } ``` ---- 一個正確的遞迴(費波那契數): ```cpp= #include <iostream> using namespace std; long long c=0; int fib(int a){ if(a==1||a==2){ return 1; } return fib(a-1)+fib(a-2); } int main(){ cout << fib(6); } //8 ``` --- ## 簡單練習題:(用遞迴做做看吧~) 1. 請做出階乘函式。 2. 請做出[阿克曼函式](https://zh.wikipedia.org/wiki/%E9%98%BF%E5%85%8B%E6%9B%BC%E5%87%BD%E6%95%B8)。 3. 請做出[最大公因數函式](https://judge.tcirc.tw/ShowProblem?problemid=b001)。 4. 請做出根號函式(精度~1e-6)。 --- ## struct 我們知道「相同型態的變數們」可以一格一格的放進「**陣列**」這個資料型態 可是...有些情況是不能(或不適合)用陣列把多個變數綁在一起的 ---- 比如說...你沒辦法用一個 2*x 的陣列把不同資料型態的兩個變數綁在一起(例如: int+char) 或者...當兩種以上資料型態相同但「性質不同」的變數綁在一起時,這個情況也不適合用陣列當「<font color='#ff0000'>複合型別</font>的資料型態」(例如:<font color='#87CEFA'>xyz座標.身高+體重+視力</font>) ---- 你說這些你都可以用(一或多個)一維或二維陣列解決!? 那你就用5*n的(或5個)陣列記錄每個人的<font color='#87CEFA'>身高.體重.視力.年齡.體指率</font>阿😑😑 保證你被一堆索引或零散的陣列搞瘋(╯°□°)╯︵ ┻━┻ ---- ### struct 是一種<font color='#ff0000'>複合型別</font> (derived data type),在寫程式時可以大幅增加程式的結構與可讀性,減少冗餘,是個很棒的東西。 簡單來說它是一種自創的資料型態(可以將相同或不同資料型態的變數綁在一起) 還可以將不直覺的索引編號用直覺的名字代替(如:<font color='#87CEFA'>x.y</font>) ---- #### 宣告 - 用法: `struct 自訂資料型態{成員1;成員2;...函式1{ }...};` - 說明: -資料成員(data member):在某一個struct裡面宣告一些資料型態,而這些宣告的資料型態就是資料成員(類似於陣列中的索引) -成員函式(member function):可以想成放在某struct目錄內的函式 ---- - 寫法:等同於在struct區塊宣告變數和函式 - <font color='#ff0000'>注意:</font>struct 要在大括號後加分號,函式不用 ```cpp= struct people{ int height,weight; double bmi; double get_bmi(){ return bmi=weight/(height*height/100.0/100);} }; ``` ---- #### 呼叫 用法: 先宣告一個資料型態為【struct名稱】的變數,再利用 `變數名稱.某成員或函式` 呼叫該變數的指定成員或函式 ```cpp= int main() { people room[50]; while(cin>>n){ for(int i=0;i<n;i+=1){ cin>>room[i].height>>room[i].weight; cout<<room[i].get_bmi()<<' '; } } } ``` ---- ``` /*---input 5 160 45 180 70 173 75 164 55 158 63 ---------*/ ``` ``` /*---output 17.5781 21.6049 25.0593 20.4491 25.2363 ---------*/ ``` --- ## class class也是一種複合型別,而struct跟class是實現物件導向(Object Oriented Programming/OOP)的重要角色, - class和struct類似,但相較於struct,class多了存取標籤的功能,標籤可以拿來設定存取權限,避免因為撞名而誤用了class裡面的某些成員或函式 - 另外,我們可以用class自創標頭檔,自己寫一個好用的函式工具給其他程式使用 ---- ### 程式內 如果class只是單支程式的一部分的話,它就是能分類成員存取權限的struct 權限分為public、private和protected(這個我們不討論) - public : 此成員或函式在class內、外都可以使用 - private : 只能在class內使用,或是透過public內的函數使用 透過private可以把資料封在class內(<font color='#ff0000'>封裝</font>),讓外界不要隨便存取class的資料 ---- #### 宣告 - 列出成員及函式,為了方便尋找成員和函式,先不寫函式內容,以求簡短 - class中需頻繁使用的變數通常設在private,因為可以設成簡短的變數,不怕和class外的變數重複 ```cpp= class division{ public: void set_a(int n);//set=賦值 void set_b(int n); int get_a();//get=取值 int get_b(); double do_div();//相除 private: double a; double b; }; ``` ---- #### 實作 在class外寫函式的內容,記得在函式名稱前面加上所屬class的名稱,並接上 `::` ,不然會被認定為一般的function ```cpp= void division::set_a(int n){ a=double(n); } void division::set_b(int n){ b=double(n); } double division::get_a(){ return a; } double division::get_b(){ return b; } double division::do_div(){ return get_a()/get_b(); } ``` ---- #### 呼叫 呼叫就跟struct的用法一樣啦-- 先宣告一個資料型態為【class名稱】的變數,再利用 `變數名稱.某成員或函式` 呼叫該變數的指定成員或函式 ```cpp= int main() { int x,y; while(cin>>x>>y){ division ans; ans.set_a(x); ans.set_b(y); cout<<ans.do_div()<<'\n'; } } ``` ---- ``` /*---input 180 70 164 55 158 63 ---------*/ ``` ``` /*---output 2.57143 2.98182 2.50794 ---------*/ ``` ---- ### .h 不常用的零件在程式內額外寫ok 但常用的零件如果每次要用的時候都得花時間寫一次,是不是很麻煩? 就像我們只需要知道電腦要怎麼用就好,不需要知道怎麼裝電腦,更不會在每次開電腦前,就組裝一次電腦 我們可以自己寫一個工具箱,做成.h的標頭檔 ---- 實作方式就是-- - 1.宣告 => 將class宣告的部分存成一個.h檔 - 2.實作 => 將實作的部分存成一個同名的.cpp檔 - 3.呼叫 => 再開一個檔案寫你的程式,就可以使用你自製的標頭檔了 - <font color='#ff0000'>注意:</font>2.3.的程式要引入第一個檔名做標頭檔,且此標頭檔要用雙引號括住而不是 `<>` 如:include"division4" ---- #### 範例 檔案名稱:division4.h ```cpp= class division{ public: void set_a(int n);//set=賦值 void set_b(int n); int get_a();//get=取值 int get_b(); double do_div();//相除 private: double a; double b; }; ``` ---- 檔案名稱:division4.cpp ```cpp= include"division4" void division::set_a(int n){ a=double(n); } void division::set_b(int n){ b=double(n); } double division::get_a(){ return a; } double division::get_b(){ return b; } double division::do_div(){ return get_a()/get_b(); } ``` ---- 檔案名稱:main555.cpp ```cpp= include<iostream> incude"division4.h" using namespace std; int main() { int x,y; while(cin>>x>>y){ division ans; ans.set_a(x); ans.set_b(y); cout<<ans.do_div()<<'\n'; } } ``` ---- 最後在terminal同時編譯 division4.cpp main555.cpp 就可以了 ---- ### 建構子(constructor) 剛剛的例子中,每當我們需要將兩個數相除,就得在main裡面用兩個函式來初始化a、b的值,難道我們不能在宣告變數型態時就初始化兩數的值? 可以的,這個方法叫建構函式 ---- 建構函式就是寫個與 struct 或 class 同名的函式,在宣告變數的時候就能順便進行這個函式 ---- 建構函式: ```cpp= division::division(int n1,int n2){ set_a(n1); set_b(n2); } ``` 額外加上函式這個即可 ---- 呼叫: ```cpp= int main() { int x,y; while(cin>>x>>y){ division ans(x,y); cout<<ans.do_div()<<'\n'; } } ``` 少了兩行是不是簡潔許多阿 ---- #### 多載 我們還可以根據輸入資料型態的不同,對應不同的建構函式,這個方法叫多載 --- 建構函式: ```cpp= division::division(int n1,int n2){ set_a(n1); set_b(n2); } division::division(int n1,double n2){ set_a(n1); set_b(n2); } division::division(double n1,int n2){ set_a(n1); set_b(n2); } division::division(double n1,double n2){ set_a(n1); set_b(n2); } ``` ---- 呼叫: ```cpp= int main() { division ans(7,2); division ans2(6.25,3.11); cout<<ans.do_div()<<'\n'; cout<<ans2.do_div()<<'\n'; } } ``` 這樣我們就不用限定輸入的資料型態必須是整數,才能使用這個函式了 ---- 我們能方便的呼叫的函式庫裡的函式,正是因為那些函式有經過「建構」與「多載」的處理😁。 --- ## 指標 ---- ![](https://i.imgur.com/kvFr4cZ.png) 在第三次社課時有出現過這張圖,當時是在講變數儲存的方法,而變數所處存的位置(地址)就稱作為指標,指標可以直接讀取記憶體,利用這個特性除了可以讓程式進行得更快速,還可以達成區塊外讀取或更改變數值。 ---- 想要取得一個變數的指標可以透過取址運算子(`&`)來達成,這裡與位元運算子的AND不同,AND是用來運算兩個數字,而取址運算子是加在變數前的。 ```cpp= #include <iostream> using namespace std; int main(){ int a=7; cout << "a=" << a << "\na的位置:" << &a; } ``` ``` /*----output a=7 a的位置:0x61fe1c ----------*/ ``` 指標是由一串16進位的數字組成,這串數字所代表的就是記憶體的位置代碼,沒有其他意義。 --- ### 宣告指標變數 可以透過指標變數來儲存一個變數的指標,宣告方法如下: ```cpp= 要儲存的變數資料型態 * 指標變數名稱 = &變數; int num = 100; int *ptr = &num; ``` 指標前的變數型態並不代表指標的變數型態,只是用與變數相同的資料型態會更方便識別。 --- ### 取值 如果想要得知這個指標位置所存的資料值就需要用到取值運算子(`*`),用法一樣是加在變數前。 ```cpp= #include <iostream> using namespace std; int main(){ int a=7; int *ptr=&a; cout << *ptr; } //7 ``` 在宣告時的`*`與取值運算子是不一樣的,宣告時的`*`代表我要宣告的是一個指標變數,而取值運算子代表的是取得該記憶體位置的值。 ---- ```cpp= #include <iostream> using namespace std; int main(){ int a=7; int *ptr=&a; cout << "The value of a is " << a << endl; cout << "The address of a is " << ptr << endl; cout << "The value in " << ptr << " is " << *ptr << endl; cout << "The address of ptr is " << &ptr << endl; } ``` ``` /*----------output The value of a is 7 The address of a is 0x61fe1c The value in 0x61fe1c is 7 The address of ptr is 0x61fe10 ----------------*/ ``` --- ### 陣列指標 直接上範例,不然很難講 ```cpp= #include <iostream> using namespace std; int main(){ int arr[5]={10,15,20,25,30}; int *ptr=arr; for(int i=0;i<5;i++){ cout << arr[i] << " " << &arr[i] << " " << ptr+i << endl; } } ``` ``` /*---output---------- 10 0x61fdf0 0x61fdf0 15 0x61fdf4 0x61fdf4 20 0x61fdf8 0x61fdf8 25 0x61fdfc 0x61fdfc 30 0x61fe00 0x61fe00 --------------------*/ ``` 陣列的名稱本身就是一個指標,等於`&arr[0]`,且`&arr[i]`等同於`arr+i`。 --- ### 陣列函式 因為C++無法直接寫出回傳值為陣列的函式,所以只能夠用指標來間接達成。 ```cpp= #include <iostream> using namespace std; for(int i=0;i<n;i++){ arr[i]*=arr[i]; } return arr; } int main(){ int arr1[5] = {3,6,7,9,12}; int *arr2 = square(arr1,5); for(int i=0;i<5;i++){ cout << arr2[i] << " "; } } ``` --- ## 練習題 其實大部分的題目都可以用函式寫,很少有專門的題目練習。
{"title":"C++教學講義-第五次","slideOptions":{"theme":"blood","transition":"slide"}}
    769 views
   owned this note