ETH官方钱包

創(chuàng)作內(nèi)容

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 篇留言

超巨型戀戀
OAO

10-14 07:20

美好的過去漸行漸遠
才華洋溢

10-14 07:22

奚隹 奚隹
一大早看到這些又想睡了

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

雪之王女?F?巧可奈
一大早就PO文也太辛苦

10-14 08:43

洨布丁
豪 謝豬腳

10-14 08:49

穴穴尼
我看到前面的數(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ī)文字。

前一篇:[達人專欄] 微積分到底... 後一篇:[達人專欄] 微分的運算...

追蹤私訊切換新版閱覽

作品資料夾

------------------ (0)

豬腳生活 (1)
日常雜談、巴哈大小事 (194)
煞氣a國中生 (7)
高中生活日誌 (55)
大學生活日誌 (34)
冬令營回憶錄 (19)
也許藏有一些小祕密吧? (3)
各式各樣的開箱文 (11)
貓科動物時間 (15)

------------------ (0)

繪圖創(chuàng)作 (1)
電繪插圖、草稿 (199)
短篇漫畫、單幅標語 (61)
上課太無聊的手繪塗鴉 (8)
不知道該怎麼分類的綜合作品 (18)

文字創(chuàng)作 (1)
草莓兵的國軍紀實 (14)
我與らい的點點滴滴 (12)
那些榮耀的時刻與心跳加速的瞬間 (60)
有感而發(fā)的隨筆之作、無法分類的短文 (17)

------------------ (0)

語言學習 (1)
日語:天気がいいから (5)
粵語:唔好再淨係識講粗口喇 (6)
英語:Hey, you! (1)

程式設(shè)計及電腦網(wǎng)路 (1)
系列文:跟著豬腳 C 起來 (10)
系列文:論壇網(wǎng)站運作原理 (3)
Rust (7)
Go(Golang) (11)
Ruby / RGSS (7)
Visual Basic (13)
JavaScript (1)
各種原理 (17)

思想:多思考一下,世界會更不一樣 (1)
網(wǎng)路經(jīng)驗、社會觀察 (23)
檸檬庫 (21)

數(shù)學:我來拯救你的期中考了 (1)
各類基礎(chǔ)觀念 (5)
國中生也能懂的微積分 (9)
微分方程 (0)

小說:用筆鋒劃出新世界的入口 (1)

繪圖:我也想畫出私巴拉西的美圖 (0)
擺脫廉價感的九種方法 (3)
兄弟,一起畫圖嗎? (7)
未分類繪圖筆記 (7)

------------------ (0)

施工中 (22)

不堪回首的痕跡、雜物堆放 (31)

------------------ (0)

未分類 (0)

colanncolann
【天文研究】2024/11/2 C/2023 A3 (紫金山-阿特拉斯)彗星 http://www.jamesdambrosio.com/creationDetail.php?sn=6032975看更多昨天22:20


face基於日前微軟官方表示 Internet Explorer 不再支援新的網(wǎng)路標準,可能無法使用新的應(yīng)用程式來呈現(xiàn)網(wǎng)站內(nèi)容,在瀏覽器支援度及網(wǎng)站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現(xiàn)和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業(yè)系統(tǒng)版本才可使用)

face我們了解您不想看到廣告的心情? 若您願意支持巴哈姆特永續(xù)經(jīng)營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】