Dart基本語法

重要概念

  • 可以放在變量中的都是對象,所有對象都是類的實例,包括數字,函數,null都是對象, 所有對象都是繼承自 Object類
  • Dart類型是強類型語言,但是Dart也支持類型推斷,如果要明確說明不需要任何類型,請使用特殊類型dynamic。
  • Dart支持泛型,比如List<int>(包含int的數組),List<dynamic>(包含任意類型對象的數組)
  • Dart支持top-level函數(比如main()),以及方法(靜態方法和實例方法). 也可以在函數中創建函數(內嵌函數或本地函數)
  • 同樣的,Dart支持top-level變量,以及綁定到類或對象的變量(靜態變量和實例變量),實例變量有時候被稱為 字段或者屬性
  • 不像Java,Dart沒有public,protected,private關鍵字.如果標識符以下劃線(_)開頭,則它就是私有變量
  • 標識符可以以字母或下劃線(_)開頭

變量

var name = 'Bob';

變量存儲引用, name變量包含了一個String對象的引用,該對象的值是'Bob'.

name被推斷為String類型,但是可以通過指定來改變其類型,如果對象不局限于單一類型,可以指定為Object或者dynamic

dynamic name = 'Bob';

默認值

未初始化的變量的初始值為null,即使數字類型的初始值也是null,因為在Dart中, everything is object!

int lineCount;
assert(lineCount == null);

重點: 生產環境下,代碼會忽略 assert()調用, 開發環境中, 斷言會在條件為false時拋出異常

final 和 const

如果永遠不會修改變量,使用 final 或者 const。而不是使用var或者其他類型。

final 變量只會被設置一次,一個 const 變量是編譯時常量(const是隱式的final),final 修飾的top-level變量和類變量在第一次使用時初始化

Note: 實例變量可以是final,但不能是const, final 實例變量必須在構造函數體開始之前初始化(可以在變量聲明,構造函數參數,或者在構造函數的初始化列表)

final name = 'Bob'; // 沒有類型聲明
final String nickname = 'Bobby';

不能修改final變量的值

name = 'Alice'; // Error: a final variable can only be set once.

const 用于編譯時常量。 如果const在類中使用, 使用static const標識。在聲明該變量的地方,將值設置為編譯時常量,比如數字或者字符串,


const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

const 關鍵字并不只是用來聲明常量變量,也可以用來創建常量值,以及聲明創建常量值的構造函數。任意變量都可以有一個常量值


var foo = const [];
final bar = const[];
const baz = []; // 等同于 const[]

可以從const聲明的初始化表達式中省略const,就像上面的 baz

可以改變 non-final,non-const變量的值,即使該變量有過一個 const 值:

foo = [1,2,3];// 之前是 const[]

但是不能修改const變量的值:

baz = [42];// Error

同樣的,var foo = const [];foo這個數組的值也不能再改變,即foo不能添加/移除元素
foo.add(1); error
Unsupported operation: Cannot add to an unmodifiable list

內置類型

Dart語言特別支持以下類型:

  • numbers
  • strings
  • booleans
  • lists (也就是數組)
  • sets
  • maps
  • runes (為了在字符串中表示Unicode字符)
  • symbols

可以使用字面量來初始化這些類型。

Number

Dart數字有兩種類型

int

不超過64位的整數,具體取決于平臺。在Dart VM上,值的范圍:-2^{63}\2^{63}-1。 編譯為JavaScript的Dart使用的是Javascript number,值的范圍是 -2^{53}2^{53}-1

double

64位(雙精度)浮點型數字。由IEEE 754標準規定

int和double都是num的子類。 num類包含基礎運算符,比如+,-,*,/,以及abs(),ceil(),floor(),(在int類里有位運算符,比如<<)

在Dart2.1中,整型會在需要時自動轉為double類型

double z = 1;// 等同于 double z = 1.0;

下面是字符串轉為數字,反之亦然

// String -> int
var one = int.parse('1');
assert(one == 1);


// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

String

Dart字符串是一系列UTF-16代碼單元。 可以使用單引號或雙引號來創建字符串:

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

可以使用${expression}將表達式的值放在字符串中,如果表達式是標識符,可以省略{}, 要獲得與對象對應的字符串,可以調用對象的toString()方法

可以使用相鄰的字符串或者 + 號來連接兩個字符串

// 相鄰的字符串
var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

// +號
var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

創建多行字符串的方法:使用單引號/雙引號的三重引號

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

可以添加一個前綴 r來創建一個‘raw’字符串

var s = r'In a raw string, not even \n gets special treatment.';

bool

Dart中的布爾類行為bool,有兩個值:true,false;

Dart是類型安全的,也就意味著不會像OC那樣有非0即真的情況。條件表達式中必須明確傳遞一個布爾值。

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

List

Dart中的數組,有序集合。

var list = [1, 2, 3];

注意: Dart會類型推斷list的類型為 List<int>. 如果之后向其中添加其他非int的對象,編譯器會拋出錯誤

跟其他語言的數組一樣,List的下標索引從0開始,

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

要創建編譯時常量的列表,請在列表前添加const:

var constantList = const [1,2,3];
// constantList[1] = 1; 會有錯誤,因為列表是常量 不能再修改了

Dart2.3擴展運算符(...)和空值感知運算符(...?),它提供了一種將多個元素插入到集合的簡潔方法。

比如,可以使用擴展運算符將一個列表中的所有元素插入到另一個列表中

var list = [1,2,3];
var list2 = [0, ...list];

assert(list2.length == 4);

如果擴展運算符右邊的表達式有可能為null,可以使用空值感知運算符來避免異常。

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

Dart2.3也引入了collection ifcollection for來創建集合。

下面是使用collection if的例子,列表包含三個/四個 item:

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

使用collection for來操作列表item,然后將它們添加到另一個列表:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

Set

Dart中的無序集合是Set,

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

創建一個空的Set,請使用前面帶有類型參數的{},或者將{}賦給類行為Set的變量

var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

Set還是Map? Map和Set的字面亮語法類似。由于Map首先出現,所以{}默認是Map類型。如果忘記了{}的類型注釋,Dart會創建一個Map<dynamic, dynamic>類型的對象,也就是說{}默認會是Map類型

add(),addAll()方法來為Set添加元素:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用.length來獲取Set的元素個數:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

要創建一個編譯時常量的Set,在Set前添加const

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // error

類似于List, Set也支持擴展運算符(...)和空值感知運算符(...?);

Map

類似于iOS中的字典。key和value都可以是任意類型的對象,key只能出現一次,value可以出現多次。

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

也可以使用Map的構造函數來創建Map:

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

向一個Map中添加鍵值對,類似于JS:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

如果查詢一個不存在的key,會返回null:

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用.length來獲取Map元素的個數:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

創建一個編譯時常量的Map,在Map字面量前使用const

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; //error

Dart2.3之后,Map也支持......?

Runes

在Dart中,Runes是字符串的UTF-32代碼點。

Unicode為世界上所有書寫系統中的每個字母,數字和符號定義了唯一的數值。由于Dart字符串是 UTF-16編碼的序列,因此在字符串中表示32位的Unicode值需要特殊的語法。

表達Unicode代碼點的常用方法是 \uXXXX,其中XXXX是4位十六進制值。 例如,心臟字符(?)是 \u2665。 要指定多于或少于4個十六進制數字,請將值放在大括號中。 例如,笑的表情符號(??)是\u{1f600}

String類有幾個屬性可用于提取rune信息。codeUnitAtcodeUnit屬性返回16位的code unit。使用runes屬性獲取字符串的runes

以下示例說明了runes(符文)、16位代碼單元和32位代碼點之間的關系:

main() {
  var clapping = '\u{1f44f}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}

打印信息:

flutter: ??
flutter: [55357, 56399]
flutter: [128079]
flutter: ?  ??  ??  ??  ??  ??

Symbol

Symbol對象表示Dart程序中聲明的運算符或標識符。可能永遠也用不到Symbol。。。

要獲取標識符的符號,可以使用symbol字面量,后跟標識符:

#radix
#bar

Function

Dart是真面向對象的語言,所以即使是函數也是一個對象,類型為Function。這意味著函數可以分配給變量或者作為參數傳遞給其他函數。

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

或者

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

=> expr{ return expr; }的縮寫,

函數可以有兩種類型的參數:必需和可選。必需參數放在首位,后面跟著一些可選參數,

可選參數

可選參數可以是位置參數,也可以是命名參數

可選命名參數

當調用一個函數的時候,可以使用paramName: value的形式指定命名參數:

enableFlags(bold: true, hidden: false);

當定義一個函數時,使用{param1, param2, …}的形式來制定命名參數:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

Flutter實例創建表達式可能變得復雜,因此Widget構造函數僅使用命名參數。 這使得實例創建表達式更易于閱讀。

你可以在任何Dart代碼(不僅是Flutter)中使用@required來注釋一個命名參數,來表明該參數是必須的:

const Scrollbar({Key key, @required Widget child})

當構建Scrollbar時,如果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');

參數默認值

可以使用=來為命名參數或者位置參數設置默認值。默認值必須是編譯時常量,如果沒有提供默認值,那默認值就是null。

舉個例子:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be 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');

也可以傳一個List或者Map作為默認值:

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

main()函數

每一個app都有一個頂層的main()函數,作為應用的入口點。該函數返回值為void,并接收一個可選的List<String>參數。

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}
// Run the app like this: 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');
}

函數作為first-class對象

函數本身可以作為一個參數傳遞給另一個函數,比如:

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

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

也可以把函數賦值給一個變量:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名函數

大多數函數是有名字的,比如main()或者printElement (),我們也可以創建一個沒有名字的函數-匿名函數,或者創建lambda以及閉包。你可以把匿名函數賦值給一個變量,方便添加到一個集合中,或者從集合中刪除。

匿名函數看起來類似于命名函數-零個或多個參數,

以下示例定義了一個匿名函數,有一個無類型參數item。

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

詞匯范圍

Dart是一種詞法范圍的語言,這意味著變量的范圍是靜態確定的(只需通過代碼的布局)。

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

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

閉包

閉包是一個函數對象,它可以訪問其詞法范圍中的變量,即使該函數在其原始范圍之外使用也是如此。

函數可以關閉周圍范圍中定義的變量。 在以下示例中,makeAdder()捕獲變量addBy。 無論返回的函數在哪里,它都會記住addBy。

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

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

函數相等

這里有個例子來測試頂層函數,靜態函數,以及實例函數的相等性:

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

返回值

所有的函數都有返回值,如果沒有指定返回值,則返回null。

foo() {}

assert(foo() == null);

操作符

描述 操作符
一元后綴 expr++?? expr--?? ()?? []?? ? .??? ?.
一元前綴 -expr?? !expr?? ~expr?? ++expr?? --expr
乘法 * ?? /?? %?? ~/
加法 +????? -
位移 <<???? >>???? >>>
按位與 &
按位異或 ^
按位或 |
關系和類型測試 >= ??? >?? <=??? < ?? as ?? is?? is!
相等 == ????????? !=
邏輯與 &&
邏輯或
if null ??
條件 expr1 ? expr2 : expr3
級聯 ..
賦值 =??? *= ??? /=??? +=?? -=??? &=??? ^= 等

這里有幾個操作符的用法

a++
a + b
a = b
a == b
c ? a : b
a is T

在上述表格中,每個操作符都比其下一行的操作符有更高的優先級。比如,乘法運算符%比相等運算符==有更高的優先級(因此在==之前先執行%)。相等運算符==優先級高于邏輯與運算符&&。該優先級意味著以下兩行代碼以相同的方式執行:

// 使用括號提高可讀性
if ((n % i == 0) && (d % i == 0)) ...

// 比較難讀,但跟上面是相等的
if (n % i == 0 && d % i == 0) ...

算數運算符

Dart支持以下的算數運算符

運算符 含義
+ 加法
- 相減
-expr 取反
* 相乘
/ 相除,返回的是double
~/ 相除,返回的是整數int
% 取余

比如:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart還支持自增自減運算:


var a, b;

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1

a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0

相等和關系運算符

跟其他語言的一樣

類型判斷運算符

as, is, 和 is! 操作符在運行時檢查類型非常方便。

操作符 含義
as 類型轉換(也用于指定庫前綴)
is 如果對象具有指定的類型則返回true
is! 如果對象具有指定的類型返回false

如果obj實現了由T指定的接口,obj is T的結果為true。比如,obj is Object永遠都是true。

使用as運算符將對象強制轉換為特定的類型。 通常情況下應該將as作為is的簡寫,比如:

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

可以使用as來簡寫:

(emp as Person).firstName = 'Bob';

注意: 該代碼并不是等價的。如果 emp是null或者不是Person類的對象。使用is不會有什么影響,使用as的話會拋出異常。

賦值運算符

// 將value賦值給a
a = value;

// 如果b是null,將value賦值給b;否則,b將保持不變
b ??= value;

邏輯運算符

跟其他語言類似

位運算

與C一樣

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right

條件表達式

Dart有兩個運算符,可以簡明地計算可能需要if-else語句的表達式:

condition ? expr1 : expr2

三目運算符,跟其他語言一樣

expr1 ?? expr2

如果expr1是 non-null,則返回它的值,否則,計算并返回expr2的值。

如果要基于布爾表達式來賦值的話使用三目運算

var visibility = isPublic ? 'public' : 'private';

如果布爾表達式要測試null,請考慮使用??。

String playerName(String name) => name ?? 'Guest';

前面的例子至少可以用其他兩種方式編寫,但不夠簡潔:


// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }

級聯表示法(..)

級聯(..)允許對同一對象進行一系列操作。 除了函數調用,還可以訪問同一對象上的字段。 這通常可以節省創建臨時變量的步驟,并允許編寫更多流暢的代碼。


querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
  

上面的代碼等同于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

也可以內嵌我們的級聯表達式,比如:

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

小心在返回實際對象的函數上構造級聯。 例如,以下代碼會失敗:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

sb.write()函數返回void,不能在void上構建級聯。

注意:嚴格來說,級聯的“雙點”符號不是運算符。 它只是Dart語法的一部分。

其他運算符

只介紹下?.:最左邊的操作數可以為null,比如:foo?.bar,如果foo不為null,則從foo中選擇bar屬性,如果foo為null,則foo?.bar為null。

控制流語句

if-else,for循環,while/do-while跟其他語言一樣

Dart中的Switch語句使用==來比較整數,字符串或者編譯時常量。比較對象必須是同一個類的實例(而不是其子類),并且該類不能覆蓋==。

每個非空case子句以break語句結束。 結束非空case子句的其他有效方法是continue,throw或return語句。

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();
}

斷言

跟其他語言的斷言一樣。Flutter中只有在debug模式下才開啟斷言。

異常

與Java相比,Dart的所有異常都是未經檢查的異常。 方法不會聲明它們可能引發的異常,并且不需要捕獲任何異常。

Dart提供了Exception和Error類型,以及許多預定義的子類型。 當然,也可以自定義異常。 Dart程序可以拋出任何非null對象(不僅僅是Exception和Error對象)作為異常。

Throw

拋出異常:

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

也可以拋出任意對象:

throw 'Out of llamas!';

Catch

捕獲異常會阻止異常傳播(除非重新拋出異常),并有機會處理它:

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

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

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

如前面的代碼所示,可以使用oncatch或兩者結合使用。 需要指定異常類型時使用on, 在需要異常對象時使用catch

可以指定兩個參數到catch()。第一個參數是拋出的異常,第二個是堆棧跟蹤信息(一個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

為了確保無論異常是否拋出都會執行一些代碼,可以使用finally語句。如果catch語句沒有匹配到該異常,則該異常會在執行finally語句的代碼之后拋出。

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

finally語句會在匹配異常的catch語句之后執行:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

Dart是一種面向對象的語言,具有類和基于mixin的繼承。 每個對象都是一個類的實例,所有類都來自Object。 基于Mixin的繼承意味著雖然每個類(除了Object)只有一個超類,但是類的body可以在多個類層次結構中重用。

類的成員

類具有函數、方法以及實例變量等成員。

使用點語法(.)來引用實例變量或者方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

使用?.來代替.,可以防止左邊運算對象為null的異常。

// If p is non-null, set its y value to 4.
p?.y = 4;

使用構造函數

可以使用構造函數創建對象。 構造函數名稱可以是ClassNameClassName.identifier。 例如,以下代碼使用Point()Point.fromJson()構造函數創建Point對象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

一些類提供了編譯時構造函數,創建一個編譯時常量。在構造函數名稱前加const關鍵字:

var p = const ImmutablePoint(2, 2);

構造兩個相同的編譯時常量時,只會產生一個實例:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // a和b是同一個實例

在常量上下文中,可以在構造函數或字面量之前省略const:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

除了第一個const,其他的都可以省略:

// 只有一個const,它建立了恒定的上下文。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果常量構造函數在常量上下文之外,并且在沒有使用const,則會創建一個非常量對象:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

獲取對象類型

在運行時獲取對象的類型,可以使用對象的runtimeType屬性,返回一個Type對象/

print('The type of a is ${a.runtimeType}');

實例變量

聲明實例變量:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有未初始化的實例變量默認值都是null.

所有的實例變量都會生成一個隱式的getter方法。非final實例變量也會隱式的生成setter方法。

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

構造函數

通過創建與其類同名的函數來聲明構造函數。

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

this關鍵字代表著當前實例。在名稱沖突時使用this,一般情況下,Dart會省略this.

Dart具有語法糖,使其變得簡單:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

默認構造函數

如果沒有聲明構造函數,則會提供一個默認的構造函數。默認的構造函數沒有參數,并且會調用其父類的無參數的構造函數。

構造函數不能繼承

子類不能繼承父類的構造函數!

命名構造函數

使用命名構造函數為類實現多個構造函數:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

重定向構造函數

有時構造函數的唯一目的是重定向到同一個類中的另一個構造函數。 重定向構造函數的body是空的,構造函數調用出現在冒號(:)之后。

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
// 重定向到main函數
  Point.alongXAxis(num x) : this(x, 0);
}

常量構造函數

如果類生成的對象永遠不會改變,則可以使這些變量為編譯時常量。定義一個const構造函數來確保所有的實例變量都是final

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

工廠(Factory)構造函數

當構造函數不需要每次都創建新的實例時,可以使用factory關鍵字。例如,一個工廠構造函數可能從緩存中返回實例,或者可能返回一個子類的實例。

下面的例子展示了工廠構造函數從緩存中返回實例:

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

注意: 工廠構造函數不能訪問this.

調用工廠構造函數跟其他的構造函數一樣:

var logger = Logger('UI');
logger.log('Button clicked');

方法

方法是為對象提供行為的函數。(函數是獨立存在的,方法需要依賴對象,這就是函數與方法的區別)。

實例方法

實例方法可以訪問實例變量和this。下面的distanceTo()就是一個實例方法:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

getter & setter

每個實例變量都有一個隱式的getter,合適的話還有一個setter。可以通過setget關鍵字實現setter和getter來創建其他的屬性

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定義兩個計算屬性: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

抽象方法

實例方法,setter和getter可以是抽象的,可以定義接口,但將其實現留給其他類。抽象方法只能存在于抽象類。

使用分號(;)而不是方法體來定義一個抽象方法:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // 定義抽象方法
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

抽象類

使用abstract修飾符來定義抽象類(無法實例化的類)。抽象類對于定義接口非常有用,通常還有一些實現。如果希望抽象類看起來是可以實例化的,請定義工廠構造函數。

抽象類一般具有抽象方法:


// 這個類被定義為抽象類,因此它不能實例化
abstract class AbstractContainer {
  // 定義構造函數,字段,方法...

  void updateChildren(); //抽象方法
}

隱式接口

每個類都隱式定義一個接口,該接口包含該類的所有實例成員及其實現的所有接口。如果要在不繼承class B實現的情況下,創建一個class A來支持class B的API,class A應該implementsB的接口。

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

下面是一個類實現多個接口的例子:

class Point implements Comparable, Location {...}

擴展類

使用extends創建子類,使用super引用父類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

override 成員

子類可以覆蓋實例方法、gettersetter。可以使用@override注釋來表示要覆蓋一個成員:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

覆蓋運算符

您可以覆蓋下表中顯示的操作符。例如,如果您定義一個向量類,您可能定義一個+方法來添加兩個向量。

> / ^ []=
< + | []
<= ~/ & ~
>= * << ==
- % >>

注意: !=是不能覆蓋的,因為e1 != e2!(e1 == e2)的語法糖。

下面是一個覆蓋+-操作符的例子:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果你覆蓋了==,你也應該覆蓋對象的hashCodegetter方法。

noSuchMethod()

當代碼試圖調用不存在的方法或者實例變量,可以覆蓋noSuchMethod()來檢測或響應。

class A {
  // 除非覆蓋了noSuchMethod。使用不存在的成員會導致NoSuchMethodError
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

枚舉類型

使用

使用enum關鍵字聲明一個枚舉類型:

enum Color { red, green, blue }

每個枚舉值都有一個index getter,它返回枚舉值的位置。比如,第一個值的index為0,第二個值的index為1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

要獲取枚舉中所有的值,可以使用枚舉中的values常量。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

枚舉有以下限制:

  • 不能子類化、mixin或者implment一個枚舉
  • 不能顯式的實例化枚舉

向類添加feature: mixin

mixin是一種在多個繼承類中重用代碼的一種方式。

若要使用mixin,請使用with關鍵字后跟一個或多個mixin名稱。下面的例子顯示了兩個使用mixin的類:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

要實現mixin,創建一個擴展Object的類,并且不聲明構造函數,除非希望mixin像常規類一樣使用。

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

比如,指定只有某些類型可以使用mixin,這樣你的mixin可以調用它沒有定義的方法-使用on來指定所需的父類:

mixin MusicalPerformer on Musician {
  // ···
}

Dart 2.1版本中引入了對mixin的支持,早期版本中通常使用抽象類

類變量和方法

使用static關鍵字來實現類范圍的變量和方法。

靜態變量(Static變量)

靜態變量(類變量)對于類范圍內的狀態和常量很有用:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

靜態變量在使用之前不會初始化

靜態方法

靜態方法(類方法)不能操作實例,因為不能訪問this:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

可以使用靜態方法作為編譯時常量,比如,可以傳遞一個靜態方法作為靜態構造函數的參數

注意: 對于通用的或者廣泛使用的功能函數,考慮使用頂層函數,而不是靜態方法

泛型

如果查看List的API文檔,會發現List的實際類型是List<E><...>表示法將List標記為泛型(或者參數化)類型-具有正式類型參數的類型。按照慣例,大多數類型變量都有單字母名稱,例如E,T,S,K,V

為什么使用泛型

類型安全通常需要泛型,但有更多的好處:

  • 正確指定泛型類型會產生更好的代碼
  • 使用泛型來減少代碼重復

如果想要數組僅僅包含字符串,可以聲明為List<String>。這樣可非字符串插入到列表中的就會有錯誤:


var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

使用泛型的另外一個理由是減少代碼的重復。泛型允許在多個類型之間分享單個接口和實現。比如:創建一個用于緩存對象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

然后發現想要一個特定于字符串的版本,然后可以這樣實現:


abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

后來,你可能需要更多的類型...

泛型可以省去所有這些接口的麻煩,創建一個帶有類型參數的接口:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在這段代碼中,T是一個替身類型,一個占位符,

使用集合字面量

List,Set,Map可以參數化:

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

跟構造函數一起使用參數化類型

要在使用構造函數時指定一個或多個類型,請將類型放在類名后面的尖括號中(<...>),比如:

var nameSet = Set<String>.from(names);

var views = Map<int, View>();

泛型集合以及其所包含的類型

Dart泛型被具體化,這意味著它們在運行時攜帶類型信息。例如,你可以測試一個集合的類型:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

限制參數化類型

當實現一個泛型時,可能想要限制參數的類型,可以使用extends:

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

使用SomeBaseClass或其子類作為泛型參數是OK的:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

不指定泛型參數也是可以的:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

指定任意非SomeBaseClass類型會導致錯誤:

var foo = Foo<Object>();

使用泛型方法

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

first(<T>)上的泛型類型參數允許在幾個地方使用類型參數T:

  • 函數的返回類型(T)
  • 參數的類型(List<T>)
  • 局部變量(T tmp)

支持異步

Dart庫充滿了返回Future或者Stream對象的函數。這些函數是異步的:它們在一個可能非常耗時的操作(比如I/O)之后返回,而不需要等待操作完成。

asyncwait關鍵字支持異步編程,允許我們編寫看起來類似同步的異步代碼

處理Future

當需要一個完整Future的結果時,有兩種選擇:

使用asyncawait的代碼是異步的,但看起來是同步的。比如:

await lookUpVersion();

要使用await,代碼必須在async函數中:一個標記為async的函數:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

注意:雖然async函數可能會執行耗時操作,但并不需要等待這些操作。相反,async函數只執行到第一個await表達式,然后返回一個Future對象,僅在await表達式完成后才恢復執行。

使用trycatch,和 finally在使用await的代碼中來錯誤:

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

可以在async函數中多次使用await

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await表達式中,表達式的值通常是Future,如果不是,該值也會自動包裝在Future中,await表達式會使執行暫停,知道該對象可用。

如果在使用await時遇到編譯時錯誤,請確保await是在async函數中。
比如在app的main()函數中使用await,則必須時main函數標記為aycnc

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

處理Stream(流)

當需要從流中獲取值的時候,有兩個選項:

  • 使用async和異步for循環(await for)
  • 使用 Stream API

異步for循環:

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

表達式(expression)的值必須具有Stream類型。執行過程如下:

  1. 等流發出一個值
  2. 執行for循環的主體,將變量設置為流發出的值
  3. 重復1和2,直到流關閉

停止監聽流,可以使用break或者return語句,該語句會中斷for循環并且取消訂閱流

如果實現一個異步for循環時出現編譯時錯誤,確保await for在異步函數中

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

Generators(生成器)

當想要懶加載一系列值時,可以考慮使用generator函數,dart內置兩種該函數:

  • 同步生成器:返回Iterable對象
  • 異步生成器:返回Stream對象

要實現同步生成器函數,將函數主體標記為sync*,并使用yield語句來傳遞值:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

要實現異步生成器函數,將函數標記為async*,并使用yield語句來傳遞值:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

如果生成器是遞歸的,可以使用yield*來提高性能:

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Isolates(隔離區)

大多數計算機,即使在移動平臺上,也有多核CPU。 為了利用所有這些核心,開發人員傳統上使用并發運行的共享內存線程。 但是,共享狀態并發容易出錯,并且可能導致代碼復雜化。

所有Dart代碼都在隔離區內運行,而不是線程。 每個隔離區都有自己的內存堆,確保不會從任何其他隔離區訪問隔離區的狀態。


文章來源:https://dart.dev/guides/language/language-tour

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容