第七章 副程式
本章學習目標 讓讀者瞭解主程式與副程式的呼叫方式及如何傳遞參數。 說明遞迴呼叫方式及應用。
本章內容 7-1 副程式 7-2 參數傳遞方式 7-3 傳值呼叫(Call By Value) 7-4 傳址呼叫(Call By Address) 7-5 陣列傳遞呼叫 7-6 自定函數 7-7 內建類別函數庫 7-8 遞迴函數
前言 當撰寫程式時,都不希望重複撰寫類似程式。因此最簡單做法,就是把某些會重複使用程式獨立出來,這個獨立出來的部分就稱做「副程式(Subroutine)」或「函數(Function)」。這樣可以避免一再重複撰寫相似的程式碼,讓程式看起來更有結構,有利於日後維護。
7-1 副程式(1/5) 副程式(Subroutine)就是一個獨立程式區塊,當主程式或其它程式須要它時,直接呼叫副程式即可使用,而為什麼要寫副程式呢?因為可把一些常用且重複撰寫的程式,集中在一個獨立程式中,使程式更加的簡短而易於維護。
7-1 副程式(2/5) 一、副程式的呼叫過程: 一般而言,「原呼叫的程式」稱之為「主程式」,而「被呼叫的程式」稱之為「副程式」。當主程式在呼叫副程式的時候,會把「實際參數」傳遞給副程式的「形式參數」,而當副程式執行完成之後,又會回到主程式呼叫副程式的「下一行程式」開始執行下去。如圖7-1所示:
7-1 副程式(3/5) 【說明】 實際參數實際參數1,實際參數2,……,實際參數N 形式參數 形式參數1,形式參數2,……,形式參數N 定義副程式時,可分為兩種:一種是「有傳回值」,另一種則是「無傳回值」。 如果使用「無傳回值」時,就必須要在副程式名稱的前面加上「void」。主程式呼叫副程式時,不一定要傳遞參數。 結束副程式的方式: (1)副程式執行到右大括號”}” (2)副程式執行到Return
7-1 副程式(4/5) 二、撰寫副程式的優點 可使程式更簡化,因為把重覆程式模組化。 增加程式可讀性。 提高程式維護性。 節省程式所佔用的記憶體空間。 節省重覆撰寫程式時間。
7-1 副程式(5/5) 三、撰寫副程式的缺點:降低執行效率,因為程式會Call來Call去。
7-2 參數的傳遞方式(1/2) 在前面的實例中,主程式呼叫副程式時,並沒有傳遞任何參數,但是為了提高副程式實用性,往往會在呼叫副程式的同時,主程式也會傳遞一些參數給副程式。
7-2 參數的傳遞方式(2/2)
7-3傳值呼叫(Call By Value) (1/5) 是指主程式呼叫副程式時,主程式會將實際參數的「值」傳給副程式的形式參數,而不是傳送位址。因此在副程式中改變了形式參數的值(內容)時,也不會影響到主程式的實際參數值(內容)。其運作原理如下所示:
7-3傳值呼叫(Call By Value) (2/5)
7-3傳值呼叫(Call By Value) (3/5)
7-3傳值呼叫(Call By Value) (4/5)
7-3傳值呼叫(Call By Value) (5/5)
7-4 傳址呼叫(Call By Address) (1/5) 是指主程式呼叫副程式時,主程式會將實際參數的「位址」傳給副程式的形式參數,使得主程式與副程式共用相同的記憶體位址。因此一旦副程式中改變了形式參數的值(內容)時,也將隨之影響到主程式的實際參數值(內容)。因此在使用傳址呼叫時不能使用常數做為參數。系統預設參數傳遞方式為「傳值呼叫」,因此在傳址呼叫時,副程式中的參數必須要加入「ref」關鍵字。其運作原理如下所示:
7-4 傳址呼叫(Call By Address) (2/5)
7-4 傳址呼叫(Call By Address) (3/5)
7-4 傳址呼叫(Call By Address) (3/5)
7-4 傳址呼叫(Call By Address) (4/5)
7-5 陣列傳遞呼叫 (1/3) 在前面單元中,主程式呼叫副程式時,將主程式的實際參數傳遞給副程式的形式參數。而這些參數一般都只是單一變數。但是如果想要一次傳遞非常多變數時,可以利用「陣列」方式達成之。 步驟: 在主程式(呼叫程式)中的實際參數中,只要在陣列名稱前面加上「ref」關鍵字即可。如下列程式中的行號160的BubSort(ref A, 10); 在副程式(被呼叫程式)中的形式參數中,在陣列名稱前面加上「ref」關鍵字,並且資料型態後面的中括號內,不要設定陣列大小。如下列程式中行號230 static void BubSort(ref int[] A, int n)
7-5 陣列傳遞呼叫 (2/3)
7-5 陣列傳遞呼叫 (3/3)
7-6 自定函數(1/3) 函數(Function)是另一種特殊形式的副程式,這種副程式有些存在於VC#2008語言系統裡,稱為「內建類別函數庫」。 例如:曾經使用過的Random( )、Replace( )等等,就是VC#2008的內建類別函數庫。另一種函數就像副程式一樣可以由使用者自己定義的,稱為「自定函數」。
7-6 自定函數(2/3)
7-6 自定函數(3/3)
7-7 內建類別函數庫(1/3) 為了讓程式設計師快速完成程式的需求,VC#2008環境中將一些常用且標準的運算及問題處理方法,設計在VC#2008的函數庫中,此稱為「內建類別函數庫」(Built-In Class Function)。亦即VC#2008程式內部本來就提供的函數,使用者不須再自行定義撰寫,只要拿出來套用即可。
7-7 內建類別函數庫(2/3)
7-7 內建類別函數庫(3/3)
7-8 遞迴函數(1/12) 何謂的「遞迴(Recursion)」是指不斷的呼叫本身函數,並將尚未傳回的函數值暫時存放在主記憶體中(堆疊),等到最後結果產生時,再將主記憶體中(堆疊)的值,逐一傳回。亦即函數本身又可呼叫自己的副程式。遞迴函數呼叫的過程,如下圖所示:
7-8 遞迴函數(2/12) 【說明】 當主程式呼叫Total(4)時,第一次進入Total函數中,N=4,則執行Total=N*Total(N-1)部份,因此,又是呼叫Total函數,所以N=3,則又執行Total=N*Total(N-1)部份,直到N=0時,Total=1,才開始Return回到上一層,逐一回到原先呼叫它的副程式,並且將值傳回去。
7-8 遞迴函數(3/12)
7-8 遞迴函數(4/12) 由以上的例子中,可清楚得知,遞迴函數呼叫過程是不能無限次的呼叫本身。否則會產生無窮迴圈。因此基本上一個合乎演算法的遞迴函數必須要有下列條件: 遞迴函數必須要設定「初值」與「終值」。例如:以上例子中,行號20中的Max=10就是「初值」設定。 行號80中的if (N ==0) 就是「終值」設定。 遞迴函數必須要有更新值。例如:以上例子中,行號110中的MyFunction(N - 1)。變數N的值每次都會遞減,不可以是常數。 遞迴函數必須要自己呼叫自己。例如:以上例子中,行號60的遞迴函數名稱(MyFunction)必須要與行號110 的函數呼叫相同。
7-8 遞迴函數(5/12)
7-8 遞迴函數(6/12)
7-8 遞迴函數(7/12)
7-8 遞迴函數(8/12)
7-8 遞迴函數(9/12)
7-8 遞迴函數(10/12)
7-8 遞迴函數(11/12)
7-8 遞迴函數(12/12)