基礎程式設計(11)-函數與副程式

程式的基本單位是”“,配合流程控制的語法會產生”區塊(Blocks)“,而本篇要談論的是更大一點的函數(Function)與副程式(Subroutines)。當我們撰寫越來越多的程式時,會發現有些程式碼被重覆使用,一旦這些程式碼需要修改,可能會造成前後不一致,或是變數名稱不符等種種問題。總而言之是不容易被維護的。為了方便維護程式,並且節省程式設計師的力氣,因此發展出了函數與副程式的寫法。執行相同動作的程式碼只需要撰寫一次。除此之外,現今的軟體設計講求”物件導向”與”模組化”程式設計,這使得函數與副程式的使用變得更加頻繁。

函數(Function)與副程式(Subroutines)的基本概念

由於不同程式語言撰寫函數與副程式的語法差異很大,所以先用文字描述基本概念,再以VB.NET的語法做範例講解,最後才舉例各種語言的語法(目前只提供Java語法)。選擇VB.NET做範例是因為個人認為它比較好理解並區分函數與副程式的使用差異,而在C風格語言裡兩者語法是一樣的,這是因為函數與副程式的差異不大,看下面基本概念的第一點就可以知道:

  1. 函數與副程式最基本的差異就是:函數有”回傳值”,副程式沒有。
  2. 每一個函數或副程式只負責解決”一個”小問題。
  3. 函數與副程式是程式裡的”黑盒子(Black Box)“,在黑盒子外面不需要知道裡面實作的方法,只需要知道輸入參數(Parameters)與回傳值。
  4. 黑盒子出入口的介面(參數與回傳值)必須定義清楚並且容易了解。

VB.NET Sub副程式

Module Module1
    Sub Main()  '程式執行的入口
        Console.WriteLine("呼叫副程式")
        printStar(1)
        printStar(2)
        printStar(3)
        Console.WriteLine("副程式結束")

        Console.ReadKey()   '讀取鍵盤以暫停視窗
    End Sub

    'Sub 副程式名稱(傳遞方式 變數名稱 As 變數型態)
    Sub printStar(ByVal n As Integer)   '輸出n個星星
        If n <= 0 Then
            Exit Sub
        End If

        For i As Integer = 1 To n
            Label1.Text &= "*"
        Next i
        Label1.Text &= vbCrLf   '輸出換行符號vbCrLf
    End Sub
End Module

讓我們先看看上面範例程式碼第 13 到 22 行(黑盒子)在做些什麼事情。根據第 12 行的說明我們可以知道這個副程式的名稱是”printStar”,並且有一個傳入的 Integer 參數 n (傳遞方式請參考傳值與傳址)。如果副程式有二個以上的參數,用逗號(,)隔開即可。在第 13 行副程式的宣告之後,14 到 21 行是執行的內容。首先為了確保 n 的數值正常,我們用一個 if 來判斷 n 是否小於等於 0,如果是的話就執行第 15 行直接跳出副程式。接著我們用一個很簡單的For迴圈輸出 n 個星星(*),最後再輸出換行符號,這個副程式的內容就到此結束了。

回到整段程式碼的開頭,看看電腦會如何執行這段程式碼。第 3 行輸出”呼叫副程式”的文字後斷行,到第 4 行呼叫副程式 printStar,並且傳入參數 n = 1。這個時候程式會從第 4 行直接跳到第 13 行開始執行副程式。按照先前的理解,我們知道這個副程式會印出 n 個星星之後斷行,所以在輸出 * 之後程式回到第 4 行的下面開始執行第 5 行。第 5、第 6 行的狀況和第 4 行是一樣的,只不過傳入的 n 不同,所以輸出星星的數量也不同。每一次呼叫 printStar 都會跳到第 13 行,並且在執行到 22 行結束時返回剛才呼叫副程式的地方繼續往下執行。因此最後輸出結果參考如下:

example_sub

VB.NET Function函數

Module Module1
    Sub Main()  '程式執行的入口
        Dim a As Double = 3
        Dim b As Double = 4
        Dim c As Double = trigonometric(a, b)
        Console.WriteLine("直角三角型二邊長為 " & a & " 和 " & b & ",則第三邊長為 " & c)

        Console.ReadKey()   '讀取鍵盤以暫停視窗
    End Sub

    'Function 函數名稱(傳遞方式 變數名稱 As 變數型態) As 回傳型態
    Function trigonometric(ByVal a As Double, ByVal b As Double) As Double  '輸入直角三角型二個短邊長
        Dim cSquare As Double = a ^ 2 + b ^ 2   '二邊長的平方相加等於鈄邊平方

        'Return cSquare ^ 0.5
        trigonometric = cSquare ^ 0.5   '回傳鈄邊長
        Exit Function
    End Function
End Module

函數與副程式的基本差異是函數有回傳值,所以在VB.NET除了關鍵字變成 Function 外,第 12 行函數宣告的語法中,後面也多了”回傳型態”。由於一個函數只會有一個回傳值,所以回傳型態也只會有一個。從 12 行可以看到 trigonometric 函數必須輸入 a 和 b 兩個 Double 參數,13 行計算鈄邊平方後( ^ 表示次方),15 到 17 行回傳鈄邊的長度( ^0.5 表示0.5次方=開根號)。一般程式語言習慣使用 15 行的 Return 指令回傳值,不過VB.NET還可以用另一種特殊的回傳方法。首先 16 行將回傳值指定給”與函數名稱相同的變數”(不需宣告),17 行 Exit Function 時就會把”與函數名稱相同的變數值”回傳;如果執行 Exit Function 時這個變數值沒有被指定,系統就會傳送”回傳型態”的預設值。

程式執行的順序類似上面的副程式。由於函數有回傳值,因此第 5 行使用變數 c 來接函數 trigonometric 的回傳值。這裡要注意的是雙方型態必須一致(除非是會自動轉換的鬆散型態程式語言),所以 3、4 行的變數型態必須與 12 行輸入的變數型態一致,而第 5 行則是和 12 行的回傳型態一致。最後就會輸出「直角三角型二邊長為 3 和 4,則第三邊長為 5」的結果。

Java Method方法

Java是物件導向程式語言,所以它用「方法(Method)」來稱呼函數,因為它的概念和傳統函數不太一樣,這在後面物件導向的章節中會加以說明。Java是屬於C風格程式語言,所以它的函數與副程式語法一樣,差別只在於有沒有回傳值。下面用一個完整範例來說明Java方法的使用方式,細部的解說請參考註解:

public class test
{
    //main方法是程式的入口,執行程式從這裡開始
    public static void main(String[] args)
    {
        System.out.println("呼叫副程式");
        printStar(1);
        printStar(2);
        printStar(3);
        System.out.println("副程式結束");
        System.out.println();   //斷行

        double a = 3;
        double b = 4;
        double c = trigonometric(a, b);
        System.out.println("直角三角型二邊長為 " + a + " 和 " + b + ",則第三邊長為 " + c);
    }

    //方法基本宣告方式:
    //物件導向的修飾字 回傳值 方法名稱(傳入參數) {}

    //void表示沒有回傳值
    private static void printStar(int n) {  //輸出n個星星
        if(n != 0) {
            for (int i = 0; i<n; i++) {
                System.out.print("*");
            }
            System.out.println();
        }
    }

    private static double trigonometric(double a, double b) {   //輸入直角三角型二個短邊長
        //Math.pow(底數, 次方):Java計算次方的方法,回傳double值
        double cSquare = Math.pow(a, 2) + Math.pow(b, 2);   //二邊長的平方相加等於鈄邊平方

        return Math.pow(cSquare, 0.5);  //回傳鈄邊長
    }
}

方法的過載(Overload)

Java和C++允許存在兩個以上相同名稱的方法,只要方法傳遞的參數”個數”或是”資料型態”不同即可,稱為「過載(Overload)」。例如下面的程式碼是可以被接受的:

double trigonometric(double a, double b) {}
double trigonometric(double a, double b, double c) {}

程式在執行時,系統會根據呼叫時傳入的參數(數量及資料型態)自動判斷要使用那一個方法。範例可以參考「基礎程式設計(15)-建構子」。