1、課程大綱
2、第1部份第9課:函數
3、第1部份第10課預告: 練習題+習作
我們的課程分為4大部份,每個部份結束后都會有練習題,并會公布答案。還會帶大家用C語言編寫3個游戲。
C語言編程基礎知識
甚么是編程?
工欲善其事,必先利其器
你的第1個程序
變量的世界
運算那點事
條件表達式
循環語句
實戰:第1個C語言小游戲
函數
練習題
習作:完善第1個C語言小游戲
模塊化編程
進擊的指針,C語言王牌
數組
字符串
預處理
創建你自己的變量類型
文件讀寫
動態分配
實戰:“懸掛小人”游戲
安全的文本輸入
練習題
習作:用自己的語言解釋指針
安裝SDL
創建窗口和畫布
顯示圖象
事件處理
實戰:“超級瑪麗推箱子”游戲
掌握時間的使用
用SDL_ttf編輯文字
用FMOD控制聲音
實戰:可視化的聲音譜線
練習題
鏈表
堆,棧和隊列
哈希表
練習題
最近各種社交媒體和新聞都被姚貝娜和杰倫刷屏了。1個使人惋惜,1個使人高興。
我覺得怎樣也得讓編程來刷1下屏吧。哈哈,開玩笑的,今天就用函數來刷屏好了。
說是刷屏,其實也是翻頁,由于這1課我們將會用函數這個重中之重來結束《C語言探索之旅》的第1部份(基礎部份),而第2部份將要迎接我們的就是C語言的高級技術了。刷走了1個部份,準備迎接新的挑戰。
第2部份會比較難哦,不過不用擔心,我們1點點學習。只要方向對,肯花時間,C語言其實不可怕。
這1課里我們也會給大家講C語言程序所基于的原則。
我們將要學習如何將程序分塊管理,有點像樂高積木...
其實所有C語言的大型程序都是小程序塊的集合,而這些小程序塊我們稱之為函數。在面向對象的語言如Java里面,函數又被稱為方法,固然這里我們只討論C語言(面向進程的語言),不討論面向對象的語言,sorry,我又空話了... 好吧,我們只面向C語言這個對象。
函數的創建和調用
在之前的課程中我們已學過所有的C語言程序都是由main函數開始運行的。那時候我們也展現了1個概要圖,里面有1些術語:
最上面的部份我們稱之為“預處理指令”,很容易辨識,由于以#號開頭,而且總是放在程序的最前面。
下面的部份就是我們要學習的函數了,這里的示例是main函數。
前面說過,C語言的程度都是以main函數為入口函數的。1個C程序要運行,必須要有main函數。只不過,目前為止我們寫的所有程序 包括上1課的小游戲,也只是在main函數里面搗鼓而已,我們還沒跳出過main函數過。沒辦法,有錢任性,沒錢只能…
那你要問了:這樣不好嗎?
答案是:其實不是說這樣不好,但這其實不是C程序員平時所做的。幾近沒有程序員會只在main函數的大括號內部寫代碼。到目前為止我們所寫的程序都還比較短小,但是想象1下如果程序變得很大,代碼幾千幾萬乃至上百萬行,難道我們還把這些代碼都塞在main函數里面嗎?
所以我們現在來學習如何更好地計劃我們的程序。我們要學習將程序分成很多小塊,就像樂高積木的每個小塊1樣,這些小塊搭起來卻可以組成很多好玩的形狀。這些程序小塊我們稱其為函數。
1個函數會履行某些操作,并返回1個值。程序就是1個代碼序列,負責完成特定的任務。
我們說1個函數有輸入和輸出,以下圖所示:
可以把函數想象成1臺制作香腸的機器,在輸入那1頭你把豬裝進去,輸出那1頭就出來香腸了。這酸爽。
當我們在程序中調用1個函數的時候,會順次產生3個步驟:
輸入:給函數傳入1些信息(借著給函數1些參數)
運算:因著輸入里傳進去的信息,函數就能夠完成特定任務了
輸出:做完運算后,函數會返回1個結果。我們稱其為輸出或返回值
舉個實際例子,比如我們有個函數叫做multipleTwo,作用是將輸入乘以2,以下所示:
函數的目的是為了讓源代碼更加結構分明,也節省源代碼數目,由于我們就不用每次都輸入重復的代碼片斷而只需要調用函數就行了。
再假想1下:以后我們可能會想要創建1個叫showWindow(顯示窗口)的函數,作用是在屏幕上顯示1個窗口。1旦函數寫好以后(固然寫的進程是最難的),我們就只需要說:“那個誰,給我去打開1個窗口”,showWindow函數就會為我們在屏幕上顯示1個窗口。我們也能夠寫1個displayPersonage的函數,作用是為我們在屏幕上顯示1個游戲人物。
函數的構成
我們在之前的課中已接觸過函數了,就是非常重要的main函數。但是還是需要我們來介紹1下1個函數的構成究竟是怎樣樣的。
下面是函數的語義學的結構,這是1個需要了解的模板:
類型 函數名(參數)
{
// 函數體,在這里插入指令
}
關于這個模板我們需要掌握4點:
函數類型:對應輸出類型,也能夠把其看作函數的類型。和變量類似,函數也有類型,這類型取決于函數返回值的類型。如果1個函數返回1個浮點數(帶小數點的),那末自然我們會將函數類型定為float或double;如果返回整數,那末我們1般會將類型定為int或long。但是我們也能夠創建不返回任何值的函數。
函數名:這是你的函數的名字。你可以給你的函數起任意名字,只要遵從給變量命名的相同的規則就好。
函數的參數(對應輸入):參數位于函數名以后的圓括號內。這些參數是函數要用來做操作(運算)的數據。你可以給函數傳入任意數量的參數,也能夠不傳入任何參數。
函數體:大括號規定了函數的起始和結束范圍。在大括號中你可以寫入任意多的指令。對上面的multipleTwo函數,需要寫入將輸入的數字乘以2的相干操作指令。
根據函數類型,函數可以分為兩類:
返回1個值的函數,這樣的函數,我們將其類型定為對應的值的類型(char,int,long,double等)
不返回任何值的函數,這樣的函數,我們將其類型定為void(void在英語中是“空的,無效的”之意)
創建函數
我們不要再遲延了,馬上給出1個實例。用的還是我們上面提過的multipleTwo這個函數:這個函數的輸入是1個整型int,輸出也是int類型的數。
int multipleTwo(int number)
{
int result = 0;
result = 2 * number; // 我們將提供的數乘以2
return result; // 我們將2倍的數返回
}
這就是你的第1個除main之外的函數,自豪不?
return result;
這句話1般放在函數體的最后,用于返回1個值。這句話意味著:“函數你給我停下,然后返回這個值”。這里的result必須是int類型的,由于函數類型是int,所以返回值也必須是int類型。
result這個變量是在multipleTwo函數中聲明/創建的,所以它只能在這個函數里面用,不能在另外一個函數比如main中使用,所以是multipleTwo函數的私有變量。
但上面的代碼是否是最簡單的呢?
不是,還可以簡化,以下:
int multipleTwo(int number)
{
return 2 * number;
}
上面的代碼做的是1樣的事情,寫起來也更簡單,函數體內只有1句話。
通常來講,我們寫的函數都會有多個變量,以便做運算,multipleTwo這個函數算是相當簡單了。
多個參數,或沒有參數
多個參數
我們的multipleTwo函數只有1個參數,但是我們也能夠創建有幾個參數的函數,比以下面這個加法函數addition:
int addition(int a, int b)
{
return a + b;
}
可以看到,只需要用1個逗號來分隔參數就行了。
沒有參數
有些函數,雖然不太常見,可能會沒有參數。例如1個用來顯示Hello(你好)的函數:
void hello()
{
printf("Hello");
}
如上所見,這個函數沒有任何參數,另外,可以看到我們還把函數類型定為了void,就是空,所以也沒有return語句用于返回1個值,所以這個函數也沒有返回值。徹徹底底的黑5類…
調用函數
現在我們來看1個程序,溫習1下我們之前學的內容。
我們要用到我們的multipleTwo函數,來計算1個數的兩倍的值。
我們暫時把multipleTwo函數寫在main函數之前,如果放在main函數以后會出錯,以后的課程我們會解釋為何。
#include <stdio.h>
int multipleTwo(int number)
{
return 2 * number;
}
int main(int argc, char *argv[])
{
int initial = 0, twice = 0;
printf("請輸入1個整數... ");
scanf("%d", &initial);
twice = multipleTwo(initial);
printf("這個數的兩倍是 %d ", twice);
return 0;
}
我們的程序是從main函數開始運行的,這個大家已知道了。
我們首先要求用戶輸入1個整數,將其值傳遞給multipleTwo函數,并且把multipleTwo函數的返回值賦給twice這個變量。
仔細看下面這1行,這是我們最關心的1行代碼,由于正是這1行調用了我們的multipleTwo函數。
twice = multipleTwo(initial);
在括號里,我們將變量initial當作輸入傳遞給函數,也正是這個變量,函數將要用于其內部的處理。
這個函數返回1個值,這個值我們賦給twice這個變量。
其實這1行中,我們就是命令電腦:“讓multipleTwo函數給我計算initial的兩倍的值,并且將結果貯存到twice這個變量中”。
詳細的分步解釋
或許對初學者,理解起來還是有些許困難。
不用擔心,我相信通過下面的分步解釋,大家會明白得更透徹。
這個特殊注釋的代碼向大家展現了程序的運行順序:
#include <stdio.h>
int multipleTwo(int number) // 6
{
return 2 * number; // 7
}
int main(int argc, char *argv[]) // 1
{
int initial = 0, twice = 0; // 2
printf("請輸入1個整數... "); // 3
scanf("%d", &initial); // 4
twice = multipleTwo(initial); // 5
printf("這個數的兩倍是 %d ", twice); // 8
return 0; // 9
}
上面的編號表示了履行的順序:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
程序從main函數開始履行
在main函數中的命令1行1行地被履行
履行printf輸出
履行scanf讀入數據,賦值給變量initial
讀入指令... 啊,調用multipleTwo函數了,因此程序跳到上面的multipleTwo函數中去履行
我們運行multipleTwo函數,并接受1個數作為輸入(number)
我們對number做運算,并且結束multipleTwo函數,return意味著函數的結束,并且返回1個值
我們重新回到main函數的下1條指令,用printf輸出
又1個return,這次是main函數的結束,因而全部程序運行終了。
變量initial被傳值給multipleTwo的參數number(另外一個變量),稱為值傳遞。固然其實原理是做了1份變量initial的拷貝,把拷貝賦值給了number,這個值傳遞的概念以后學習指針那1章會再詳述。
這里如果我們把initial改名為number也是可以的,其實不會與函數multipleTwo的參數number沖突。由于參數number是屬于multipleTwo這個函數的專屬變量。
測試程序
下面是程序運行起來的1個實例:
請輸入1個整數... 10
這個數的兩倍是 20
固然你沒必要將multipleTwo函數的返回值賦給1個變量,也能夠直接將multipleTwo函數的返回值傳遞給另外一個函數,就好像multipleTwo(intial)是1個變量1般。
仔細看下面這個程序,跟上面幾近是1樣的,但是修改了最后1個printf的行動,我們也沒有使用twice這個變量,由于沒必要要:
#include <stdio.h>
int multipleTwo(int number)
{
return 2 * number;
}
int main(int argc, char *argv[])
{
int initial = 0, twice = 0;
printf("請輸入1個整數... ");
scanf("%d", &initial);
// 函數的結果(返回值)直接傳遞給printf函數,而沒有通過第3方變量
printf("這個數的兩倍是 %d ", multipleTwo(initial));
return 0;
}
我們可以看到,這次的程序直接將multipleTwo函數的返回值傳遞給了printf函數。
當程序運行到這1行會產生甚么呢?
很簡單,電腦看到這1行是printf函數,所以調用標準輸入輸出庫的printf函數,向printf函數傳遞我們給的所有參數。第1個參數是要顯示的語句,第2個參數是1個整數。電腦又知道要把這個整數值傳遞給printf函數,必須先調用multipleTwo函數,所以它就乖乖地去調用multipleTwo函數,做兩倍乘法運算,并且直接把結果傳遞給printf函數。
這就是函數的層疊式調用,這樣做的好處是,1個函數可以按需調用另外一個函數。
只要愿意,我們的multipleTwo函數也能夠再調用其他的函數,只要你肯寫,然后這個函數再調用其它函數,順次類推。
這就是C語言程序所基于的原則。所有的代碼都是有計劃地組合在1起的,類似樂高積木。
最后,最艱巨確當然是編寫函數了,1旦完成,你就只需要調用它就行了,不需要太擔心函數內部所做的運算。使用函數可以大大下降代碼的重復度,相信我,你會非常需要函數的。
1些函數的實例
如果1起學習過之前的課程,你應當會有這類印象:我就是個例子狂人。
是的,由于我很喜歡用實例來加深理解。我可不是甚么superhero(超級英雄),比如Iron Man,綠偉人,蟻人,等。
由于我覺得理論雖好,但如果只有理論,那我們就不能很好地掌握知識,而且不知道怎樣利用,那就很惋惜了。想起了“勁酒雖好,可不要貪杯哦”那句廣告詞…
所以下面我們會1起看幾個函數的實例,以便讀者對函數有更深入的了解。我們盡可能展現不同情況,使大家看到可能出現的各種函數類型。
歐元/人民幣轉換
最近歐元大貶值,已降到7左右了,我們就來寫1個函數,用于轉換歐元到人民幣。
查了1下最新的匯率:
1歐元=7.1874人民幣元
#include <stdio.h>
double conversion(double euros)
{
double rmb = 0;
rmb = 7.1874 * euros;
return rmb;
}
int main(int argc, char *argv[])
{
printf("10 歐元 = %f 人民幣 ", conversion(10));
printf("50 歐元 = %f 人民幣 ", conversion(50));
printf("100 歐元 = %f 人民幣 ", conversion(100));
printf("200 歐元 = %f 人民幣 ", conversion(200));
return 0;
}
你也能夠寫1個人民幣轉換為歐元的小程序。
懲罰
接下來看1個函數,這個函數不會返回任何值,所以類型是void。這個函數會根據傳入的參數在屏幕上顯示1定次數的信息。這個函數只有1個參數,那就是顯示懲罰語句的次數:
#include <stdio.h>
void punish(int lineNumber)
{
int i;
for (i = 0 ; i < lineNumber ; i++)
{
printf("我不應當有錢任性 ");
}
}
int main(int argc, char *argv[])
{
punish(5);
return 0;
}
顯示結果以下:
我不應當有錢任性
我不應當有錢任性
我不應當有錢任性
我不應當有錢任性
我不應當有錢任性
矩形面積
矩形的面積很容易計算:長 x 寬。
我們來寫1個求矩形面積的函數,它有兩個參數:矩形的長和矩形的寬。返回值是矩形的面積:
#include <stdio.h>
double rectangleArea(double length, double width)
{
return length * width;
}
int main(int argc, char *argv[])
{
printf("長是10,寬是5的矩形面積是 %f ", rectangleArea(10, 5));
printf("長是3.5,寬是2.5的矩形面積是 %f ", rectangleArea(3.5, 2.5));
printf("長是9.7,寬是4.2的矩形面積是 %f ", rectangleArea(9.7, 4.2));
return 0;
}
顯示結果:
長是10,寬是5的矩形面積是 50.000000
長是3.5,寬是2.5的矩形面積是 8.750000
長是9.7,寬是4.2的矩形面積是 40.740000
我們可以直接在函數里顯示 長,寬和計算所得的面積嗎?
固然可以。這樣的情況下,函數就沒必要返回任何值了,函數計算出矩形面積,然后直接顯示在屏幕上:
#include <stdio.h>
void rectangleArea(double length, double width)
{
double area = 0;
area = length * width;
printf("長為 %f 寬為 %f 的矩形的面積是 %f ", length, width, area);
}
int main(int argc, char *argv[])
{
rectangleArea(10, 5);
rectangleArea(3.5, 2.5);
rectangleArea(9.7, 4.2);
return 0;
}
我們可以看到,printf函數在函數體內被調用,顯示的結果和之前把printf放在main函數里是1樣的。只不過我們用的方法不1樣罷了。
菜單
還記得之前的課程中菜單的那個例子嗎?(皇上,您還記得大明湖畔的夏雨荷么?)
這次我們用自定義的函數來重寫1次,會更詳細和優化:
#include <stdio.h>
int menu()
{
int choice = 0;
while (choice < 1 || choice > 4)
{
printf("菜單 : ");