2014年3月21日 星期五

函式的奧義

函式(Functions)是將可能會被重複執行的程式碼片段封裝後,將變數因子提取成傳入參數(Parameters),然後再依照傳入參數處理的結果返回(return)唯一的結果值。

在某些狀況下,函式並不一定得要有傳入參數及返回結果值,函式可能只處理固定的程序,故不需要傳入參數,也可能不需要將結果返回,所以不用返回任何處理結果。


函式宣告的語法:


一、標準式

以標準型式宣告函式時,需要針對回傳值資料型別及各個傳入參數的資料型別作嚴謹詳細的定義,語法如下:

回傳值資料型別 函式名稱(參數資料型別 參數1, 參數資料型別 參數2,... 參數資料型別 參數n){
   ﹕
  函式內容
   ﹕
  return 返回值;
}


例:

int max(int v1, int v2) {
  int max = v1;
  if(v2>v1){
    max = v2;
  }
  return max;
}

二、簡式

由於 Dart 的語法特性可以省略回傳值及各個傳入參數的資料型別宣告,所以上例經簡化之後可以表示如下:

max(v1, v2) {
  int max = v1;
  if(v2>v1){
    max = v2;
  }
  return max;
}


三、快捷式

當函式內容只有一行描述時,即可使用快捷式來宣告函式,語法:

函式名稱(參數1, 參數2,... 參數n)=>函式內容;
 
例:

max(v1, v2)=> v1>v2 ? v1 : v2;

以上是 Dart 基本的三種函式宣告方式,雖然簡式和快捷式在編碼過程中會較為方便,但是標準式所顯示的資訊最為完整,也更有利於程式碼的閱讀及除錯。



 選擇性參數


一、指名參數


一般叫用函式時,傳入的參數需要依照函式宣告時的參數順序才能正確執行。Dart 提供指名參數的函式叫用方法,即是以指定參數名的方式傳入參數值,而不需在意參數的順序為何,只要在函式宣告時用大括號 {  } 將所有的參數包夾起來即可。

回傳值資料型別 函式名稱({參數資料型別 參數1, 參數資料型別 參數2,... 參數資料型別 參數n}){
   ﹕
  函式內容
   ﹕
  return 返回值;
}

叫用以指名參數方式宣告的函式時,需以 參數名:參數值 方式來傳入參數,例:

函式名(參數名:參數值, 參數名:參數值...);

範例如下:

//標準式
String showV1({int v1, int v2}) {
  return "showV1 :\t $v1";
}
//簡式
showV1Simple({v1, v2}) {
  return "showV1Simple :\t $v1";
}
//快捷式
showV1Fast({v1, v2}) => "showV1Fast :\t $v1";

void main() {
  print(showV1(v2: 50, v1: 51));
  print(showV1Simple(v2: 50, v1: 52));
  print(showV1Fast(v2: 50, v1: 53));
}

輸出:
showV1 :  51
showV1Simple :  52
showV1Fast :  53


二、不定參數

除了固定的傳入參數之外,還有能依照需求情況才帶入的參數,這類不一定會傳入的參數,就叫作「不定參數」。以不定參數宣告的函式,需依宣告時的參數順序為準,不得與指定參數並用。

回傳值資料型別 函式名稱(參數資料型別 固定參數1,[參數資料型別 不定參數1, 參數資料型別 不定參數2,...]]){
   ﹕
  函式內容
   ﹕
  return 返回值;
}

例(簡式):

addItem(name, [color, int num]) {
  var item = name;
  //若有 color 參數值
  if (color != null) {
    item = "$color $item";
  }
  //若有 num 參數值
  if (num != null) {
    item = "$item+$num";
  }
  return item;
}


void main() {
  print(addItem("pen"));
  print(addItem("pen", "red"));
  print(addItem("pen", "red", 10));
}

輸出:
pen
red pen
red pen+10


例(快捷式):

addItemFast(name, [color, num]) =>
    '${color!=null ? "$color " : ""}$name${num!=null ? "+$num" : ""}';

void main() {
  print(addItemFast("paper"));
  print(addItemFast("paper", "white"));
  print(addItemFast("paper", "white", 8));
}

輸出:
paper
white paper
white paper+8


三、參數預設值

1. 指名參數的預設值宣告方法,函數名({參數名預設值}) :

例:
show({v1: 10, v2:20}) {
  print("v1:$v1, v2:$v2");
}
//或是快捷式:
//show({v1: 10, v2}) => print("v1:$v1, v2:$v2");

main() {
  show(); //v1使用預設值 10;v2 使用預設值 20
  show(v1: 50); //v1=50;v2 使用預設值 20
  show(v2: 70); //v1使用預設值 10;v2=70
  show(v2: 200, v1: 100); //v1=100; v2=200
}

輸出:
v1:10, v2:20
v1:50, v2:20
v1:10, v2:70
v1:100, v2:200


2. 不定參數的預設值宣告方法,函數名([不定參數名=預設值])

例:
show([v1=10, v2=0]) => print("v1:$v1, v2:$v2");


main() {
  show(); //v1 使用預設值10;v2 使用預設值 0
  show(50); //v1=50;v2 使用預設值 0
  show(20, 50); //v1=20; v2=50
  //小註:不定參數無法像指名參數,可以跳過 v1 值不指定只指定 v2 值。
}


輸出:
v1:10, v2:0
v1:50, v2:0
v1:20, v2:50

函式的回傳值

所有的函式皆會有一個回傳值,即使是沒有回傳值的函式,系統仍會自動回傳一 null 值。

例:
main() {
  func() {}
  print(func());
}

輸出:
null

main() 函式

每個 Dart App 都需要一個固定名稱為 main() 的函式,而 main() 函式是位於程式架構中的頂層(不是指在程式碼內容的上端) 以作為 Dart App 的起始進入點。

標準 main() 的宣告:
//以 String 資料型別的 List 傳入參數,無返回值(或說返回 void)
void main(List<String> arguments){
…
}


傳入的參數 List<String> arguments 主要是接收當程式是從命令列模式(command line) 執行時所傳入的參數。

無傳入參數的簡式宣告:
main(){
…
}

一級函式物件(First-Class Function Objects)

函式除了以命名的方式宣告後再被叫用外,函式還能直接以一級函式物件的型態儲存於變數值中,也就是說整個函式就是一個變數值:

var 函式變數名稱=(傳入參數){
函式內容
};

例:

//一般命名函式

createFunctionObject() {
  //一級函式物件設定在變數值 funcObj 中
  var funcObj = ({int num, String title}) {
    print("$title num=$num");
  };
  //返回一級函式物件。
  return funcObj;
}

main() {

  //用 funcObj1 變數名稱承接回傳的一級函式物件。
  var funcObj1 = createFunctionObject();
  funcObj1(title:"Func-Obj-1", num:100);
  
  //用 funcObj2 變數名稱承接回傳的一級函式物件。
  var funcObj2 = createFunctionObject();
  funcObj2(title:"Func-Obj-2", num:200);
}

輸出:
Func-Obj-1 num=100
Func-Obj-2 num=200


如上例所示,傳入的參數依然可以套用指名參數、不定參數以及參數預設值等一般命名函式的特色。


函式界限及變數視野

函式的界限以函式區塊符號 {....} 為區隔。若從函式區塊內向外看,則可以存取上層函式已經宣告過的變數;若從上層函式向內看內層函式,則只能看到內層函式的宣告,無法看透進內層函式的內容,故不能存取內層函式的變數,上述即為函式的界限及變數視野。


內層函式可直接存取上層函式內的變數:

main() {
  //宣告上層變數
  var variable = "top";
  innerFunc(){
    //真接存取上層變數值
    variable="$variable/level1";
    print(variable);
  }
}

例內層函式無法存取在內層函式後才宣告的上層變數:

main() {
  
  innerFunc(){
    //真接存取上層變數值
    variable="$variable/level1"; //<<報錯!以函式界限角度來看,上層變數 variable 尚未宣告。
    print(variable);
  }
  //在內層函式後才宣告的上層變數
  var variable = "top";
}

例,外層函式無法存取宣告在內層函式的變數:

main() {
  
  innerFunc(){
    var innerVariable = "inner";
  }
  
  print(innerVariable); //<<報錯!會被認為是尚未宣告的變數
}



函式閉包(Closure/Lexical Closure)

當內層函式有取用外層函式的變數值時,即使內層函式以一級物件方式離開了外層函式被叫用,仍舊會記憶外層函式的變數值,此一特性即是函式閉包。(詳細請參考 維基:閉包 )
例:

main(){
  //外層函式 person
  person(name){
    //內層函式 showAge
    showAge(yearsOld){
      //被叫用時仍會記憶外層函式變數 name 的值
      var whosAge = "$name is $yearsOld years old.";
      return whosAge;
    }
    //返回一級函式物件
    return showAge;
  }
    
  //以 sowmTomAge 承接 函式 person 回傳的 showAge 一級函式物件
  var showTomAge = person("Tom");
  print(showTomAge(18));
  
  var showSueAge = person("Sue");
  print(showSueAge(17));
  
  //不另外宣告變數直接叫用 showAge 的方式
  print(person("Jamie Olive")(38));
}


輸出:

Tom is 18 years old.
Sue is 17 years old.
Jamie Olive is 38 years old.















沒有留言:

張貼留言