# <div id=animation_title>**C++ Beginner**<div> ###### tags: `C++` ### 參考資料 - [Mes's C++ notebook](https://hackmd.io/@Mes/mes_note/https%3A%2F%2Fhackmd.io%2F%40Mes%2FCPP_Note) - [從零開始的演算法競賽入門教學](https://emanlaicepsa.github.io/2020/10/19/overall/) - [語言技術:C++ Gossip](https://openhome.cc/Gossip/CppGossip/index.html) ## 變數、邏輯符號、基本語法 ### 變數型態 | 型態 | 代表意義 | |--------|----------------------| | int | 整數 | | double | 浮點數 | | char | 字元 ex : "a"、"@" | | string | 字串 | | bool | 布林值 ex : true/false | | long | 長整數 | #### int, long, long long的範圍 | 類型 | 最小值 |最大值 | |---------|----------------|------| | unsigned int | 0 | 4294967295 (2^32 - 1)| |int | -2147483648 | 2147483647 (2^31 - 1)| |unsigned long | 0 | 4294967295 (2^32 - 1)| |long | -2147483648 | 2147483647 (2^31 - 1)| |Unsigned long long | 0 | 18446744073709551615 (2^64 - 1)| |long long |- 9223372036854775808 | 9223372036854775807 (2^63 - 1)| |unsigned __int64 | 0 | 18446744073709551615 (2^64 - 1)| |__int64 | -9223372036854775808 | 9223372036854775807 (2^63 - 1)| ## 字元運算 - https://emanlaicepsa.github.io/2020/10/29/0-5/#%E5%AD%97%E5%85%83%E9%81%8B%E7%AE%97 - [ASCII Table](http://kevin.hwai.edu.tw/~kevin/material/JAVA/Sample2016/ASCII.htm) ***字元在運算的時候,將會被視為其ASCII碼!*** ```cpp= 'a'+10 = 107; // 因為 ‘a’ 的 ASCII 碼是 97,在運算時便將其視為 97 ``` ### cout ```cpp= std::cout << "要輸出的內容" ; ``` ### cin ```cpp= std::cin >> "要輸入的內容" ; ``` ### scanf https://vimsky.com/zh-tw/examples/usage/cpp-programming_library-function_cstdio_scanf.html ### printf ### if - else if - else 條件式 ```cpp= if ( 條件 ) { 如果條件成立時做什麼...; } else if ( 條件 ) { 否則如果條件成立時做什麼...; } else { 否則做什麼...; } ``` - 當 { } 內的東西只有一列的時候,可以省略大括號, 看起來會較為美觀,但是初學者建議還是都加上大括號,避免混淆。 ```cpp= if ( 條件 ) 如果條件成立時做什麼...; else if ( 條件 ) 否則如果條件成立時做什麼...; else 否則做什麼...; ``` - if,else能夠以三元運算子表示(a ? b : c) ```cpp= if(x == true) val =y; else val =z; //能夠被寫成 val = x ? y :z; ``` ### 基礎邏輯 Logic <li> > 大於</li> <li> < 小於 </li> <li> == 等於 (注意,不是=)</li> <li> != 不等於</li> <li> >= 大於等於</li> <li> <= 小於等於</li> <li> && 且</li> <li> || 或</li> <li> ! 否</li> ### switch ```cpp= int number = 2; switch(number) { case 1: //要做的事 std::cout << " " ; break; case 2: //要做的事 std::cout << " " ; break; } ``` ## 迴圈 Loop ### while ```cpp= while ( 條件式 ) { 只要當條件成立時,就重覆做的事…; } ``` ### for ```cpp= for ( [A]一開始先做什麼事 ; [B]條件式 ; [D]等C每作完一次,就做什麼事 ) { [C]當B條件成立時,就重覆做的事…; } ``` - 輸出陣列常用單行的for迴圈來印出 ```cpp= for(int i=0; i < sizeof(array)/sizeof(*arr); i++){ std::cout << arr[i] << endl; } ``` - 也可以利用for range的寫法(C++ 11才可以用) ```cpp= for(int i : arr ) { std::cout << i << endl; } ``` ## 陣列 Array ### 一維陣列 #### 宣告 C++內可以宣告一個以索引(index)作為識別的資料結構,稱作陣列,能夠用來儲存資料,宣告陣列的方式為 `資料型態 名稱[長度];` 長度必須是個編譯時期的常數,例如: ```cpp= int number[10]; // 宣告 10 個元素的整數陣列 double score[10]; // 宣告 10 個元素的浮點數陣列 char ascii[10]; // 宣告 10 個元素的字元陣列 ``` 宣告陣列後,陣列內的元素並不會自動初始化,宣告僅僅是分配了一段記憶體空間給他,並以陣列的名字標記他在哪,此時陣列內元素的值是原本記憶體的殘值。例如: ```cpp= int a[5]; for (int i = 0; i < 5; i++) cout << a[i] << " "; 輸出: 1 38055040 591560705 35368912 1 ``` 因此我們需要手動初始化陣列的值,若想在宣告時一次初始陣列全部的元素值,可以如下: ```cpp= int number[10] = {0}; double score[10] = {0.0}; char ascii[10] = {'\0'}; bool flag[10] = {false}; ``` 上面的幾個宣告,整數陣列中的元素都會被初始為 0,浮點數陣列則會被初始為 0.0,字元陣列會被初始為空字元('\0'),而 bool 陣列會被初始為 false。 當然,我們也可以指定不同的初始值給陣列,如: ```cpp= int number[5] = {0, 1, 2, 3, 4}; double score[5] = {87.0, 78.0, 99.5, 69.5, 82.5}; char ascii[5] = {'A', 'B', 'C', 'D', 'E'}; bool flag[5] = {false, true, false, true, false}; ``` #### 存取元素 要存取陣列中的元素值時,可以使用下標(Subscript)運算子 [] 加上索引」,索引值由 0 開始,下面這個簡單的程式是個示範: ```cpp= #include <iostream> using namespace std; int main() { constexpr int LEN = 10; int number[LEN] = {0}; for(int i = 0; i < LEN; i++) { cout << number[i] << " "; } cout << endl; for(int i = 0; i < LEN; i++) { number[i] = i; } for(int i = 0; i < LEN; i++) { cout << number[i] << " "; } cout << endl; return 0; } 輸出: 0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 ``` 陣列在使用時,一定要先得知陣列長度,不可以存取超過陣列長度的記憶體,這會發生無法預期的結果 (因為超出了有定義值的記憶體位址),陣列本身並不知道自己的長度資訊,在上面的範例中,使用了 LEN 來記錄長度,不過,有沒有辦法計算出長度呢?可以使用底下的方式: ```cpp= #include <iostream> using namespace std; int main() { int number[5] = {0, 1, 2, 3, 4}; int length = sizeof(number) / sizeof(number[0]); for(int i = 0; i < length; i++) { cout << number[i] << " "; } cout << endl; return 0; } ``` 陣列索引值由 0 開始不是沒有原因的,陣列名稱儲存了陣列記憶體的首個位置的位址,而索引值表示陣列元素是相對於陣列首個記憶體位址的位移量(offset),位移的量與資料型態長度有關,如果是 int 整數,每次位移時是一個 int 整數的長度,例如在上例中 number[0] 索引值為 0 時,表示位移量為 0,自然就是指第一個元素,而 number[9] 就是指相對於首個元素的位移量為 9。 若在宣告陣列時指定各個索引處的的值,可以不用宣告陣列元素大小,例如: ```cpp= int number[] = {1, 2, 3}; double weight[] = {0.4, 3.2, 1.0, 4.2}; char ch[] = {'A', 'B'}; ``` 上面宣告中,number[] 的元素個數會是 3,weight[] 的個數會是 4,而 chs[] 的個數會是 2。 <br> 如果使用 const 或 constexpr 來修飾陣列,每個索引位置就成為唯讀。例如: ```cpp= constexpr int number[] = {1, 2, 3}; number[1] = 10; // error: assignment of read-only location 'number[1]' ``` 另外,陣列無法像vector直接複製。如果要複製陣列,只能一個一個元素複製,如: ```cpp= constexpr int LENGTH = 5; int arr1[LENGTH]; int arr2[LENGTH]; ... for(int i = 0; i < LENGTH; i++) { arr1[i] = arr2[i]; } ``` 同樣的,要比較兩個陣列是否相同的話,也是要一個一個元素比較。 ### 二維(多維)陣列 #### 宣告 二維(多維)陣列的宣告與一維陣列相似,要兩個(多個)索引值來指定存取陣列元素 ```cpp= int maze[5][10]; ``` #### 存取元素 ```cpp= #include <iostream> using namespace std; int main() { constexpr int ROWS = 5; constexpr int COLUMNS = 10; int maze[ROWS][COLUMNS]; for(int row = 0; row < ROWS; row++) { for(int i = 0; i < COLUMNS; i++) { maze[row][i] = (row + 1) * (i+ 1); } } for(int row = 0; row < ROWS; row++) { for(int i = 0; i < COLUMNS; i++) { cout << maze[row][i] << "\t"; } cout << endl; } return 0; } 輸出: 1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 ``` 上面這個程式宣告了 5 列(Row)、10 行(Column)的陣列,第一個 [] 是用來指定存取哪一列,第二個 [] 是用來指定存取哪一行。 二維陣列在宣告的同時也可以指定元素的值,例如: ```cpp= int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; ``` 從上面這個程式來看,可以清楚地看出 maze[2][3] 中 2 與 3 的意義,maze[2] 表示 maze 有兩個元素,各是 {1, 2, 3} 與 {4, 5, 6},也就是說,這兩個元素是一維陣列,而長度是 3。 其實二維陣列存取時的行與列,只是為了便於理解陣列元素索引,索引值的意義,仍是指相對於陣列第一個元素的位移量。 例如,在一維陣列中的陣列配置與索引意義如下圖所示(若 int 長度為 4 個位元組): ![](https://i.imgur.com/QwG8lKo.png) 在上面的例子中,二維陣列將得到的記憶體分為兩個區塊,宣告陣列 maze[2[4],表示 maze[0][0] 與 maze[1][0] 相對位移量為 4,存取 maze[1][3] 時,表示存取位置相對於 maze[1][0] 位移3個單位。 <br> 若宣告 maze[3][5] 的話,記憶體位置的指定是如何呢?在這個陣列中 5 的意義是 maze[0][0]、maze[1][0] 與 maze[2][0] 間都是 5 個位移量,如下圖所示: ![](https://i.imgur.com/UDyuCOL.png) ## 指標 Pointer ### & 取址運算子 &n 是n的位置,位置採16進位 ### * 指標 ```cpp= 資料型態* 變數名 int *n ``` n的型態為int* 此變數用來儲位置 ```cpp= int main() { int n = 10; int *p = &n ; cout << "n 變數的位址:" << &n << endl << "p 儲存的位址:" << p << endl; return 0; } output: n 變數的位址:0x61feb8 p 儲存的位址:0x61feb8(不一定一樣) ``` ### * 反參照運算子 與指標的符號一模一樣,可以使用提取運算子來拿指標儲存位置處的物件 ```cpp= int main() { int n = 10; int *p = &n; cout << "指標 p 儲存的位址:" << p << endl << "提取 p 儲存位址處的物件:" << *p << endl; return 0; } 輸出: 指標 p 儲存的位址:0x61feb8(不一定一樣) 提取 p 儲存位址處的物件:10 ``` ### 指標與 const 型態 用 const 宣告的變數,必須使用對應的 const 型態指標才可以。 ```cpp= const int n = 10; const int *p = &n; ``` 要留意的是,const int *p 宣告的 p 並不是常數,p 可以儲存不同的位址。 ```cpp= #include <iostream> using namespace std; int main() { const int n = 10; const int m = 20; const int *p = &n; cout << p << endl; p = &m; cout << p << endl; return 0; } 輸出: 0x61feb8 0x61feb4(不一定一樣) ``` 如果想令指標儲存的值無法變動,必須建立指標常數,先來看看來源變數沒有 const 的情況: ```cpp= int n = 10; int m = 20; int* const p = &n; p = &m; // error: assignment of read-only variable 'p' ``` 如果 n、m 被 const 修飾,那麼就必須如下建立指標常數: ```cpp= const int n = 10; const int m = 20; const int* const p = &n; p = &m; // error: assignment of read-only variable 'p' ``` ### 指標與函數 對於傳址進function是如何運作有疑問的人可以看看這個例子: ```cpp= #include <bits/stdc++.h> using namespace std; void sw( int *a, int *b ) { cout << "現在有一個變數在sw內,名字叫a,存的東西是:" << a << endl << "然後有一個變數在sw內,名字叫b,存的東西是:" << b << endl << endl; int *x = a; //創了一個變數x,x存的東西要是一個位址,a現在存的東西是位址,所以可以給他 a = b; //把sw內,a存的位址改成b存的位址 b = x; //把sw內,b存的位址改成剛剛x存的位址 cout << "現在sw內,a裡面存的東西是:" << a << endl << "現在sw內,b裡面存的東西是:" << b << endl << endl; cout << "實際上sw內a的記憶體位置是:" << &a << endl << "實際上sw內b的記憶體位置是:" << &b << endl << endl; } int main() { int a = 10; int b = 20; cout << "現在在main裡面,經過sw前,a的位址是:" << &a << endl << "現在在main裡面,經過sw前,b的位址是:" << &b << endl << endl; sw( &a, &b ); cout << "現在在main裡面,經過sw後,a的位址是:" << &a << endl << "現在在main裡面,經過sw後,b的位址是:" << &b << endl << endl; system( "pause" ); return 0; } 輸出: 現在在main裡面,經過sw前,a的位址是:0x61ff0c 現在在main裡面,經過sw前,b的位址是:0x61ff08 現在有一個變數在sw內,名字叫a,存的東西是:0x61ff0c 然後有一個變數在sw內,名字叫b,存的東西是:0x61ff08 現在sw內,a裡面存的東西是:0x61ff08 現在sw內,b裡面存的東西是:0x61ff0c 實際上sw內a的記憶體位置是:0x61fef0 實際上sw內b的記憶體位置是:0x61fef4 現在在main裡面,經過sw後,a的位址是:0x61ff0c 現在在main裡面,經過sw後,b的位址是:0x61ff08 ``` 可以看見在sw內仍會間接的產生一個變數來複製a與b的位置,但相較於值皆傳值進去,傳址的效率仍會快一些。 ### 指標的運算 指標加法與減法與一般數值的加減不同,在指標運算上加 1 ,表示前進一個資料型態的記憶體長度,例如 int ,長度為 4 個位元組,在 int* 型態的指標上加 1,表示在位址上前進 4 個位元組的長度: ```cpp= #include <iostream> using namespace std; int main() { int *p = 0; cout << "p 位址:" << p << endl << "p + 1:" << p + 1 << endl << "p + 2:" << p + 2 << endl; return 0; } 輸出: p 位址:0 p + 1:0x4 p + 2:0x8 ``` 如果你不曉得某型態的記憶體空間時,可以使用sizeof()函式: ```cpp= cout << sizeof(double); 輸出: 8 ``` ### 指標與陣列 https://openhome.cc/Gossip/CppGossip/PointerAndArray.html ### 指標的指標 指標的指標是拿來儲存記憶體位址的,差別在於指標的**型態**,可以看看底下的範例: ```cpp= #include <iostream> using namespace std; int main() { int n = 10; int *p1 = &n; int **p2 = &p1; cout << "n 位址:" << p1 << endl << "p1 位址:" << p2 << endl; return 0; } 輸出: n 位址:0x61feb8 p1 位址:0x61feb4 ``` > n 儲存了 10,n 的位址 0x61feb8,指定給 p1 儲存,而 p1 的位址是 0x61feb4,指定給 p2 儲存。 n的型態是int,因此 &n 的型態為`int*` ,所以宣告p1時宣告的型態為int*,而 &p1 的型態則為`int**`, 因此宣告p2時宣告的型態為int**。 指標的指標好用的地方在於運算多維陣列扮演的作用,因為多維陣列是由陣列的陣列所組成的,實際上就是數段一維陣列所構成的,而若對變數取址後遞增1,則會位移一整個陣列空間。 <br> 在宣告二維陣列時,我們需要宣告每段一維陣列的長度為何: ```cpp= int arr[2][3] = {{10, 20, 30}, {40, 50, 60}}; int (*p)[3] = arr; ``` 同樣的,在宣告三維陣列時,我們需要宣告每段二為陣列的長度為何: ```cpp= int arr[2][2][3] = { {{1, 2, 3},{4, 5, 6}} , {{7, 8, 9},{10, 11, 12}} }; int (*p)[2][3] = arr; ``` 在使用時可以直接使用auto比較方便: ```cpp= int arr1[2][3] = {{10, 20, 30}, {40, 50, 60}}; auto p1 = arr1; int arr2[1][2][3] = {{{10, 20, 30}, {40, 50, 60}}}; auto p2 = arr2; ``` 再利用前面談二維陣列時的例子,如果我們要印出一個二維陣列,也可以利用指標的方式來操作,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(int (*it)[3] = begin(maze); it < end(maze); it++) { int *row = *it; for(int i = 0; i < 3; i++) { cout << row[i] << "\t"; } cout << endl; } return 0; } 輸出: 1 2 3 4 5 6 ``` ### new and delete ## 物件別名 (Reference 參考) https://blog.xuite.net/chuangyf0917/blog/13649687 https://www.geeksforgeeks.org/references-in-c/ Reference是物件的別名,你可以把他想成某個變數的綽號,對Reference進行的任何存取,都會對原物件進行操作。 在C++裡,物件不單只是指類別,而是指記憶體裡的一塊資料,換句話說,Reference類似pointer,但reference指到的是記憶體位址內的數值,pointer則是記憶體的位址。 另外,一個函式內不能有相同名稱的兩個Reference。 要定義Reference,是在型態關鍵字後加上&運算子,例如: ```cpp= int n = 10; // 定義變數 int *p = &n; // 定義指標,儲存 n 的位址 int &r = n; // 定義參考,是 n 的別名 ``` <br> 要注意的是,Reference後面必須要有指定的物件,否則無法通過編譯,如: `int &r;` 就會出錯。 對Reference的進行的任何存取,都會對原物件進行操作,如: ```cpp= #include <iostream> using namespace std; int main() { int n = 10; int &r = n; cout << "n:" << n << endl << "r:" << r << endl; r = 20; cout << "n:" << n << endl << "r:" << r << endl; return 0; } 輸出: n:10 r:10 n:20 r:20 ``` 指標也可以使用Reference,例如: ```cpp= int n = 10; int *p = &n; int *&r = p; // 也就是 int* &r = p, int* 是型態, r是指標p的Reference ``` 陣列也可以,但需要指定長度,如: ```cpp= int arr[] = {1, 2}; int (&r)[2] = arr; ``` <br> 既然陣列也可以使用,那麼再一次借用前面二維陣列的例子,我們也可以使用reference來幫助我們印出二維陣列,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(int (*it)[3] = begin(maze); it < end(maze); it++) { int (&row)[3] = *it; // 使用reference for(auto offset = begin(row); offset < end(row); offset++) { int n = *offset; cout << n << "\t"; } cout << endl; } return 0; } ``` 不過這樣寫有點複雜,可以再搭配 for range 的語法與 auto 進行簡化,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(auto &row : maze) { // 使用reference for(auto n : row) { cout << n << "\t"; } cout << endl; } return 0; } ``` ### Rvalue Reference ### Lvalue Reference ## 函式 ### 語法 回傳值型態 函式名稱(參數1型態 參數名稱1, 參數2型態 參數名稱2, ... ) { 執行內容; return 回傳值; } ```cpp= int pay( int hour ) { if( hour <= 3 ) { return hour*30; } else { return 3*30 + (hour-3)*20; } } int main() { int n; while( cin >> n ) { cout << pay(n) << endl; } return 0; } ``` 函式一旦執行到return,就會立刻回傳,略過之後所有程式碼 ### 引數(Argument) 與 參數(Parameter) 在呼叫函式時,提供給函式的資料稱為引數(argument),接受引數的稱為參數(parameter)。 例如以下的範例, n 是 parameter,型態為int,呼叫函式時提供 x 作為argument。 ```cpp= #include <iostream> using namespace std; int increment(int n) { n = n + 1; return n; } int main() { int x = 10; cout << increment(x) << endl; cout << x << endl; return 0; } 輸出: 11 10 ``` 可以看見, n 雖然作了遞增運算,但是對 x 的儲存值沒有影響,x 最後仍是顯示 10,這是因為當我們呼叫function時,並不會真正改變我們傳進去的值。 在function中他會建一個新的變數(n),值與我們傳進的值(x)一樣。 若想真正改變到傳入的值,我們必須傳入變數的記憶體位址,直接對那塊記憶體位址的值進行操作,如下: ```cpp= #include <iostream> using namespace std; int increment(int *n) { *n = *n + 1; return *n; } int main() { int x = 10; cout << increment(&x) << endl; cout << x << endl; return 0; } ``` 或者,我們可以傳入變數的Reference,如下: ```cpp= #include <iostream> using namespace std; int increment(int &n) { n = n + 1; return n; } int main() { int x = 10; cout << increment(x) << endl; cout << x << endl; return 0; } 輸出: 11 11 ``` ## Templete 樣板 ```cpp= template < 樣板參數型態 樣板參數名 [, 其他樣板參數] > 原型回傳型態 函數名(參數型態 原型參數名, ...) { //prototype codes; } ``` ### Function Template (函數樣板) ```cpp= //normal function: int myAdd(int a, int b) { return a + b; } //function template template <class T> T myAdd(T a, T b) { return a + b; } ``` ### Class Template (類別樣板) ```cpp= //normal class: class myClass { myClass& add(const myClass& a) { return *this; } }; //class template: template <class T> class myTClass { T& add(const T& a) { return *this; } }; ``` <style> .green { color:#29E5A9; } .brown { color:#990000; } .pink { color:#DD9FBD; } .red { color:#E71B18 ; } .blue { color:#0b5394; } .purple { color:#AC9FDD; } @-webkit-keyframes A { 0% { color:#C10066;} 10% { color: #CC0000;} 20% { color: #E63F00; } 30% { color:#EE7700; } 40% { color: #DDAA00; } 50% { color:#EEEE00;} 60% { color: #99DD00;} 70% { color:#66DD00;} 80% { color: #00DDDD;} 90% { color: #0044BB;} 100% { color: #A500CC;} } #animation_title{ animation: A 3s ease 0s infinite alternate; -webkit-animation: A 3s ease 0s infinite alternate; } </style> <style> a.redlink { color:#DB1859; } a.redlink:link { color:#DB1859; text-decoration:none; } a.redlink:visiteid { color:#DB1859; text-decoration:none; } a.redlink:hover { color:#19CABC; text-decoration:none; } a.redlink:active { color:#000000; text-decoration:underline; background:#FFFFFF; } </style> <style type="text/css"> h1 { font-size:; color:#0b5394; } h2 { font-size:; color:#0b5394; } p { font-size:; color:; } h3 { font-size: ; color:#990000; } h4 { font-size:; color:#990000; } </style>