C++ 小社

函式與遞迴

【簡報】蘇西

【講師】children

什麼是函式

抽象化 (abstraction): 

電腦科學中,抽象化(英語:Abstraction)是將資料與程式,以它的語意來呈現出它的外觀,但是隱藏起它的實作細節。抽象化是用來減少程式的複雜度

簡言之,如果要重複處理相同的運算,使用一個函式把那組運算包起來,可以

1) 減少寫重複的程式碼  2) 方便除錯

什麼是函式

f(x) = x^2 + x + 1

數學上的函式:

輸入一個值

對輸入的值進行什麼運算

這樣我們在遇到不同的 x 值時,可以直接丟入 f(x)

什麼是函式

程式上的函式:

int function(int x){
	return x*x + x + 2;
}
int main (){
	
    int x;
    cin >> x;
    cout << function(x);

}

創造這個函式

呼叫這個函式

註:return 就是回傳一個結果的意思

怎麼寫函式

int function (int x){

	return x*x + x + 2;
}

回傳值的變數型態

函式的名稱

輸入的值

  • 若函示沒有要回傳任何值,則「回傳的變數型態」可以寫 void
  • 函式的名稱可以自己隨意取

運算

怎麼叫函式

function(3);

函式的名稱

輸入的值

定義 vs 宣告

int func(int a); // 宣告

int func(int a){ // 進行定義
    return a;
}
  • 宣告結尾要加分號,而且只有一行。
  • 定義時加上兩個中括號,裡面就開始寫內容了。

其實直接定義就好,不一定要宣告。但宣告可以增加專案時程式碼的易讀性。

預設函數值

int sum(int a, int b=20) // 預設 b 為 20
{
  int result;
 
  result = a + b;
  
  return (result);
}
 

我們可以為函數當中的參數設定預設值,如果我們沒有輸入值進去的話,則預設使用這個數字。

呼叫函式值 

// 函數宣告
void swap(int x, int y);

// 函數定義
void swap(int x, int y)
{
   int temp;
 
   temp = x; /* 保存 x 的值 */
   x = y;    /* 把 y 指定给 x */
   y = temp; /* 把 x 指定给 y */
  
   return;
}

假設我們今天寫一個函式來交換兩個數值:

(把 x 變 y,把 y 變 x)

呼叫函式值 

看起來很正確,那我們呼叫看看

int main ()
{
   int a = 100;
   int b = 200;
 
   cout << "交換前,a 的值:" << a << endl;
   cout << "交換前,b 的值:" << b << endl;
 
   // 呼叫函數交換值
   swap(a, b);
 
   cout << "交換後,a 的值:" << a << endl;
   cout << "交換後,b 的值:" << b << endl;
 
   return 0;
}

呼叫函式值 

跑出來超怪

交換前,a 的值:100
交換前,b 的值:200
交換後,a 的值:100
交換後,b 的值:200

為什麼呢?

呼叫函式值 

呼叫函式其實有三種方式:

1. 傳值呼叫 (call by value)

2. 傳址呼叫 (call by address / call by pointer)

3. 傳參考呼叫 (call by reference) 

目前為止我們用的是第一種 call by value,

函式收到的事實上只有 變數的「副本」(非變數本體)

(優點:不會不小心改變原本變數)

(缺點:不會改變原本變數(?)

所以我們要學另外兩種

呼叫函式值 - 傳址呼叫

仙貝芝士🍘🧀 :指摽 (pointer) 與 參考值 (reference)

int a = 10;
int *p = &a;  

p 是指向 int a 的指標,存放 a 在電腦記憶體裡的位址

& a 可以「取 a 的記憶體位址」

*p 可以得到 a 的值

呼叫函式值 - 傳址呼叫

也就是說

int a = 10;

int *p = &a;  

cout << &a;  // 印出 a 的地址 (=一串亂碼)

cout << p;  // 一樣印出 a 的地址 (=一串亂碼)

cout << *p; // 印出在 p 地址的東西的數值(= a的數值 = 10)

呼叫函式值 - 傳址呼叫

#include <iostream>
using namespace std;

// 函數宣告
void swap(int *x, int *y);

// 函數定義
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;        /* 把 y 指定给 x */
   *y = temp;    /* 把 x 指定给 y */
  
   return;
}

利用指標直接從記憶體抓輸入值,就不會抓到副本,可以抓到本體囉

呼叫函式值 - 傳址呼叫

int main ()
{
   int a = 100;
   int b = 200;
 
 
   swap(&a, &b);

   
   return 0;
}

記得呼叫函式時,要用 & 取址

這樣就可以成功交換 a 和 b 了!

呼叫函式值 - 傳參考值呼叫

另外,還有另一種方法可以達到一樣的效果

就是「傳參考值呼叫」(call by reference)

void swap(int &x, int &y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 指定给 x */
   y = temp; /* 把 x 指定给 y  */
  
   return;
}
int main ()
{
   int a = 100;
   int b = 200;
 
   swap(a, b);

   return 0;
}

注意我們 & 只寫了一遍,比較方便。

* 是在 C 就有的東西,& 是在 C++ 才發明出來的方便好東西

遞迴 Recursion

函式最常見的應用就是遞迴了!

 

遞迴就是一個函式(function)在執行的過程中又呼叫自己本身

例如:費波那契數列 (每項為前兩項的和)

1, 1, 2, 3, 5, 8, 13, 21, ...
可以寫成 f(n) = f(n-1) + f(n-2)

這就是一種自己呼叫自己

遞迴 Recursion

構成遞迴需要兩個要件:

1. Base case (基本情況 = 何時停止遞迴)

2. Recursive Case (遞迴情況)

int fibonacci(int n) {
    if (n == 0)      // base case 1
        return 0;
    else if (n == 1) // base case 2
        return 1;
    else              // recursive case
        return fibonacci(n - 1) + fibonacci(n - 2);
}

(註:我們在前面多補一項,把 n=0 時的值設為0)

費波那契用程式實作:

base case 存在讓函式知道什麼時候不用在繼續「往下呼叫自己」

總不可能 f(1) 讓它呼叫 f(0) 和 f(-1)

遞迴 Recursion

從遞迴可以看出函數的實用性了吧!

你們可以實作看看階乘的遞迴嗎

階乘的定義:

n! = n * (n-1)!

舉例來說:

5! = 5 * 4 * 3 * 2 * 1 = 120

遞迴 Recursion

無聊的話可以寫寫看帕斯卡三角形的遞迴

怎麼寫程式輸出這坨東西

解答在下一頁

遞迴 Recursion

進階挑戰:把他依照三角形的格式對齊

沒解答自己想

int pascal(int n, int k) {
    // Base cases
    if (k == 0 || k == n)
        return 1;
    // Recursive case
    return pascal(n - 1, k - 1) + pascal(n - 1, k);
}

下課囉

記得寫作業

記得報寒訓

寒訓連結在 DC

C++ 小社 - 函式與遞迴

By Suzy Huang

C++ 小社 - 函式與遞迴

  • 121