Related Posts Plugin for WordPress, Blogger...

2020年10月27日

【程式學習】哈佛CS50x學習筆記_Week1:C語言

that wanaka tree
寫了第一個C語言程式後,感覺自己都變得像是專業駭客一樣聰明了

 *本文是CS50x的中文學習筆記,是原課程的筆記補充,建議先看完課影片,將原課程筆記與本文對照閱讀。由於是學習筆記,裡頭記載的知識可能有誤,請小心服用。

Week0的課程只是小菜,Scratch雖然是完整的程式語言,但是大部分被拿來當教學用,要學真正的程式語言,還是得從基本學起。
所以這週從C語言開始,據聞如果能夠理解C語言,基本上學什麼語言都能夠快速上手,一起來學吧!
不過如果你是超新手,在這個階段會面臨很大很大的挫折,你會無法理解,然後開始懷疑人生。這都是很正常的,因為有太多資訊要記,所以放寬心,一點一點地接觸,熟悉了就不會再害怕了。基礎的程式語言真的非常非常簡單,只是量太多你一下子記不住而已。所以雖然cs50x是9週課程,但是其實你大可以花12週、16週、18週(我自己也打算這麼學),重點就是靜下心,大量練習
此外,我建議練習時,不要複製貼上課程筆記裡面的語法,請自己一個字一個字打上去,增加熟悉度。如果遇到有不懂的語法,請先自行Google。在初學者等級時遇到的問題,基本上都能在網路上找到答案。
你可以參考胡立的在2017年的解說影片,內容可能與2020年的課程有些不同,但是大致上概念相同。
C語言
如果你有好好地玩過一次Scratch,對於C語言就不會那麼陌生。因為兩者其實很類似。
that wanaka tree
點擊綠旗會說hello, world

這段程式碼是同樣的行為。
程式碼最上方的#include <stdio.h>是一個工具箱,它稱為前置處理指令 (preprocessing directive),所以就是把 stdio.h 這個工具箱(在安裝編譯器的子資料夾下) 放在原始程式的最開頭,那麼電腦就可以執行stdio.h定義的指令。比如說你的電腦不認識什麼叫做printf函式,執行#include <stdio.h>,你的電腦就會去找stdio.h工具箱裡的工具,然後它就會知道printf的意思,是要把括弧裡面的字串印出來。
在Scratch裡面,要點下綠旗才開始執行程式積木。這個動作有一點類似int main(void)這串,每個C語言程式的開頭都會有這串,可視為程式的進入點。int就是整數integer的意思,意思是程式執行完後,會回傳一個整數給系統。
那main(void)裡的void是什麼意思呢,void就是虛無、空白的意思,意思是在main()裡面不要放入任何參數。
printf指print,f是formatted的意思,就是會按照格式印出來。最後結尾會用一個 ; 符號,代表程式結束了。這個結尾符號超級容易忘記放,所以執行程式發生錯誤,要趕快檢查一下是不是忘記放結尾符號。
在Scratch平台,可以透過圖形化執行你的程式。我們如何執行C語言呢,CS50x提供了一個C語言沙盒,你可以不用在你的電腦安裝C語言環境。
*裝語言環境這件事,一開始也是讓我感到疑惑,語言環境到底是什麼意思呢。我現在對於此事的理解是,因為電腦只懂得2進位,所以我們必須告訴電腦C語言每一個符號代表的意思,電腦該如何執行。這個東西就叫做語言環境。所以不管是C語言、Python,或是JavaScript,都要安裝對應的語言環境。你也可以直接安裝IDE,也就是整合開發環境,把常用的語言環境一口氣裝一裝。例如Visual Studio、Visual Studio Code、Sublime Text、ATOM、Jupyter。因為這裡你要學C語言,所以如果你要在你自己的電腦執行C語言,我推薦Visual Studio。至於要怎麼在Visual Studio執行C語言,可閱讀這篇文章
進入CS50沙盒後,首先看到底下畫面。

that wanaka tree
直接按create就好

that wanaka tree
要開啟一個新程式,就在上方按+

that wanaka tree
新創一個C語言檔案,副檔名就叫c,然後按「Create File」
that wanaka tree
輸入程式碼之後,按一下旁邊資料夾圖案

that wanaka tree
可以看到這裡有一個檔案叫hello.c,接著我們在底下的終端機(terminal)編譯這份檔案,不然電腦看不懂
that wanaka tree
首先我輸入ls,確認這裡有一份檔案叫hello.c,輸入clang hello.c 會產生一個a.out的檔案,clang意思是c language,a.out意思是組合輸出(assembly output)的意思。這裡面的檔案就是2進位的檔案,專門給電腦讀的。接著你輸入./a.out,就會執行這個檔案。

*你在終端機輸入的這些指令叫做command line,如果你使用的是Windows 10,可以在下方的搜尋欄直接輸入power shell,按下Enter,這就是你電腦裡的終端機,你可以在這裡直接跟電腦溝通。你之後勢必要學習command line的,不過現在可以先跳過。
噠啦,你寫了你的第一個C語言程式。
你可能會注意到printf("hello, world\n");,裡面的\n不知道是什麼意思。\n就是換行的意思。如果沒換行,游標會停在hello, world後面。要注意\n必須在引號""裡面。
在終端機使用clang hello.c會自動產生a.out的檔案。如果你想取新檔名呢?每次都叫a.out,這樣會搞混。簡單,你可以輸入clang -o hello hello.c,意思是編譯成hello這份機器碼,然後把來源檔案hello.c放在最後。你也可以直接輸入make hello,就是要把hello.c編譯成hello機器碼。
*由於C語言是編譯式語言,所以每次都要要依賴編譯器(compiler)將程式編譯成機器碼。這樣的好處是執行速度比較快。我第一個學會的語言Python是直譯式語言,不需要編譯器,在執行時會一行一行編譯,速度會稍微慢一點,好處是可以馬上測試,就會知道有沒有哪邊打錯。
接著試試看另一個常用的scratch程式積木

that wanaka tree

這個積木會詢問你叫什麼名字,當你輸入名字後。你的名字會儲存在answer這個變數裡。如果我輸入我的名字Kai,接著會印出hello, Kai。
我們把上面的積木改為C語言。
首先你會注意到多了一個cs50.h,這是因為課程在教學時,教師發現可能會需要使用一些常用的函式,所以乾脆都寫在cs50.h這個工具箱裡面,你在沙盒裡面可以直接使用,但是如果在本地端就無法使用哦!所以先用沙盒學習就好了。
string是指要把name這個變數定義為string。提了這麼多次變數,變數(variable)是什麼意思?變數就是一個記憶體空間,可以把它視為一個盒子。string name就是說我要把這個記憶體空間取名叫做name,然後這個空間我要拿來放string,也就是專門用來儲存文字符號。
*要注意如果把整數定義為string,電腦也會視為string。例如我們若把 5 定義為string,要把這個 5 跟 3 相加,就不會變成 8 。因為這邊的 5 視為文字。
這裡的=不是「等於」的意思。在程式語言裡,=是指將右邊的值,賦值給左邊。如果我們把=右邊的get_string("What's your name?\n");,視為一個物體,然後把=左邊的name,視為標籤,則=就是把name標籤貼在物體上的行為。那如果要表示「等於」怎麼辦,那就直接用兩個等號:==。
在printf()函式裡面的範例,長得跟前面的範例不太一樣。%s叫做placeholder,它有各種型態,如%c、%f、%i,對應到不同的資料型態。使用方法就是放在引號裡面,然後在引號外面逗點,再寫上你要放的變數。
*資料型態是學習程式語言必須要知道的知識,程式語言裡,記憶體可以儲存的資料型態有下列幾種,例如string(文字)、bool(True或False)、int(整數)、float(含有小數的數字),其他還有好幾種,請見原課程筆記。
因為我們有加入新的前置處理指令cs50.h,C語言預設沒有這個,所以要在終端機編譯的話,得在命令行裡輸入 clang -o get get_string.c -lcs50 ,最後面的-l,是for link的意思。就是要把cs50.h跟此檔案連結起來。不過也可以直接輸入make "檔名",就會自動連結。
加數字
接著來說說一個對新手很疑惑的語法(至少我當初很疑惑)。

that wanaka tree
將變數counter設為0

that wanaka tree
counter每次迴圈都+1

如果你仔細閱讀過上面的文章,第一行的int counter = 0;應該不難理解,就是把一個記憶體取名counter,定義為int整數,然後賦值為0。可是第二行是什麼東西?
基本上你比較少會看到counter = counter + 1;的呈現方式,一般來說你會看到counter +=1;或者counter++。這個意思是把counter+1的意思,通常是搭配迴圈一起使用,所以counter才會一直+1,就可以數數啦。
迴圈與條件運算式
接著介紹幾個常用的迴圈與條件運算式。
無窮迴圈:
要關掉無窮迴圈,直接按ctrl+c即可。
這個迴圈的意思就是,當while裡面定義的事情為真,就會一直執行裡面的程式。那如果裡面定義的事情為假呢?就會停下來。
while迴圈也可以寫為有限迴圈。
while()裡面要放布林值,也就是問True或False。當某個情況為真,裡面的程式就會不斷執行,所以每次迴圈都會先檢查是否為真,再執行。while迴圈可以用在你根本不知道要跑幾次迴圈的情況
do-while迴圈
跟上面的迴圈很像,他會執行大括號裡面的程式,然後確認while裡面的布林值是否為真。
兩者有何不同呢,至少do-while迴圈會執行一次,一般的while迴圈,如果檢查為假,就不執行了。do-while迴圈用在你至少想跑一次迴圈的情況
接著示範上面說的加數字,在程式碼裡是這樣寫的。
while裡面是布林值,因為我一開始設i=0,然後每次都會檢查i是否小於50,答案當然不是嘛,所以就會執行程式。程式第二行的i++,意思是i要+1。接著程式會回到while去檢查布林值。反覆這樣50次以後,當i=50,程式就會停止。
for迴圈
for迴圈跟while迴圈很像,兩者也能混用。不過主要是用在你已經知道要跑幾次迴圈的情況。for裡面的函式,意思是把一個記憶體空間取名為i,i宣告為int整數,i小於50,每次執行完,i+1。所以這一個迴圈,也會跟上面的while迴圈一樣,列印50次hello, world。
if...else條件運算式
if...else條件運算式,用中文念起來就是:如果怎樣,就執行某程式,反之如果怎樣,那就執行另一段程式。else if專門用在if後面,如果if裡面的布林值為假,就跳到else if,看看裡面的布林值是否為真,假設還不是,那就執行else。else都是放在最後,代表說上面的都沒有的話,那就執行最後的式子。
課程筆記中有許多範例使用了上述的迴圈及條件運算式,請盡量去試著做看看。
巢狀迴圈
對於新手來說,巢狀迴圈(nested loop)會有點棘手,不太好理解,這部分得花一些時間。
巢狀迴圈,就是指在一個迴圈裡,還有一個迴圈。
課程舉的例子其實很好理解,所以在這邊我不用課程舉的例子,另外用台灣人熟習的九九乘法表示範給大家看。
結果如下:

that wanaka tree

在第一層迴圈裡,i會從1跑到9。
在第二層迴圈裡,j也會從1跑到9。
所以當i = 1時,i會分別乘上1、2、3...9;當i=2時,i繼續分別乘上1、2、3...9,以此類推。
這部分一定要試試看才知道,建議多練習幾次。
不精確的浮點數
電腦需要仰賴記憶體來儲存資料,然而記憶體的容量是有限的,因此程式語言在設計上,也會限制儲存容量。這個問題導致浮點數運算時,在小數點後會有奇怪的數字。
例如底下這個例子:
首先說明一下%.50f,意思是那裡將取小數點後50位。
如果我們設x = 1, y = 10時,執行結果如下:
x: 1
y: 10
x / y = 0.10000000149011611938476562500000000000000000000000
奇怪,電腦壞了嗎?x / y不是應該是0.1嗎?後面那串是什麼?如果是取小數點後50位,也應該是0.10000000000000000000000000000000000000000000000000啊。
這裡就要先來談基本的數學概念了。浮點數是什麼意思?
整數我們都知道什麼意思,例如12塊蛋糕,這個12就是整數。
我們拿出其中一塊,切成4等分,每份就是0.25塊蛋糕,如果我們切成8等分,就是0.125塊蛋糕。理論上我們能夠無限制一直切下去。
然而就像是前面提到的,電腦的記憶體有容量極限,可是浮點數卻可以無限地分下去,那怎麼會有空間容納所有浮點數呢?
所以就有了一個IEEE 754的浮點數運算標準,就是一個大家說好的標準,透過這個標準,大家有一個準則來計算浮點數。
詳細要解釋IEEE 754太複雜了,況且我也不是很清楚,還是回頭來講為什麼浮點數運算會有不精確的情況。根據我閱讀相關文章的理解,浮點數運算的問題,出在10進位與2進位之間的轉換。
比如,5的2進位計算方式是2² + 2⁰,表現為101。
that wanaka tree

那5之後如果有小數點,例如5.5呢?計算方式必須將小數點乘以2,一直乘到小數為0,且依序由上往下取出整數。用說得太模糊了,直接計算在下面給大家看。
that wanaka tree

0.5換算成2進位,其運算方式為0.5*2=1.0,取小數點前的數字1,做為2進位小數點後的第一位。
因此,5.5的2進位是101.1
再來一個,5.25

that wanaka tree

小數點後的第一位:0.25*2=0.5,取0
小數點後的第二位:0.5*2=1.0,取1
5.25的2進位是101.01
那5.7呢?
that wanaka tree

小數點後的第一位:0.7*2=1.4,取1
小數點後的第二位:0.4*2=0.8,取0
小數點後的第三位:0.8*2=1.6,取1
小數點後的第四位:0.6*2=1.2,取1
小數點後的第五位:0.2*2=0.4,取1
...
...
可以看出0.7在轉換成2進位的過程中,會變成無限循環的小數。所以這就造成了在電腦裡運算浮點數,會造成錯誤的原因。
整數逸出
整數逸出(integer overflow)跟浮點數計算出錯的問題很相似,都跟記憶體不足有關。我們先看課堂的示範。
如果你有照著課程示範,自己實作一次,會發現這個語法就是把數字以等比級數的方式,不斷地乘以2上去,sleep函式就是讓程式停一秒,避免一下就爆掉結果你不知道發生什麼事。
所以這個程式不斷執行,到了1073741824就出錯了。請記得按ctrl+c終止。
怎麼會這樣?前面說到小數點後的數字因為能夠無限切下去,所以如果有循環小數就會出錯。同樣的整數也無法無止盡地加上去,記憶體也無法容納。你可能覺得不太對勁,不是啊,我的電腦記憶體明明就有32G,好啦扣除系統、Spotify或者一些其他零零總總在背景執行的程式,至少還有16G以上吧。結果跑到1073741824就給我當掉了,怎麼回事?
原因是C/C++語言是以32位元來設計的,不管你系統是不是64位元,或者記憶體有多大都一樣。所以如果數字超過32位元可以容納的範圍,程式就會出錯。
假設我有一台電腦是4位元的,數字最大只能從-7到+7,然後我用等差級數的方式,計算數字,直到7。
that wanaka tree
算到7,剛好每個位元都填滿
那8呢?
that wanaka tree
8無法正確地轉換成4位元的有號數(有正負符號的),所以就變成0。

作業
作業有兩個,一個是畫馬利歐兄弟裡面的磚頭,一個是檢查信用卡是哪一家的,然後是不是有效的卡片號碼。
他們的難易度又分成簡單與困難兩種,我挑困難的來寫,畢竟都學了快一年的Python了,總是有點基礎。
如果你真的想好好地練習,建議您最好別參考我的答案,試著自己寫看看。
但是如果你已經卡了好幾天了,那就試試看我的答案,然後自己想一次。
此外,課程提供了檢查程式style50,由於程式不只是要轉譯給電腦看,更是要給人閱讀的。所以style50會檢查你有無留空白,有無加註comment之類的。建議最好是讓style50檢查一次。
Mario

Credit Card
Happy coding!

沒有留言:

張貼留言

謝絕廣告,感恩