函數進階介紹

前一章中,我們介紹了基本的函數組成,怎麼應用函數,以及為什麼我們需要他。

在這一章中,我們要來更進一步的介紹函數的其他應用以及細節!

參數

一個函數的參數就是在進行函數宣告時出現在 () 中的變數,以前一章的例子來說,num 就是 square() 這個函數的參數,同時也是唯一的參數。

參數的運作方式和一個函數裡的變數的運作方式其實幾乎是一樣的。唯一的差別在於,參數初始化的值是在呼叫這個函數時給予的,而函數裡的變數想當然肯定是在函數裡進行初始化的。

一個函數的參數可以是零個也可以是任意個,比如說

int test()                // 沒有參數
{
    return 0;
}

int test(int a)           // 一個參數
{
    return a;
}

int test(int a, int b)    // 兩個參數
{
    return a + b;
}

參數初始化

那麼一個函數的參數在初始化的時候到底做了些什麼事呢?我們先來看一下一個簡單的函數還有他的呼叫方式

int func(int a)
{
    a = a + 1;
    return a;
}

int main()
{
    int n = 4;
    int result = func(n);

    return 0;
}

在這個例子中,func() 這個函數有一個參數 a,並且這個參數會在函數中被加 1 並且返還。

接著我們來看主程式碼,在 main() 裡面,我們新增了一個變數 n,並讓他的值等於 4。然後我們將 n 傳進函數 func() 中,最後將結果送回給另一個變數 result。在這裡,變數 na 是兩個不一樣的變數,這句話看似是廢話,但其實概念非常重要!每當我們在呼叫函數時,函數的所有參數都會被創建為變數以供函數使用,並且每個傳進來的值都會被複製到相對應的參數作為初始值。這個動作叫做 pass by value。

這是什麼意思呢?也就是說當我們在做這個動作 func(n) 時,程式碼會複製變數 n 的值,並且傳給變數 a,然後函數無論怎麼改動 a,原本的變數 n 都不會被動到。所以其實我們也可以將變數 n 改為 a,就像這樣:

int func(a)
{
    a = a + 1;
    return a;
}

int main()
{
    int a = 4;
    int result = func(a);
    
    return 0;
}

要記得即使現在變數都叫做 a,但是此 a 非彼 a,不論函數如何更改內部的變數,外面的變數都不會被影響到!我們可以分別將 a 印出來就知道了!

int func(a)
{
    a = a + 1;
    std::cout << "函數裡:" << a << std::endl;

    return a;
}

int main()
{
    int a = 4;
    int result = func(a);
    std::cout << "函數外:" << a << std::endl;

    return 0;
}

這樣我們就會看到這樣的輸出:

函數裡:5
函數外:4

但其實在這個很簡單的例子中,我們也可以直接將數字傳給函數,這樣也同樣可以做到參數的初始化!

int main()
{
    int result = func(4);
    return 0;
}

回傳

函數的回傳是另一個大重點,我們一樣拿上面的例子來講解。

在那個例子中,我們可以知道函數 func() 是回傳一個整數 int,並且在函數尾巴時回傳了 a 這個變數。函數會在 return 出現後結束,並且會將要回傳的值,也就是這裡 a 所擁有的值,複製一份並回傳給呼叫函數的地方,也就是這裡的 result。這個步驟我們叫做 return by value。

我們可以發現,這個複製的概念其實跟前一段介紹的參數初始化有著異曲同工之妙。

然而,並不是所有函數都需要回傳一個值。一個函數可以不用回傳任何東西,而這一類的函數我們叫它 void 函數。你可能會問說那這個函數可以做什麼勒?一個常見的例子就是印出當前程式碼的狀態。比如說

void printStatus(int a)
{
    std::cout << "數字 a 是:" << a << std::endl;
}

int main()
{
    int a = 5;
    printStatus(a);

    return 0;
}

注意看在這個函數中我們並不會看到 return 這個關鍵字,而函數也是由 void 起頭。這是因為 void 函數並不會回傳任何東西。所以執行程式碼後,結果就會是

數字 a 是:5

你可能會說,誒這麼簡單的東西我幹嘛要另外寫一個函數啊?在這個簡單的例子裡是這樣沒錯,但是在實際應用中,我們常常會把一個使用者定義的物件傳進函數中,這個物件裡面會有很多資料,然後我們會在這個函數中選擇我們想要印出的物件資訊來了解當前的狀態。

另一個可能會用到 void 函數的例子是當我們有用到「引用」也就是 pass by reference 這個概念,這個概念影響了我們如何操控函數的輸入和輸出。但這個進階的概念我們就留到以後再講吧!如果想現在就瞭解更多的話,我覺得這篇寫得很好,他仔細的比對了 pass by value 和 pass by reference 的差別還有重要性。

回顧 main 函數

理解到函數怎麼運作後,我們來看看 main 是怎麼運作的!我想很多讀者可能早就已經發現,main 本身也是一個函數,這個函數會回傳一個整數 int,而這個函數並不需要參數(有時候可能會看到 argc 和 argv,但這個我們以後再說~)。

當我們執行程式後,電腦會呼叫 main() 這個函數,並一行一行執行我們寫在其中的程式碼。最後,我們會寫下 return 0 表示程式碼執行完畢。為什麼我們要回傳 0 呢?如果是回傳一個整數的話我們可不可以回傳 1?或是 100?其實是可以的!回傳任意整數都可以。但是其實這個數字我們叫做狀態碼 status code。定義上來說,0 這個 status code 告訴了我們電腦整個程式有被成功執行。如果程式碼沒有被成功執行的話,則會回傳一個非零整數,比如說 1 或是 -1。

但其實,在 main() 中我們並不需要特別將 return 0 給寫出來,編譯器如果發現我們沒有特別回傳一個整數的話,它會自動幫我們回傳整數 0。但是常規來說,我們還是會將 return 0 寫出來,不僅告訴別人說我的程式碼在這邊就結束了,也表示我的程式碼應該要可以成功執行。