###### tags: `社團事務` # 工工系的各位加油 # ✔ C++ 從入門到放棄 ![](https://i.imgur.com/5tFFBKp.png) C++是目前世界上最廣泛使用的程式語言之一,也是對程式新手來說一大福音 ,簡單易懂、規格整齊、功能大致齊全,新手也可以在專人整理之[資料庫](http://www.cplusplus.com/)上找許多資源。 ## 🎨 選擇編輯器(Editor) 基本上入門學習使用任何編輯器都可,但是長久來說選擇編輯器也是一門學問 推薦的編譯器有 + Code Blocks : APCS指定編譯器之一 ![](https://i.imgur.com/xSzIRJm.jpg)tommybear大電神推薦! + Dev C++ : 不會更新的編輯器 + VS code : 功能齊全,設置不難,推 + VS : 超肥,功能超級齊全,但基本上不用那麼高級也不會怎樣 + Xcode : mac仔才有的東西,但據說挺垃圾的 + sublime : 電神用爆 + spyder : python專用 最後溫馨提醒,記得用**暗色主題** --- ## 🧨 程式碼基本架構 在寫程式之前,我們需要先告訴電腦需要用到的函式庫 ```cpp #include <函式庫> ``` 例如: ```cpp #include <iostream> #include <string> using namespace std ; //這行先記起來 ``` 之後是重頭戲 --- **主函式** 顧名思義,主函式就是最重要進入的函式,程式執行時也是以此為起點開始執行 之後寫上return 0 為最後終點 ```cpp int main(){ /* 你寫的程式 */ return 0; } ``` 將剛剛所有說的全部合併 + 縮排 ```cpp #include <iostream> using namespace std; int main(){ /* 你寫的程式 */ return 0; } ``` 這樣就是我們程式碼的大體架構了 ## 👓 輸入及輸出 首先我們先學如何與電腦互動,也就是輸入及輸出 如果想要**輸出**應該這麼打: ```cpp cout << 欲輸出變數或字串 ; ``` 如果想要**輸入**應該這麼打: ```cpp cin >> 變數名稱 ; ``` 我們來試試印出"Hello, world!"吧 ```cpp= #include <iostream> using namespace std; int main(){ cout << "Hello, world!" << endl ; return 0; } ``` ### 補充 : printf & scanf + **printf** 用法 ```c= #include <stdio.h> int main(void) { int count = printf("This is a test!\n"); printf("%d\n", count); return 0; } ``` **規則如下**: ![](https://i.imgur.com/i4nIb2Y.jpg) + **scanf** 用法 在接受輸入時,可以接受多個值,也可以指定輸入的格式,例如: ```c= ##include <stdio.h> int main(void) { int number1, number2; printf("請輸入兩個數字,中間使用空白區隔):"); scanf("%d %d", &number1, &number2); printf("你輸入的數字:%d %d\n", number1, number2); printf("請再輸入兩個數字,中間使用-號區隔):"); scanf("%d-%d", &number1, &number2); printf("你輸入的數字:%d-%d\n", number1, number2); return 0; } ``` ## 🎹 認識變數 變數(variable) 簡單來說,就是對一特定值取名字。 變數是箱子,把你想放的東西(**值value**)放進去,可以用變數名稱(箱子的標籤)找到它。 ```cpp <變數型態> <變數名稱> (= 起始值); int a = 10; float b; ``` ### 變數名稱 變數的取名也是一大藝術,為了增加程式碼的可讀性,我們會將變數名稱盡量使我們看得懂像是speed、sum 等等。 但同時避免使用到程式保留字、特殊字元或其他,例如:for、int、!@$%&+-、0oO1lI 等。 ### 變數型別 箱子也是有分各種意義的箱子,特定型別、大小的箱子只能裝特定型態、大小的箱子。 + 對照表 ![](https://i.imgur.com/nFTL7XC.png) 變數型別有: + **整數**:short int long + **浮點數**:float double + **布林值**:bool - 判斷是否,True = 1,False = 0 + **字元、字串**:char、string - 跳脫字元 ![](https://i.imgur.com/PVYO32w.png) ### 型別轉換 + 觀念釐清 - float 轉 int ``` cpp int = float + float ; ``` - 我們來細看過程 1. ``` cpp int = 0.1111 + 0.2222; ``` 2. ``` cpp 0.3333 = 0.1111 + 0.2222; ``` 3. 因為是 int 所以只會取**整數部位** 0.~~3333~~ --> **0** 4. ``` cpp 0 = 0.1111 + 0.2222; ``` + 以上範例帶出一個問題 --- **型別轉換** - 當對一個數作運算時,變數之型態會隨需求轉換 - 進行強制轉換格式(**短暫**): ```cpp float a = 3.14; (int)a //(預轉換型別)變數 ``` - 變換實驗 + **浮點數**轉**整數** ``` cpp= float a = 3.14; cout << (int)a << '\n'; ``` + **整數**轉**布林值** ``` cpp= int a = 2; cout << (bool)a << '\n'; // 數字只要不是 0 bool值都會是 1 ``` + **字元**轉**整數** ``` cpp= char a = 'a' ; cout << (int)a << '\n'; ``` ![](https://i.imgur.com/0ngjeg6.gif) ### 變數使用範圍 + **全域變數( global )** 宣告在函式外的變數,可在所有接下來程式讀取的地方使用 ```cpp 函式 A const float pi = 3.14 ; 函式 B 函式 C 主函式 ``` ps: **[const](https://blog.xuite.net/coolflame/code/16605512-C%2B%2B%E4%B8%AD%E7%9A%84const%E7%94%A8%E6%B3%95%E8%A9%B3%E8%A7%A3)** 的補充資料 + **區域變數( local )** 宣告在 **{** 夾著之中的變數 **}**,在 **{** 這裡面都可以用 **}** 外面不行 ```cpp for(int i = 0 ; i < 10 ; i++) { cout << i << endl; } cout << i << endl; //會報錯(無此變數) ``` + **區塊變數( block )** 將**static**放置在變數宣告前,則不管此函式結束與否,記憶體都會保存此一變數 ```cpp static int a = 10 ; // 不管之後程式運行static之變數都會存在記憶體中 ``` ## ✨ 運算子 聽起來很專業的名字,但其實你們都學過,下面我們來看看有那些ㄅ ### 算術運算子 簡單來說,加減乘除餘而已啦! ```CPP 7 + 4 = 11 // 加號 + 7 - 4 = 3 // 減號 - 7 * 4 = 28 // 乘號 * 7 / 4 = 1 // 除號 / 7 % 4 = 3 // 餘號 % ``` ### 指派運算子 好酷的名字,但是新手最大的噩夢,認真看好囉! ```CPP int a = 0 ; // 指派(assign),將右值指派給左邊的值 a += 10 ; // 等於 a = a + 10 a -= 10 ; // 等於 a = a - 10 a *= 10 ; // 等於 a = a * 10 a /= 10 ; // 等於 a = a / 10 ``` ### 關係運算子 以確認關係(比較)為用途的運算子~ ```cpp int a = 10 ; a == 10 ; // 真正數學意義上的等於(equal),常與 assign 搞混,多加留意 a > 5 ; // 大於 a < 15 ; // 小於 a >= 9 ; // 大於等於 a <= 11 ; // 小於等於 a != 4 ; // 不等於 ``` ### 邏輯運算子 條件式的好幫手,增加判斷邏輯是否正確的條件。 以下介紹: + &&:**布林 AND 運算**,當左右 2 個條件皆為 **True** 時,才會回傳 1 + ||:**布林 OR 運算**,當左右 2 個條件其中 1 個為 **True**,就會回傳 1 + ! :**布林 NOT 運算**,後面的變數 **True** 轉 **False**、**False** 轉 **True** ps 還有一大堆喔! ```cpp int num = 75; cout << (num > 70 && num < 80) << endl ; // 輸出 1 ( True ) cout << (num > 80 || num < 75) << endl ; // 輸出 0 ( False) cout << !(num > 80 || num < 75) << endl ; // 輸出 1 ( True ) ``` ### 位元運算子 挖,抽象的概念來了,首先我們要知道電腦是以 1 與 0 所構成,將每個數字、字元等等轉變成所謂的 **2 進位**( binary )。 例如: 10 => 00001010 18 => 00010010 好了大家繫上安全帶,準備飆車囉! + **左移運算子**:將 01數字串 全部往左 n 格,多的被擠掉,空的補 0。 ```cpp int a = 10 ; // 00001010 a << 2 // 00101000 ``` + **右移運算子**:將 01數字串 全部往右 n 格,多的被擠掉,空的補 0。 ```cpp int a = 10 ; // 00001010 a >> 1 // 00000101 ``` + **AND 運算(&)**:若兩數皆為 1 則輸出是 1,其餘狀況輸出皆為 0 ```cpp char foo = 147 ; // 10010011 char bar = 2 ; // 00000010 (int)foo & bar // 00000010 ``` + **OR 運算(|)**:若兩數只要有 1 則輸出 1,其餘狀況輸出皆為 0 ```cpp char foo = 147 ; // 10010011 char bar = 2 ; // 00000010 (int)foo | bar // 10010011 ``` + **XOR 運算(^)**:若兩數不同( 1、0 ) 則輸出是 1,其餘狀況輸出皆為 0 ```cpp char foo = 147 ; // 10010011 char bar = 2 ; // 00000010 (int)foo ^ bar // 10010001 ``` **值得注意**:其實就算範例都取 8 位元的 01數字串 ,實際計算得依照型態作改變。 假如我使用 int 儲存該變數,應該有 4 位元組,也就是 32 位元下去計算喔! ### 優先順序 正如同數學上的 先乘除後加減,有括號先算 ,運算子之中也是有分順序級數ㄉ,在程式運行的時候需多加留意。 ![](https://i.imgur.com/UXsxSMP.png) ## 🎊 條件選擇 ### 概念 當程式碼運行到某些地方時,如果需要加些條件選擇性執行程式碼,就會需要用到這種好用的東西。 如果我們將其細分,此語法結構可分為 2 個部分:**條件** 以及 **敘述句( 該做甚麼事 )** 而 **條件** 裡我們可以運用 **條件運算子** 和 **邏輯運算子** 以達成我們的目的 ### 真值表 判斷 **True**、**False** 的規則,可參考邏輯運算子的定義 ![](https://i.imgur.com/Q22i3zY.png) ### **if-else 條件句** + 圖示 ![](https://i.imgur.com/Lfn6ktp.png) + 結構 ```cpp if(條件句) { 該做甚麼事; } /* else if(第二條件句) { 該做甚麼事_2 } . . . */ else { 該做甚麼事; } ``` **值得注意**:如果敘述句只有 1 行,可以將其前後大括號省略 + 巢狀 也就是將 if 條件句包在另一 if 條件句之內 ```cpp if(條件句_1) { 做甚麼事; if(條件句_2) { 做甚麼事; } 做甚麼事; } ``` ### **switch-case 條件句** + 結構 ```cpp switch(變數) { case value_1: 做甚麼事; break; // 跳脫switch case value_2: 做甚麼事; break; // 跳脫switch default: // 類似於 else,但此敘述裡不必加break,會自動跳脫 做甚麼事; } ``` :::danger **應該注意**:如果沒有在 case 裡加上 **break** 會繼續執行下一個 case ::: ## 🎡 迴圈控制 ### 概念 **迴圈**( loop )就是將同一件事重複執行 n 次, 而我們可以將迴圈細分成: 1. **起始值** 1. **間距** 1. **執行條件** ( **結束條件** ) 1. **內容** ### **for 迴圈** **for 迴圈** 為較易理解之迴圈,結構完整但語法需記住,是初學者常忘記的一個難關。 不過,其實 **for 迴圈**有許多變形,此型為最基礎,詳細內容之後會介紹~ + 結構 ```cpp for( 起始值 ; 執行條件 ; 間距 ) { 內容; } /* 執行順序: for( 1 ; 2 ; 4 ) { 3 } */ ``` + 範例: ```cpp= for( int i = 0 ; i < 10 ; i++ ) { cout << "Hello, world!\n" ; } ``` ### **while 迴圈** **while 迴圈** 較為注重結束條件,寫不出 for 迴圈 時,可以試著用 while 迴圈 解 + 結構 ```cpp 起始值宣告 while( 針對起始值變化執行條件 ) { 內 容 ; 起 始 值 + 間 距 ; } ``` + 範例: ```cpp= int i = 0; while(i < 10) { cout << "Hello, world!\n" ; i++; } ``` ### **do-while 迴圈** + 結構 **do-while迴圈** 會先執行迴圈本體,然後再判斷條件是否成立,決定是否繼續執行。 ```cpp do{ 內容 }while( 條件 ); ``` + 範例 ```cpp= int ans = 6 ; do{ cout << " Enter a number(1~10) : " ; cin >> input ; } while( input != ans ); ``` + 與 **while 迴圈** 的差異 ![](https://i.imgur.com/rJfpw1C.jpg) ### 相關功能 + **break** **break** 可以**離開**目前 switch、for、while、do-while 等區塊,並前至區塊後下一個陳述句。 ```cpp= for( int i = 0 ; i < 10 ; i++ ) { if(i == 8) // i 值為 8 時脫離迴圈執行下一陳述句 break; cout << "Hello, world!\n" ; } cout << "Break the loop!\n" ; // 脫離迴圈完執行 ``` + **continue** **continue** 會結束接下來區塊中的陳述句,並跳回迴圈開頭**繼續**下一個迴圈。 ```cpp= for( int i = 0 ; i < 10 ; i++ ) { if(i == 8) // i 值為 8 時直接跳到 i++ 的部分,執行下一輪迴圈。 continue; cout << "Hello, world!\n" ; } ``` + **goto** **goto** 可以在程式中任意跳躍,跳躍前必須先設定好目的地,跳躍時必須指定目的地,但此方便的功能可能導致程式難以理解、或破壞程式架構,需較為謹慎小心。 ```cpp= begin: cout << "Jump to here!\n" ; for( int i = 0 ; i < 10 ; i++ ) { if(i == 8) goto begin; cout << "Hello, world!\n" ; // i 值為 8 時仍會執行 } ``` ## 🚌 陣列 ### 概念 **陣列**( array )可以將 **同一型別** 且 **一連串有關聯** 的變數儲存的一系列記憶體。 在 C++ 中,稱之為 **C-style Array**,這種陣列較為死板,使用前需要先指定使用空間,若需要變動,則需要使用 malloc、free 等等函式,所以在此先不多作介紹,之後會教 C++ 中更人性化的函式。 最後要講一個超級重點,所有關於array的**索引值**( index )都是**從 0 開始**: | Index | 0 | 1 | 2 | 3 | 4 | | -------- | -------- | -------- | -------- | -------- | -------- | -------- | | Value | 10 | 20 | 30 | 40 | 50 | ### 一維陣列 如前言所述, 陣列是同一種資料型態的變數在連續的記憶體上併排在一起的東西。而一維陣列就是只有一個維度的陣列,宣告一維陣列的方法如下: + 宣告 ```cpp <資料型態> <陣列名稱>[元素數量]; int number[40]; //宣告含有40個元素的int陣列,名為number double weight[40]; //宣告含有40個元素的double陣列,名為weight char saying[40]; //宣告含有40個元素的char陣列,名為saying ``` + 初始化 - 統一初始化 宣告完陣列後,陣列所儲存的數都還是未知的(各種詭異的數字),因此我們可以先將他們都初始化為0。 ```cpp int arr[5]={0}; //將arr中所有元素都初始化為0 ``` :::info 初始化 int 陣列時,大括號中只能填入 0,不能填入其他數字。 ::: - 設定初始化 而我們可以在一開始宣告陣列時就順便設定各元素的值: ```cpp int arr[5]={1,5,6,9,8}; //或是省略元素數量: int arr[]={1,5,6,9,8}; ``` - 不完全初始化 如果填入的數量比元素數量還少,其他都會被設為0: ```cpp int arr[5]={1}; //{1,0,0,0,0} ``` + 使用 當要存取一維陣列的值時,可以使用運算子[]和索引來存取特定的元素。 ```cpp= int arr[5]; for(int i=0;i<5;i++) arr[i] = i*5; for(int i=0;i<5;i++) cout << arr[i] << endl; /* 輸出: 0 5 10 15 20 */ ``` - **sizeof()** 可以注意到我們上例中,i < **5** 是因為我們知道陣列長度為 5,不過這其實很容易出錯,假如不小心弄成大於 5 的數或之後針對陣列空間進行更改,就會出現問題。 所以我們可以使用 **sizeof()** 這個函式,此函式功能為取得陣列占記憶體空間之大小(bytes): ```cpp= int arr[5]; for(int i=0 ; i<sizeof(arr)/sizeof(arr[0]) ; i++) arr[i]=i*5; for(int i=0 ; i<sizeof(arr)/sizeof(arr[0]) ; i++) printf("%d ",arr[i]); /* 可以 sizeof(陣列)/sizeof(陣列[0]) 也就是所有的bytes除以單獨一格的bytes 這樣就會取得陣列長度ㄌ */ ``` 這樣就能更安心的寫程式了!不用怕超出陣列範圍。 :::danger **應該注意**:如果存取超過陣列長度的記憶體,會發生不可預期的結果。 ::: - **begin()**、**end()** **begin** 會回傳陣列首個元素的位址,**end** 則回傳最後一個元素**下個**位置的位址 ```cpp int num[] = { 3,7,2,5,6,4,9 }; for (auto it = begin(num); it != end(num); ++it) cout << *it <<' '; // 需要配合指標運用 ``` - **迴圈變形** 關於陣列的迴圈可謂多變,這邊做個總整理 ```cpp int arr[]={7,1,0,2,8,5}; for(int i=0 ; i < sizeof(arr)/sizeof(arr[0]) ; i++) cout << arr[i]<<' '; cout << endl; for (auto it = begin(arr); it != end(arr); ++it) cout << *it <<' '; cout << endl; for(auto j : arr) // 這個用法要特別注意喔,之後會一直提到 cout << j <<' '; cout << endl; ``` ### 二維、多維陣列 上面的 一維陣列若將其視為一條線( **X 軸** ), 二維陣列則可視為一個面( **X、Y軸** )、 三維則是一個體( **X、Y、Z軸** ), 而更高維的就因超出我們的理解範圍,所以我們也較少用。 不過,在 C++ 中其實沒有所謂的多維陣列,所謂的陣列不過是**陣列中的陣列**罷了。 + **二維陣列** 二維陣列又稱矩陣,此陣列主要用於有 2 組數字與你所需要存取的對象有關聯, 如:幾班幾號的**數學成績**、幾月幾日的**體重**等等。 下方以幾班幾號的數學成績作舉例: - **宣告** ```cpp <資料型別> <陣列名稱>[x][y]; ``` 若我們共有 5 個班,每班 40 人,宣告: ```cpp int score[5][40]; ``` - **初始化** 也可以在一開始就設值: ```cpp int score[5][40]={{58,64,66,(省略),91,77}, {47,85,32,(省略),85,66}, {75,33,43,(省略),87,66}, {78,45,87,(省略),24,54}, {89,74,69,(省略),54,24}}; //不註明5也是可以的(但40不能被省略): int score[ ][40]={{58,64,66,(省略),91,77}, {47,85,32,(省略),85,66}, {75,33,43,(省略),87,66}, {78,45,87,(省略),24,54}, {89,74,69,(省略),54,24}}; ``` 這時,第1班1號的分數就是 score[0][0]、第1班2號的分數是 score[0][1]。 同理,第5班40號的分數就是 score[4][39]。 + **多維陣列** C++ 也可以宣告三維陣列與三維以上的陣列。例如: ```cpp int arr3[10][20][30]; //三維陣列 int arr4[10][20][30][40]; //四維陣列 ``` 但超過四維就比較難以想像,因此一般較少使用。 ## 🧿 指標 ### 概念 **指標**( pointer ),簡單來說,就是變數(不管是整數、浮點數甚至是指標)在宣告時,記憶體會自動配置一個位址給它儲存,而使用指標就是將變數的位址存起來。 說明一下地址的規則,在每個程式中都是隨機分配為只給變數的,因此跟變數名稱並沒有任何關聯,而那一長串看起來像亂碼的字串是以 **16 進位** 編成,代表該變數在記憶體的位置。 ### 宣告 ```cpp <資料型態> *<指標名稱>; //例如: int *ptr; //或 int* ptr; ``` 指標儲存的是記憶體位址,因此我們可以使用 **取址運算子(&)** 來給予記憶體位址,例如: ```cpp int a = 10; int *ptr = &a; //取得 變數a 的地址,比如0x61ff18 ``` :::warning **應該注意**:指標的形態要與變數的型態相同 ::: ### 使用 如果要取得指標所指向的變數的內容,可以使用 **定址內容運算子(*)**,例如: ```cpp int a = 10 ; int *ptr = &a ; cout << *ptr << endl ; //印出 a 值 ``` 也可以透過指標更改變數的值: ```cpp int a = 10; int *ptr = &a; *ptr = 100; ``` 這裡的「 * 」,和宣告指標變數的 int* pointer 的意義不太一樣。 反而是和「 & 」相對應——「 & 」代表**取出地址**、「 * 」代表**取出內容**。 :::danger **應該注意**:透過指標更改變數值,首要條件是指標已指向存在的變數,否則如果指標指向奇怪的位址然後更改它的內容,是非常危險的。 ::: ### 與陣列的關係 指標跟陣列是密不可分的,也是 APCS 中必考的項目之一,開始飆車! 我們先以**二維陣列**作舉例: ```cpp int [2][3] = {{ 9 , 10 , 11 }, { 56 , 54 , 55 }}; ``` 我們會將二維陣列依照我們的想像認為在記憶體中也是這樣的相對位置,其實不然。 真正的樣子,其實長這樣: ```cpp int a[2][3] = {{9 , 10 , 11 },{ 56 , 54 , 55 }}; ``` | a[0][0] | a[0][1] | a[0][2] | a[1][0] | a[1][1] | a[1][2] | | -------- | -------- | -------- | -------- | -------- | -------- | | 9 | 10 | 11 | 56 | 54 | 55 | |00AFF8**38** | 00AFF8**3C** | 00AFF8**40** | 00AFF8**44** | 00AFF8**48** | 00AFF8**4C** | 這裡我們可以發現陣列中每一個地址呈等差遞增,而這中間的公差就是當初宣告變數型態所佔的byte數,例如此處陣列型態為 int ,因此公差為 4 。 其實,陣列有著**指標**的概念,而陣列指向第一個元素的位置: ```cpp= #include <iostream> using namespace std; int main(){ int arr[3]; cout<<"arr="<<arr<<endl; cout<<"&arr[0]="<<&arr[0]<<endl; cout<<"&arr[1]="<<&arr[1]<<endl; cout<<"&arr[2]="<<&arr[2]<<endl; } /* 輸出: arr=0x61ff14 &arr[0]=0x61ff14 &arr[1]=0x61ff18 &arr[2]=0x61ff1c */ ``` 我們可以發現 arr 跟 &arr[0] 指向相同的位址。 事實上,**[]**這個運算子在程式中其實代表位移值: > arr+0 代表 &arr[0] > arr+1 代表 &arr[1] > arr+2 代表 &arr[2] > *(arr+0) 代表 arr[0] > *(arr+1) 代表 arr[1] > *(arr+2) 代表 arr[2] :::info **應該注意**:arr+1 並不是將 arr 記憶體位置的值加 1,而是加上 4 個位元組,因為 arr 指向 int(int 占 4 個位元組)。 ::: 不過由於陣列不是普通的指標,所以我們不能直接更改其指向: ```cpp int arr[3]; //錯誤 arr++; ``` 總之該注意:**陣列與指標是截然不同的東西**,編譯器也會將兩者看待為不同的東西。 詳見:https://stackoverflow.com/questions/3959705/arrays-are-pointers ## 🛹 函式 ### 概念 簡單來說,**函式**就是外包給其他地方加工啦,而且可以順便達到簡化跟美化程式碼的功能。 函式中最注重的就是在於參數之中的傳遞,而且盡量將函示放置在主函式前,這樣一來,不僅不會破壞程式的結構,還能增加程式的可讀性。 ### 架構 ```cpp <傳回值型態> <函式名稱>(參數 A ,參數 B ){ 內容; } ``` ### 函示傳遞 傳遞是函式中最重要的環節。 將一個值傳遞給函式做,稱為**參數**; 函式執行完傳遞回來的值,稱為**回傳值**。 假如需要回傳時,使用 **return** 幫助傳回。 我們以2數相加為舉例: ```cpp= #include <iostream> using namespace std; int add(int a,int b){ int c = a+b; return c ; // c 為回傳值, } int main(){ int m = 2 , n = 4; int result = add(m,n); cout << result << endl; } ``` 假如不需要回傳,此時函式宣告時,將<傳回值型態>改成 **void**,void 就是一個沒有回傳值( 只進不出 )的型態。 我們以變數交換為舉例: ```cpp= #include <iostream> using namespace std; void swap(int a,int b){ int temp = a; a = b; b = temp; } int main(){ int foo,bar; cin >> foo >> bar ; swap(foo,bar); cout << foo ; cout << bar << endl; } ``` ### 函示多載 幫自己找麻煩的環節,這邊強烈不建議製造這種狀況。 **函式多載**,就是具有相同函式變數名稱,但參數型態不同,C++會自動配對: ```cpp= #include <iostream> using namespace std; void print(int m,int n){ cout << m << n << '\n'; } void print(int n){ cout << n << '\n'; } void print(float n){ cout << n << '\n'; } int main(){ int a = 10,b = 20; float c = 30; print(a); // 配到第二個 print(a,b); // 配到第一個 print(c); // 配到第三個 } /* 輸出: int: 10 int , int: 10,20 double: 30.000000 */ ``` ### 遞迴 **遞迴**就是自己呼叫自己,與數學上的遞迴關係式很像。 我們拿費氏數列作舉例: 數學上遞迴關係式: ![](https://i.imgur.com/jdq1nJl.png) 程式上: ```cpp int F(int n){ if(n==0) return 1; else if(n == 1) return 1; else return F(n-1) + F(n-2) ; } ``` 若我們以計算 n=5 時的費波那契數列為例,他在進行函式執行時的呼叫順序如下: ![](https://i.imgur.com/lu8tYSZ.jpg) 將其拆項,看他一步一步的步驟就會這樣: >1. 主函式傳入 n=5 >2. 進入 else 中,執行 F(4) + F(3),所以呼叫 n=4 與 n=3 >3. 循環 > 4. 一直到某次執行 F(1) + F(0),所以呼叫 n=1 與 n=0 > 5. 分別進入 if 與 else if 中,回傳 1 與 1 > 6. 因此 F(1) + F(0) = 1 + 1 > 7. 循環 > 8. 得到F(4) + F(3) = 5 > 9. F(5) = 5 而我們會發現這個效率不佳,重複呼叫太多次,導致時間複雜度超級高, 所以這時候我們就可以進入到演算法的世界拉 ~ ### 補充:DP(動態規劃)版本的費氏數列 :::warning ### 動態規劃的核心:同樣的問題不要再算 ::: ```cpp #include <iostream> using namespace std; long long F[100000]; long long int f(int n) { if(n==1||n==2) return 1; if(F[n]!=-1) return F[n]; //若F陣列中值經過改變,則不用再算一次 F[n]=f(n-1)+f(n-2); return F[n]; } int main() { for(int i=0;i<100000;i++) F[i]=-1; //初始化陣列 int N; cin >> N; cout << f(N); } ``` ## 🧤 函式庫 函式庫是進入程式領域中最重要的一環,在這邊會根據你的需求,在[**這邊**](http://www.cplusplus.com/reference/)找對應需求之函式,這裡會是學程式最孤單的路,要在一大堆英文中找到你所需要的功能,祝各位 --- **武運昌隆**(? 總之我會在這邊先交基礎函式,剩下的加油 ~ ### 格式 前面要先 **include <函式庫名稱>**,這樣電腦才會知道你要用哪些函式。 然後就能在程式使用該函式庫的函示了o(* ̄▽ ̄*)o ```cpp #include <函式庫名稱_1> #include <函式庫名稱_2> using namespace std; int main(){ 函式_1(參數); 函式_2(參數_1,參數_2); } ``` 在[**這邊**](http://www.cplusplus.com/reference/)找到所需函式,要特別留意 **回傳值和參數的資料型別** ### string.h 函式庫 這個函式庫就其實比較偏向 C 了,不太會用ㄉ東西 o(* ̄▽ ̄*)ブ + **strcpy** ```cpp // char * strcpy ( char * destination, const char * source ); char str1[]="Sample string"; char str2[40]; char str3[40]; strcpy (str2,str1); strcpy (str3,"copy successful"); puts(str1); // 印出:Sample string puts(str2); // 印出:Sample string puts(str3); // 印出:copy successful ``` + **strcat** 將 2 個字串拼接在一起 ```cpp // char * strcat ( char * destination, const char * source ); char str[80]; strcpy(str,"these "); strcat(str,"strings "); // 將第 2 個參數貼在地 1 個參數後 strcat(str,"are "); strcat(str,"concatenated."); puts(str) // 這邊偷渡一下,puts()也是印出的功能 // 印出:these strings are concatenated. ``` + **strcmp** 比較 2 個字串是否相同 ```cpp // int strcmp ( const char * str1, const char * str2 ); char str1[] = "hello"; char str2[] = "hello"; char str3[] = "hellw"; puts(strcmp(str1,str2)); // 輸出: 0,表示 2 字串相同 puts(strcmp(str2,str3)); // 輸出: 8,表示 2 字串不同, // 且第一個不同的字元在ASCII表中相差= 8 (W(119)-O(111)) ``` --- ### math.h函式庫 這個就比較重要點了,可以做到大家最愛的許多數學功能(づ ̄ 3 ̄)づ + 次方系列 ```cpp pow(32.01,1.54); // 32.01 ^ 1.54 = 208.036691 sqrt(1024); // sqrt(1024.000000) = 32.000000 cbrt(27); // cbrt (27.000000) = 3.000000 ``` + 三角函數 ```cpp // 這邊要特別注意,三角函數的運算是 rad 不是 degree 喔 #define PI 3.14159265 #include <iostream> #include <math.h> using namespace std ; double rad(double degree){ // degree 轉 rad double rad_1 = degree * PI / 180.0; return rad_1 ; } double degree(double rad){ // rad 轉 degree double deg = rad / PI * 180.0; return deg ; } int main(){ sin(rad(30)); // sin 30°= 0.500000 cos(rad(60)); // sin 60°= 0.500000 tan(rad(45)); // tan 45°= 1.000000 // 反函數 degree(asin(0.5)); // asin 0.5 = 30 degree(acos(0.5)); // acos 0.5 = 60 degree(atan(1)); // atan 1 = 45 return 0; } ``` + 其他常用功能 ```cpp abs(-10.6) = 10.6 // 絕對值 log10(100) = 2.000000 // 以 10 為底之對數 log(5.5) = 1.704748 // 以 e 為底之對數 ``` --- ### string 函式庫 還記得 string 嗎 ? 如果忘記的話幫你複習一下吧 ! ```cpp string s; getline(cin, s); // 讀取一整行 s.empty(); // 如果 s 是空的回傳true,否則回傳false s.size(); // 回傳 s 的字元數 s.length; // 等同於 s.size() s[n]; // s 中在位置 n 的字元,從 0 開始數 s1 + s2; // 串接兩個字串 ``` 再來是新的東西! ```cpp assign(begin, n) ; // 從 str 的第 begin 個字元取出 n 個字元來指定給另一字串物件。 append(str, begin, num) ; // 從 str 的第 begin 個字元取出 n 個字元來附加至另一字串物件之後。 find(str, 0); // 從引發 find() 的字串物件中第 0 個字元尋找是否有符合 str 的子字串。 insert(begin, str); // 將 str 插入引發 insert() 的字串物件第 begin 個字元之後。 ``` + **clear** , **erase** 和 **pop_back** ```cpp str.clear(); // 清空字串 str.erase(10, 8); // 通則:字串.erase( 起始位置, 結束位置) str.erase(str.begin()+9); // 起始位置後的 n 的元素 str.erase(str.begin()+5, str.end()-9); // end : 從尾巴算 str.pop_back() // 刪除最後一個元素 ``` + 補充:**cctype函式** ```cpp= #include<iostream> #include<cctype> isalnum(c) // 如果 c 是數字或字母,回傳 ture isalpha(c) // 如果 c 是字母,回傳 ture isdigit(c) // 如果 c 是數字,回傳 ture islower(c) // 如果 c 是小寫,回傳 ture isupper(c) // 如果 c 是大寫,回傳 ture tolower(c) // 如果 c 是一個大寫字母,回傳其小寫 toupper(c) // 如果 c 是一個小寫字母,回傳其大寫 ``` :::warning **儘量記住英文單字的意思,就能如意的使用這些函式啦 ~~** ::: --- ### sstream 函式庫 在這邊介紹這個強大的功能,輸入輸出流是個初學者比較不會碰的東西,但真的很重要也挺方便,所以帶入這個函式庫當一個引薦。 總之,因為是從根本改變輸入及輸出的功能,所以功能不同,語法也不同,以下就來做介紹(* ̄3 ̄)╭ + **stringstream** 這個功能能讓 **string 的資料型別轉換成 int**,反之亦可,當作是個中間的橋樑就可以囉。 **>>** 為放出功能,**<<** 為放入功能,中間的管道就用經過 **stringstream** 宣告過的橋梁連接 ```cpp= #include <iostream> #include <sstream> using namespace std; int main() { stringstream s1; int number =1234; string output; // 要把 number 轉成字串型態的容器 cout<<"number="<<number<<endl; // 顯示number=1234; s1<<number; // 將以int宣告的 number 放入 stringstream 中 s1>>output; cout<<"output="<<output<<endl; // 顯示output=1234; return 0; } ``` - 初始化 重複使用一個 **stringstream** 非常浪費 CPU 的時間,在解題目以及應用的時候不太建議重複宣告,而是使用完之後就初始化再次利用。 ```cpp stringstream s1; s1.str(""); // 將 s1 內預設為空,也可以像 s1.str("example") 這樣宣告喔 s1.clear(); // 上面 2 行缺一不可喔,有興趣可以自己去查看看 ``` ps:**stringstream** 裡若有空格,利用 **>>** 會自動將空格分開,且可以搭配 **while()** 繼續讀取,~~但原理我也不知道~~。 ```cpp stringstream s.str("1 51 4 8"); int sum = 0,tmp; while(s >> tmp) { sum += tmp; } ``` 是不是超厲害的||ヽ(* ̄▽ ̄*)ノミ|Ю,總之函式庫的介紹就到這邊了,大家要自己鑽研喔!