---
tags : DIT 12th 教學 -- 新生教學
---
<!-- {%hackmd @HungPin/Dark %} -->
# 基本程式介紹
Arduino 的框架是建立在 C / C++ 的基礎上,並且把一些所需要的參數設置都函數化,因此即使不了解底層的設置也能輕鬆上手。在此,我們簡單介紹寫程式的一些基本觀念吧!
所謂的寫程式,其實包含了很多很多步驟,最重要的幾個就是:
:::info
分析 -> 寫 -> 執行 -> 除錯 -> 完成
:::
雖然看起來「寫」好像是主體,但其實分析可以說是這裡面最重要的一個步驟,有一句話是這麼說的:越早開始寫程式,就會越晚結束。如果分析做得太過倉促,常常會出現一些不該有的 Bugs ,讓你在後續的除錯過程當中感到痛苦,所以要記住,好好分析一下程式的輸入跟輸出應該要是甚麼東西,中間的架構、邏輯可以寫下流程圖,如此便會減少除錯的痛苦時光。
---
:::success
:::spoiler 點這裡一下
\
教學文件內有一些這種形式的說明欄,
裡面有很多有趣或是重要的資訊,記得要閱讀喔 ~
如果你準備好了,就讓我們開始一一介紹基本程式的概念吧。
:::
---
## <font color="FFC300">變數 (Variables)</font>
變數是程式中最基礎的東西,它是用來儲存資料的記憶體空間,使用者可以通過宣告不同的變數來將自己所需要的數值、資料存到記憶體當中,並且在日後從該處讀取出來。
用盒子來比喻的話:儲存類型是箱子的類型,特定的物品只能放入特定的箱子;記憶體大小是箱子容量,當物品大小(可儲存數值)超過箱子容量就會發生溢位;而變數的名稱就是箱子的名稱了。
下表為常用的資料型態與其相關資料
| 儲存類型 | 型態 | 占用記憶體大小 | 可儲存數值範圍 |
|:--------:|:---------:|:--------------:|:-------------------------------------------------------:|
| 整數 | short | 2 Bytes | -32,768 至 32,767 |
| 整數 | int | 4 Bytes | -2,147,483,648 至 2,147,483,647 |
| 整數 | long long | 8 Bytes | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
| 小數 | float | 4 Bytes | 小數點後 7 位 |
| 小數 | double | 8 Bytes | 小數點後 15 位 |
| 字元 | char | 1 Bytes | -128 至 127<br>(這些數值會對應到不同符號,如:'@', 'z', '1'等等) |
| 布林值 | bool | 1 Bytes | false 或 true |
可以儲存的數值範圍不需要特別記下來,在一般的使用情境下,我們常常利用 int / double / char / bool 這幾種變數型態來儲存資料,像是整數的部分,除非已經知道需要儲存的資料非常龐大,否則使用 int 應該就非常足夠了。
另外,只要我們理解電腦是如何利用記憶體儲存資料,就可以輕鬆計算出其可儲存的數值範圍。以 short 為例,因為 short 是 2 Bytes,每一個 Byte 相當於 8 bits,而每一個 bit 可以儲存 0 或 1,所以利用二進位的方式表示數字,就可以推算出一個 short 可以儲存的資料範圍為 0 ~ 2^16,但為了可以表示負數,習慣利用一個 bit 來判別,所以一個 short 真正可以表示的數值範圍為 -2^15 ~ 2^15 - 1,也就是上表寫的 -32,768 ~ 32,767。
千萬要記得,根據不同的資料選擇對應的資料型態儲存相當重要,否則可能會發生 溢位(overflow)的情形。
:::success
:::spoiler 舉例說明 : 溢位
\
一個 int 只有 4 Bytes(32 bits),其中有 1 個 bit 用來表達正負號。因此它能儲存的範圍為 -2^31 ~ 2^31-1,也就是上表寫的 -2,147,483,648 至 2,147,483,647。若使用 int 來儲存範圍以外的數字,就會發生溢位而造成程式錯誤,然而因為這種錯誤在語法上是沒有任何問題的,必須等到執行時才會發現錯誤,需要額外注意。
> 在歷史上,就曾經有一艘火箭因為沒注意到溢位問題,導致程式接收到的感測器數值錯誤,影響後面的控制,最終導致墜毀 ... [相關解釋影片](https://www.youtube.com/watch?v=5tJPXYA0Nec)
:::
以下示範如何宣告變數 :
```cpp=
/*
* 語法 :
* 變數型態 變數名稱_1, 變數名稱_2, ... ;
*/
int A; // 宣告一個整數型態的變數,變數名稱為 A
float B = 5.2;// 宣告一個浮點數型態的變數,變數名稱為 B ,儲存的值為5.2
char C, D, E; // 若是需要同時宣告多個變數,可以使用逗號連結。
```
==變數的命名很自由,只有幾個規定== :
- 只能使用大小寫英文、數字、底線
- 不能是數字開頭
- 不能使用保留字(Reserved Keywords),避免編譯器誤解語句的意思。
:::success
:::spoiler 保留字
\
故名思義,就是編譯器保留下來的單字,儘管符合前兩點的限制,但仍然不能使用。
在 C++ 當中有許多保留字,可以參考 [這裡](https://en.cppreference.com/w/cpp/keyword)。
每一個保留字都是有其意義的,像是 int 就是為了表示一種變數型態、 for 就是為了表示迴圈等等,不需要刻意背下來有哪些保留字,只要多多練習程式就會自然記下大部分的保留字 !
:::
合法的變數名稱 : StepMotor_1, ArmServo ...
不合法的變數名稱 : 1_StepMotor, bool, while ...
另外,雖然命名的規則很自由,但普遍會根據變數的功能來命名,讓變數名稱一看就知道是做什麼用的,如此便可以提升程式的可讀性。
:::danger
:::spoiler 補充說明 : 容易被忘記的分號 :angry:
\
C / C++ 有一個比較惱人的語法,就是要在每一個敘述句後面加上分號,代表語句結束。
這是因為 C / C++ 不像是 Python 有嚴格的縮排規定。
剛開始寫程式的時候常常會忘記,要小心 ~
另外,希望大家養成好習慣,寫出乾淨整齊的程式碼,這部分可以多多參考程式專家是如何排版的。若是不喜歡把程式碼排版整齊,可以考慮報名以下比賽。 :see_no_evil:
[International Obfuscated C Code Contest](https://en.wikipedia.org/wiki/International_Obfuscated_C_Code_Contest)
:::
:::success
:::spoiler 註解
\
註解使指一段被特定符號括起來的文字,其不會被編譯器編譯,所以並不會影響程式碼的執行。寫註解是為了幫助自己與別人可以更容易看懂程式碼,就像是寫筆記般。
一個優秀的 programmer 會適時地加上註解,希望大家養成好習慣。
```=
// -> 這個是單行註解,顧名思義,只有當前這一行不會被編譯。
/* */ -> 這個是多行註解,顧名思義,只要在兩個符號之間就可以橫跨很多行。
```
:::
:::success
==<font color="35524a">陣列 (Array)</font>==
\
當資料越來越多的時候,使用陣列來儲存就是一個不錯的方法。傳統的陣列就像是把數個變數綁在一起,有著連續的特性,其在記憶體當中的位置是緊鄰的,如此一來在存取相關的資料時,就可以大幅度地降低時間成本。例如一個 int 陣列會長得像這樣:
```cpp=
// 陣列的起始為0,所以arr[0] = 1,arr[4] = 5,沒有arr[5]
int arr[5] = {1, 2, 3, 4, 5};
```
另外,由於字元組成的陣列實在太常用到了,人們便給了他一個名詞:字串(String),例如 "hello" 就是由5個字元所組成的字串。並且為了讓電腦知道字串到哪裡結束,==<font color="35524a">在每個字串後面都會加一個結束字元'\0'</font>==。例如:
```cpp=
char str[6] = "hello";
```
裡面的6個字元分別為:'h', 'e', 'l', 'l', 'o', '\0'
:::
---
## <font color="FFC300">基本運算符號與表達式 (Operators and Expressions)</font>
常見的運算符號有 *加、減、乘、除* 等等基本數學運算,另外也有邏輯的運算符號,像是 *且、或、大於、小於、等於、不等於* 等等的運算。而表達式就是由一連串的基本運算符號組成,像是 2 * 3 + 4 就是一個表達式,而表達式是從左至右來計算結果的,就像是我們計算等式的方法。
以下簡單舉例一下 :
```cpp=
int num = 0;
num = 5 + 2; // 7
num = num * 2; // 14 (若是左右都有同樣的變數,可以縮寫成 num *= 2; )
num++; // 15 (兩個加號放在一起代表加一,兩個減號就表示減一。放在變數的前後有意義上的差別。)
num = num % 2; // 1 ( % 是指取餘數,所以 15 / 2 = 7 ... 1 )
```
:::success
:::spoiler 介紹 : 常見的運算子
\
C / C++ 有許多種運算子,對於控制 Arduino 來說只需要常見的幾個就夠了。
有興趣的可以到 [這裡](https://en.cppreference.com/w/cpp/language/expressions) 看詳細的定義。
| 運算子 | 功能 |
|:------:|:---------------------------:|
| + | 相加 |
| - | 相減 |
| * | 相乘 |
| / | 相除 |
| % | 取餘數 |
| += | A += B 等同於 A = A + B |
| -= | A -= B 等同於 A = A - B |
| ${*=}$ | A ${*=}$ B 等同於 A = A * B |
| /= | A /= B 等同於 A = A / B |
| %= | A %= B 等同於 A = A % B |
| ++ | 加一的意思 |
| - - | 減一的意思 |
| > | 大於 |
| < | 小於 |
| >= | 大於或等於 |
| <= | 小於或等於 |
在邏輯上面來說 :
- ! : 否定
- == : 等於
- != : 不等於
- || : 或 (or)
- && : 且 (and)
:::warning
:::spoiler 補充說明 : ++ 與 - -
\
這個運算子比較特殊,因為往後會常常遇到,需要特別注意一下。
若是把這個運算子放在變數的前面,代表程式會先把這個變數做加一或減一的動作,再繼續執行。相反的,若是放到變數的後面,就會先執行這一行程式碼,再做加一或減一的動作。
簡單舉例一下 :
```cpp=
int A = 0, B = 0, C = 1;
A = ++C * 5; // 程式會先把 C 加一,再做剩下乘法的部分,也就是說到最後 A 會是 10,而 C 會是 2
B = C-- / 2; // 程式會先做除法,並儲存進 B 後再做減法,也就是說到最後 B 會是 1,而 C 也會是 1
```
:::
---
## <font color="FFC300">判斷式(Decision)</font>
判斷式主要用於決策,當我們希望程式依照不同情境做出不同動作時,就可以使用判斷式來處理。
```cpp=
// 語法 :
if ( 判斷式 1 ) {
// 程式片段 1
}
else if ( 判斷式 2 ) {
// 程式片段 2
}
else if ( 判斷式 ... ) {
// 程式片段 ...
}
else {
// 程式片段 最後
}
```
若是 (判斷式 1)邏輯上是對的,就會執行(程式片段 1),
同理應用在剩下的判斷式。
如果所有判斷式邏輯上都是錯的,就會執行最後一個程式片段。
舉例 :
```cpp=
int num;
if (num % 2 == 0) {
// num 是偶數的話執行這裡
}
else if (num % 2 == 1) {
// num 是奇數的話執行這裡
}
else {
// num 不是前者的任意一個的話執行這裡
}
```
## <font color="FFC300">陳述式 (Expression)</font>
```cpp=
// 語法 :
switch(整數陳述式){
case 整數陳述式1:
// 程式片段 1
break;
case 整數陳述式2:
// 程式片段 2
break;
case 整數陳述式...:
// 程式片段 ...
break;
default:
// 程式片段 ...
}
```
若(case後的整數陳述式1)符合switch()中的陳述式,就會執行該case後的(程式片段 1),default則是默認執行(非必要)。switch會依序判斷每個case,當執行到break時跳出整個switch函式。
舉例 :
```cpp=
int num = 1;
switch(num){
// num == 1 的話執行這裡
case 1:
num = 2;
// num == 2 的話執行這裡
case 2:
num = 3;
// num == 3 的話執行這裡
case 3:
num = 4;
}
// 最後num = 4
```
此時加入break的話......
```cpp=
int num = 1;
switch(num){
// num == 1 的話執行這裡
case 1:
num = 2;
break;
// num == 2 的話執行這裡
case 2:
num = 3;
break;
// num == 3 的話執行這裡
case 3:
num = 4;
break;
}
// 最後num = 2,因為在執行第一個case碰到break跳出來了
```
:::success
:::spoiler 程式區段
\
程式區段就是一段利用左右大括號括起來程式碼,而一個程式區段是可以包含另一個程式區段。
簡單舉例一下 :
```cpp=
if (a == 1) {
a = 2;
}
else if (a > 5) {
if (a < 10) {
a = 0;
}
}
```
在上述的程式碼當中, 1~3 行是一個程式區段,4~8 行是一個程式區段,而 5~7 行也是一個程式區段。
:::
:::success
介紹 : 區域變數與全域變數
## 區域變數 (Local Variable)
每一個變數都有其有效範圍,宣告在某個程式區段內的變數,其有效範圍就只在該程式區段內,==<font color="35524a">離開這個有效範圍,像是在別的程式區段內,就不能夠使用這個變數了</font>==。這種僅存在程式區段內的變數,就稱為區域變數。
## 全域變數 (Global Variable)
不同於區域變數,如果把變數宣告在所有程式區段外,就可以在所有程式區段內共用這個變數,這種變數就稱為全域變數,一旦程式配置了一個全域變數的記憶體空間,這個記憶體空間在程式執行的過程中,不論任何時間、任何位置都將是有效的,不像是區域變數只在特定的程式區域內才有效。
簡單舉例一下 :
```cpp=
int A = 0;
void setup() {
int B = 0;
if (B > 5) {
int C = 0;
}
}
void loop() {
int D;
}
```
在上述的程式碼當中,只有 A 變數為全域變數,其餘都是區域變數。
當宣告了相同名稱的全域變數和區域變數時,在函式中會優先使用它裡面的區域變數,==<font color="35524a">不會影響同名稱的全域變數</font>==。
:::
---
## 迴圈(Loop)
迴圈可以重複執行片段的程式,減少程式碼的重複性,同時它也可以展現出更高維度的動作,像是多重迴圈,如同字面上的意思,就是迴圈裡面又有好幾個迴圈。通常迴圈是程式新手最頭痛的地方,但是只要多加練習,一定就可以體會出它的奧妙了。
C/C++ 最常見實現迴圈的方法有兩種,for-loop 以及 while-loop :
### <font color="FFC300">==for-loop==</font>
```cpp=
// 語法 :
for ( 初始化 ; 判斷式 ; 運算式 ) {
// 重複執行的程式片段
}
```
for 迴圈可以填入三種表達式,可以三種都填入,也可以選擇性填入。
- 初始化 主要用來宣告迴圈用來迭代的變數。
- 判斷式 主要用來確認是否結束迭代。
- 運算式 主要用來調整每次迭代後的變數。
```cpp=
// 假定我們的目標是從 1 累加到 5
int num = 0;
// 不使用迴圈 :
num += 1;
num += 2;
num += 3;
num += 4;
num += 5;
// 使用迴圈 :
// (初始化) i 從 1 開始
// (判斷式) i 大於 5 就結束迴圈
// (運算式) 每次迭代結束後 i 加 1
for (int i = 1; i <= 5; i++) {
num += i;
}
```
### <font color="FFC300">==while-loop==</font>
```cpp=
// 語法 :
while ( 判斷式 ) {
// 重複執行的程式片段
}
```
while 迴圈只使用一個判斷式來決定是否結束迴圈,
只要判斷式成立,迴圈就會不停地跑下去。
上面的例子這次改用 while 迴圈來達成
```cpp=
int num = 0;
int i = 1;
while (i <= 5) {
num += i;
i++;
}
```
上述的目標可以不使用迴圈就達到,但當程式的功能越來越複雜時,
使用迴圈可以有效地提升可讀性,也可以省下寫類似程式片段的時間。
所以請各位務必善用迴圈,不要暴力寫程式碼,否則編譯器會生氣喔 ! :face_with_head_bandage:
:::success
:::spoiler 補充說明:使用 for 還是 while? 該選哪一個?
從 for 迴圈的設計可以看出來,它適合從一個數值數到另一個數值,
而 while 迴圈做起這件事則比較費工夫。因此**通常**我們會說 for 在固定次數時使用,while則在不固定次數時使用。但實際上沒有一定標準,還是要依當時狀況做調整喔!
:::
:::success
:::spoiler 補充說明 : break 和 continue
## break
在一些特別的情況之下,我們會希望程式不要跑完全部的迴圈,在這個時候我們就可以利用 break 來強制程式跳出迴圈。
簡單舉例一下 :
```cpp=
int count = 0; // 初始化 count 為 0
// i 從 1 開始,每次加一,結束條件沒有宣告
for (int i = 1 ; ; i++) {
if (i > 5) { // 如果 i 大於 5 就結束迴圈
break;
}
count += i;
}
```
上面這段程式碼可以從 1 開始累加到 5 為止,也就是說 count 最後會等於 15。
## continue
類似於 break 的想法,同樣用在迴圈內部。使用 continue 會讓程式直接忽略掉剩下的程式碼,直接進入下一次的迭代。
簡單舉例一下 :
```cpp=
int count = 0; // 初始化 count 為 0
// i 從 1 開始,每次加一,加到 10 就停止迴圈
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { // 如果 i 除以 2 餘零,就跳過剩下的程式碼,直接進入下次迭代
continue;
}
count += i;
}
```
上面這段程式碼可以計算出從 1 開始,到 10 為止的奇數總和,也就是說 count 最後會等於 25。
:::
## <font color="FFC300">函式(Function)</font>
若要打個比方,函式就像是一本書,不論使用對象是誰,當有片段程式需要重複使用時,就可以直接翻開書本使用裡面的文章,而不需要再次從頭寫下一樣的內容,對於大型程式開發有著顯著的地位。
```cpp=
// 語法 :
回傳資料型態 函式名稱( 傳入的參數 ) {
// 程式片段 ...
// ...........
// ... 程式片段
return 資料;
}
```
同樣的,我們簡單舉例一下 :
```cpp=
// 目標 : 寫一個判斷是否為偶數的函式
// 回傳值是一個布林值,傳入的參數是一個整數。
bool isEven(int num) {
if (num % 2 == 0) {
return true;
}
else {
return false;
}
}
```
```cpp=
// 我們要使用的時候直接呼叫函式名稱就可以
bool answer = isEven(10);
```
> 迴圈與函式都可以用來重複執行部分片段的程式碼,至於兩者有什麼差別,可以自己上網找 "遞迴" 的相關概念,因為其較為複雜,考慮到這是給程式新手的教學文件,就不在這裡解釋。
:::success
:::spoiler void
\
當不想要函式回傳任何資料時以void作為回傳資料型態,因此也不需要return。若也不須傳入參數的話,括號中不須填入任何參數。
```cpp=
// 目標 : 將全域變數 a, b, c, d 都乘特定的值
int a = 1, b = 2, c = 3, d = 4;
void mult() {
a *= 4;
b *= 3;
c *= 2;
d *= 1;
}
```
```cpp=
// 使用的時候直接呼叫函式名稱
mult();
```
:::
:::info
:::spoiler 好習慣
\
撰寫程式時可以適當的使用空格使程式更方便閱讀。
```cpp=
// 例如
int a = 0, b = 3, c = 7;
a = b * c;
```
```cpp=
// 而非
int a=0,b=3,c=7;
a=b*c;
```
由於C語言沒有嚴格的縮排限制,所以可以把程式碼塞在同一行,但同樣為了程式的可讀性,可適當的在程式間加入空行及縮排。
```cpp=
// 例如
int a = 0, b = 3, c = 7;
float d, e = 2.3;
a = b * c;
d = e;
```
```cpp=
// 可進一步將宣告變數和運算區隔
int a = 0, b = 3, c = 7;
float d, e = 2.3;
a = b * c;
d = e;
```
```cpp=
// 錯誤示範
int a=0,b=3,c=7;float d,e=2.3;a=b*c;d=e;
```
:::
:::success
:::spoiler 補充資料 : 函式庫 ( Library )
\
若是把函式比喻成書本,那麼函式庫就會是一間超大的圖書館,在這裡可以找到許多已經完成的函式,只需要在自己的程式碼當中引入需要的函式庫,就可以快速地達成目標,將寶貴的時間省下來,這就是在程式領域裡常常提到的 :
> Don't reinvent the wheel. :+1:
附上 C/C++ 的參考網址,裡面涵蓋了所有標準函式庫的介紹,以後有看到不懂的函式就可以到網站中尋找解釋。
[C++ Reference](https://cplusplus.com/reference/)
:::
---
# <font color="FFC300">結語</font>
在結束之前,來複習一下前面的概念吧
```cpp=
int main() { // 程式主要跑的地方,他也是一個函式
int x = 0, y = 0; // 宣告 int 變數
x++;
x *= 5; // 進行各種運算
if (x > 3) { // if-else 判斷式
// for 迴圈,讓 y 加上 x 重複 5 次
for (int i = 1; i <= 5; i++) {
y += x;
}
}
else {
// while 迴圈,只要 y 還小於 10,就會一直加上 x
while (y < 10) {
y += x;
}
}
return 0; // 程式結束,回傳值為0
}
```
看完上面的程式簡介,相信大家對於程式都有了一些基本的認識,若一時沒辦法消化也不用擔心,程式的概念本身就需要相當多的時間與練習,網路上也有各種教學文章與練習網站,希望大家可以抱持著好奇的心繼續專研程式。 :slightly_smiling_face:
若是對 C/C++ 比較熟悉了,想要玩玩看 Arduino,但手邊沒有器材的話,可以到以下的網站練習看看,我們以後也會使用這個線上模擬環境來帶領大家使用 Arduino。
[Tinkercad](https://www.tinkercad.com/)