---
tags: 初階班
---
# 10th 初階班 上課講義
## 上學期
### 程式的基礎架構
- 預估上完的時間:第一週
- 講課:方旭誠
#### 課程內容
首先來看看大多數人學習的第一個程式 --- *Hello, world*
```cpp=
#include<iostream> // 標頭檔,引入函數庫
using namespace std; // 命名空間,區隔函式的意義
int main() // 主程式碼
{
cout << "Hello, world" << endl; // 輸出
system("pause"); // 程式暫停
return 0; // 回傳值
}
```
#### 標頭檔與命名空間
接著來試著解構內部的內容
首先是標頭檔和命名空間
標頭檔就像是一部字典一樣
賦予包含於其中的函式意義
因此當我們需要不同功能的函式時
便須引入相應的標頭檔
> 先將函式看做一種特殊的指令
> 在後面的課程會細講
而命名空間則像是一個識別編號
因為不同功能的函式可能會有相同的名稱
在使用上便會有所衝突
這時運用命名空間的不同
便能精確的定義該函數所應該套用的定義
而我們使用 `std` 命名空間
便是因為 C++ 的標準庫是定義在他之下的
如果不在程式碼前打上`using namespace std;`的話
在使用上會變成這樣
```cpp=
#include<iostream> // 標頭檔,引入函數庫
int main() // 主程式碼
{
std::cout << "Hello, world" << std::endl; // 輸出
system("pause"); // 程式暫停
return 0; // 回傳值
}
```
可以看到
必須在使用某些涵蓋於標準庫內的程式碼時
精確的用 `std::` 標明是 `std` 這個命名空間底下
才能被系統有效執行
再舉個更簡單的例子好了
假如今天復旦國中和~~某間山上學校~~
都有一個人叫王小明
那這時教育部要怎麼不搞混學生身分呢?
便是在前面冠上他們所屬的學校
把命名空間想像成校名
就好理解很多了
#### 主程式
接下來的 `int main(){ code... }` 就很簡單明瞭了
就是整個程式運行時
主要程式碼所在的地方
如果你亂改名稱
編譯器會因為找不到 `main` 而錯誤喔~
#### 輸出
再來就是 `cout` 和 `endl`
`cout` 是輸出
與之對應的是 `cin // 輸入`
而 `endl` 則是換行
前面有說到
他們是被定義在 `std // 命名空間` 、`iostream // 函式庫`之下的
所以需要引入該函式庫與命名空間才有意義
不過有些關鍵字是 C++ 最初的基底
並不是被後續定義出來的功能
因此不用引入便能執行程式
而這些關鍵字在下回便會提及
這裡先埋個伏筆
關於輸出的規則
大致為`cout << "想輸出的文字";`
其中的 `<<` 為必要語法
`endl` 則視需求加在其中或結尾
```cpp=
// example
cout << "Hello, world" << endl;
cout << "Hello" << endl << "world";
```
#### 程式終止
來到最後的兩行
`system("pause");` 的功用主要在執行檔(.exe)上較為顯著
而 `main(){ }` 裡的 `return 0;` 可以理解為回傳一個值
告訴電腦程式已經執行完畢
而 `return` 在 C++ 還有別的用途
這也是後話
上述的兩行程式碼的有無造成的影響
請各位在電腦上用執行檔(.exe)實際操作看看
並觀察小黑窗的顯示資訊有何不同
> **注意**
> 在Judge平台上解題時
> 請勿加入 `system("pause");`
> 不然系統會出錯哦
#### 技術總結
1. 標頭檔和命名空間關乎程式碼是否具有意義和功能
2. `main(){ }` 是程式運行的主要區域,不要改他的名字
3. `cout` 要加 `<<` 後並用 `""` 包覆要輸出的文字
4. 每行程式碼後**要加分號!!!**,這對電腦來說是一個指令的結束,並不是以 `enter` 或 `space` 作為結束
5. 在Judge平台上不要使用 `system("pause");`
6. `return` 會回傳值,在 `main` 裡作為程式的結束
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.033
> a.196
---
### 資料型態與運算子
- 預估上完的時間:第二週
- 講課:羅時瀚
#### 課程內容
這節課我們要上的是資料型態與運算子
而他們也是上回說到的
可以不用呼叫任何標頭檔與命名空間便可使用的指令喔!
#### 資料型態
接下來我們先講到資料型態
資料型態在C或C++是非常重要的
在想使用任何變數前
都要告訴電腦它的資料型態
這個動作叫做宣告
若沒有宣告它的資料型態或搞錯資料型態
都有可能會出現無法編譯的情況
以下就是常見的資料型態
| 資料型態 | 位元組 | 表示範圍 |
| -------- | -------- | -------- |
| int | 4 | $-2,147,483,648$ ~ $2,147,483,647$ |
| unsigned int | 4 | $0$ ~ $4,294,967,295$ |
| long long | 8 | $-9,223,372,036,854,775,808$ ~ $9,223,372,036,854,775,807$ |
| unsigned long long | 8 | $0$ ~ $18,446,744,073,709,551,615$ |
| bool | 1 | $true$ $or$ $false$ |
| char | 1 | $-128$ ~ $127$ |
| float | 4 | $1.2*10^{-38}$ ~ $3.4*10^{38}$ |
| double | 8 | $2.2^*10^{-308}$ ~ $1.8^*10^{308}$ |
#### 運算子
|設定運算子 | 功能 | 示範 |
| -------- | -------- | -------- |
| = | 設定 | a = 1 |
| 單元運算子 | 功能 | 示範 |
| -------- | -------- | -------- |
| + | 正 | +a |
| - | 負 | -a |
| ++ | 遞增 | \++a,a++ |
| \-\- | 遞減 | \-\-a,a\-\- |
| ! | 否 | !a |
| ~ | 取1的補數 |~a |
| 算數運算子 | 功能 | 示範 |
| -------- | -------- | -------- |
| + | 加 | a + b |
| - | 減 | a - b |
| * | 乘 | a * b |
| / | 除 | a / b |
| % | 取餘數 | a % b |
| 關係運算子 | 功能 | 示範 |
| -------- | -------- | -------- |
| > | 大於 | a > b |
| < | 小於 | a < b |
| >= | 大於等於 | a >= b |
| <= | 小於等於 | a <= b |
| == | 等於 | a == b |
| != | 不等於 | a != b |
| 邏輯運算子 | 功能 | 示範 |
| -------- | -------- | -------- |
| && | AND | a && b |
| \|\| | OR | a \|\| b |
| ^ | XOR | a ^ b |
|AND | true | false |
| --- | --- | --- |
|**true**| true | false |
|**false**| false | false |
| OR | true | false |
| --- | --- | --- |
|**true**| true | true |
|**false**| true | false |
| XOR | true | false |
| --- | --- | --- |
|**true**| false | true |
|**false**| true | false |
#### 技術總結
1. 注意資料型態不同的變數不能做運算
2. 做題目記得,題目給的範圍不能超過你要用的資料型態範圍
3. 邏輯運算子要熟記結果
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.014
> a.032
> a.035
---
### if(判斷)
- 預估上完的時間:第三週
- 講課:方旭誠
#### 課程內容
今天要教的是 `if`
顧名思義就是做一個假設
再用電腦驗證這個假設是否正確
並做出回應
其實如果要去解構一個程式
就是不停判斷、運算、儲存的過程
所以學習判斷雖簡單
卻至關重要
#### 判斷
先來簡單看看 `if` 的架構
```cpp=
if(判斷條件) {
程式碼...//當條件成立時執行
}
else if(判斷條件) {
程式碼...//當if不成立才進行判斷
}
else if(判斷條件) {
程式碼...//當上個else if不成立才進行判斷
}
else {
程式碼...//當所有都不成立直接執行
}
```
可以看到
在程式碼內的 `if` 是層層遞近的
先判斷最初的 `if` 是否成立
再一步步往下排查
值得注意的是
**`else if` 可以有多個**
**但 `else` 卻能只有一個**
並且他們都必須在前面先有 `if` 的情況下才有效
而如果是使用多個 `if`
則每個 `if` 都會被判斷
互不干擾
就不如 `else if` 那樣只要其中之一成立
便不再判斷
#### 操作示範
接下來我們來試著使用 `if` 搭配之前的上課內容
來做一點簡單的使用示範
```cpp=
#include<iostream>
using namespace std;
int main()
{
int x; // 宣告變數 x
cin >> x; // 輸入 x 的值
//判斷 x 與 0 的大小關係
if(x > 0) {
cout << x << '>' << 0 << endl;
}
else if(x == 0) {
cout << x << '=' << 0 << endl;
}
else {
cout << x << '<' << 0 << endl;
}
return 0;
}
```
這裡面便運用到了宣告、輸入、判斷、關係運算、輸出
關於關係運算所用的運算子
在上一章節已有提及
而條件判斷除了 `if` 其實還有一種寫法
稱為三元條件運算子
在後面的章節會教給大家~
#### 多條件判斷
不知道大家是否還記得上一章教的邏輯運算子
如果善加利用的話可以達到一次判斷多個條件的效果喔
話不多說
上範例
```cpp=
//example
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
if(n >= 0 && n % 4 == 0) // n大於0且為4的倍數
cout << n << endl; // if的指令僅一行時不用大括號
return 0;
}
```
可以看到我們同時判斷了兩個條件
當他們同時成立時才得以輸出
這就是邏輯運算子的應用
#### 技術總結
1. `if` 後的括號裡要放入判斷條件
2. 對於一個 `if`,`else if` 可以有很多個,`else` 只能有一個
3. 運用邏輯運算子做出複合判斷
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.015
> a.020
> a.021
> a.022
> a.023
---
### loop & mod (迴圈)
- 預估上完的時間:第五週
- 講課:羅時瀚
#### 課程內容
這節課我們要教的是 loop & mod
也就是迴圈和取餘數
迴圈是一個非常重要的東西
它可以控制你的程式要重複執行多少遍
在C++中,有提供三種迴圈可以使用
分別是`for`、`while`、`do while`
其中`for`與`while`較常利用到
而取餘數應用不會那麼廣泛
不過在答案很大時
就會需要取餘數了!
#### for loop
`for` 迴圈是最常運用到的迴圈
如果很清楚迴圈要重複執行的次數就可以使用 `for` 迴圈
```cpp=
for (設定迴圈初值; 判斷條件; 設定變化量) {
迴圈主體; // 重複執行的地方
} // 大括號後面不用加分號
```
以下為 `for` 迴圈執行的流程
1. 首次進入 `for` 迴圈,設定`迴圈控制變數`的初始值
2. 判斷是否要執行迴圈,當條件判斷值為`true`時,繼續執行`迴圈主體`;反之,若為`false`,則跳出迴圈
3. 執行完`迴圈主體`以後,`迴圈控制變數`會根據`設定變化量`,去更改`迴圈控制變數`的值,再重新執行步驟2
```flow
st=>operation: 設定迴圈初值
cond=>condition: 判斷條件
op=>operation: 迴圈主體
op3=>operation: 跳出迴圈
op2=>operation: 設定增減量
st->cond
cond(false)->op3
cond(true)->op->op2(left)->cond
```
如果我們要計算1加到100的話,就可以用```for```迴圈
```cpp=
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
```
其實上面的東西很像數學的一個東西
\begin{equation}
{sum = \sum^{100}_{i=1} i}
\end{equation}
然後可能會有人好奇為什麼`i`要從```0```開始,而不是```1```
因為有部份情況用到for迴圈時
我們需要搭配到陣列(下章會提)
就習慣將for迴圈的初始值定為```0```
當然可以自己決定
沒有強迫
#### while loop
```while```迴圈也算很常用的迴圈
有時候我們會不知道迴圈要重複執行多少次
就可以考慮```while```迴圈或 ~~```do while```迴圈~~
```cpp=
迴圈控制變數;
while (判斷條件) {
迴圈主體;
設定增減量;
} // 大括號後面不用加分號
```
1. 首次進入 `while` 迴圈前,設定`迴圈控制變數`的初始值
2. 檢查判斷條件的內容,當條件判斷值為`true`時,繼續執行`迴圈主體`;反之,若為`false`,則跳出迴圈
3. 執行完`迴圈主體`以後,`迴圈控制變數`會根據`設定增減量`,去更改`迴圈控制變數`的值,再重新執行步驟2
```flow
st=>operation: 設定迴圈初值
cond=>condition: 判斷條件
op=>operation: 迴圈主體
op3=>operation: 跳出迴圈
op2=>operation: 設定增減量
st->cond
cond(false)->op3
cond(true)->op->op2(left)->cond
```
如果我們要計算1加到n,我們也可以用```while```迴圈
```cpp=
int n, i = 1, sum = 0;
cin >> n;
while (i <= n) {
sum += i;
i++;
}
```
#### do while loop
`do while`迴圈也是用在不知道重複執行迴圈次數時
不過`do while`迴圈是「先做再說」
這個迴圈是最少用到的
```cpp=
迴圈控制變數;
do {
迴圈主體;
設定增減量;
} while (判斷條件); // 記得要加分號
```
1. 首次進入 `do while` 迴圈前,設定`迴圈控制變數`的初始值
2. 先執行`迴圈主體`,再檢查判斷條件的內容,當條件判斷值為`true`時,繼續執行`迴圈主體`;反之,若為`false`,則跳出迴圈
3. 執行完`迴圈主體`以後,`迴圈控制變數`會根據`設定增減量`,去更改`迴圈控制變數`的值,再重新執行步驟2
```flow
st=>operation: 設定迴圈初值
op=>operation: 迴圈主體
op3=>operation: 跳出迴圈
op2=>operation: 設定增減量
cond=>condition: 判斷條件
st->op->op2->cond
cond(false)->op3
cond(true)(left)->op
```
`do while`迴圈實在是太少用了,所以我就不加上範例了 :poop:
#### break & continue
```break``` 其實也是我們很常用的東西
是用來搭配迴圈的
如果我們在迴圈裡做到某件以達成我們需要做的事時
就可以在之後打上```break```
那就會直接跳出迴圈
以免增加時間或記憶體
甚至造成無限迴圈
以```while```為例
```flow
st=>operation: 設定迴圈初值
cond=>condition: 判斷條件
op=>operation: 迴圈主體
op3=>operation: 跳出迴圈
op2=>operation: 設定增減量
cond2=>condition: break
op1=>condition: 跳出迴圈
st->cond
cond(false)->op3
cond(true)->op->cond2(true)->op2(left)->cond
cond(true)->op->cond2(false)->op1
```
```cpp=
int ans = 0;
unsigned int a;
for (int i = 0; i < 100000; i++) {
cin >> a;
ans += a;
if (ans > 100) {
cout << "answer is more than 100\n";
break;
}
}
```
像這裡我們要判斷ans有沒有超過100
如果超過了
ans不管如何都還是會超過100
就沒有做下去的意義
那就可以直接用break
--
```continue```是比較少用到
但還是要學
```continue```會略過迴圈剩下的部分
進行下一次的迴圈
以```while```為例
```flow
st=>operation: 設定迴圈初值
cond=>condition: 判斷條件
op=>operation: 迴圈主體
op3=>operation: 跳出迴圈
op2=>operation: 設定增減量
cond2=>condition: continue
st->cond
cond(false)->op3
cond(true)->op->cond2(true)->op2(left)->cond
cond(true)->op->cond2(false)->cond
```
我想不到範例code
有用到再說 :poop:
#### mod
mod,又名模除,符號記做`%`
是一個很好用的東西
簡單來說就是取餘數
就是國小算除法時
```被除數 / 除數 = 商數 ... 餘數```
的餘數
```cpp=
int a = 5;
double b = 5;
int c = 2;
cout << a / c << '\n'; // 2
cout << b / c << '\n'; // 2.5
cout << a % c << '\n'; // 1
```
它可以應用在判斷奇偶數中
```cpp=
int n;
cin >> n;
if (n % 2 == 1) cout << "n is an odd number."
else if (n % 2 == 0) cout << "n is an even number."
```
然後寫到後面的題目 ~~ex:期中考~~ 就有可能要你把答案```mod 1e9 + 7```
原因是因為
1. 它是一個很大的質數且小於 $2^{30}$
2. 將兩個 ```mod 1e9 + 7``` 的數相加會在```int```的範圍
3. 將兩個 ```mod 1e9 + 7``` 的數相乘會在```long long```的範圍
再來講一些```mod```的運算
- ($a \;mod \;n) \;mod\; n = a \;mod \;n$
- $n^x \;mod \;n = 0$
- $(a + b)\; mod \;n = [(a\; mod \;n) + (b \;mod\; n)]\; mod\; n$
- $a*b \;mod\; n = [(a\; mod\; n)*(b\; mod \;n)]\; mod\; n$
-
#### 技術總結
1. `for`迴圈的小括弧中間是用`;`間隔
2. `for`跟`while`迴圈後面不要加`;`
3. `while`迴圈裡面記得加上可以更改`迴圈控制變數`的程式碼
4. ```break```有時候很必須,記得要會用
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.020
> a.023
> a.191
> a.192
---
### array( 陣列 )
- 預估上完的時間:第六週
- 講課:方旭誠
#### 課程內容
這堂課要教的是陣列
陣列是一個能用單一資料型態存儲多個資料的容器
讓你免於宣告太多變數
而陣列搭配迴圈也有奇效喔!
#### 宣告陣列
首先來觀察一個陣列的圖示
| arr[4] | `empty` | `empty` | `empty` | `empty` |
| --- | --- | --- | --- | --- |
| 編號 | arr[0] | arr[1] | arr[2] | arr[3] |
當我們宣告一個陣列時
就會如上方的示意圖一樣
在電腦產生一串連續的存放空間
具體的宣告方式是
```cpp=
int arr[4]; // 以 int 型態宣告一個名為 arr,共 4 格的陣列
```
> **注意**
> 在中括號裡的數字可以是變數
此時陣列中還只是殘值(亂碼)
我們可以選擇從宣告時就指定裡面的內容
或是在後續程式中存入內容
這裡先示範事先指定的方法
```cpp=
//example
int arr[4] = {3, 5, 6, 4}
```
陣列內部現在變成如下的樣子
| arr[4] | 3 | 5 | 6 | 4 |
| --- | --- | --- | --- | --- |
| 編號 | arr[0] | arr[1] | arr[2] | arr[3] |
#### 更改內容
那如果後續要更改怎麼辦呢?
要知道
陣列雖然是一串連續的資料
但他的每一格是分開處理的
所以當要指定某一格做出操作時
就要用到他的編號了
在開始操作前要先了解到
**陣列的編號由 `0` 開始**
所以一個 `n` 格的陣列
最後一格的編號是 `n-1` 呦
接下來便實際操作陣列的指定修改吧
```cpp=
#include<iostrem>
using namespace std;
int main()
{
int arr[4] = {3, 5, 6}; // 只先宣告三個
arr[3] = 4; // 指定第四格的值為 4
}
```
以上是陣列的指定修改
非常直覺與簡單
但當今天有未知或超多筆數的資料要存入陣列呢?
因為前面有說陣列每格是單獨處理的
這樣每格一個指令去修改太沒效率了
我們可以運用陣列編號累加的特性搭配迴圈達到這個目的!
#### 陣列與迴圈
記得 `for` 迴圈每執行一遍參數就會產生一次變化嗎
如果將這個規律對應陣列的編號
就能用短短幾行程式掃過陣列每一格了
上範例!
```cpp=
// 連續輸入
int n;
cin >> n;
int arr[n]; // 用變數決定陣列大小
for(int i = 0; i < n; i++) {
cin >> arr[i]; // 用 i 的變動性跑完陣列每一格
}
---分隔線---
// 連續輸出
for(int i = 0; i < n; i++) {
cout << arr[i] << endl;
}
```
這樣就能輕輕鬆鬆的對每格陣列做出處理了
因此在接下來的題目裡
迴圈搭配陣列會是很常見的用法喔!
#### 多維陣列
當今天變數後的中括號不只一個時
就可以建構出多維陣列
在這先示範如何宣告
```cpp=
int arr[2][3]; // 宣告一個 2*3 的陣列
```
這時陣列長這樣
| 編號 | arr[x][0] | arr[x][1] | arr[x][2] |
| --- | --- | --- | --- |
| **arr[0][y]** | | | |
| **arr[1][y]** | | | |
就猶如一維陣列一樣
可以透過任意一格的編號來對其進行操作
不過要記得第一格是橫行的編號
第二格是直列的編號
想當然
更高維的陣列就更加複雜了
四維更是只存在於想像中
因此在這裡僅做提及
#### 技術總結
1. 陣列編號由 `0` 開始
2. 每格陣列都可以被單獨指定與操作
3. 陣列搭配迴圈可以使操作效率許多
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.188
> a.203
> a.233
> a.372
---
### string
- 預估上完的時間:第八週
- 講課:羅時瀚
#### 課程內容
今天要說的是string,它有以下幾點特點
- string型別支援長度可改變的字元陣列
- 提供大量的函式
- 屬於C++ STL容器庫
- 使用string的程式必須先引入函數庫
```#include <string>```
#### 定義 string 並初始化
``` cpp=
string str1; // str1將會是個空字串
cout << str1 << '\n'; //
string str2 = "Fudan"; // 把str2初始化為字串字面常數的副本
cout << str2 << '\n'; // Fudan
string str3("Fudan"); // 把str3初始化為字串字面常數的副本
cout << str3 << '\n'; // Fudan
string str4(str3); // 把str4初始化為str3的副本
cout << str4 << '\n'; // Fudan
string str5(5, 'f'); // 把str5初始為5個f
cout << str5 << '\n'; // fffff
```
其實幾乎只會用到第一種跟第二種 :poop:
#### 讀入 string
``` cpp=
string str1, str2, str3;
cin >> str1; // 把「以空白分隔的或換行」字串讀入str1
getline(cin, str2); // 把一整行字串讀入str2
cin.get(str3); // 把一格字元讀入str3
```
造成getline()返回的那個換行字元會被捨棄
不會儲存在string
#### 字串陣列
因為字串像是陣列一樣存取
所以也可以利用下標取得特定值
``` cpp=
string str = "fudan";
cout << str[0] << '\n'; // 'f'
cuot << str[2] << '\n'; // 'd'
```
#### string 的常用函式
- str.empty(): 測試字串是否為空
``` cpp=
string str;
cout << (str.empty() ? "yes" : "no") << '\n'; // yes
```
- str.size(): 傳回字串長度
``` cpp=
string str = "Fudan";
cout << str.size() << '\n'; // 5
```
- str.length(): 傳回字串長度
``` cpp=
string str = "Fudan";
cout << str.length() << '\n'; // 5
```
- str.find("fd"): 傳回字串fd在str的位置
``` cpp=
string str = "i dont like fd fd";
cout << str.find("fd") << '\n'; // 12
cout << str.find("fd", 13) << '\n'; // 15
cout << str.find("Fd") << '\n'; // 18446744073709551615
```
$\;\;\;\;\;\;\;$這裡因為字串中沒有"Fd"
$\;\;\;\;\;\;\;$則會輸出string::npos
$\;\;\;\;\;\;\;$而npos是指這個容器的最大容量
$\;\;\;\;\;\;\;$用來表示不存在的位置
- str.clear(): 清空str
``` cpp=
string str = "fudan";
str.clear();
cout << (str.empty() ? "yes" : "no") << '\n'; // yes
```
- str1.swap(str2): 把str1與str2交换
``` cpp
string str1 = "f", str2 = "d";
str1.swap(str2);
cout << str1 << ' ' << str2 << '\n'; // d f
```
- str[n] = toupper(str[n]): 將指定字元轉為大寫字母,若非英文字母則不處理
``` cpp=
string str = "fudan";
str[0] = toupper(str[0]);
cout << str << '\n'; // Fudan
```
- str[0] = tolower(str[0]): 將指定字元轉為小寫字母,若非英文字母則不處理
``` cpp=
string str = "FUDAN";
str[0] = tolower(str[0]);
cout << str << '\n'; // fUDAN
```
#### ASCII碼
前面提過字串像是陣列一樣存取,而每格都以ASCII碼的格式存取
1. 大寫英文字母A ~ Z (65 ~ 90)
2. 小寫英文字母a ~ z (97 ~ 122)
3. 數字0 ~ 9 (48 ~ 57)

因此可以用來
1. 字元轉成整數
2. 整數轉成字元
3. 判斷字元內容是大寫英文字母、小寫英文字母、數字或其他
4. 轉換字元英文大小寫
可以結合前面講過的字串陣列
``` cpp=
string str1 = "F", str2 = "FuDan";
int a = 68;
cout << (int)str1 << '\n'; // 70
cout << (char)a << '\n'; // D
for (int i = 0; i < str2.length(); i++)
cout << (int)str2[i] << ' '; // 70 117 68 97 110
```
#### 技術總結
1. `string` 的用法、運用地方十分廣泛,一定要學好
2. `getline(cin, str)`是用在要讀入一個字串,但中間有空格時
3. `字串陣列`也是從`0`開始
4. 每個函數都要記住使用方法
5. `ASCII碼`可以只記`0`、`A`、`a`的,剩下自己去推敲
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.001
> a.007
> a.012
> a.013
> a.188
> a.199
> a.261
> a.423
### bubble sort (氣泡排序法)
- 預估上完的時間:第九週
- 講課:方旭誠
#### 課程內容
今天上的是排序法
顧名思義就是將數字或文字進行排序
以呈現由大到小或由小到大的分布
這章節的內容牽引了許多進階語法的基礎
因此相當重要
#### 原理
排序法為了讓數列採取規律排序
因此會需要不斷的比較兩者的大小
再進行交換
因此會需要一個陣列來儲存一連串資料
再來需要一個迴圈掃過陣列的每一格
以及一個判斷如何交換的標準
這就是泡沫排序法的基本架構
#### 基本架構
因為泡沫排序法較難用文字描述其概念
因此直接上最基本的程式碼
```cpp=
#include<iostream>
using namespace std;
int main()
{
int n = 10, tmp; // 宣告陣列大小和暫存變數
int arr[n] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
for(int i = 0; i < n; i++) { // 陣列共有n格
for(int j = 0; j < n-1; j++) { // 判斷每一格大小關係
if(arr[j] > arr[j+1]) { // 如果前比後小就交換
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
// 最後陣列為 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
return 0;
}
```
這樣應該不難理解這是一個將數列由**小排到大**的程式
但其中的程式碼卻略顯繁雜
而且存在著效率的問題
因此我們馬上要看到如何優化它
#### 優化
首先要處理的是中間交換部份的問題
`C++` 其實提供了一個實用的函式給我們
用法如下
```cpp=
swap(前一格編號, 後一格編號);
```
他可以用於交換順序
將中間三行的功能替代掉
接著處理效率問題
仔細想會知道
其實有些格子是已經在前面被判斷過與交換過了
所以我們應該在程式碼
透過修改運行邏輯來忽略已判斷部分
並加上可自行定義的範圍
最後整合出最精簡的版本
```cpp=
#include<iostream>
using namespace std;
int main()
{
int n;
while(cin >> n) {
int arr[n];
for(int i = 0; i < n; i++)
cin >> arr[i];
for(int i = n-1; i > 0; i--)
for(int j = 0; j <= i-1; j++)
if(arr[j] > arr[j+1])
swap(arr[j], arr[j+1]);
}
return 0;
}
```
這便是最終的版本了
但不幸的是
`C++`其實還提供了一個函式可以概括~~氣泡~~排序法的整個流程
:::spoiler `進階教學有話要說`
欸欸這排序法不是氣泡排序喔,
它是由 `quick sort` 和 `heap sort` 組合起來的,
有興趣可以去查查看喔!
:::
#### sort 函式
```cpp=
#include<algorithm> // 要先引入這個函式庫
sort(初始位置, 末位置+1);
```
這個函式只要放入陣列的參數
就能將它**由小到大**排列完畢
當然它有別種排序邏輯可供調整
在這裡先賣個關子
總之如果使用了 `sort`
程式碼會縮短成
```cpp=
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int n;
while(cin >> n) {
int arr[n];
for(int i = 0; i < n; i++)
cin >> arr[i];
sort(arr, arr + n);
}
return 0;
}
```
相當的精簡
精簡到我不知怎麼描述了
不過程式的領域博大精深
比`sort`還快的排序法比比皆是
因此學好基礎觀念才是王道
不要濫用方便的函式就失去了學習的動力
#### 技術總結
1. 排序的核心就是比較與交換
2. 學好排序必須觀念紮實,不要好逸惡勞
3. **DanDanJudge上關於sort的題目通常是毒瘤** :poop:
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
> a.083
> a.208
> a.248
---
### 函式&遞迴
:::spoiler `進階教學有話要說`
函式不一定是遞迴,遞迴一定是函式。
:::
- 預估上完的時間:第十週
- 講課:羅時瀚
#### 課程內容
C++裡面有很多函式了,其實大多是有人去寫並加入進去的
當然,如果你在一個程式裡有要一直重複的動作,也可以把它寫成函式
今天就要教大家怎麼自訂函式還有遞迴
#### 函式
在自訂函式之前,有幾件事需要先了解
- 參數:函式的輸入
- 回傳值:函式的輸出
- 宣告函式在```using namespace std;```之下、```int main()```之上
- 函式一旦執行到```return```,就會立刻回傳,略過之後所有程式碼
- 函式有分為兩種類型:```有回傳值```、```無回傳值```
宣告函式的格式如下
```cpp=
#include <bits/stdc++.h>
using namespace std;
//有回傳值
資料型態 函式名稱 (變數1的資料型態 變數1的名稱, 變數2的資料型態 變數2的名稱...) {
//do anything you want
return 回傳值;
}
//無回傳值
void 函式名稱 (變數1的資料型態 變數1的名稱, 變數2的資料型態 變數1的名稱...) {
//do anything you want
}
int main () {
//your code
}
```
**前面雖然說宣告函式在```using namespace std;```之下、```int main()```之上
但只要先宣告函式,函式主體就可以寫在```int main()```之下**
```cpp=
#include <bits/stdc++.h>
using namespace std;
void 函式名稱 (變數1的資料型態, 變數2的資料型態...);
int main () {
//your code
}
void 函式名稱 (變數1的資料型態 變數1的名稱, 變數2的資料型態 變數2的名稱...) {
//do anything you want
}
```
範例(有回傳值)
```cpp=
#include <bits/stdc++.h>
using namespace std;
int add(int x, int y) {
return (x + y);
}
int main () {
int a, b;
while (cin >> a >> b) {
int ans = add(a, b);
cout << ans << '\n';
}
}
```
範例(無回傳值)
```cpp=
#include <bits/stdc++.h>
using namespace std;
void hw() {
cout << "hello, world" << '\n';
}
int main () {
int n;
cin >> n;
while (n--) {
hw();
}
}
```
#### 遞迴
接下來要講的是遞迴
遞迴最大的特性就是
1. 在函式中使用函式自身
2. 通常有終止條件
一般來說,終止條件就是```return```
不知道大家有沒有聽過```費式數列```
就是```1.1.2.3.5.8.13.21...```
其中我們可以知道
$\begin{cases}
f(0) = 1 \\
f(1) = 1 \\
f(n) = f(n - 1) + f(n - 2) & n \geq 2
\end{cases}$
那如果我們要求$f(4)$
就可以先去求$f(3)$和$f(2)$
要求$f(3)和f(2)$
我們可以從$f(0)和f(1)$推敲
```cpp=
#include <bits/stdc++.h>
using namespace std;
int f(int n) {
if (n == 0 || n == 1) return 1;
else return f(n - 1) + f(n - 2);
}
int main () {
int n;
while (cin >> n) cout << f(n) << '\n';
}
```
那如果沒有回傳值的話
就直接在適當的位置加上```return```
就會回朔到上一次的遞迴
遞迴在之後的演算法可能會很常用到
像是DFS、TREE 都會用到
不過真的沒什麼範例
等真正用到的時候應該可以更加理解
這裡我還是丟一小段我寫DFS的code來解說一下
```cpp=
void findQ (int y) {
for (int i = 0; i < n; i++) {
if (check (i, y)) {
vec[y] = i;
if (y == n - 1) {
if (flag2) cout << '\n';
else flag2 = true;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (j == vec[i]) cout << "Q";
else cout << "_";
}
cout << '\n';
}
ans++;
return;
}
else findQ (y + 1);
}
}
}
```
#### 技術總結
1. 要注意哪時要有回傳值,哪時要用```void```
2. 如果要將函式主體寫在```int main()```之下,那記得一開始宣告只要打資料型態,不用打變數名稱
3. 演算法會很頻繁的用到函式,不過現在真的沒甚麼範例
#### 課堂練習
[DanDanJudge ](http://203.64.191.163/)
我找不到練習 :poop:
------------
## 下學期
### vector
- 預估上完的時間:第一週
- 講課:羅時瀚
#### 課程內容
其實```vector```有點類似```array```
像是```array```可以存```string```,```vector```也可以
只是```vector```的容量比```array```大
而```vector```可以做的事也更多
舉例來說,可以:新增或移除元素、取得長度(雖然應該array也可以)......
#### 宣告
那先來舉例```vector```如何宣告
```cpp=
vector <資料型態> 名稱(長度);
vector <int> vec(5);
```
當然,也可以不用宣告長度
```cpp=
vector <int> vec;
```
#### 函式
1. 存取
- 就像array一樣
```cpp=
vector <int> vec(5);
for (int i = 0; i < 5; i++)
vec[i] = i;
```
2. 新增或移除
- vec.push_back()
新增元素至 vector 的尾端,不過因為要改變記憶體,所以會比一般存取還耗時間
```cpp=
vector <int> vec(5);
for (int i = 0; i < 5; i++)
vec[i] = i;
int n = 5;
vec.push_back(n);
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << ' ';
```
:::info
output : 0 1 2 3 4 5
:::
- vec.pop_back()
刪除 vector 最尾端的元素
```cpp=
vector <int> vec(5);
for (int i = 0; i < 5; i++)
vec[i] = i;
vec.pop_back();
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << ' ';
```
:::info
output : 0 1 2 3
:::
- vec.clear()
清空 vector 裡所有的元素
3. 取得長度
- vec.size()
取得 vector 目前的長度
```cpp=
vector <int> vec;
for (int i = 0; i < 3; i++)
vec.push_back(i);
cout << vec.size() << '\n';
```
:::info
output :3
:::
4. 迭(ㄉ一ㄝˊ)代 ( iterator )
- vec.begin()
回傳一個 iterator,指向vector的第一個元素
- vec.end()
回傳一個 iterator,指向vector的最後一個元素的下一位
```cpp=
vector <int> vec(5);
for (int i = 0; i < 5; i++)
cin >> vec[i];
sort (vec.begin(), vec.end());
```
#### 技術總結
1. vector真的很好用,我學會之後就很少用array了
2. 新增元素的方式要記得,對你們應該是很陌生的
3. 宣告長度是用```()```,不像array是用```[]```
#### 課堂練習
[DanDanJudge](http://203.64.191.163/)
> a589
### 二分搜 (Binary Search)
二分搜顧名思義就是將東西分成兩份,然後再搜尋
不過前提當然是要先將```array``` ```sort```一遍
試玩一次範例 `code` 應該就懂了
1. 先設 `l, r`,代表搜尋的左界跟右界
2. `mid`就是中位數
3. 搜尋的數字比中位數大,`l = mid + 1`
4. 搜尋的數字比中位數小,`r = mid - 1`
:::spoiler `code`
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++) cin >> arr[i];
sort (arr, arr + n);
int m;
while (cin >> m) {
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) / 2;
if (m > arr[mid]) l = mid + 1;
else if (m < arr[mid]) r = mid - 1;
else if (m == arr[mid]) {
cout << mid + 1 << '\n';
break;
}
if (m == arr[l]) cout << l + 1 << '\n';
else cout << "404 not found\n";
}
}
```
:::
#### 技術總結
1. 二分搜在一些檢定很常考,還蠻重要的
2. 記得要先排序好數列再二分搜
#### 課堂練習
[DanDanJudge](http://203.64.191.163/)
> a702 **(其實這一題偏難,練習好基礎的可以來想想看這一題)**