CS50 原本就提供很詳細的課程筆記了(第一週筆記),所以這個筆記偏向記錄我比較不熟悉的部分而已(事實上這個筆記也是很長,廢話不小心寫太多)。
雖然我學程式的資歷還很淺,但已經不算是完全新手,看完第一週的課程影片後,覺得如果當初 CS50 是我的第一門程式課,我應該是痛苦不堪然後快速投降 XD
剛開始學程式的時候,我花了不少時間才完全理解第一週裡面教到的東西,像是函式、迴圈、條件判斷、資料型態等等,有些甚至到現在都還不算完全理解,像是浮點數。而 CS50 用一堂課就全部都講完了。雖然看完課程之後,我自認有 80% 以上的內容對我而言都是以前學過的東西,或容易理解的概念,但是在寫作業的時候仍然卡關嚴重(因為之前都沒有認真練寫程式 XD)。
即使第一週課程大部分的內容對我來說都不陌生,不過上完課還是收穫很多,Malan 教授上課的脈絡非常清晰,課程的步調也抓得很好,我甚至懷疑他在課堂上連出錯都是原本就安排好的,因為想要讓學生了解寫程式會發生的錯誤和一些邏輯思考,不過到底是不是真的出錯這就不得而知了。
課程影片如果是點進 Lecture 可以選不同觀看模式:
進入之後左下角有 4 種觀看模式,1) Main Cameras 就是一般的觀看模式,2) Multi Screen 是主畫面加上老師的投影片畫面,是蠻好用的觀看模式:
3) 360 Video 觀看模式在一般解析度下(720 或 1080)畫質都比較差,需要把解析度設定成 4k 才會有清楚的畫質。4) Explained 的觀看模式蠻有趣的,在時間軸有灰點的地方,會跑出 Malan 教授跟一個助教以聊天的方式,解釋那一段落為什麼要這樣講解之類的,有興趣的人可以看看:
除了主要的課程影片之外,還有助教對當週課程的補充影片 Shorts,如果單看課程影片還是有不懂的地方,非常推薦把助教的補充影片都看過:
--
不知不覺又打了很多廢話,總算要進入筆記的部分了:
1. 從原始碼 (Source code) 到機器碼 (Machine code)。
原始碼就是人類可讀的電腦語言指令,像是這個:
但是電腦只懂得由 0 和 1 組成的機器碼,像是這個:
要把原始碼「翻譯」成機器碼,這樣電腦才能理解,就要利用編譯器 (compiler)。這三者的關係就是:
原始碼 (Source code) -> 編譯器 (compiler) 翻譯 -> 機器碼 (machine code)
2. CS50 IDE。
CS50 IDE 是 CS50 課程統一使用的雲端整合開發環境 (Integrated Development Environment),可以從 cs50.io 登入。進入 cs50.io 之後在選單下選 edX:
然後輸入 edX 的帳號跟密碼就可以進入 CS50 IDE 裡面。
3. 執行 C 語言所寫的檔案。
建立新的檔案時要在檔名後面加上
.c
的副檔名,這樣電腦才知道那個檔案是 C 的檔案。以
hello.c
這個檔案為例,要執行就要輸入下面的兩個指令:其中的
clang
是 C language 的意思,它是編譯器,所以輸入 clang hello.c
就是叫它編譯 hello.c
這個檔案。編譯後的程式的預設名字是
a.out
,所以輸入 ./a.out
就是執行檔案的意思。a.out
不是直觀易記的指令,所以我們可以藉由輸入參數,叫 clang
把編譯後的程式改成容易記的名字,於是就可以輸入下面兩個指令:其中
-o
是 output
,並指定 hello.c
編譯後輸出到 hello
。所以現在我們只要輸入
./hello
就可以執行檔案了。但看起來似乎沒有比一開始的指令還少,所以最後用
make
這個程式來幫我們簡化:make
不是編譯器,它是一個程式,用來觸發編譯器 clang
執行。所以 make hello
就會自動從 hello.c
這個檔案建立執行檔 hello
。所以以後我們只要利用
make
就可以編譯檔案了,然後輸入 ./hello
就可以執行檔案。4. command line 指令。
由於在 CS50 IDE 裡面需要使用
terminal
來執行許多指令,所以需要熟悉一些常用的指令。在 Linux 作業系統(因為 CS50 IDE 雲端電腦是用 Linux 作業系統)的 command line 指令,常見的有:-
cd
:(change directory) 用來在不同資料夾之間移動。.
表示目前所在的資料夾,..
則表示上一層的資料夾。-
pwd
:(present working directory) 用來印出目前所在資料夾的名稱。-
ls
:(list) 用來列出所在資料夾內的所有檔案。-
mkdir
:(make directory) 用來建立一個資料夾。-
cp
:(copy) 用來複製一個檔案,在指令後面需要打兩個檔案名稱及副檔名,第一個是被複製的檔案名稱,第二個是要複製到的目的檔案名稱。如果要複製資料夾,要加上參數 --r
(recursive),才能夠一起複製資料夾內的所有檔案。-
rm
:(remove) 用來刪除一個檔案。刪除檔案前 terminal
會再次確認是否要刪除檔案,如果想要略過確認問題,可以加上參數 -f
(force),如果要刪除資料夾可以加上參數 -r
,如果想要刪除資料夾而且略過 terminal
的確認問題,可以加上參數 -rf
,使用 -rf
一定要小心,刪除之後就無法回復了。-
rmdir
:(remove directory) 用來刪除一個資料夾。-
mv
:(move) 用來修改檔案的名稱,在指令後面需要打兩個檔案名稱及副檔名,第一個是原始檔案名稱,第二個是新的檔案名稱。5. CS50 Library。
CS50 的團隊有寫一個函式庫 (Library),用來幫助初學者快速學習 C 語言。只要在檔案的開始引入 CS50 Library 即可:
#include <cs50.h>
函式庫顧名思義就是有很多函式 (function) 的地方,在檔案一開始引入的
stdio.h
(Standard Input and Output 的意思)也是函式庫:#include <stdio.h>
stdio.h
內含 C 語言的基本函式,像是 printf
這個函式。要把字串印出來都會用到 printf
(print formatted 的意思)。C 語言在取得使用者的輸入值 (input) 這方面比較弱,所以 CS50 Library 裡包含一些可以取得使用者輸入值的函式,像是:
CS50 Library 還有包含 C 語言裡沒有定義的一些資料型態 (Data type),像是
bool
跟 string
:C 語言在創建變數的時候,第一次使用那個變數時要指定資料型態,這在課程之後會學到的 Python 跟 JavaScript 這兩個語言則不需要指定變數的資料型態。
-
bool
:布林值(只有 True 跟 False 兩種),在 C 語言是 1 byte (8 bits) 的大小。-
char
:用來儲存一個字母的資料型態,在 C 語言是 1 byte (8 bits) 的大小,儲存的值範圍是 -2^7 到 2^7 - 1。-
double
:用來儲存浮點數的資料型態,但大小是 float
的雙倍,所以在 C 語言裡是 8 bytes (64 bits) 的大小,由於大小比 float
大,所以有比較高的精準度。-
float
:浮點數,也稱為實數 (real numbers),在 C 語言裡是 4 bytes (32 bits) 的大小,浮點數較難定義它的值的範圍。浮點數要注意精準度的問題。-
int
:(正/負)整數,在 C 語言裡是 4 bytes (32 bits) 的大小,所以整數範圍是 -2^31 到 2^31 -1。-
unsigned int
:unsigned
並不是一種資料型態,而是 qualifier,可以放在某些資料型態前面(例如 int
)。當你確定所需要的整數只有正整數時,unsigned int
可以拿掉負整數佔用的空間並將正整數加倍,所以數字範圍變成 0 到 2^32 - 1(這個是在助教的 Data Types 補充影片裡)。-
long long
:用來儲存整數的資料型態,大小是 int
的雙倍,所以在 C 語言是 8 byte (64 bits) 的大小。-
string
:字串,用來儲存一串字母(單字、句子或一個段落都可以),在 C 語言是 8 byte (64 bits) 的大小(之後會教到如何增加字串大小)。6. 佔位字元 (placeholder)。
因為變數有不同的資料型態,所以它們的 placeholder 也要區分不同的資料型態。
%c
=> char
%f
=> float
%d
=> double
%i
=> int
%lld
=> long long
%s
=> string
其中浮點數的 placeholder 可以指定要顯示小數點以下幾位的數字,例如要小數點以下二位數字,就是
%.2f
,小數點以下十位數字,就是 %.10f
。7. 數學符號。
+
:加號。-
:減號。*
:乘號,是用星號而不是用 x,因為 x 是變數。/
:除號。%
:(modulus) 計算餘數的符號。=
:一個等號是「指定」的意思,int i = 0
表示把變數 i
設成 0。一個等號的運作方式是:把等號右邊的值「指定」給等號左邊的值。==
:兩個等號才是「等於」的意思。x = x + 1;
等於 x += 1;
等於 x++;
。x = x - 1;
等於 x -= 1;
等於 x--;
。x = x * 5;
等於 x *= 5;
。x = x / 2;
等於 x /= 2;
。8. 布林表式 (Boolean expressions)。
1) Logical operators:
-AND(用 && 表示)
-OR(用 || 表示)
-NOT(用 ! 表示)
(圖片是斜的,因為是從助教的 Operators 補充影片裡截圖下來的,請見諒)
2) Relational operators:
小於等於:x <= y
大於:x > y
大於等於:x >= y
等於:x == y
不等於:x != y
9. 單引號與雙引號。
C 語言裡面有分單引號 (' ') 是用在一個字母的
char
,而雙引號 (" ") 是用在字串 string
。10. 條件判斷 (conditional statements)。
1) if else:
if
跟 else
組合後變成 else if
,可用在判斷多個條件上,這裡我只有截圖最簡單的一個條件判斷。2) switch:有加
break;
跟沒有加 break;
都可以,端看需求而決定,這裡我只有截圖有加 break;
。3) ternary operator (? :):比較特殊的用法,用在快速判斷上。
(圖片是從助教的 Conditional statements 補充影片裡截圖下來的)
11. 迴圈 (loops)。
1) while loop:通常在不知道需要重複幾次的情況下使用,也有可能一次都沒執行。
無限迴圈:
while (true) { } // this is a infinite loop, ctrl + c to kill infinite loop
有限迴圈:
while (boolean-expr) { } // this is a finite loop, it continues until it's false
2) do-while loop:通常在不知道需要重複幾次的情況下使用,但希望它至少執行一次。
do { } while(boolean-expr); // it will run at least one time
3) for loop:通常在知道要重複幾次的情況下使用。
for (int i = 0; i < 10; i++) { }
12. 原型 (prototype)。
這個部分是關於如何設計程式碼,除了用函式庫裡面的函式之外,自己也能定義函式,讓程式碼的可讀性跟可利用性提高。
在定義函式的時候,要另外指定函式是否會回傳值,如果不會的話用
void
,會回傳整數用 int
,並且在 int main(void)
函式前面要先宣告自己定義的函式。用課堂上的例子解說不會回傳值的函式:
#include <cs50.h> #include <stdio.h> void print_name(string name); int main(void) { string s = get_string(); print_name(s); } void print_name(string name) { printf("hello, %s\n", name); }
int main(void)
函式裡面的第 9 行 是自己定義的函式,自己命名 print_name
,並拿使用者的輸入值 s
當作參數,所以在下面第 12 行寫我們想要 print_name(s);
做什麼事情,因為要教 C 語言我們要 print_name(s);
做什麼。 一開始要先指定
print_name()
會不會回傳值,我們只要它做 printf
,所以不會回傳值,於是在函式前面加上 void
。小括號內的 (string name)
代表使用的參數是字串 string
,並且我們把它命名為 name
。而
void print_name(string name)
這個函式要做什麼事情?就是 printf("hello, %s\n", name);
,把 hello 這個字串跟使用者輸入的字串結合起來。最後在
int main(void)
函式前面先宣告 void print_name(string name);
,告訴 C 之後有我們自己定義的函式。因為程式碼是由上往下讀取,如果沒有先宣告函式,C 會出現錯誤。但如果把 print_name(string name)
函式整個搬到 int main(void)
之前,則會喧賓奪主跟影響程式的可讀性,畢竟主要程式是在 int main(void)
裡面,所以傳統上會讓他在前面。另一個例子是有回傳整數,所以用
int
指定函式:#include#include int square(int n); int main(void) { printf("x is "); int x = get_int(); printf("x^2 is %i\n", square(x)); } int square(int n) { return n * n; }
這次我們自己定義的函式叫做
square
,並拿使用者的輸入值 x
當作參數。要先指定
square()
會不會回傳值,我們要它做整數運算,所以會回傳整數,於是在函式前面加上 int
。小括號內的 (int n)
代表使用的參數是整數 int
,並且我們把它命名為 n
。而
int square(int n)
這個函式要做什麼事情?就是把使用者輸入的整數平方,並且回傳 (return)。回傳的概念有點像是,把拿到的值先儲存起來,而不是像 printf
直接印出來,儲存起來之後回傳到我們使用它的地方。拿以前數學學過的 y = f(x) 舉例,f 是方程式,x 是參數,y 就是 return 的值(感謝胡立老師補充)。最後在
int main(void)
函式前面一樣要先宣告 int square(int n);
。如果有人跟我一樣剛開始分不清「變數」 (variable) 跟「參數」 (parameter) 的話,它們的差別在於,參數是在函式裡面的變數,所以在函式小括號內的會稱為「參數」,在函式外的會稱為「變數」。
還有一個是「引數」 (argument),引數跟參數類似,詳細的區分是「引數是用於呼叫函式,參數是方法宣告」(參考:引數 (Argument) vs. 參數 (Parameter))。剛開始只要能分別變數與參數就可以,熟悉之後再細分引數與參數的差別。
把自訂的函式抽離出主要函式,讓每個函式負責小部分,都是為了讓程式碼的重複利用性以及可讀性提高,這些步驟可以稱為抽象化 (abstraction),在多人一起工作寫程式時非常重要。
第一週的課程筆記就到這裡,其實還有幾個重要的部分沒有寫出來,像是浮點數的精準度、數字 overflow 跟編譯的詳細情況,但這篇的篇幅已經蠻長了,所以寫在胡立老師的中文導讀筆記裡 [CS50] Week 1 C - 中文導讀筆記,如果有需要也可以參考那篇筆記。
寫那麼多字可能多少會出錯,如果有錯還請指正。
No comments:
Post a Comment