flutter--Dart基礎語法(二)流程控制、函數、異常

一、前言

Flutter 是 Google 開源的 UI 工具包,幫助開發者通過一套代碼庫高效構建多平臺精美應用,Flutter 開源、免費,擁有寬鬆的開源協議,支持移動、Web、桌面和嵌入式平臺。

Flutter是使用Dart語言開發的跨平臺移動UI框架,通過自建繪製引擎,能高性能、高保真地進行Android和IOS開發。Flutter採用Dart語言進行開發,而並非Java,Javascript這類熱門語言,這是Flutter團隊對當前熱門的10多種語言慎重評估後的選擇。因爲Dart囊括了多數編程語言的優點,它更符合Flutter構建界面的方式。

本文主要就是簡單梳理一下Dart語言的一些基礎知識和語法。關於編程語言的基本語法無外乎那麼些內容,註釋、變量、數據類型、運算符、流程控制、函數、類、異常、文件、異步、常用庫等內容,相信大部分讀者都是有一定編程基礎的,所以本文就簡單地進行一個梳理,不做詳細的講解。大家也可以參考 Dart編程語言中文網

上一篇文章主要是寫了Dart語言的一些基本語法,本文將接着上一篇文章繼續往後寫。

二、Dart中的流程控制

流程控制涉及到的基本語法其實很簡單,但是這一塊也是編程語言基礎中最難的一部分,主要難點在於解決問題的邏輯思路,流程控制知識實現我們解決問題的邏輯思路的一種表達形式。所以,大家在學習編程語言的過程中,學習基本語法是一部分,更重要的部分其實是鍛鍊自己解決問題的邏輯能力,而這一塊的加強,必須加以大量的練習才能熟練掌握。本文主要是給大家羅列一下Dart中的流程控制相關的基本語法。

流程控制主要涉及到的內容無外乎條件分支結構、switch分支結構和循環結構,此外,還有一些特殊的語法break、continue等。對於有過編程經驗的同學而言,這些內容都是so easy。下面就簡單給大家羅列一下。

2.1 條件分支結構 

Dart 中的條件分支結構就是 if - else 語句,其中 else 是可選的,Dart 的if判斷條件必須是布爾值,不能是其他類型。比如下面的例子。

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

2.2 switch分支結構

在 Dart 中 switch 語句使用 == 比較整數,字符串,或者編譯時常量。 比較的對象必須都是同一個類的實例(並且不可以是子類), 類必須沒有對 == 重寫。 枚舉類型 可以用於 switch 語句。在 case 語句中,每個非空的 case 語句結尾需要跟一個 break 語句。 除 break 以外,還有可以使用 continuethrow,者 return。當沒有 case 語句匹配時,執行 default 代碼

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}
// case 程序示例中缺省了 break 語句,導致錯誤
switch (command) {
  case 'OPEN':
    print('open');
    // ERROR: 丟失 break

  case 'CLOSED':
    print('close');
    break;
}
// Dart 支持空 case 語句, 允許程序以 fall-through 的形式執行
var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

// 在非空 case 中實現 fall-through 形式, 可以使用 continue 語句結合 lable 的方式實現
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

2.3 循環結構

和其他編程語言中的循環結構一樣,Dart中的循環結構也是有for、while、do...while三種,這三種循環結構可以相互轉換,大家根據自己的編程習慣進行選擇即可。

2.3.1 for循環

進行迭代操作,可以使用標準 for 語句。 例如:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

閉包在 Dart 的 for 循環中會捕獲循環的 index 索引值, 來避免 JavaScript 中常見的陷阱。 請思考示例代碼:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

和期望一樣,輸出的是 0 和 1。 

// 如果要迭代一個實現了 Iterable 接口的對象, 可以使用 forEach() 方法, 如果不需要使用當前計數值, 使用 forEach() 是非常棒的選擇
candidates.forEach((candidate) => candidate.interview());

//實現了 Iterable 的類(比如, List 和 Set)同樣也支持使用 for-in 進行迭代操作 iteration 
var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2

2.3.2 while和do...while循環

// while 循環在執行前判斷執行條件:
while (!isDone()) {
  doSomething();
}

// do-while 循環在執行後判斷執行條件:
do {
  printLine();
} while (!atEndOfPage());

2.4 break和continue語句

使用 break 停止程序循環:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用 continue 跳轉到下一次迭代:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

如果對象實現了 Iterable 接口 (例如,list 或者 set)。 那麼上面示例完全可以用另一種方式來實現:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

2.5 assert語句

如果 assert 語句中的布爾條件爲 false , 那麼正常的程序執行流程會被中斷。  下面是一些示例:

// 確認變量值不爲空。
assert(text != null);

// 確認變量值小於100。
assert(number < 100);

// 確認 URL 是否是 https 類型。
assert(urlString.startsWith('https'));

提示: assert 語句只在開發環境中有效, 在生產環境是無效的; Flutter 中的 assert 只在 debug 模式 中有效。 開發用的工具,例如 dartdevc 默認是開啓 assert 功能。 其他的一些工具, 例如 dart 和 dart2js, 支持通過命令行開啓 assert : --enable-asserts

  • assert 的第一個參數可以是解析爲布爾值的任何表達式。 如果表達式結果爲 true , 則斷言成功,並繼續執行。 如果表達式結果爲 false , 則斷言失敗,並拋出異常 (AssertionError) 。
  • assert 的第二個參數可以爲其添加一個字符串消息。
assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

三、Dart中的函數

Dart 是一門真正面向對象的語言, 甚至其中的函數也是對象,並且有它的類型 Function 。 這也意味着函數可以被賦值給變量或者作爲參數傳遞給其他函數。 也可以把 Dart 類的實例當做方法來調用。

3.1 函數的定義

下面是函數實現的示例:

// 模板
returnType funcName(paramsList) {
    // function code
    // return statement
}

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

3.1.1 可選參數

函數有兩種參數類型: required(必需參數,函數調用時不傳就會報錯) 和 optional(可選參數,函數調用時可以不傳)。 required 類型參數在參數最前面, 隨後是 optional 類型參數。 命名的可選參數也可以標記爲 “@required” 。

可選參數可以是命名參數或者位置參數,但一個參數只能選擇其中一種方式修飾。

  • 命名可選參數:定義函數時,使用 {param1param2, …} 來指定命名參數,並且可以使用 @required 註釋表示參數是 required 性質的命名參數。調用函數時,可以使用指定命名參數 paramNamevalue
    // 定義函數是,使用 {param1, param2, …} 來指定命名參數:
    void enableFlags({bool bold, bool hidden}) {...}
    
    // 調用函數時,可以使用指定命名參數 paramName: value。 例如:
    enableFlags(bold: true, hidden: false);
    
    // 使用 @required 註釋表示參數是 required 性質的命名參數, 該方式可以在任何 Dart 代碼中使用(不僅僅是Flutter)。
    // 此時 Scrollbar 是一個構造函數, 當 child 參數缺少時,分析器會提示錯誤。
    const Scrollbar({Key key, @required Widget child})
  • 位置可選參數:將參數放到 [] 中來標記參數是可選的,調用函數時,按位置順序傳遞參數。

    // 將參數放到 [] 中來標記參數是可選的:
    String say(String from, String msg, [String device]) {
      var result = '$from says $msg';
      if (device != null) {
        result = '$result with a $device';
      }
      return result;
    }
    
    // 下面是不使用可選參數調用上面方法 的示例:
    assert(say('Bob', 'Howdy') == 'Bob says Howdy');
    
    // 下面是使用可選參數調用上面方法的示例:
    assert(say('Bob', 'Howdy', 'smoke signal') ==
        'Bob says Howdy with a smoke signal'); 

3.1.2 默認參數

在定義方法的時候,可以使用 = 來定義可選參數的默認值。 默認值只能是編譯時常量。 如果沒有提供默認值,則默認值爲 null。

注意:舊版本代碼中可能使用的是冒號 (:) 而不是 = 來設置參數默認值。 原因是起初命名參數只支持 : 。 這種支持可能會被棄用。 建議 使用 = 指定默認值。

下面是設置可選參數默認值示例:

/// 設置 [bold] 和 [hidden] 標誌 ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold 值爲 true; hidden 值爲 false.
enableFlags(bold: true);

下面示例演示瞭如何爲位置參數設置默認值:

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

3.1.3 返回值

所有函數都會返回一個值。 如果沒有明確指定返回值, 函數體會被隱式的添加 return null; 語句。

foo() {}

assert(foo() == null);

3.2 main()函數

任何應用都必須有一個頂級 main() 函數,作爲應用服務的入口。 main() 函數返回值爲空,參數爲一個可選的 List<String> 。

下面是 web 應用的 main() 函數:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

下面是一個命令行應用的 main() 方法,並且使用了輸入參數:

// 這樣運行應用: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

3.3 匿名函數

多數函數是有名字的, 比如 main() 和 printElement()。 也可以創建沒有名字的函數,這種函數被稱爲 匿名函數。 匿名函數可以賦值到一個變量中, 舉個例子,在一個集合中可以添加或者刪除一個匿名函數。

匿名函數和命名函數看起來類似— 在括號之間可以定義一些參數或可選參數,參數使用逗號分割。後面大括號中的代碼爲函數體:

([[Type] param1[, …]]) { 
  codeBlock; 
}; 

// 下面例子中定義了一個包含一個無類型參數 item 的匿名函數。 list 中的每個元素都會調用這個函數,打印元素位置和值的字符串。
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

3.4 箭頭函數

不管是匿名函數還是命名函數,如果函數中只有一句表達式,可以使用箭頭語法,簡寫如下:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr 語法是 { return expr; } 的簡寫。 => 符號 有時也被稱爲 箭頭 語法。

提示: 在箭頭 (=>) 和分號 (;) 之間只能使用一個 表達式 ,不能是 語句 。 例如:不能使用 if 語句 ,但是可以是用 條件表達式.

3.5 函數是一等對象

一個函數可以作爲另一個函數的參數。 例如:

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 將 printElement 函數作爲參數傳遞。
list.forEach(printElement);

同樣可以將一個函數賦值給一個變量,例如:

// 使用匿名函數
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

3.6 變量的作用域

Dart 是一門詞法作用域的編程語言,就意味着變量的作用域是固定的, 簡單說變量的作用域在編寫代碼的時候就已經確定了。 花括號內的是變量可見的作用域。下面示例關於多個嵌套函數的變量作用域:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;
    
    // 注意 nestedFunction() 可以訪問所有的變量, 一直到頂級作用域變量。
    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

3.7 閉包

3.7.1 閉包的概念

閉包這個概念好難理解,身邊朋友們好多都稀裏糊塗的,我也是學習了很久才理解這個概念。下面請大家跟我一起理解一下,如果在一個函數的內部定義了另一個函數,外部的我們叫他外函數,內部的我們叫他內函數。

閉包: 在一個外函數中定義了一個內函數,內函數裏運用了外函數的臨時變量,並且外函數的返回值是內函數的引用。這樣就構成了一個閉包。

一般情況下,在我們認知當中,如果一個函數結束,函數的內部所有東西都會釋放掉,還給內存,局部變量都會消失。但是閉包是一種特殊情況,如果外函數在結束的時候發現有自己的臨時變量將來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,然後自己再結束

函數可以封閉定義到它作用域內的變量。 接下來的示例中, makeAdder() 捕獲了變量 addBy。 無論在什麼時候執行返回函數,函數都會使用捕獲的 addBy 變量。

/// 返回一個函數,返回的函數參數與 [addBy] 相加
Function makeAdder(num addBy) {
  // //返回的函數就是一個閉包,封閉了局部變量 addBy
  return (num i) => addBy + i;
}

void main() {
  // 創建一個加 2 的函數。
  var add2 = makeAdder(2);

  // 創建一個加 4 的函數。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

3.7.2 閉包的特點

由於變量的作用域的限制,全局變量可以在整個代碼範圍內使用,但是帶來的問題就是任何地方都可以修改該全局變量,函數內局部變量又只能在函數內部使用。所以閉包就讓外部訪問函數內部變量成爲可能,同時也讓局部變量可以常駐在內存中。

  • 讓外部訪問函數內部變量成爲可能;
  • 局部變量會常駐在內存中;
  • 可以避免使用全局變量,防止全局變量污染;
  • 會造成內存泄漏(有一塊內存空間被長期佔用,而不被釋放)

閉包就是可以創建一個獨立的環境,每個閉包裏面的環境都是獨立的,互不干擾。閉包會發生內存泄漏,每次外部函數執行的時候,外部函數的引用地址不同,都會重新創建一個新的地址。但凡是當前活動對象中有被內部子集引用的數據,那麼這個時候,這個數據不刪除,保留一根指針給內部活動對象。

閉包內存泄漏爲: key = value,key 被刪除了 value 常駐內存中; 局部變量閉包升級版(中間引用的變量) => 自由變量;

四、異常

Dart 代碼可以拋出和捕獲異常。 異常表示一些未知的錯誤情況。 如果異常沒有被捕獲, 則異常會拋出, 導致拋出異常的代碼終止執行。和 Java 有所不同, Dart 中的所有異常是非檢查異常。 方法不會聲明它們拋出的異常, 也不要求捕獲任何異常。

Dart 提供了 Exception 和 Error 類型, 以及一些子類型。 當然也可以定義自己的異常類型。 但是,此外 Dart 程序可以拋出任何非 null 對象, 不僅限 Exception 和 Error 對象。

4.1 拋出異常 throw

下面是關於拋出或者 引發 異常的示例:

throw FormatException('Expected at least 1 section');

也可以拋出任意的對象:

throw 'Out of llamas!';

提示: 高質量的生產環境代碼通常會實現 Error 或 Exception 類型的異常拋出。

因爲拋出異常是一個表達式, 所以可以在 => 語句中使用,也可以在其他使用表達式的地方拋出異常:

void distanceTo(Point other) => throw UnimplementedError();

4.2 異常處理 try...catch...finally

Dart中的異常處理和Java中的比較類似,也是使用try...catch...finally的語句進行處理,不同的是,Dart中海油一個特殊的關鍵字on。使用 on 來指定異常類型, 使用 catch 來 捕獲異常對象,捕獲語句中可以同時使用 on 和 catch ,也可以單獨分開使用

捕獲異常可以避免異常繼續傳遞(除非重新拋出( rethrow )異常)。 可以通過捕獲異常的機會來處理該異常:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

通過指定多個 catch 語句,可以處理可能拋出多種類型異常的代碼。 與拋出異常類型匹配的第一個 catch 語句處理異常。 如果 catch 語句未指定類型, 則該語句可以處理任何類型的拋出對象:

// 捕獲語句中可以同時使用 on 和 catch ,也可以單獨分開使用。 使用 on 來指定異常類型, 使用 catch 來 捕獲異常對象。
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一個特殊的異常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其他任何異常
  print('Unknown exception: $e');
} catch (e) {
  // 沒有指定的類型,處理所有異常
  print('Something really unknown: $e');
}

catch() 函數可以指定1到2個參數, 第一個參數爲拋出的異常對象, 第二個爲堆棧信息 ( 一個 StackTrace 對象 )。

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

如果僅需要部分處理異常, 那麼可以使用關鍵字 rethrow 將異常重新拋出。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

不管是否拋出異常, finally 中的代碼都會被執行。 如果 catch 沒有匹配到異常, 異常會在 finally 執行完成後,再次被拋出。如果catch捕獲到異常,那麼先執行catch中的處理代碼,然後再執行finally中的代碼。總而言之,finally語句塊中的代碼一定會被執行,並且是在最後被執行

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

// 任何匹配的 catch 執行完成後,再執行 finally 
try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章