Dart的語法詳解系列篇(四)-- 泛型、異步、庫等有關詳解

版權聲明:本文為博主原創文章,未經博主允許不得轉載。http://www.lxweimin.com/p/a4a9c89b85ca

轉載請標明出處:
http://www.lxweimin.com/p/a4a9c89b85ca
本文出自 AWeiLoveAndroid的博客


本文首發在公眾號Flutter那些事,未經允許,嚴禁轉載。

前言
Flutter1.0穩定版昨晚的終于發布了。我們為此感到高興。對于開發者來說,有了穩定版相當于一個定心丸。本文主要介紹Fllutter1.0的一些功能和相關工具。


Flutter系列博文鏈接 ↓:

工具安裝:

Flutter基礎篇:

Flutter進階篇:

Dart語法系列博文鏈接 ↓:

Dart語法基礎篇:

Dart語法進階篇:


本文代碼同步發布在Github: https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo

上一篇主要講了Dart的類與函數,由于內容有太多,我就把剩下的內容分開寫一篇文章。
這一篇我們講Dart的泛型、異步、庫等有關詳解,內容較多,希望大家可以耐心看完。我也是花了很長時間研究的。喜歡的就點個贊,打個賞吧。
感謝大家支持。


九、泛型(Generics)

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

(一)為什么使用泛型?

類型安全通常需要泛型,但它們比僅允許代碼運行有更多好處:

1).正確指定泛型類型可以生成更好的代碼。

如果您希望列表只包含字符串,則可以將其聲明為List<String>(將其讀作“字符串列表”)。這樣一來,工具可以檢測到將非字符串分配給列表可能是一個錯誤。
例子:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// 報錯 The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42); 
2).您可以使用泛型來減少代碼重復。

泛型允許您在多種類型之間共享單個接口和實現,同時仍然利用靜態分析。
例如:創建了一個用于緩存對象的接口:

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

如果還有其他更改,就要寫很多接口。
泛型可以省去創建所有這些接口的麻煩。你可以創建一個帶有類型參數的接口。
示例如下:T是一個占位符,您可以將其視為開發人員稍后定義的類型。

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

(二)使用集合文字

list和map文字可以參數化。參數化文字就像你已經看到的文字一樣,除了你在開始括號之前添加 <type>(對于list)或 <keyType, valueType>(對于map)。
以下是使用類型文字(typed literals)的示例:

var numbers = <String>['11', '22', '33'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'store.html': 'Store',
  'mine.html': 'Mine'
};

(三)使用帶有構造函數的參數化類型

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

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);

以下代碼創建一個具有整數的key和View類型的value的map:

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

(四)泛型集合及其包含的類型

Dart的泛型類型是具體的。也就說,它們在運行時會會攜帶類型信息。示例如下:(相反,Java中的泛型使用擦除,這意味著在運行時刪除泛型類型參數。在Java中,您可以測試對象是否為List,但您無法測試它是否是List<String>。)

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

(五)限制參數類型

實現泛型類型時,您可能希望限制其參數的類型。你可以在<>里面使用extends。
例如:

abstract class SomeBaseClass {
    // 其他操作
}

class Foo<T extends SomeBaseClass> {
  String toString() {
      return "Instance of Foo<$T>";
  }
}

class Extender extends SomeBaseClass {
     //其他操作
}

現在可以使用SomeBaseClass或它的任何子類作為泛型參數。

例如:

void main() {
    var someBaseClassFoo = Foo<SomeBaseClass>();
    var extenderFoo = Foo<Extender>();
    print(someBaseClassFoo.toString());// Instance of Foo<SomeBaseClass>
    print(extenderFoo.toString());// Instance of Foo<SomeBaseClass>
}

也可以不指定泛型參數。
例如:

var foo = Foo();
//等同于print(foo.toString());
print(foo);// Instance of Foo<SomeBaseClass>

如果指定任何非SomeBaseClass類型會導致錯誤。
例如:var foo = Foo<Object>;

(六)使用泛型方法

新版本的Dart的泛型方法,允許在方法和函數上使用類型參數。(但它同樣適用于實例方法,靜態方法,頂級函數,本地函數甚至lambda表達式。)
例如:

T first<T>(List<T> data) {
      // 做一些初始工作或錯誤檢查...
      T tmp = data[0];
  // 做一些額外的檢查或處理...
  return tmp;
}

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

  • 1). 在函數的返回類型(T)中
  • 2). 在參數類型(List<T>)中
  • 3). 在局部變量的類型(T tmp)

泛型方法可以聲明類方法(實例和靜態)以相同的方式獲取泛型參數。

class  C {
   static  int f < S,T > (int x) => 3 ;
  int m < S,T > (int x) => 3 ;
}

泛型方法也適用于函數類型參數,本地函數和函數表達式。

1.將泛型方法作為參數[callback]。

void  functionTypedParameter(T callback <T>(T thing)){}

2.聲明一個本地泛型函數本身

void  localFunction(){
   T itself<T>(T thing) => thing;
}

3.將泛型函數表達式綁定到局部變量。

void functionExpression(){
   var lambda =  <T>(T thing) => thing;
}

十、庫和可見性

該import和library指令可以幫助您創建一個模塊化的,可共享的代碼庫。庫不僅提供API,還是隱私單元(以下劃線(_)開頭的標識符僅在庫內可見)。每個Dart應用程序都是一個庫,即使它不使用library指令。可以使用包來分發庫。

(一)使用庫

使用import指定一個庫中的命名空間如何在另一個庫匯總使用。
例如,Dart Web應用程序通常使用dart:html 庫,它們可以像這樣導入:
import 'dart:html';
對于內置庫,URI具有特殊dart: 方案(scheme)。對于其他庫,您可以使用文件系統路徑或package: 方案(scheme),這個是由包管理器(如pub工具)提供的庫。
例如:
import 'libs/mylib.dart';

(二)指定庫前綴

如果導入兩個具有沖突標識符的庫,則可以為一個或兩個庫指定前綴。例如,如果test2.dart和test3.dart都有一個hello()函數,那么直接導入這兩個文件會有沖突,這種情況下我們可以使用as關鍵字給庫指定一個前綴:

test2.dart代碼如下:

void hello() {
  print('test2.dart : hello()函數');
}

test3.dart代碼如下:
void hello(){
  print('test3.dart : hello()函數');
}

現在要在test1.dart中導入這兩個文件:
// 這樣寫會報錯
// import 'test2.dart';
// import 'test3.dart';
    
  // 正確寫法:
import 'test2.dart';
// 給導入的庫指定一個前綴 方便識別
import 'test3.dart' as test3;
  
  調用方式:
  void main(){
    hello();//test2.dart : hello()函數
    test3.hello();//test3.dart : hello()函數
}

導入庫也可以使用相對路徑。例如:lib/demo1/a.dart, lib/demo2/b.dart這兩個文件。現在b.dart這個文件需要引用a.dart,可以使用import '../demo1/a.dart'導入。

(三)僅導入庫的一部分

如果只想使用庫的一部分,則可以有選擇地導入庫,可以使用show或者hide關鍵字。例如:show表示僅導入當前庫,hide表示除了當前庫之外全部導入。

// 僅導入mylib.dart里面的test2函數
// import 'libs/mylib.dart' show test2;
// 剛好和show相反 除了test2函數之外  其它的都導入
import 'libs/mylib.dart' hide test2;
//我們想導入mylib庫,但是不想用里面的otherLib這個庫 可以這樣寫
// import 'libs/mylib.dart' hide otherLib;

(四)懶加載一個庫

延遲加載(也稱為延遲加載)允許應用程序根據需要加載庫,如果需要的話。以下是您可能使用延遲加載的一些情況:

  • 1).減少應用程序的初始啟動時間。
  • 2).例如,執行A/B測試 - 嘗試算法的替代實現。
  • 3).加載很少使用的功能,例如可選的屏幕和對話框。
    要延遲加載庫,必須先使用deferred as它導入一個庫。當我們import一個庫的時候,如果使用了as 不能同時使用deferred as
    例如:
// import 'libs/mylib.dart'; // 不能同時使用
import 'libs/mylib.dart' deferred as tests;

當您需要庫時,使用庫的標識符調用loadLibrary()。
例如(注意導包:import 'dart:async';):

Future hello() async {
  await tests.loadLibrary();
  tests.test2();
}
// 然后再去使用:
void main(){
  hello(); // 結果是: mylib.dart:test2()函數
}

在上述的代碼中,await關鍵字暫停執行,直到庫被加載。
您可以在一個庫上調用loadLibrary()多次,而不會出現問題。該庫只加載一次。

使用延遲加載時請記住以下內容:

  • 1).延遲庫的常量不是導入文件中的常量。請記住,在加載延遲庫之前,這些常量不存在。
  • 2).您不能在導入文件中使用延遲庫中的類型。相反,請考慮將接口類型移動到由延遲庫和導入文件導入的*庫。
  • 3).Dart隱式插入loadLibrary()到你使用deferred as namespace定義的命名空間。loadLibrary()函數返回Future。

(五)庫的拆分

【說明】dart官網不推薦使用part ,這個僅作為了解。
使用part指令,可以將庫拆分為多個Dart文件。part of表示隸屬于某個庫的一部分。
注意事項:

  • 1.不能同時使用library和part of,它們都用于指定屬于庫的內容。
// library testlib2; 這個不能和part of同時使用 會報錯
// part of 表示這個庫是testlib庫的一部分
part of testlib1;
    1. B庫是A庫的一部分,在B庫里面聲明:part of A庫名稱
      例如:在testlib2.dart里面聲明 part of testlib1; 表示testlib2這個庫是testlib庫的yi部分。
    1. 如果B庫聲明A庫的一部分,同時A庫也想聲明它的一部分是B庫,正確寫法:B庫聲明part of A庫名稱,然后A庫聲明part 'B庫的路徑' , 同時,如果B庫沒有聲明,那么在A庫里面使用part指令會報錯。
      testlib1.dart內容:
// 第1個庫:
library testlib1;
// 可以不寫
part 'testlib2.dart';
void run() {
  print('testlib1庫 : run()函數');
}

testlib2.dart內容:

part of testlib1;
class testLib2 {}
void start() {
  print('testlib2庫 : start()函數');
}
    1. B庫聲明了part of A庫名稱,A庫可以省去聲明part 'B庫的路徑'
// 第1個庫:
library testlib1;
// 可以不寫
part 'testlib2.dart';

(六)庫的自動導入

在A庫中使用export關鍵字引入B庫,當我們使用A庫的時候,會自動引入B庫,也就是說我們導入了A庫,就可以使用B庫了。
mylib.dart內容為:

// 這是一個庫 命名為mylib
library mylib;
// 希望使用mylib的時候 自動使用otherlib.dart  可以使用export關鍵字引入其他庫
export 'otherlib.dart';
// 導入otherlib2.dart
export 'otherlib2.dart';

class MyLib {
  void test() {
    print('mylib.dart: MyLib : test()函數');
  }
}

void test2() {
  print('mylib.dart: test2()函數');
}

otherlib.dart庫內容為:

// otherlib庫
library otherlib;
class otherLib {}
void test() {
  print('otherLib庫 : test()函數');
}

otherlib2.dart庫內容為:

// otherlib2庫
library otherlib2;
class otherLib2 {}
void test2() {
  print('otherLib2庫 : test2()函數');
}

(七)庫的組成結構

庫的最低要求是:pubspec.yaml文件和lib目錄。
庫的pubspec.yaml文件與普通應用程序包的文件格式相同。
lib目錄:庫代碼位于lib 目錄下,并且對其他包是公共的。您可以根據需要在lib下創建任何層次結構。
聲明一個庫的關鍵字是library。
例如在文件test.dart文件首行加上:library mylib; 表示這個庫的名稱是mylib


十一、異步支持

Dart庫中包含許多返回Future或Stream對象的函數。這些函數是異步的:它們在設置可能耗時的操作(例如I / O)后返回,而不等待該操作完成。

Dart官網有關于異步的教學:
使用Future完成異步任務:https://www.dartlang.org/tutorials/language/futures
使用Streams(流)管理序列化數據:https://www.dartlang.org/tutorials/language/streams

asyncawait關鍵字支持異步編程,讓你寫異步代碼看起來類似于同步代碼。

(一)處理Future

當您需要完成Future的結果時,您有兩個選擇:

  • 1).使用async和await。
  • 2).使用Future API,如 庫瀏覽 中所述。

(二)使用async和await

使用asyncawait異步的代碼,但它看起來很像同步代碼。例如,這里有一些代碼await 用于等待異步函數的結果。例如:await lookUpVersion();

要使用async,代碼必須在async函數中(標記為async的函數)。
例如:

Future checkVersion() async {
  var version = await lookUpVersion();
  // 其他操作
}

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

使用try,catch,finally在使用await的代碼中處理錯誤和清理代碼。

try {
  var version = await lookUpVersion();
} catch (e) {
  // 這里可以看到是什么錯誤。
}finally{
  // 正確的解決方式寫在這里
}

您可以在異步功能中多次使用await。例如,以下代碼等待三次函數結果:

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

在await表達式中,表達式的值通常是Future; 如果不是,那么該值將自動包含在Future中。 這個Future對象表示返回一個對象的promise。 await表達式的值是返回的對象。 await表達式使執行暫停,直到該對象可用。

如果在使用await時遇到編譯時錯誤,請確保await在async函數中。
例如,要在應用程序的main()函數中使用await,main()方法必須標記為async:以下是一個完整的示例代碼:

import 'dart:async';

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

Future checkVersion() async {
  print('checkVersion()');
  var version = await lookUpVersion();
}

Future<String> lookUpVersion() async{
  print('lookUpVersion()');
  return '版本號:v1.0';
}

結果:

// checkVersion()
// lookUpVersion()
// lookUpVersion()
// In main: version is 版本號:v1.0

(三)定義一個異步函數

方法被async修飾的函數是異步函數。給一個函數添加async關鍵字,使得返回值是一個Future。

void main(){
  lookUpVersion(); //輸出結果:lookUpVersion()同步方法 返回值是:1.0.0
  lookUpVersion2(); // 輸出結果:lookUpVersion2()異步方法 返回值是:1.0.0
  lookUpVersion3(); // 輸出結果:lookUpVersion3()異步方法 沒有返回值
}

例如,看下面這個返回值是String的同步函數:

String lookUpVersion() {
      print('lookUpVersion()同步方法 返回值是:1.0.0');
      return '1.0.0';
}

如果將其更改為異步函數 - 例如,因為Future的實現將非常耗時 - 返回的值是Future:

Future<String> lookUpVersion2() async{
      print('lookUpVersion2()異步方法 返回值是:1.0.0');
      return '1.0.0';
}

如果您的函數沒有返回有用的值,請設置其返回類型Future<void>
例如:

Future<void> lookUpVersion3() async {
  print('lookUpVersion3()異步方法 沒有返回值');
}

(四)處理Stream

當您需要完成Future的結果時,您有兩個選擇:

  • 1).使用async和異步for循環(await for)。
    注意:在使用await for之前,請確保它使代碼更清晰,并且您確實希望等待所有Stream的結果。 例如,通常情況,不應該使用await for UI事件偵聽器,因為UI框架會發送無窮無盡的事件流(streams of events)。
  • 2).使用Stream API(主要是IO操作。)

異步for循環的格式:await for(var或具體類型 標識符 in 表達式){}
例如:我們讀取本地的一個文件內容,實例代碼如下:

import 'dart:io';
import 'dart:convert';
void main() {
      test();
}
// await for循環的使用示例
// 這里是讀取本地文件的內容
Future test() async {
      var config=File('d:\\test.txt');
      // 打開io流進行文件讀取
      Stream<List<int>> inputStream = config.openRead();
      var lines = inputStream
        // 設置編碼格式為utf-8
        .transform(utf8.decoder)
        .transform(LineSplitter());
      try {
          await for (var line in lines) {
              print('從Stream中獲取到的內容是: ${line} \r文本內容長度為:'+ '${line.length}\r=======');
          }
        print('文件現在沒有關閉。。。');
      } catch (e) {
        print(e);
      }
}

表達式的值必須有Stream類型,執行過程如下:

  • 1).等待,知道Stream發出一個數值。
  • 2).執行for循環的主體,講變量設置為這個發出的數值。
  • 3).重復1和2,知道關閉Stream。

要停止監聽Stream,你可以使用break或者return語句跳出for循環B并且從Stream中取消訂閱。
如果在實現異步for循環時遇到編譯時錯誤,確保await for在一個async函數中。
例如,要在應用程序的main()函數中使用await for循環,main()方法必須標記為async:以下是一個完整的示例代碼:

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

有關異步編程的更多信息,請查看庫瀏覽的 Dart庫之旅 --- dart:async 部分,另請參閱文章 Dart語言異步支持:階段2(該頁面可能過期了) 和 Dart語言規范。


十二、Isolates

大多數計算機,甚至在移動平臺上,都有多核CPU。為了利用所有這些核心,開發人員傳統上使用并發運行的共享內存線程。但是,共享狀態并發容易出錯,并且可能導致代碼復雜化。
所有Dart代碼都在隔離區內運行,而不是線程。每個隔離區都有自己的內存堆,確保不會從任何其他隔離區訪問隔離區的狀態。

Dart是單線程模型,但是使用Isolates可以用于多線程。

這個庫主要用于服務端的開發。如果你不使用Dart做服務端開發,僅作為了解即可。
源碼可以看Github:https://github.com/dart-lang/isolate
官方API文檔: https://api.dartlang.org/stable/2.1.0/dart-isolate/dart-isolate-library.html

使用isolate 需要先導入包:import 'dart:isolate';

下面來一個簡單的示例代碼:
// 在另一個隔離區()中同步讀取“D://file.json”
// 結果是{msg: [{title: 你好1, contents: yes}, {title: 你好2, contents: NO}]}
main() async {
// 在其他隔離(isolate)中同步讀取文件,然后對其進行解碼。
print(await readIsolate());
}

// 同步讀取'D//file.json'(在同一個線程中)
Map readSync() {
JsonCodec().decode(new File('D://file.json').readAsStringSync());
}

// 在另一個隔離區()中同步讀取“D://file.json”
Future readIsolate() async {
final response = new ReceivePort();
await Isolate.spawn(_isolate, response.sendPort);
return response.first;
}

/// 期望通過[Isolate.spawn]創建
void _isolate(SendPort sendPort) {
sendPort.send(readSync());
}

下面是file.json文件的內容:
{
"msg": [
{
"title": "你好1",
"contents": "yes"
},
{
"title": "你好2",
"contents": "NO"
}
]
}


十三、生成器(Generators)

當您需要懶惰地生成一系列值時,請考慮使用生成器函數。Dart支持兩種生成器功能。

(一)同步生成器,返回一個Iterable對象。

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

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

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

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

(二)異步生成器,返回一個Stream對象。

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

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

十四、類型定義

在Dart中,函數是對象,就像字符串一樣,數字是對象。一個類型定義,或功能型的別名,給出了一個函數類型聲明字段時,您可以使用和返回類型的名稱。當函數類型分配給變量時,typedef會保留類型信息。

以下代碼,它不使用typedef:我們可以看到compare是一個函數,但它是哪一種類型的函數?不是很清楚。

class SortedCollection {
  Function compare;
  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // compare是一個函數,但它是哪一種類型的函數?
  assert(coll.compare is Function);
}

接下來使用typedef改造一下:
我們將代碼更改為使用顯式名稱并保留類型信息,開發人員和工具都可以使用該信息。

typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;
  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

目前:typedef僅限于函數類型。

因為typedef只是別名,Dart提供了一種檢查任何函數類型的方法。
例如:

typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
  assert(sort is Compare<int>); // True
}

十五、元數據Metadata

使用元數據提供有關代碼的其他信息。元數據注解以字符開頭@,后跟對編譯時常量(如deprecated)的引用或對常量構造函數的調用。
元數據可以出現在庫,類,typedef,類型參數,構造函數,工廠,函數,字段,參數或變量聲明之前以及導入或導出指令之前。您可以使用反射在運行時檢索元數據。
所有Dart代碼都有兩個注解:@deprecated@override
以下是使用@deprecated 注解的示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  // Turns the TV's power on.
  void turnOn() {
//...
}
}

您可以定義自己的元數據注釋。

這是一個定義帶有兩個參數的@todo注釋的示例:

library todo;

class Todo {
  final String who;
  final String what;
  const Todo(this.who, this.what);
}

以下是使用@todo注釋的示例:

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