這份講義整理 Vol. 7 直播的內容:完全沒寫過程式也沒關係,看完你會知道程式語言是什麼,也能寫出「輸入資料、做運算、印出結果」的小程式。直播的投影片參考台灣大學資訊系統訓練班的教材,下面的範例則是照直播內容重新整理的。
這是 2021 年 8 月的內容。Dev-C++ 的下載頁面和畫面可能跟當時不一樣,記憶體位址這類數字每台電腦也都不同;C 語言的基本語法則沒有變。
雖然系列標題寫 C/C++,前面幾堂教的都是 C 語言。這裡學到的概念也能帶去學其他語言。今天的目標只有一件事:輸入、運算、輸出。
- 程式語言與工具:為什麼要有程式語言、要裝什麼軟體
- 第一支程式:基本架構、
printf、註解 - 資料與變數:電腦裡有哪些資料、要怎麼存
- 輸入輸出:
printf與scanf的格式 - 運算:算術、轉型、比較與邏輯
- 實作與延伸:梯形面積、回傳值、
system、記憶體位址
一、程式語言與工具
機器語言、程式語言與編譯
電腦聽得懂的是機器語言,一長串 0 和 1。例如(只是舉例)0110 代表加法、0111 代表減法。直接寫不是不行,但要一邊翻手冊一邊寫,實在太累。
所以人設計了程式語言:用英文和數學符號寫,人看得懂,也容易轉成機器語言。越接近人的叫高階,越接近 0 和 1 的叫低階。
把程式語言翻譯成機器語言的過程叫編譯,做這件事的工具叫編譯器。就像不會日文時請翻譯幫忙:
人 → 用編輯器寫程式碼 → 編譯器翻成機器語言 → 電腦執行
程式語言有很多種,例如 C、C++、Java、JavaScript、C#、Python、PHP。C 和 C++ 比較早出現,離機器比較近,所以初學時比較硬;但學完會更理解電腦怎麼運作,後來的許多語言也受它們影響,再學其他語言會比較輕鬆。
編輯器與 IDE
程式碼要用純文字編輯器寫,例如記事本、Visual Studio Code、Sublime Text 3。Word 不行,因為它存檔時還會存顏色、排版這些資訊,編譯器看不懂。
好的編輯器會幫你把不同意義的字標上顏色、打一個字就列出可能的選項。功能越少叫越「陽春」,寫起來越辛苦;但功能太多有時也會礙事,找到自己習慣的就好。
編輯器和編譯器分開下載很麻煩,所以通常會裝整合開發環境(IDE,Integrated Development Environment),把編輯器、編譯器,還有幫忙找 bug 的**除錯器(debugger)**包在一起。常見的有 Visual Studio、Code::Blocks、Dev-C++。
這個系列用 Dev-C++。它很陽春,開發大專案時不太適合,但介面單純,新手比較不會被嚇到,學會概念再換別的 IDE 就好。
- 搜尋
Dev-C++下載,照一般軟體的方式安裝。語言選中文或英文都可以,今天只會用到開檔、寫程式、存檔、編譯、執行。 - 想自己裝編譯器也可以,Windows 上有 MinGW(GCC)或 TCC,用記事本寫完再編譯。
二、第一支程式
基本架構
這是範例程式碼裡的 Code/c04.c,所有程式都從這個骨架開始。剛開始直接複製貼上就好,不用背,寫久了自然會記得。
#include <stdio.h>
int main(){
printf("Hello, World!\n");
return 0;
}
#include <stdio.h>:引入一個檔案。stdio.h是 Standard Input Output,標準輸入輸出的功能庫。要接收鍵盤輸入、把結果印到螢幕,都要靠它。int main():main是「主要的」,程式從這裡開始執行。大括號{ }裡是程式的內容。return 0;:結束程式並回傳 0。int表示main做完會得到一個整數。作業系統看到 0 就知道程式正常結束,其他數字通常代表有錯。
printf 與字串
printf 會把小括號裡的東西印到螢幕上,可以想成數學的函式 f(x):把東西代進去,它幫你做事。
文字要用雙引號包起來,這樣才分得出 printf 是指功能本身,還是 p、r、i、n、t、f 這六個字。
在 Dev-C++ 執行
- 開新檔案(檔案 → 開新檔案 → 原始碼),把程式碼貼進去。
- 按工具列的「編譯並執行」(Compile & Run),也就是先翻譯、再執行。
- 存檔時檔名取
practice.c。預設是 C++ 的副檔名,這裡先選.c。
執行後,上面是你的程式印出來的東西;分隔線下面是 Dev-C++ 附上的資訊,包含執行了幾秒、程式回傳了什麼值。把 return 0; 改成 return 1592;,那裡就會顯示 1592。
換行、分號與排版
\n代表換行。字串中間不能真的按 Enter,所以用符號代替。- 每個指令用分號
;結束。程式會由上而下依序執行。 - 空白和空行不影響執行,全部寫成一行也可以,但我們寫程式就是要讓人看得懂,所以大括號裡會縮排,一個指令寫一行。
下面印出四行,寫成一個 printf、中間放三個 \n 也是同樣的結果,只是比較難看。
#include <stdio.h>
int main(){
printf("abc\n");
printf("def\n");
printf("ghi\n");
printf("jkl\n");
return 0;
}
演練:印出「我是(你的名字)」,下一行印出「我開始學習寫程式了」。
有些中文輸出可能變成亂碼。文字在電腦裡也是用數字存的,怎麼對應叫文字編碼,有興趣可以用這個關鍵字查,這堂課先不談。
註解
註解是寫給人看的,編譯器會直接忽略。
#include <stdio.h> // 引入輸入輸出功能庫
/*
這是多行註解,
中間可以寫很多行。
*/
int main(){
printf("這行會印出來\n");
// printf("這行被註解掉,不會執行\n");
return 0;
}
除了記下當初在想什麼,也可以把一段程式碼暫時註解掉,測試是不是那裡出問題,這是除錯時很好用的方法。
三、資料與變數
基本資料型態
| 型態 | 例子 | C 語言怎麼處理 |
|---|---|---|
| 整數 | 3、-7 |
int 等 |
| 浮點數(小數) | 3.14 |
double、float |
| 字元(一個半形字) | 'A',用單引號 |
char |
| 字串(一串字) | "Hello",用雙引號 |
當成一堆字元 |
| 布林值(對或錯) | true、false | 用整數代替 |
C 主要直接處理前三種。布林值早期就是用整數表示:0 是錯,不是 0 就是對,後來的版本才另外定義布林值。程式語言也會進步,所以編譯器的版本也有影響。
小數叫「浮點數」,是因為電腦用固定位數記數字,再記下小數點在哪,那個點的位置是浮動的。
變數:跟記憶體要一塊空間
資料要存在某個地方。程式會跟記憶體要一塊空間,這塊空間就是變數。電腦用 bit 存 0 或 1,8 bit 是 1 byte。
| 種類 | 大小 | 寫法 | 範圍 |
|---|---|---|---|
| 整數 | 16 bit | short |
−32768 ~ 32767 |
| 整數(最常用) | 32 bit | int |
−2147483648 ~ 2147483647 |
| 整數(數字很大時) | 64 bit | long long |
最大 9223372036854775807 |
| 浮點數 | 32 bit | float |
記得住的位數較少 |
| 浮點數 | 64 bit | double |
記得住的位數較多 |
| 字元 | 8 bit | char |
也是存成數值 |
- 負數多一個值,是因為正數那邊要留一個位置給 0。
- 不存負數時,整數前面加
unsigned(例如unsigned int),正數的範圍變成約兩倍。 - 浮點數範圍很大,但能記的位數是固定的,太長的數字後面會記不住,不要拿它取代整數。
宣告與使用
用變數前要先宣告,也就是跟記憶體要空間。名字自己取,等號 = 的意思是把右邊的值存到左邊的空間。
#include <stdio.h>
int main(){
double num1 = 248.56; // 要一塊存小數的空間,叫 num1,先放 248.56
double num2 = 123.456;
double num3; // 沒給值:裡面是什麼不一定
num3 = num1 + num2; // 兩個值相加,結果存進 num3,蓋掉原本的內容
printf("%lf\n", num3); // %lf 下一節解釋
return 0;
}
沒給初始值的變數,裡面是那塊記憶體原本留下的東西,所以最好一開始就給值。記憶體的內容關機就消失,要永久保存得存到硬碟,這也是遊戲和 Photoshop 要存檔的原因。
四、輸入與輸出
格式化輸出與輸入
printf 和 scanf 的第一個參數都是格式字串,裡面用符號標出「這裡有一個什麼型態的值」,對應的東西用逗號接在後面,照順序帶入。
#include <stdio.h>
int main(){
int num;
scanf("%d", &num); // 從鍵盤讀一個整數,存進 num
printf("%d\n", num); // 把 num 的值印出來
return 0;
}
scanf 的變數前面要加 &,先記得要加,最後一節會解釋。流程是:要一塊空間、把鍵盤輸入存進去、再把值拿出來印到螢幕上。
| 型態 | 符號 |
|---|---|
int |
%d |
short |
%hd |
long long |
%lld |
unsigned int |
%u |
float |
%f |
double |
%lf |
char |
%c |
想看完整的表,可以查 printf 和 scanf 的 reference。
跳脫字元
\ 會讓後面的字跳脫原本的意思:
#include <stdio.h>
int main(){
printf("A\tB\n"); // \t 是 Tab,\n 是換行
printf("他說:\"你好\"\n"); // \" 印出雙引號
printf("要印斜線 n:\\n\n"); // \\ 印出斜線
printf("100%%\n"); // 格式字串裡的 % 要寫成 %%
return 0;
}
實作:輸入兩個整數
#include <stdio.h>
int main(){
int a;
int b;
scanf("%d%d", &a, &b);
printf("A 的值是 %d,B 的值是 %d\n", a, b);
return 0;
}
執行後畫面沒有東西,因為程式在等你輸入。兩個數字要用空白或 Enter 隔開,連在一起打就變成 34 一個數字了。
輸入 3 4,會印出 A 的值是 3,B 的值是 4。
scanf 的格式
scanf 的第一個參數是字串,因為它要描述輸入長什麼樣子。例如要輸入 (3,4):
#include <stdio.h>
int main(){
int a, b;
scanf("(%d,%d)", &a, &b);
printf("%d %d\n", a, b);
return 0;
}
一般的情況不用在 %d 中間加空白,數字本來就會靠空白或 Enter 分開。這個用法比較進階,先知道有這回事就好。
上面還用到另一個寫法:型態相同的變數可以用逗號一次宣告,例如 int a, b;。
變數命名規則
- 可以用英文字母、數字、底線,但不能用數字開頭:
ABC1234可以,1234ABC不行。 - 不能用
!、括號等特殊符號。 - 名字要讓人看得懂。複合字可以寫成
characterLevel或character_level,多人合作時尤其重要。
把 int 1234ABC; 拿去編譯會出錯,訊息大意是「ABC 是整數常數的不合法字尾」。編譯器以為你在寫數字,所以錯誤訊息有時看不太懂,但它會告訴你哪一行有問題。
演練:輸入兩個數字,交換順序印出來。輸入 3 4,要印出 4 3。
#include <stdio.h>
int main(){
int a, b;
scanf("%d%d", &a, &b);
printf("%d %d\n", b, a); // 印的格式長什麼樣子,結果就長什麼樣子
return 0;
}
五、運算
指派與算術
= 不是數學的等號,而是指派:i = 200; 把 200 存進 i;i = j; 把 j 裡的值拿出來存進 i。
算術運算有 +、-、*、/、%(取餘數)和負號。一樣是先乘除後加減,可以用小括號改變順序。
要注意整數除以整數還是整數:50 / 6 是 8,小數部分直接不見。
#include <stdio.h>
int main(){
int a = 1, b = 100, c = 50;
int sum;
sum = (a + b) * c / 2; // 梯形面積:(上底 + 下底) × 高 ÷ 2
printf("%d\n", sum); // 2525
printf("%d\n", 50 / 6); // 8
printf("%d\n", 50 % 6); // 2
return 0;
}
型別轉換
整數相除要算出小數,可以在運算時把值轉型成浮點數:
#include <stdio.h>
int main(){
int a1 = 5, a2 = 2;
double b1 = 5, b2 = 2;
double x, y, z;
x = a1 / a2; // 整數除法:得到 2,存成 2.0
y = b1 / b2; // 浮點數除法:2.5
z = (double)a1 / a2; // 先把 a1 的值轉成 double:2.5
printf("%lf %lf %lf\n", x, y, z);
return 0;
}
x 雖然能存小數,但計算時兩邊都是整數,已經用整數除法算完了。浮點數和整數一起算時,會用相容性較高的浮點數來算。
(double)a1 只有在這次運算時轉換,a1 本身還是整數。
簡寫
| 簡寫 | 意思 |
|---|---|
a++、++a |
a = a + 1 |
a--、--a |
a = a - 1 |
a += b |
a = a + b |
a -= b、a *= b、a /= b、a %= b |
同樣的道理 |
右邊是「把值拿出來算」,算完再存回去蓋掉原本的值。猜猜看最後會印出什麼:
#include <stdio.h>
int main(){
int a = 0;
a += 5; // 5
a++; // 6
a /= 3; // 2
a *= 5; // 10
a -= 4; // 6
a %= 3; // 0
printf("%d\n", a);
return 0;
}
比較與邏輯運算
比較運算有 <、<=、>、>=、==、!=,結果是對或錯。等於要寫兩個等號,因為一個等號已經被指派用掉了。
邏輯運算用來組合對錯:
| 寫法 | 意思 | 例子 |
|---|---|---|
&& |
兩邊都對才對(and) | i > 5 && i < 100:i 在 5 到 100 之間 |
|| |
一邊對就對(or) | i < 5 || i > 100:i 比 5 小或比 100 大 |
! |
相反(not) | !(i < 100):跟 i >= 100 一樣 |
C 的對錯是整數,印出來就是 1 或 0:
#include <stdio.h>
int main(){
int i = 50;
printf("%d\n", i > 5 && i < 100); // 1
printf("%d\n", i == 3); // 0
printf("%d\n", !(i < 100)); // 0
return 0;
}
運算的優先順序很長,不用全背:記得先乘除後加減,其他不確定就加小括號,例如 (i < 100) && (i > 5)。下一堂的條件判斷會大量用到這些運算。
六、實作與延伸
實作:梯形面積
輸入上底、下底、高,算出面積。任何程式的邏輯都差不多:輸入 → 運算 → 輸出。
#include <stdio.h>
int main(){
int a, b, h;
int area;
scanf("%d%d%d", &a, &b, &h);
area = (a + b) * h / 2;
printf("面積是 %d\n", area);
return 0;
}
有兩個直播中真的踩到的地方:
- 一開始忘了除以 2,輸入
2 3 8得到 40。程式會照你寫的算,公式錯了它不會告訴你。修正後是 20。 - 輸入
2 3 5得到 12,但答案應該是 12.5,因為全部都是整數運算。
把型態全部換成 double,%d 換成 %lf:
#include <stdio.h>
int main(){
double a, b, h;
double area;
scanf("%lf%lf%lf", &a, &b, &h);
area = (a + b) * h / 2;
printf("面積是 %.2lf\n", area); // .2 表示小數點後印兩位
return 0;
}
輸入 2 3 5 會印出 面積是 12.50。不加 .2 預設會印到小數點後 6 位。結果也要用 double 存,用整數存的話小數就不見了。
演練:輸入七天的訂閱數增長,算出平均。
#include <stdio.h>
int main(){
int d1, d2, d3, d4, d5, d6, d7;
int average;
scanf("%d%d%d%d%d%d%d", &d1, &d2, &d3, &d4, &d5, &d6, &d7);
average = (d1 + d2 + d3 + d4 + d5 + d6 + d7) / 7;
printf("平均是 %d\n", average);
return 0;
}
變數一多就很難寫,之後會教更好的做法。
return 0 到底有什麼用
main 回傳的值叫結束狀態(exit status,又叫 return code),是程式結束時告訴「執行它的人」的數字。用 Dev-C++ 執行時,它會拿到這個值並顯示出來。0 代表正常,其他數字代表某種錯誤。
直播把 return 0; 拿掉,Dev-C++ 顯示的回傳值變成 11。這個值沒有意義,就像沒給初始值的變數一樣。比較新的 C 標準規定 main 結尾沒寫 return 時視為回傳 0,但不是每種設定都這樣,所以還是寫上去。
邏輯運算的 && 在前面錯了時就不會再看後面,|| 在前面對了時也一樣。命令列可以利用這點串接程式,但判斷的是「是否成功」:a && b 是 a 成功才執行 b,a || b 是 a 失敗才執行 b。注意這裡 0 代表成功,剛好跟 C 語言裡 0 代表錯相反,直播時也講反了一次。
把程式給別人用:system("pause")
編譯後,存檔的資料夾會出現 .exe,這就是可以直接執行的程式(看不到副檔名的話,到檔案總管的「檢視」勾選副檔名)。
但雙擊執行時,輸入完視窗就消失了,因為印完結果就 return 0,程式結束了。
Windows 的命令提示字元(搜尋 cmd)裡有很多指令,例如 dir 列出資料夾內容;pause 會顯示「請按任意鍵繼續」並等待。程式可以用 system 呼叫這些指令:
#include <stdio.h>
#include <stdlib.h> // system 在這裡,編譯器找不到 system 時要加
int main(){
printf("Hello\n");
system("pause");
return 0;
}
stdlib.h是 Standard Library,標準函式庫。- 每個作業系統的指令不一樣,這段在 Mac 上就沒有效果,所以很多書不建議用
system。這裡是為了讓編譯出來的程式能直接給別人用。
不要亂玩系統指令:shutdown 也能這樣呼叫,直播特別宣導不要把會關機的程式傳給朋友或 VTuber。對方還沒存檔的作品沒了,友誼也沒了。
& 與 *:記憶體位址
最後回答為什麼 scanf 要加 &。
記憶體的每個 byte 都有編號。int 占 4 個 byte,假設拿到 7 號,那 7、8、9、10 號都是它的。&a 就是取得 a 開始的編號;* 則相反,從位址取出裡面的值。
#include <stdio.h>
int main(){
int a = 3;
printf("%d\n", a); // 3
printf("%p\n", (void *)&a); // a 的位址,%p 是印位址用的
printf("%d\n", *&a); // 3:取位址再取值,互相抵消
return 0;
}
- 直播是用
%d把位址當整數印出來,得到 6487580;正式寫法是上面的%p。這個數字不保證每次相同,系統只保證給你一塊沒人用的空間。 - 只有編號的話,不知道要抓幾個 byte(
char1 個、int4 個、double8 個),所以直接拿數字來取值要先轉型,例如*(int *)6487580。這個*跟取值的*意思不同,表示「整數的位置」。&a本身就帶著「這是 int 的位址」的資訊,所以不用轉型。 - 亂抓記憶體會被作業系統擋下來。直播隨便寫了一個位址,程式直接當掉,回傳值是 3221225477(Windows 的記憶體存取違規代碼)而不是 0。
現在就能解釋了:
scanf要把鍵盤輸入存進去,所以要知道變數在哪裡,要給&a。給a只是給它裡面的值,例如 3,它不知道要存到哪。printf只要印值,給a就好。不一定要是變數,printf("%d\n", 3456);也可以。
小複習
- 程式語言寫給人看,由編譯器翻成機器語言。
- 程式骨架先複製貼上,內容寫在
main的大括號裡。 - 變數是跟記憶體要的空間,要先宣告,型態決定能存什麼。
printf輸出、scanf輸入,用%d、%lf這類符號標出型態,scanf的變數前面要加&。- 整數除整數還是整數;需要小數就用
double或轉型。 - 程式的邏輯就是輸入、運算、輸出。
作業
把今天的演練做出來(自我介紹、交換兩個數字、梯形面積,或自己想一個公式),截圖發到 Twitter,加上 tag #悠太作業。