C語言探索之旅 | 第一部分第十一課:函數

做者 謝恩銘,公衆號「程序員聯盟」。 轉載請註明出處。 原文:www.jianshu.com/p/148564646…git

《C語言探索之旅》全系列程序員

內容簡介


  1. 前言
  2. 函數的建立和調用
  3. 一些函數的實例
  4. 總結
  5. 第一部分練習題預告

1. 前言


上一課是 C語言探索之旅 | 第一部分第十課:第一個C語言小遊戲github

這一課咱們將會用函數這個重中之重來結束《C語言探索之旅》的第一部分(基礎部分),而第二部分將要迎接咱們的就是 C語言的高級技術了。小程序

第二部分會比較難,不過不用擔憂,咱們會按部就班,一點點地學習。只要方向對,肯花時間,C語言一點也不可怕。bash

這一課裏咱們也會給你們講 C語言程序所基於的原則。函數

咱們將要學習如何將程序分塊管理,有點像樂高積木。學習

其實全部 C語言的大型程序都是小程序塊的集合,而這些小程序塊咱們稱之爲函數。函數的英語是 function,function 表示「功能;[數]函數」。測試

在面向對象的語言(如 Java,C++)裏面,函數又被稱爲方法(method)。固然這裏咱們只討論 C語言(面向過程的語言),不討論面向對象的語言。優化

2. 函數的建立和調用


在以前的課程中咱們已經學過:全部的 C語言程序都是由 main 函數開始運行的。那時候咱們也展現了一個概要圖,裏面有一些術語:ui

最上面的部分咱們稱之爲「預處理指令」,很容易辨識,由於以 # 號開頭,並且一般老是放在程序的最前面。

下面的部分就是咱們要學習的函數了,這裏的示例是 main 函數。

前面說過,C語言的程度都是以 main 函數爲入口函數的。一個 C程序要運行,必需要有 main 函數。只不過,目前爲止咱們寫的全部程序,包括上一課的小遊戲,也只是在 main 函數裏面搗鼓而已,咱們還沒跳出過 main 函數過。

那你也許會問:「這樣很差嗎?」

答案是:並非說這樣很差,但這並非 C語言的程序員一般所作的。幾乎沒有程序員會只在 main 函數的大括號內部寫代碼。

到目前爲止咱們所寫的程序都還比較短小,可是想象一下若是程序變得很大,代碼幾千幾萬甚至上百萬行,難道咱們還把這些代碼都塞在 main 函數裏面嗎?

因此咱們如今來學習如何更好地規劃咱們的程序。咱們要學習將程序分紅不少小塊,就像樂高積木的每個小塊同樣,這些小塊搭起來卻能夠組成不少好玩的形狀。

這些程序小塊咱們稱之爲函數(function)。

一個函數會執行某些操做,並返回一個值。程序就是一個代碼序列,負責完成特定的任務。

一個函數有輸入和輸出,以下圖所示:

咱們能夠把函數想象成一臺製做香腸的機器,在輸入那一頭你把豬裝進去,輸出那一頭就出來香腸了。這酸爽,不言而喻~

函數就像香腸製造機

當咱們在程序中調用一個函數的時候,會依次發生三個步驟:

  1. 輸入:給函數傳入一些信息(經過給函數一些參數)。
  2. 運算:使用輸入裏傳進去的信息,函數就能夠完成特定任務了。
  3. 輸出:作完運算後,函數會返回一個結果,被稱爲輸出或者返回值。

舉個例子,好比咱們有個函數叫作 multipleTwo,做用是將輸入乘以二,以下所示:

函數的目的是爲了讓源代碼更加結構分明,也節省源代碼數目,由於咱們就不用每次都輸入重複的代碼片斷而只須要調用函數就行了。

再設想一下:

以後咱們可能會想要建立一個叫 showWindow(「顯示窗口」)的函數,做用是在屏幕上顯示一個窗口。

一旦函數寫好以後(固然寫的過程是最難的),咱們就只須要說:「那個誰,給我去打開一個窗口」,showWindow 函數就會爲咱們在屏幕上顯示一個窗口。

咱們也能夠寫一個 displayCharacter(「顯示角色」) 的函數,做用是爲咱們在屏幕上顯示一個遊戲角色。

函數的構成


咱們在以前的課中已經接觸過函數了,就是很是重要的 main 函數。不過咱們仍是須要介紹一下一個函數的構成究竟是怎麼樣的。

下面是函數的語義學的結構,這是一個須要瞭解的模板:

類型 函數名(參數)
{
    // 函數體,在這裏插入指令
}
複製代碼

關於這個模板咱們須要掌握四點:

  1. 函數類型:對應輸出類型,也能夠把其看作函數的類型。和變量相似,函數也有類型,這類型取決於函數返回值的類型。若是一個函數返回一個浮點數(帶小數點的),那麼天然咱們會將函數類型定爲 float 或者 double;若是返回整數,那麼咱們通常會將類型定爲 int 或 long。可是咱們也能夠建立不返回任何值的函數。

  2. 函數名:這是你的函數的名字。你能夠給你的函數起任意名字,只要聽從給變量命名的相同的規則就好。

  3. 函數的參數(對應輸入):參數位於函數名以後的圓括號內。這些參數是函數要用來作操做(運算)的數據。你能夠給函數傳入任意數量的參數,也能夠不傳入任何參數。

  4. 函數體:大括號規定了函數的起始和結束範圍。在大括號中你能夠寫入任意多的指令。對於上面的 multipleTwo 函數,須要寫入將輸入的數字乘以 2 的相關操做指令。

根據函數類型,函數能夠分爲兩類:

  1. 返回一個值的函數。這樣的函數,咱們將其類型定爲對應的值的類型(char,int,long,double,等)。

  2. 不返回任何值的函數。這樣的函數,咱們將其類型定爲 void(void 表示「空的,無效的」)。

建立函數


仍是用一個實例來講明吧,用的仍是咱們上面提過的 multipleTwo 這個函數:

這個函數的輸入是一個整型 int,輸出也是 int 類型的數。

int multipleTwo(int number)
{
    int result = 0;
    result = 2 * number;  // 咱們將提供的數乘以 2
    return result;  // 咱們將 2 倍的數返回
}
複製代碼

這就是你的第一個除了 main 之外的函數,自豪不?

return result; 這句話通常放在函數體的最後,用於返回一個值。這句話意味着:「函數你給我停下,而後返回這個值」。這裏的 result 必須是 int 類型的,由於函數類型是 int,因此返回值也必須是 int 類型。

result 這個變量是在 multipleTwo 函數中聲明/建立的,因此它只能在這個函數裏面用,不能在另外一個函數(好比 main)中使用,因此是 multipleTwo 函數的私有變量。

但上面的代碼是否是最簡單的呢?

不是,還能夠簡化,以下:

int multipleTwo(int number)
{
    return 2 * number;
}
複製代碼

上面的代碼作的是同樣的事情,寫起來也更簡單,函數體內只有一句話。

一般來講,咱們寫的函數都會有多個變量,以便作運算,multipleTwo這個函數算是至關簡單的了。

多個參數,或沒有參數


多個參數

咱們的 multipleTwo 函數只有一個參數,可是咱們也能夠建立有幾個參數的函數,好比下面這個加法函數 addition:

int addition(int a, int b)
{
    return a + b;
}
複製代碼

能夠看到,只須要用一個逗號來分隔參數就行了。

沒有參數

有些函數可能會沒有參數。例如一個用來顯示 Hello(「你好」)的函數:

void hello()
{
    printf("Hello");
}
複製代碼

如上所示,這個函數沒有任何參數。此外,能夠看到咱們還把函數類型定爲了 void,因此也沒有 return 語句用於返回一個值,因此這個函數也沒有返回值。

調用函數


如今咱們來看一個程序,複習一下咱們以前學的內容。

咱們要用到咱們的 multipleTwo 函數,來計算一個數的兩倍的值。

咱們暫時把 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("請輸入一個整數... ");
    scanf("%d", &initial);

    twice = multipleTwo(initial);
    printf("這個數的兩倍是 %d\n", twice);

    return 0;
}
複製代碼

咱們的程序是從 main 函數開始運行的,這個你們已經知道了。

咱們首先請求用戶輸入一個整數,將其值傳遞給 multipleTwo 函數,而且把 multipleTwo 函數的返回值賦給 twice這個變量。

仔細看下面這一行,這是咱們最關心的一行代碼,由於正是這一行調用了咱們的 multipleTwo 函數。

twice = multipleTwo(initial);
複製代碼

在括號裏,咱們將變量 initial 做爲輸入傳遞給函數。也正是 initial 這個變量,函數將要用於其內部的處理。

這個函數返回一個值,這個值咱們賦給 twice 這個變量。

其實這一行中,咱們就是「命令」電腦:「讓 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("請輸入一個整數... ");  // 3
    scanf("%d", &initial);  // 4

    twice = multipleTwo(initial);  // 5
    printf("這個數的兩倍是 %d\n", twice);  // 8

    return 0;  // 9
}
複製代碼

上面的編號表示執行的順序:

1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9

  1. 程序從 main 函數開始執行;

  2. 在 main 函數中的命令一行一行地被執行;

  3. 執行 printf 輸出;

  4. 執行 scanf 讀入數據,賦值給變量 initial;

  5. 讀入指令,調用 multipleTwo 函數了,所以程序跳到上面的 multipleTwo 函數中去執行;

  6. 運行 multipleTwo 函數,並接受一個數做爲輸入(number);

  7. 對 number 作運算,而且結束 multipleTwo 函數。return 意味着函數的結束,而且返回一個值;

  8. 從新回到 main 函數的下一條指令,用 printf 輸出;

  9. 又一個 return,此次是 main 函數的結束,因而整個程序運行完畢。

變量 initial 被傳值給 multipleTwo 的參數 number(另外一個變量),稱爲值傳遞。

固然其實原理是作了一份變量 initial 的拷貝,把拷貝賦值給了 number,這個值傳遞的概念之後學習指針那一章時會再詳述。

這裏若是咱們把 initial 更名爲 number 也是能夠的,並不會與函數 multipleTwo 的參數 number 衝突。由於參數 number 是屬於 multipleTwo 這個函數的專屬變量。

測試程序


下面是程序運行起來的一個實例:

請輸入一個整數... 10
這個數的兩倍是 20
複製代碼

固然你沒必要將 multipleTwo 函數的返回值賦給一個變量,也能夠直接將 multipleTwo 函數的返回值傳遞給另外一個函數,就好像 multipleTwo(intial) 是一個變量。

仔細看下面這個程序,跟上面幾乎是同樣的,可是修改了最後一個 printf 的行爲,咱們也沒有使用 twice 這個變量,由於沒必要要:

#include <stdio.h>

int multipleTwo(int number)
{
    return 2 * number;
}

int main(int argc, char *argv[])
{
    int initial = 0, twice = 0;

    printf("請輸入一個整數... ");
    scanf("%d", &initial);

    // 函數的結果(返回值)直接傳遞給printf函數,而沒有經過第三方變量
    printf("這個數的兩倍是 %d\n", multipleTwo(initial));

    return 0;
}
複製代碼

咱們能夠看到,此次的程序直接將 multipleTwo 函數的返回值傳遞給了 printf 函數。

當程序運行到這一行會發生什麼呢?

很簡單,電腦看到這一行是 printf 函數,因此調用標準輸入輸出庫的 printf 函數,向 printf 函數傳遞咱們給的全部參數。

第一個參數是要顯示的語句,第二個參數是一個整數。

電腦又知道要把這個整數值傳遞給 printf 函數,必須先調用 multipleTwo 函數,因此它就乖乖地去調用 multipleTwo 函數,作兩倍乘法運算,而且直接把結果傳遞給 printf 函數。

這就是函數的層疊式調用,這樣作的好處是,一個函數能夠按需調用另外一個函數。

只要願意,咱們的 multipleTwo 函數也能夠再調用其餘的函數,只要你肯寫。而後這個函數也能夠再調用其它函數,依次類推。

這就是 C語言程序所基於的原則。全部的代碼都是有規劃地組合在一塊兒的,相似樂高積木。

最後,最艱難的固然是編寫函數了。一旦完成,你就只須要調用它就行了,不須要太擔憂函數內部所作的運算。

使用函數能夠大大下降代碼的重複度。相信我,你會很是須要函數的。

3. 一些函數的實例


若是一塊兒學習過以前的課程,你應該會有這種印象:我就是個「例子狂人」。

是的,由於我很喜歡用實例來加深理解。

由於我以爲理論雖好,但若是隻有理論,那咱們就不能很好地掌握知識,並且不知道怎麼應用,那就很惋惜了。想起了「勁酒雖好,可不要貪杯哦」那句廣告詞…

因此下面咱們會一塊兒看幾個函數的實例,以便讀者對函數有更深刻的瞭解。咱們儘可能展現不一樣狀況,使你們看到可能出現的各類函數類型。

歐元/人民幣轉換


咱們來寫一個函數,用於轉換歐元到人民幣。

查了一下最新的匯率:1 歐元 = 7.8553 人民幣元。

#include <stdio.h>

double conversion(double euros)
{
    double rmb = 0;

    rmb = 7.8553 * euros;
    return rmb;
}

int main(int argc, char *argv[])
{
    printf("10 歐元 = %f 人民幣\n", conversion(10));
    printf("50 歐元 = %f 人民幣\n", conversion(50));
    printf("100 歐元 = %f 人民幣\n", conversion(100));
    printf("200 歐元 = %f 人民幣\n", conversion(200));

    return 0;
}
複製代碼

你也能夠寫一我的民幣轉換爲歐元的小程序。

懲罰


接下來看一個函數,這個函數不會返回任何值,因此類型是 void。這個函數會根據傳入的參數在屏幕上顯示必定次數的信息。

這個函數只有一個參數,那就是顯示懲罰語句的次數:

#include <stdio.h>

void punish(int lineNumber)
{
    int i;

    for (i = 0 ; i < lineNumber ; i++)
    {
        printf("我不該該有錢任性\n");
    }
}

int main(int argc, char *argv[])
{
    punish(5);

    return 0;
}
複製代碼

顯示結果以下:

我不該該有錢任性
我不該該有錢任性
我不該該有錢任性
我不該該有錢任性
我不該該有錢任性
複製代碼

矩形面積


矩形的面積很容易計算:長 x 寬。 咱們來寫一個求矩形面積的函數,它有兩個參數:矩形的長和矩形的寬。返回值是矩形的面積:

#include <stdio.h>

double rectangleArea(double length, double width)
{
    return length * width;
}

int main(int argc, char *argv[])
{
    printf("長是 10,寬是 5 的矩形面積是 %f\n", rectangleArea(10, 5));
    printf("長是 3.5,寬是 2.5 的矩形面積是 %f\n", rectangleArea(3.5, 2.5));
    printf("長是 9.7,寬是 4.2 的矩形面積是 %f\n", 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\n", 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 函數裏是同樣的。只不過咱們用的方法不同罷了。

菜單


還記得以前的課程中菜單的那個例子嗎?(「皇上,您還記得大明湖畔的夏雨荷麼?」)

此次咱們用自定義的函數來重寫一次,會更詳細和優化:

#include <stdio.h>

int menu()
{
    int choice = 0;

    while (choice < 1 || choice > 4)
    {
        printf("菜單 :\n");
        printf("1 : 北京烤鴨\n");
        printf("2 : 麻婆豆腐\n");
        printf("3 : 魚香肉絲\n");
        printf("4 : 剁椒魚頭\n");
        printf("您的選擇是 ? ");
        scanf("%d", &choice);
    }

    return choice;
}

int main(int argc, char *argv[])
{
    switch (menu())
    {
        case 1:
            printf("您點了北京烤鴨\n");
            break;
        case 2:
            printf("您點了麻婆豆腐\n");
            break;
        case 3:
            printf("您點了魚香肉絲\n");
            break;
        case 4:
            printf("您點了剁椒魚頭\n");
            break;
    }

    return 0;
}
複製代碼

這個程序還能夠改進:

你能夠在用戶輸入一個錯誤的數字時顯示一個錯誤信息,而不是直接繼續讓其點單。

4. 總結


  1. 函數之間能夠互相調用,所以 main 函數能夠調用 C語言系統定義好的函數,例如 scanf 和 printf 等,也能夠調用咱們本身定義的函數。

  2. 一個函數接受一些變量做爲輸入,咱們將其稱爲函數的參數(也有空(void)參數的函數)。

  3. 函數會用這些參數來作一系列的操做,以後會用 return 返回一個值(也有無返回值的函數)。

5. 第一部分練習題預告


今天的課就到這裏,一塊兒加油吧!

下一課:C語言探索之旅 | 第一部分練習題

下一課咱們來作一些幫助鞏固知識點的練習題吧!


我是 謝恩銘,公衆號「程序員聯盟」運營者,慕課網精英講師 Oscar 老師,終生學習者。 熱愛生活,喜歡游泳,略懂烹飪。 人生格言:「向着標杆直跑」

相關文章
相關標籤/搜索