方法與函數

      在〈方法與函數〉中留言功能已關閉

函數(function)

在C/C++中, 把重複的工作, 獨立成一個副程式, 稱為函數. 副程式是早期Basic的說法. 副程式沒有傳回值, 但函數可以有傳回值.

在下面的代碼中, 製定了一個叫 CircleArea的函數, 專門處理計算圓面積. 而在Main中, 只要呼叫(調用)這個函數, 就會跳到CircleArea裏去執行, 執行完畢, 就傳回計算的結果

class Program
{
	static void Main(string[] args)
	{
		int[] r = { 10, 15, 20, 30 };
		for (int i = 0; i < r.Length; i++)
		{
			Console.WriteLine("半徑:{0}, 圓面積 : {1}", r[i], CircleArea(r[i]));
		}
	}
	static double CircleArea(int r)
	{
		return Math.PI * r * r;
	}
}

語法

函數的語法如下

返回值 函數名(參數型態 參數名稱, ……), 如 double CircleArea(int r)

1. 上面少了static這個字, 因這個觀念比較困難, 留待物件導向時再說明, 現在只需記得在程式中一定要加static
2. 每個函數最多只能有一個返回值, 也可以沒有返回值. 當沒有返回值時, 需填入 void
3. 函數名稱需作整体的規畫, 方便日後的維護管理
4. 參數可以有多個, 也可以沒有參數

函數原型宣告

在C/C++中, 必需將函數寫在main的上方, 或者在main之前作函數原型宣告, 然後將函數主体寫在main後面, 如下

#include <iostream>
using namespace std;
double circle_area(int); //函數原型宣告 
int main(){
	printf("半徑 : %d, 圓面積 : %f", 10, circle_area(10));
}

//底下是函數主体區 
double circle_area(int r){
	return 3.14159*r*r;
}

結果 : 
Add中的 x:50, y:50
Main中的 x:5, y:5
Add(x, y)的結果 :150
請按任意鍵繼續 . . .

而在C#裏, 不需要函數原型宣告, 函數寫在前面或後面隨你高興. 為什麼C#這麼方便呢, 因為C#編譯器在編譯之前, 會先去掃瞄一次有那些函數, 傳回值是什麼, 參數是什麼, 然後在編譯時, 比對調用的程式碼是否有符合函數的規定. 這是新型語言比較聰明的作法.

方法(Method)

其實方法就是函數, 只不過稱呼不一樣而以. 在一般語言裏稱為函數, 但到了物件導向時改稱為方法.

不過方法跟函數有個不太一樣的地方, 就是方法可以加入 public, private, protected等權限修飾子, 規範是否可提供外界調用.

函數方法的好處

函數的作法, 可以將大型專案作切割, 變成一個個模組, 然後將不同模組交由不同的人來開發.

另外也可以將函數獨立編譯成二進位檔, 如此開發者就可以把自行研發的演算法放入函數中, 編譯成 .dll, .so檔, 達到保護智慧財產權的目的.

將函數包裝成 .dll, .so檔進行發佈或販賣, 整包檔案目錄, 稱為函數庫(Library).
若將class集合編譯, 比如編譯成 .jar, .dll, 則稱為SDK(Software development kit)

Call by value(傳值呼叫)

調用函數時, 如果傳入的參數是一般的值, 然後在函數中改變其值, 當返回調用端時, 其值還是不變.

如下程式碼說明, 在Main中, 將x, y傳入Add, 而在Add中改變了x, y各放大10倍. 當返回Main後, x, y維持原本的值, 沒有放大.

雖然同樣是x, 但這是不同的變數. 好比在Main家中有個孩子叫x. 而在Add家中, 也有個孩子, 同樣叫 x.  這是不同的二個人, 只是名字同名而以.

Add中的x, y, 稱為區域變數. 當Add一結束, 裏面的區域變數就立即死亡. 所以在Main中是無法存取Add的x, y. 這就好比你不可以去打別人家小孩一樣的道理.

 class Program
{
	static void Main(string[] args)
	{
		int x = 5, y = 10;
		int z = Add(x, y);
		Console.WriteLine("Main中的 x:{0}, y:{0}", x, y);
		Console.WriteLine("Add(x, y)的結果 :{0}", z);
	}
	static int Add(int x, int y)
	{
		x = x * 10;
		y = y * 10;
		Console.WriteLine("Add中的 x:{0}, y:{0}", x, y);
		return x + y;
	}
}
結果 : 
Add中的 x:50, y:50
Main中的 x:5, y:5
Add(x, y)的結果 :150
請按任意鍵繼續 . . .

Call by reference(傳參考呼叫)

下面的代碼中, 則是將皮卡丘Pikachu p這個物件傳給了Add, 在Add中改變了p的等級(level), 待返回Main時, 還是同一隻皮卡丘, 所以level印出來是已經被改過的.

這種將物件傳入函數的方式, 會改變原本的值, 稱為Call by reference

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Pikachu p = new Pikachu();
            p.level = 10;
            Add(p);
            Console.WriteLine("Main中的 p.level : {0}", p.level);
        }
        static void Add(Pikachu x)
        {
            x.level = 100;
            Console.WriteLine("Add中的 x.level : {0}",x.level);
        }
    }
    class Pikachu
    {
        public int level;
    }
}
結果 :
Add中的 x.level : 100
Main中的 p.level : 100
請按任意鍵繼續 . . .

基本資料型態也可以使用傳參考呼叫

    class Program
    {
        static void Main(string[] args)
        {
            int i=10;
            Add(ref i);
            Console.WriteLine("Main中的 i: {0}", i);
        }
        static void Add(ref int i)
        {
            i = 100;
            Console.WriteLine("Add中的 i: {0}", i);
        }
    }

參數若為陣列的話, 那情形如何呢. 下面代碼中, Main 裏的Add(ref array) 使用ref 將陣列傳入, Add中使用 Add(ref int[] array) 接收. 其實陣列本身就是物件了, 不加ref 也是使用傳參考呼叫, 這跟 Java是一樣的. 所以加 ref 是多餘的.

class Program
{
	static void Main(string[] args)
	{
		int []array= {1,2,3,4,5 };
		Add(ref array);
		for (int i = 0; i < array.Length; i++)
		{
			Console.WriteLine("Main中的 array[{0}]:{1}", i, array[i]);
		}

	}
	static void Add(ref int []array)
	{
		for (int i = 0; i < array.Length; i++)
		{
			array[i] = array[i] * 10;
			Console.WriteLine("Add中的 array[{0}]:{1}", i, array[i]);
		}
		
	}
}
結果 :
Add中的 array[0]:10
Add中的 array[1]:20
Add中的 array[2]:30
Add中的 array[3]:40
Add中的 array[4]:50
Main中的 array[0]:10
Main中的 array[1]:20
Main中的 array[2]:30
Main中的 array[3]:40
Main中的 array[4]:50

Call by Address(傳址呼叫)

在C#中其實是可以使用指標的, 但需先從專案名稱按右鍵/屬性, 然後選取建置, 再將容許不安全的程式碼打勾

csharp_pointer1

csharp_pointer2

最後, 在要使用指標的函數名稱前, 需加上 “unsafe” 修飾子

下面的代碼, 將變數 i 的記憶体位址, 使用 “&” 取出來, 然後傳給Add. 而Add中, 使用 int *i 接收其位址

在Add中改變了i的值後, 返回Main後其值跟著改, 這種方式稱為 Call by Address. 這就好比Main直接報了 i 的地址給Add, 而Add就直接上門去把 i 給改掉.

class Program
{
	static unsafe void Main(string[] args)
	{
		int i=10;
		Add(&i);
		Console.WriteLine("Main中的 i: {0}", i);
	}
	static unsafe void Add(int *i)
	{
		*i = 100;
		Console.WriteLine("Add中的 *i: {0}",*i);
	}
}

C#不建議使用指標, 所以稱為不安全的程式碼. 所以盡量少用. 可以使用委派(delegate)取代指標

傳出參數

這是C#的新發明, 致少還沒在其他程式中看過. 先看一下代碼

class Program
{
	static void Main(string[] args)
	{
		int i;
		Add(out i);
		Console.WriteLine("Main中的 i: {0}", i);
	}
	static void Add(out int i)
	{
		i = 10;
		Console.WriteLine("Add中的 i: {0}", i);
	}
}
結果 : 
Add中的 i: 10
Main中的 i: 10
請按任意鍵繼續 . . .

在調用端Main中, 並沒有指定 i 的值. 呼叫函數時, 使用 out i 將變數傳入. 然後在被調用端 Add中使用 out int i接收. 此時Add一定要先指定值才可以使用. Add結束後, 會把 i 的值再傳回去, 所以結果是二端印出皆為10

方法重載(Overload)

假設有一函數Sum(int x, int y), 只接收二個int參數. 那當要計算(int, float) 時, Sum就無計可施了. 此時就要再寫另一個函數, 如 Sum1(int x, float y).

但這樣下去還得了, 因為還有 (float, int), (float, float) , 而且, 還有 double…., 如 (int, double), 所以總不能一直 Sum1, Sum2, Sum3….這樣沒完沒了的下去吧.

為了解決這種困擾, C#/Java 允許重複使用相同的名稱 Sum, 這就叫重載(OverLoad)

class Program
{
	static void Main(string[] args)
	{
		int x = 10;
		int y = 20;
		float z = 20;
		int m=Sum(x, y);
		float n = Sum(x, z);
		Console.WriteLine("m={0}", m);
		Console.WriteLine("n={0}", n);
	}
	static int Sum(int x, int y)
	{
		Console.WriteLine("現在跑第一個 Sum");
		return x + y;
	}
	static float Sum(int x, float y)
	{
		Console.WriteLine("現在跑第二個 Sum");
		return x + y;
	}
}

重載有一個規則. 首先認識一下簽名(Signature)這個名詞

方法名稱 + 參數型態 + 參數數量 稱為簽名, 簽名不一樣,  才可以重載

比如有一個函數

static void Sum(int x, int y), 下面幾種寫法請仔細參照一下

static void Sum(int a, int b) : 簽名相同, 不能重載
static void Sum(int x, int y, int z) : 數量不同, 所以簽名不同, 可以重載
static void Sum(int x, float y) : 參數型態不同, 所以簽名不同, 可以重載
static int Sum(int a, int b) : 簽名不包含傳回值, 所以不可重載

變數總類

變數總類, 有區域變數, 區塊變數, 類別變數, 物件變數. 物件變數待物件導向時再說明

class Program
{
	static int a = 10;//類別變數
	static void Main(string[] args)
	{
		int b = 20;//Main的區域變數
		{
			int c = 30; //區塊變數
		}
		Console.WriteLine("a={0}", a);
		//Console.WriteLine("c={0}", c); c 離開區塊就死亡, 所以不能在此使用
		//Console.WriteLine("d={0}", d); d 是別人家的孩子, 不可以随便亂打

	}
	static int Sum(int x, int y)
	{
		Console.WriteLine("a={0}", a);
		int d = 40;
		return x + y;
	}
}

a : 前面加上static, 稱類別變數, 其效力可達Main, Add, 也就是凡是在Class內, 都可以使用
b : Main的區域變數, 只能在Main中使用
c : Main的區塊變數, 一離開區塊就立即死亡
d : Add的區塊變數, 所以不可以在Main裏使用