78 GP
[達人專欄] 跟著豬腳 C 起來:程式語言的函數(shù),不只是算數(shù)
作者:解凍豬腳│2020-10-14 07:16:23│巴幣:1,154│人氣:4415
今天來講講 C 語言裡的函數(shù)吧!這次的篇幅有點長,建議你先把開發(fā)環(huán)境準備好,一邊看文一邊試著自己動手做。 還記得上次介紹過了數(shù)學裡的「函數(shù)」 是怎麼一回事。我們知道在研究數(shù)學的時候可以自行設(shè)計一個函數(shù)的規(guī)則,然後再把這個函數(shù)當成工具來使用,例如我們曾經(jīng)提到的「攝氏-華氏溫度轉(zhuǎn)換函數(shù)」: 電腦既然最初是為了計算而生,那麼在程式設(shè)計的領(lǐng)域裡,自然就少不了函數(shù)的蹤影。。在 C 語言要怎麼定義函數(shù)? 我們每次試圖憑空用 C 語言寫東西出來的時候,起手式總是這樣的: #include <stdio.h> int main(void) { // code here return 0; } 在 C 語言裡定義函數(shù)的做法非常簡單,就好像宣告變數(shù)一樣。假如我們想要定義一個從攝氏溫度轉(zhuǎn)換成華氏溫度的函數(shù),只要決定好資料型態(tài)、取好名字,然後放在 main 的上面就可以了: #include <stdio.h> float temp_c_to_f(float c) { return c*9/5+32; } int main(void) { printf("攝氏 56 度等於華氏 %f 度\n", temp_c_to_f(56)); return 0; } 因為程式通常都是從上往下讀取,所以我們得把這些函數(shù)擺在 main 的上頭,這樣 main 才能曉得你要呼叫的函數(shù)到底是個什麼東西。 我把函數(shù)取名為 temp_c_to_f。你可以從現(xiàn)在就開始養(yǎng)成好習慣,我們盡量把函數(shù)命名成可以一看就懂的,這樣當以後遇到程式碼累積了成千上百行又需要編輯、維護的情況,我們?nèi)匀荒軌蚝芸斓卣业轿覀兿胍臇|西、馬上弄懂它在幹嘛。 定義好以後,只要在程式裡呼叫 temp_c_to_f(56) 就會得到 132.8,用起來就像數(shù)學的函數(shù)一樣(這裡的範例圖我改用 %.2f 表示顯示到小數(shù)點後第二位): 當我們呼叫 temp_c_to_f(56) 的時候,系統(tǒng)就會開始執(zhí)行這個函數(shù)的內(nèi)容,並且把這裡面的變數(shù) c 設(shè)為 56,然後做了 56*9/5+32 的計算(得到 132.8),接著再將 132.8 這個結(jié)果回傳。當 132.8 這個值回傳回來,也就終於知道了這個 temp_c_to_f(56) 的值是 132.8。。函數(shù)的型態(tài) 我們在定義函數(shù)的時候,首先要注意到的是 float temp_c_to_f 這裡的 float。函數(shù)開頭的資料型態(tài)表示的是 temp_c_to_f(c) 函數(shù)值的型態(tài),簡單來說它用來決定我們回傳的結(jié)果最後應(yīng)該要是什麼類型。 比如說,我們知道攝氏溫度轉(zhuǎn)成華氏溫度的時候可能會有小數(shù)點,例如攝氏 56 度轉(zhuǎn)換成華氏溫度會變成 132.8 度,既然有了這樣的預期,那我們就應(yīng)該使用 float 或 double 型態(tài)來回傳這個 132.8。 如果你把這個函數(shù)用 int 的格式來回傳資訊的話,那就會發(fā)生強制轉(zhuǎn)型——小數(shù)點以後的資訊都會丟失,本來計算出 132.8 這樣的結(jié)果,就會被轉(zhuǎn)成 132: 再來看看函數(shù)定義括號裡的 float c。這裡的 float c 其實跟前面同理,只是這裡的 float 負責規(guī)範 c 這個變數(shù)的型態(tài)。如果我們改用 int c 來定義函數(shù),卻輸入 56.3 這種含有小數(shù)點的浮點數(shù),一樣會先被強制轉(zhuǎn)成 56,以 int 的模式來計算,最後這個計算結(jié)果又強制轉(zhuǎn)成 float 才傳回來: 所以說,無論是輸入的值還是輸出的值,一個優(yōu)秀的程式設(shè)計師都應(yīng)該要先想好它們的可能情況,然後按照需求決定好應(yīng)該用什麼型態(tài)來傳遞。 注意,這個函數(shù)的變數(shù) c 只會在這函數(shù)裡面有效,這個概念就好像「在 if、for 等等區(qū)塊裡面宣告的變數(shù),只在這個區(qū)塊裡有效」一樣,我們稱它為區(qū)域變數(shù)(local variable);直接在 main 外面宣告的變數(shù),稱為全域變數(shù)(global variable),在各個函數(shù)之間都可以共用。。原型宣告 有的人就是不喜歡把 main 擺在最下面,我們其實也可以把其他函數(shù)移到下面來宣告,但在 main 以前就要預先描述好函數(shù)的大致輪廓,好讓系統(tǒng)有所準備,這樣的宣告方式叫做「原型宣告」: #include <stdio.h> float temp_c_to_f(float); int main(void) { printf("攝氏 56 度等於華氏 %f 度\n", temp_c_to_f(56)); return 0; } float temp_c_to_f(float c) { return c*9/5+32; } 這麼做的好處是,你可以從上半部一眼看到你的所有函數(shù)會以什麼樣的資料型態(tài)來傳遞,如果函數(shù)的原型跟你實際寫出來的函數(shù)長得不一樣,編譯器也會警告你,好讓你避免一些不必要的錯誤:。多變數(shù)函數(shù) 有的時候,某些計算也不是只有一個變數(shù)那麼單純。 假設(shè)我們想要定義一個函數(shù)用來計算手上的比特幣(BTC)和以太幣(ETH)的總價值,這時候變數(shù)就會有兩個了:比特幣的數(shù)量和以太幣的數(shù)量。 我們可以使用逗號來分隔多個自變數(shù): double totalAsset(double btcAmount, double ethAmount) { return btcAmount*11500 + ethAmount*385; } 這樣就能透過 totalAsset(1.27, 5) 直接計算出「1.27 顆比特幣和 5 顆以太幣總共價值多少美金」了。。函數(shù),不只是算數(shù) 不過,程式語言當中的函數(shù)並不只有這麼簡單的功能。它除了「計算」以外,也可以有「行為」的功能,甚至可以不輸入值、不回傳值,連函數(shù)的型態(tài)都省了: sayHello() { printf("hi\n"); printf("我是豬腳\n"); } 在程式設(shè)計的領(lǐng)域裡,由於函數(shù)不單只能被用來做純計算用途,因此「函數(shù)」也可以被稱為「函式」。實際上我們最早學到的 printf() 也是一個函式,只是它被定義在 stdio.h 這個標頭檔裡面,所以我們平常不會看到它的定義內(nèi)容。 我們只要呼叫 sayHello(),就會執(zhí)行函式裡面的程式碼: 不過,一般來說即使沒有任何東西,我們還是會用 void 來維持格式的統(tǒng)一(void 的意思就是「空」,什麼東西也沒有),否則有的函式有寫型態(tài)、有的函式?jīng)]寫型態(tài),那多難看。 void sayHello(void) { printf("hi\n"); printf("我是豬腳\n"); } 推薦大家遇到這種狀況的時候還是要使用 void,程式碼才會漂亮易讀。 要注意的是,函式裡的程式碼在執(zhí)行 return 以後就會回去執(zhí)行外面的程式碼了,也就是說在 return 行為之後的程式碼不會被執(zhí)行:。遞迴函數(shù) 在數(shù)學裡面,有些函數(shù)的值是參考前項的值而得成的。例如很有名的費波納契數(shù)列: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610…… 除了 f(1) 和 f(2) 都是 1 以外,每一項的值都是前兩項的總和: f(3) = f(1)+f(2) = 1+1 = 2 f(4) = f(2)+f(3) = 1+2 = 3 f(5) = f(3)+f(4) = 2+3 = 5 f(6) = f(4)+f(5) = 3+5 = 8 f(7) = f(5)+f(6) = 5+8 = 13 …… 我們得到規(guī)律: 在數(shù)學上,這種以自己作為參考的函數(shù),就稱為遞迴函數(shù)。遞迴函數(shù)在程式語言的定義跟在數(shù)學上的定義方法差不多:我們知道 f(x) = f(x-1)+f(x-2),當程式要求 f(5) 的時候,就會去呼叫 f(4) 和 f(3);想要求 f(4) 的時候,就會去呼叫 f(3) 和 f(2)……如此循環(huán)。 當然我們也不可能讓它一直無限循環(huán)下去,總不可能讓它在求 f(1) 的時候還跑去接著求 f(0) 和 f(-1) 吧?這樣不就沒完沒了了嗎? 所以,我們既然已經(jīng)知道頭兩項的結(jié)果是 1 了,那我們就得在 f(1) 和 f(2) 的時候把函數(shù)值準備好,避免它無限下沉: int fibonacci(int n) { if (n==1 || n==2) { return 1; } return fibonacci(n-1) + fibonacci(n-2); } 當然也可以寫成這樣: int fibonacci(int n) { if (n==1 || n==2) { return 1; } else { return fibonacci(n-1) + fibonacci(n-2); } } 這兩種是沒有差別的,畢竟剛才提到 return 做完就會離開這次的函數(shù)計算了,在邏輯上本來就是二選一。 我們呼叫 f(5),程式就會呼叫 f(4)+f(3),然後先從 f(4) 處理,這裡的 f(4) 呼叫 f(3) 和 f(2),接著從這邊的 f(3) 優(yōu)先處理,呼叫 f(2) 和 f(1),得到了 f(3) = 1+1 = 2,再回頭往上層處理…… 把呼叫順序畫成圖的話會變成這樣(左上角的小數(shù)字是順序): 不信的話我們可以做個實驗,每次函數(shù)被呼叫就 print 一行字出來,這樣我們就可以知道呼叫的順序了,可以知道它是一層一層堆起來的:。程式模組化 我們每次用 C 語言寫程式的時候總會寫到 int main(void) {...},實際上這個 main 本身就是一個函式了。C 語言把程式編譯起來的時候,會預設(shè)先找到叫做 main 的函式,然後從這個函式當作起點開始執(zhí)行,這就是我們老是要定義 main() 的原因。 這些被呼叫的函式也被稱為「副程式」。我們在 main() 裡面呼叫了 sayHello(),讓電腦去執(zhí)行這個函式裡的東西,執(zhí)行完了才又回到 main() 繼續(xù)執(zhí)行下去,這個 sayHello() 就是一個相對於 main() 的副程式。 把時常重複的動作包裝成函式是相當重要的事!假設(shè)你想設(shè)計一個會問人吃飯了沒或拉屎了沒的程式,而且決定在每次問候以前都先自我介紹,你可以這麼做: 這麼做的好處在於,當你想要修改自我介紹內(nèi)容的時候,只要修改一個地方就好了: 如果不使用函式來把這些重複的行為包裝起來,程式碼就會看起來很醜又很難集中起來一起修改: 拿到現(xiàn)實生活來看,沒有好好運用函式來包裝各項功能的程式,就會是這副德性:。Main 函式也可以是別人的副程式 如果你寫好的程式 123.exe 被拿來呼叫,那你這個程式就相當於是別人的副程式,你在這程式的 main 函式裡面 return 的值,就會回傳到當初呼叫的來源。這聽起來有點複雜,直接做一次就知道了。 假設(shè)我們寫一個程式,讓它完成任務(wù)以後回傳一個 5,然後我們把這個程式編譯成執(zhí)行檔並把執(zhí)行檔取名叫做 123.exe: 然後從另一個程式呼叫 123.exe: 我們會發(fā)現(xiàn) 123.exe 的 main() 也可以成為別人的副程式,也可以接收到來自 123.exe 回傳的資訊。 對於一個程式設(shè)計的學習者來說,一般情況下是不會沒事把專案寫成多個 exe 檔啦(這個之後會講到),不過瞭解程式如何呼叫函式、傳遞資料,這點還是蠻重要的。將來如果你想寫的專案相當龐大,那就會需要瞭解這些常識了。 這些就是函式的基本觀念。當然關(guān)於「函式」還是有一些延伸的東西沒說到,不過至少有這些已經(jīng)很夠了。這系列之後看看有什麼東西好講,我再拿出來分享吧。
引用網(wǎng)址:http://www.jamesdambrosio.com/TrackBack.php?sn=4948064
All rights reserved. 版權(quán)所有,保留一切權(quán)利
相關(guān)創(chuàng)作
同標籤作品搜尋:第一次踏入墳場就上手 |從入門到入墳 |從入門到放棄 |C語言 |程式設(shè)計 |C |C++ |寫程式 |程式 |回收業(yè)者
留言 共 14 篇留言
奚隹 奚隹 :
一大早看到這些又想睡了
10-14 08:09
洨布丁 :
學C語言 數(shù)學 邏輯 要很強嗎?
10-14 08:14
解凍豬腳 :
邏輯好的話對於寫程式當然有好處
至於「數(shù)學」的話比較籠統(tǒng),不知道你說的數(shù)學是指哪些方面
至少我認為寫基本的東西不太需要什麼複雜的數(shù)學底子,反倒是流程控制比較吃重
10-14 08:19
洨布丁 :
數(shù)學的部分就是函數(shù)跟微積分,這兩個很常用到嗎?
豬腳有推薦的C語言入門書嗎?
10-14 08:23
解凍豬腳 :
基礎(chǔ)不可能用到微積分,函數(shù)的觀念一定會用到
書本的話我是覺得其實不太需要,我以前開始學寫程式的時候就沒翻過半本書
10-14 08:25
解凍豬腳 :
網(wǎng)路上對於 C 語言大大小小的觀念都可以查得到,你可以試試看直接去 zerojudge 之類的網(wǎng)站做題目當益智遊戲來玩
10-14 08:26
雞塊 :
在C++遇到lambda後就回不去了(X
10-14 08:42
穴穴尼 :
我看到前面的數(shù)學公式就開始昏昏欲睡了…看來本人我完全沒有這方面的天賦 OTZ
10-14 09:25
芊芊∣ㄑㄑ :
豬腳產(chǎn)量極高 佩服!
10-14 09:41
解凍豬腳 :
為好久不見的動力感動得痛哭流涕 [e3]
我的小屋上一次發(fā)文頻率像現(xiàn)在這麼高已經(jīng)是七年前的事
10-14 16:41
鄭曉麥 :
人家不懂硬體和流程啦(′?ω?`),雖然我最早學的就是 C
10-14 10:11
燒餅油條 :
每個敘述後面都有那個/n是做什麼用的
10-14 16:31
解凍豬腳 :
是「\n」,換行的意思
10-14 16:37
義式香草焗烤狐 :
突然為主攻js跟python感到感動,直接放棄型別的概念
10-14 18:56
香菇 :
一開始學C覺得根本外星人語 為了逃避它後來甚至只去修硬體相關(guān)的課
10-15 10:27
我要留言 提醒:您尚未登入,請先
登入 再留言
送出 78 喜歡 ★johnny860726 可決定是否刪除您的留言,請勿發(fā)表違反站規(guī)文字。
前一篇:[達人專欄] 微積分到底...
回創(chuàng)作列表 回頂端
後一篇:[達人專欄] 微分的運算 ...