Dart語法篇之函數的使用(四)

簡述:c++

在上一篇文章中咱們詳細地研究了一下集合有關內容,包括集合的操做符的使用甚至咱們還深刻到源碼實現原理,從原理上掌握集合的使用。那麼這篇文章來研究一下Dart的另外一個重要語法: 函數數組

這篇主要會涉及到: 函數命名參數、可選參數、參數默認、閉包函數、箭頭函數以及函數做爲對象使用。markdown

1、函數參數

在Dart函數參數是一個比較重要的概念,此外它涉及到概念的種類比較多,好比位置參數、命名參數、可選位置參數、可選命名參數等等。函數老是有一個所謂形參列表,雖然這個參數列表可能爲空,好比getter函數就是沒有參數列表的. 此外在Dart中函數參數大體可分爲兩種: 位置參數和命名參數,來一張圖理清它們的概念關係閉包

一、位置參數

位置參數能夠必需的也能夠是可選app

  • 無參數
//無參數類型-這是不帶函數參數或者說參數列表爲空
String getDefaultErrorMsg() => 'Unknown Error!';
//無參數類型-等價於上面函數形式,一樣是參數列表爲空
get getDefaultErrorMsg => 'Unknown Error!';
複製代碼
  • 必需位置參數
//必需位置參數類型-這裏的exception是必需的位置參數
String getErrorMsg(Exception exception) => exception.toString();
複製代碼
  • 可選位置參數
//注意: 可選位置參數是中括號括起來表示,例如[String error]
String getErrorMsg([String error]) => error ?? 'Unknown Error!';
複製代碼
  • 必需位置參數和可選位置參數混合
//注意: 可選位置參數必須在必需位置參數的後面
String getErrorMsg(Exception exception, [String extraInfo]) => '${exception.toString()}---$extraInfo';
複製代碼

二、命名參數

命名參數始終是可選參數。爲何是命名參數,這是由於在調用函數時能夠任意指定參數名來傳參。函數

  • 可選命名參數
//注意: 可選命名參數是大括號括起來表示,例如{num a, num b, num c, num d}
num add({num a, num b, num c, num d}) {
   return a + b + c + d;
}
//調用
main() {
   print(add(d: 4, b: 3, a: 2, c: 1));//這裏的命名參數就是能夠任意順序指定參數名傳值,例如d: 4, b: 3, a: 2, c: 1
}
複製代碼
  • 必需位置參數和可選命名參數混合
//注意: 可選命名參數必須在必需位置參數的後面
num add(num a, num b, {num c, num d}) {
   return a + b + c + d;
}
//調用
main() {
   print(add(4, 5, d: 3, c: 1));//這裏的命名參數就是能夠任意順序指定參數名傳值,例如d: 3, c: 1,可是必需參數必須按照順序傳參。
}
複製代碼
  • 注意: 可選位置參數和可選命名參數不能混合在一塊兒使用,由於可選參數列表只能位於整個函數形參列表的最後。
void add7([num a, num b], {num c, num d}) {//非法聲明,想一想也沒有必要二者一塊兒混合使用場景。因此
   ...
}
複製代碼

三、關於可選位置參數[num a, num b]和可選命名參數{num a, num b}使用場景

可能問題來了,啥時候使用可選位置參數,啥時候使用可選命名參數呢?源碼分析

這裏給個建議: 首先,參數是非必需的也就是可選的,若是可選參數個數只有一個建議直接使用可選位置參數[num a, num b];若是可選參數個數是多個的話建議用可選命名參數{num a, num b}. 由於多個參數可選,指定參數名傳參對總體代碼可讀性有必定的加強。post

四、參數默認值(針對可選參數)

首先,須要明確一點,參數默認值只針對可選參數才能添加的。可使用 = 來定義命名和位置參數的默認值。默認值必須是編譯時常量。若是沒有提供默認值,則默認值爲nullui

  • 可選位置參數默認值
num add(num a, num b, num c, [num d = 5]}) {//使用=來賦值默認值
    return a + b + c + d;
}
main() {
    print(add(1, 2, 3));//有默認值參數能夠省略不傳 實際上求和結果是: 1 + 2 + 3 + 5(默認值)
    print(add(1, 2, 3, 4));//有默認值參數指定傳入4,會覆蓋默認值,因此求和結果是: 1 + 2 + 3 + 4
}
複製代碼
  • 可選命名參數默認值
num add({num a, num b, num c = 3, num d = 4}) {
    return a + b + c + d;
}
main() {
    print(add(100, 100, d: 100, c: 100));    
}
複製代碼

2、匿名函數(閉包,lambda)

在Dart中能夠建立一個沒有函數名稱的函數,這種函數稱爲匿名函數,或者lambda函數或者閉包函數。可是和其餘函數同樣,它也有形參列表,能夠有可選參數。this

(num x) => x;//沒有函數名,有必需的位置參數x
(num x) {return x;}//等價於上面形式
(int x, [int step]) => x + step;//沒有函數名,有可選的位置參數step
(int x, {int step1, int step2}) => x + step1 + step2;////沒有函數名,有可選的命名參數step一、step2
複製代碼

閉包在dart中的應用

閉包函數在dart用的特別多,單從集合中操做符來講就有不少。

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  //reduce函數實現累加,reduce函數中接收的(prev, curr) => prev + curr就是一個閉包
  print(numbers.reduce((prev, curr) => prev + curr));
  //還能夠不用閉包形式來寫,可是這並非一個好的方案,不建議下面這樣使用。
  plus(prev, curr) => prev + curr;
  print(numbers.reduce(plus));
}
//reduce函數定義
 E reduce(E combine(E value, E element)) {//combine閉包函數
    Iterator<E> iterator = this.iterator;
    if (!iterator.moveNext()) {
      throw IterableElementError.noElement();
    }
    E value = iterator.current;
    while (iterator.moveNext()) {
      value = combine(value, iterator.current);//執行combine函數
    }
    return value;
  }
複製代碼

3、箭頭函數

在Dart中還有一種函數的簡寫形式,那就是箭頭函數。箭頭函數是隻能包含一行表達式的函數,會注意到它沒有花括號,而是帶有箭頭的。箭頭函數更有助於代碼的可讀性,相似於Kotlin或Java中的lambda表達式->的寫法。

main() {
  List<int> numbers = [3, 1, 2, 7, 12, 2, 4];
  print(numbers.reduce((prev, curr) {//閉包簡寫形式
        return prev + curr;
  }));
  print(numbers.reduce((prev, curr) => prev + curr)); //等價於上述形式,箭頭函數簡寫形式
}
複製代碼

4、局部函數

在Dart中還有一種能夠直接定義在函數體內部的函數,能夠把稱爲局部函數或者內嵌函數。咱們知道函數聲明能夠出現頂層,好比常見的main函數等等。局部函數的好處就是從做用域角度來看,它能夠訪問外部函數變量,而且還能避免引入一個額外的外部函數,使得整個函數功能職責統一。

//定義外部函數fibonacci
int fibonacci(int n) {
    //定義局部函數lastTwo
    List<int> lastTwo(int n) {
        if(n < 1) {
           return <int>[0, 1];  
        } else {
           var p = lastTwo(n - 1);
           return <int>[p[1], p[0] + p[1]];
        }
    }
    return lastTwo(n)[1];
}
複製代碼

5、頂層函數和靜態函數

在Dart中有一種特別的函數,咱們知道在面嚮對象語言中好比Java,並不能直接定義一個函數的,而是須要定義一個類,而後在類中定義函數。可是在Dart中能夠不用在類中定義函數,而是直接基於dart文件頂層定義函數,這種函數咱們通常稱爲頂層函數。最多見就是main函數了。而靜態函數就和Java中相似,依然使用static關鍵字來聲明,而後必須是定義在類的內部的。

//頂層函數,不定義在類的內部
main() {
  print('hello dart');
}

class Number {
    static int getValue() => 100;//static修飾定義在類的內部。
}
複製代碼

6、main函數

每一個應用程序都有一個頂級的main()函數,它做爲應用程序的入口點。main()函數返回void,因此在dart能夠直接省略void,並有一個可選的列表參數做爲參數。

//你通常看到的main是這樣的
main() {
  print('hello dart');
}
//實際上它和Java相似能夠帶個參數列表
main(List<String> args) {
  print('hello dart: ${args[0]}, ${args[1]}');//用dart command執行的時候: dart test.dart arg0 arg1 =>輸出:hello dart: arg0, arg1 
}
複製代碼

7、Function函數對象

在Dart中一切都是對象,函數也不例外,函數能夠做爲一個參數傳遞。其中Function類是表明全部函數的公共頂層接口抽象類。Function類中並無聲明任何實例方法。可是它有一個很是重要的靜態類函數apply. 該函數接收一個Function對象function,一個List的參數positionalArguments以及一個可選參數Map<Symbol, dynamic>類型的namedArguments。你們彷佛明白了什麼?知道爲啥dart中函數支持位置參數和命名參數嗎? 沒錯就是它們兩個參數功勞。實際上,apply()函數提供一種使用動態肯定的參數列表來調用函數的機制,經過它咱們就能處理在編譯時參數列表不肯定的狀況

abstract class Function {
  external static apply(Function function, List positionalArguments,
      [Map<Symbol, dynamic> namedArguments]);//能夠看到這是external聲明,咱們須要找到對應的function_patch.dart實現

  int get hashCode;

  bool operator ==(Object other);
}
複製代碼

在sdk源碼中找到sdk/lib/_internal/vm/lib/function_patch.dart對應的function_patch的實現

@patch
class Function {
  // TODO(regis): Pass type arguments to generic functions. Wait for API spec.
  //能夠看到內部私有的_apply函數,最終接收兩個List原生類型的參數arguments,names分別表明着咱們使用函數時
  //定義的全部參數List集合arguments(包括位置參數和命名參數)以及命名參數名List集合names,不過它是委託到native層的Function_apply C++函數實現的。
  static _apply(List arguments, List names) native "Function_apply";

  @patch
  static apply(Function function, List positionalArguments,
      [Map<Symbol, dynamic> namedArguments]) {
    //計算外部函數位置參數的個數 
    int numPositionalArguments = 1 + // 默認同時會傳入function參數,因此默認+1
        (positionalArguments != null ? positionalArguments.length : 0);//位置參數的集合不爲空就返回集合長度不然返回0
    //計算外部函數命名參數的個數 
    int numNamedArguments = namedArguments != null ? namedArguments.length : 0;;//命名參數的集合不爲空就返回集合長度不然返回0
    //計算全部參數個數總和: 位置參數個數 + 命名參數個數
    int numArguments = numPositionalArguments + numNamedArguments;
    //建立一個定長爲全部參數個數大小的List集合arguments
    List arguments = new List(numArguments);
    //集合第一個元素默認是傳入的function對象
    arguments[0] = function;
    //而後從1的位置開始插入全部的位置參數到arguments參數列表中
    arguments.setRange(1, numPositionalArguments, positionalArguments);
    //而後再建立一個定長爲命名參數長度的List集合
    List names = new List(numNamedArguments);
    int argumentIndex = numPositionalArguments;
    int nameIndex = 0;
    //遍歷命名參數Map集合
    if (numNamedArguments > 0) {
      namedArguments.forEach((name, value) {
        arguments[argumentIndex++] = value;//把命名參數對象繼續插入到arguments集合中
        names[nameIndex++] = internal.Symbol.getName(name);//並把對應的參數名標識存入names集合中
      });
    }
    return _apply(arguments, names);//最後調用_apply函數傳入全部參數對象集合以及命名參數名稱集合
  }
}
複製代碼

不妨再來瞅瞅C++層中的Function_apply的實現

DEFINE_NATIVE_ENTRY(Function_apply, 0, 2) {
  const int kTypeArgsLen = 0;  // TODO(regis): Add support for generic function.
  const Array& fun_arguments =
      Array::CheckedHandle(zone, arguments->NativeArgAt(0));//獲取函數的全部參數對象數組 fun_arguments
  const Array& fun_arg_names =
      Array::CheckedHandle(zone, arguments->NativeArgAt(1));//獲取函數的命名參數參數名數組 fun_arg_names
  const Array& fun_args_desc = Array::Handle(
      zone, ArgumentsDescriptor::New(kTypeArgsLen, fun_arguments.Length(),
                                     fun_arg_names));//利用 fun_arg_names生成對應命名參數描述符集合
 //注意: 這裏會調用DartEntry中的InvokeClosure函數,傳入了全部參數對象數組 fun_arguments和fun_arg_names生成對應命名參數描述符集合
//最後返回result
  const Object& result = Object::Handle(
      zone, DartEntry::InvokeClosure(fun_arguments, fun_args_desc));
  if (result.IsError()) {
    Exceptions::PropagateError(Error::Cast(result));
  }
  return result.raw();
}
複製代碼

總結

到這裏有關Dart中的函數就說完,有了這篇文章相信你們對dart函數應該有個全面的瞭解了。歡迎持續,下一篇咱們將進入Dart中的面向對象...

個人公衆號

這裏有最新的Dart、Flutter、Kotlin相關文章以及優秀國外文章翻譯,歡迎關注~~~

Dart系列文章,歡迎查看:

相關文章
相關標籤/搜索