Flutter开发技术与分享(三) —— Dart 入门(一)

版本记录

版本号 时间
V1.0 2019.11.08 星期五

前言

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。目前公司的部分模块就是在使用Flutter进行开发。感兴趣的可以看下面几篇文章。
1. Flutter开发技术与分享(一) —— 基本概览(一)
2. Flutter开发技术与分享(二) —— Flutter 入门(一)

开始

首先看下主要内容

主要内容:了解Dart编程语言的基础知识,该语言可用于Flutter SDK进行移动,Web和其他应用开发。翻译地址

接着看下写作环境

Dart 2, Flutter, DartPad

您正在寻找Flutter开发入门,但只是发现Flutter使用Dart编程语言吗?本教程将向您介绍一些Dart基础知识,以便您可以起步并使用Flutter开发语言。

DartGoogle于2011年发布,最初旨在替代JavaScript。从那时起,针对iOS,Android和Web开发的Flutter SDK的发布使Dart语言成为新的焦点。录制时,Dart的最新版本是2.4

Dart与您可能已经熟悉的其他语言有很多相似之处,例如Java,C#,Swift和Kotlin。 Dart具有以下语言特征:

  • Statically typed
  • Type inference
  • String expressions
  • Multi-paradigm including OOP and functional
  • Can mimic dynamic types using dynamic

除了用于移动和Web开发之外,Flutter还是针对Fuchsia的开发套件,FuchsiaGoogle正在开发的实验性操作系统。

本教程将向您介绍Dart基础知识,例如:

  • Variables, data types, and operators
  • Conditionals and loops
  • Functions, anonymous functions, and closures
  • Arrow function syntax

您可以在本课程不仅仅了解基础知识,还可以了解有关Dart中的面向对象编程以及Dart集合类型(如列表,集合和maps)的更多信息。

完成本教程后,您应该已经准备好使用Dart直接进入Flutter开发。

为了快速起步并学习Dart,最好的方法是使用DartPad编辑器,该编辑器位于 https://dartpad.dartlang.org

DartPad的安装类似于典型的IDE。 有:

  • 左侧的“编辑器” Editor pane窗格
  • 运行(Run)按钮以在编辑器中运行代码
  • 右上方的控制台(Console)显示输出
  • 右下角的“信息”(Info)面板显示信息突出显示的代码
  • Samples下拉列表显示一些示例代码
  • 共享(Share)按钮,可用于共享已创建的DartPad文件

在右下角的下方,有一个复选框,显示更多与使用Dart进行网络编程有关的面板,以及一些文本,显示DartPad中使用的Dart当前版本。

如果愿意,可以在计算机上本地安装Dart SDK。 一种方法是安装Flutter SDK。 安装flutter也将安装Dart SDK

您也可以直接访问此处here直接安装Dart SDK


Core Concepts

Dart程序以对main函数的调用开始,并且main的语法与其他语言(例如C,Swift或Kotlin)的main语法非常相似。

清除默认DartPad中的所有代码,并向编辑器添加一个main函数:

void main() {
}

main函数之前有一个返回类型,在Dart中,该类型是void,表示不返回任何内容。 main后面的括号表示这是一个函数定义,花括号包含函数的主体。 在main内部,为程序添加Dart代码。

1. Variables, Comments, and Data Types

我们要添加到main的第一件事是变量赋值语句。 变量保存程序将要处理的数据。 您可以想到一个变量,例如计算机内存中的一个包含值的盒子。 每个盒子都有一个名称,即变量的名称。 您在Dart中使用var关键字表示变量。

main添加一个新变量:

var myAge = 35;  

像C和Java一样,每个Dart语句都以分号结尾。 您已经创建了一个myAge变量,并将其设置为35

您可以使用Dart中的内置print函数将变量打印到控制台,因此请在变量之后添加该调用:

print(myAge); // 35

点击DartPad中的Run按钮以运行代码,然后您将在右上角的控制台中看到打印出的变量。

Dart中的注释看起来与其他语言中的C中的注释类似:在一行上//后面的文本,或/ /块内的文本。

// This is a comment.
print(myAge); // This is also a comment.

/*
 And so is this.
 */

如果将光标放在myAge名称上方的编辑器中,您将在右下面板中看到Dart推断myAge是一个int变量,这要归功于它是用整数值35初始化的。像SwiftKotlin一样,Dart使用 如果您未直接指定数据类型,则使用类型推断来尝试找出数据类型。

使用类型推断的另一种方法是将var关键字替换为要使用的特定类型:

int yourAge = 27;
print(yourAge); // 27

C,Java,SwiftKotlin等许多语言类似,Dart是静态类型的。 这意味着Dart中的每个变量都具有在编译代码时必须知道的类型,并且在程序运行时不能更改变量的类型。

这与动态类型化的语言(例如PythonJavascript)形成对比,这意味着变量在运行时可以保存不同种类的数据,并且在编译代码时不需要知道类型。

Basic Dart Types

Dart使用int表示整数,使用double表示浮点,使用bool表示布尔值。 int和double均源自名为num的类型。 您使用String类型表示字符序列。 Dart还具有关键字dynamic,可让您模仿静态类型的Dart中的动态类型。

您还可以对int以外的类型使用类型推断。 输入等于3.14的变量pi,如下所示:

var pi = 3.14;
print(pi); // 3.14

pi被推断为双精度,因为您使用了浮点值对其进行了初始化。 您可以在Dart信息面板中看到

您也可以只使用double而不是var来作为类型:

double c = 299792458;
print(c); // 299792458

在这种情况下,您已经将光速符号c初始化为int,但是由于您将类型指定为double,因此c实际上是doubleDart会将int转换为double以便初始化c。 因此,与Swift不同,Dart具有隐式类型转换 (implicit type-conversion)

The dynamic Keyword

如果使用dynamic关键字而不是var,则可以得到有效地是动态类型的变量:

dynamic numberOfKittens;

您可以使用引号将numberOfKittens设置为String(有关String类型的更多信息,请参见下文)

numberOfKittens = 'There are no kittens!';
print(numberOfKittens); // There are no kittens!

numberOfKittens具有类型,因为Dart具有静态类型,所以它必须具有类型。 但是该类型是dynamic的,这意味着您可以为其分配具有其他类型的其他值。 因此,您可以在打印语句下方分配一个int值。

numberOfKittens = 0;
print(numberOfKittens); // 0

或者,如果您可以指定一个双精度值:

numberOfKittens = 0.5;
print(numberOfKittens); // 0.5

继续并单击Run,以查看在控制台中打印出的numberOfKittens的三个不同值。 在每种情况下,即使变量本身包含不同类型的值,numberOfKittens的类型仍保持dynamic

布尔类型用于保存truefalse的值。

bool areThereKittens = false;
print(areThereKittens); // false

但是,如果您也可能进行如下修改:

numberOfKittens = 1;
areThereKittens = true;
print(areThereKittens); // true

再次运行代码以在控制台中查看布尔值。

2. Operators

Dart具有您熟悉的其他语言所有常用操作符,例如C,Swift和Kotlin。

有算术运算符,相等,递增和递减,比较和逻辑运算符。

Dart还允许像C ++Kotlin这样的运算符重载,但这超出了本教程的范围。

算术运算符的工作就像您期望的那样。 向DartPad添加一堆操作:

print(40 + 2); // 42
print(44 - 2); // 42
print(21 * 2); // 42
print(84 / 2); // 42

这里有更多。

您可以使用算术表达式来初始化变量:

var atltuae = 84.0 / 2;
print(atltuae); // 42

Dart在操作之前将int转换为double,以便将结果变量推断double

Dart具有双等号相等和非等号运算符:

print(42 == 43); // false
print(42 != 43); // true

固定前和固定后增量和减量运算符:

print(atltuae++); // 42
print(--atltuae); // 42

因为增量使用的是后缀,所以在增量发生之前先打印42。 减量为前缀,因此将43减为42,然后打印值42

Dart具有典型的比较运算符,例如小于和大于或等于。

print(42 < 43); // true
print(42 >= 43); // false

也有通常的复合算术/赋值运算符

atltuae += 1; print(atltuae); // 43
atltuae -= 1; print(atltuae); // 42
atltuae *= 2; print(atltuae); // 84
atltuae /= 2; print(atltuae); // 42

Dart具有通常的模运算符。

print(392 % 50); // 42

逻辑运算符,例如&&|| 或看起来像其他语言的语言一样。

print((41 < atltuae) && (atltuae < 43)); // true
print((41 < atltuae) || (atltuae > 43)); // true

负号运算符是感叹号,将false转换为true或者将true转换为false

print(!(41 < atltuae)); // false

3. Strings

Dart字符串类型为String。 字符串在Dart中使用由单引号或双引号引起来的文本表示。

就像我们看到的其他类型一样,您可以使用vartype inferenceString来创建字符串变量:

var firstName = 'Albert';
String lastName = "Einstein";

KotlinSwift等语言类似,您可以使用美元符号$来将值和表达式嵌入字符串中以创建新的字符串。

var physicist = "$firstName $lastName";
print(physicist); // Albert Einstein

您可以组合相邻的字符串,例如,多行的长字符串,只需将字符串彼此相邻或放在单独的行上即可:

var quote = 'If you can\'t' ' explain it simply\n'
  "you don't understand it well enough.";
print(quote);
// If you can't explain it simply
// you don't understand it well enough.

在第一个字符串中,您使用单引号,因此使用了转义序列\'将不能插入的引号嵌入到字符串中。 Dart中使用的转义序列与其他类似C的语言中使用的转义序列相似,例如\ n用于换行符。

由于使用了双引号来分隔第二个字符串,因此不需要为单引号在don't上使用转义序列。

您还可以使用+运算符组合字符串:

var energy = "Mass" + " times " + "c squared";
print(energy); // Mass times c squared

您可以使用三引号使字符串运行多行并保留格式:

var model = """
I'm not creating the universe.
I'm creating a model of the universe,
which may or may not be true.""";
print(model);
// I'm not creating the universe.
// I'm creating a model of the universe,
// which may or may not be true.

如果需要在字符串中显示转义序列,则可以使用以r为前缀的原始字符串。

var rawString = r"If you can't explain it simply\nyou don't understand it well enough.";
print(rawString); 
// If you can't explain it simply\nyou don't understand it well enough.

继续,然后单击DartPadRun以在控制台中查看所有字符串。

4. Immutability

Dart的关键字constfinal表示不变的值。 const用于在编译时已知的值,final用于在编译时不必知道但在初始化后不能重新分配的值。 最后的行为,例如Kotlin中的valSwift中的let

您可以使用constfinal代替var,并让类型推断确定类型:

const speedOfLight = 299792458;
print(speedOfLight); // 299792458

因此,可以将speedOfLight推断为int,如您在DartPad的信息面板中所见。

final表示不可变,并且final值不能重新分配。 您还可以使用finalconst显式声明类型:

final planet = 'Jupiter';
// final planet = 'Mar'; // error: planet is immutable
final String moon = 'Europa';
print('$planet has a moon $moon');
// Jupiter has a moon Europa

5. Nullability

对于任何变量,无论类型如何,如果不初始化变量,则变量将被赋予null,这意味着变量中将不存储任何内容。

这与诸如SwiftKotlin之类的语言形成对比,对于这些语言,您必须将变量显式声明为nullable(在Swift中又是optional)。 这些语言的默认类型是不可为空(non-nullable)的。

事实证明,所有Dart类型,甚至包括基本类型(如intdouble)都源自名为Object的类型。 而且,如果您不初始化对象,则该对象将为空值(null)

您将在这里看到一些在Dart中安全处理null值的方法,但是它远不及Swift中的optionals或Kotlin中的nullable那样。

创建三个不同类型的变量,然后立即打印它们。

int age;
double height;
String err;
print(age); // null
print(height); // null
print(err); // null

您会在控制台中看到每个变量均为null

Dart具有一些null-aware的运算符,可用于处理null值。

双问号运算符?? 就像Kotlin中的“ Elvis运算符”:如果不为空,它将返回左侧的操作数,否则返回右侧的值:

var error = err ?? "No error";
print(error); // No error

有一个对应的赋值运算符?? =,其作用类似:

err ??= error;
print(err); // No error

由于您刚刚将error non-null设为“No error”,因此err值现在可以在此分配中获取该非空值。

还有一个运算符?.保护您避免访问空对象的属性。 如果对象本身为null,它将返回null。 否则,它将返回右侧的属性值:

print(age?.isEven); // null

如果您仅尝试age.isEven并且agenull,则将收到Uncaught异常。


Control Flow

控制流使您能够确定某些代码行已执行,跳过或重复。 在Dart中使用conditionalsloops来处理控制流。

1. Conditionals

控制流的最基本形式是根据程序运行时发生的情况来决定是执行还是跳过代码的某些部分。 用于处理条件的语言构造是if / else语句。 Dart中的if / else与其他类似C的语言中的用法几乎相同。

假设您有一个动物变量,当前是狐狸。

var animal = 'fox';

您可以使用if语句检查动物是猫还是狗,如果是,则运行一些相应的代码。

if (animal == 'cat' || animal == 'dog') {
  print('Animal is a house pet.');
}

在这里,您已经使用了等于和或运算符在if语句的条件内创建了一个bool值。

如果条件为假,则可以使用else子句运行替代代码:

} else {
  print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.

您还可以将多个if / else语句组合到一个if / else if / else构造中:

if (animal == 'cat' || animal == 'dog') {
  print('Animal is a house pet.');
} else if (animal == 'rhino') {
  print('That\'s a big animal.');
} else {
  print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.

如果需要,可以在ifelse之间有很多else if分支。

2. While Loops

循环使您可以重复编码一定次数或基于特定条件。 后者由while循环处理。

Dart中的while循环有whiledo-while两种形式。 区别在于,对于while,循环条件在代码块之前,而在do-while中,条件在之后。 因此,对于同时执行,可以保证代码块至少运行一次。

创建一个我初始化为1的变量i

var i = 1;

您可以使用while循环在递增i的同时打印i,并将条件设置为i小于10

while (i < 10) {
  print(i);
  i++;
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

运行代码,您会看到while循环将数字19打印出来。

DartPad中重置i,然后添加一个do-while循环:

i = 1;
do {
  print(i);
  ++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

此处的结果与之前相同,只是这次,在检查循环退出条件之前,循环主体运行了一次。

3. continue and break

Dart具有通常在循环和其他地方使用的continuebreak关键字。 continue将跳过循环内的其余代码,并立即转到下一个迭代。 break停止循环并在循环主体之后继续执行。

在代码中使用continue时,您必须小心。 例如,如果您从上方开始执行do-while循环,并说要在i等于5时continue执行,则可能会导致无限循环,该无限循环会一直运行,具体取决于您放置continue语句的位置:

do {
  print(i);
  if (i == 5) {
    continue;
  }            
  ++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// ...

由于一旦i5,便会无限循环,您永远不会递增i,并且条件始终为true

如果您在DartPad中运行此程序,则无限循环将导致浏览器挂起。 而是使用break,这样循环将在i达到5后结束:

do {
  print(i);
  if (i == 5) {
    break;
  }
  ++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5

现在运行代码,看看循环在5次迭代后结束。

4. For Loops

循环预定次数的循环是Dart中的for循环,再次类似于其他语言中的循环。

Dart具有类似于C的形式的for循环,带有初始化,循环条件和操作,以及for-in循环,用于迭代对象集合。 在第一种形式中,初始化在第一次循环迭代之前运行。 在进入每个迭代时检查条件,并在开始下一个迭代之前运行操作。

对于for-in形式,对于循环的每个后续迭代,将一个变量设置为Dart对象集合中的每个元素。 假设您要对前10个整数的值求和。

为总和创建一个变量:

var sum = 0;

然后使用for循环,在该循环中将循环计数器i初始化为1,检查i小于或等于10,并在每个循环后递增i。 在循环内使用复合赋值将i加到运行总和中:

for (var i = 1; i <= 10; i++) {
  sum += i;  
}
print("The sum is $sum"); // The sum is 55

DartPad中运行代码以查看总和。

Dart集合的一个示例是用方括号括起来的简单数字列表:

var numbers = [1, 2, 3, 4];

您可以使用for-in循环遍历列表:

for (var number in numbers) {
  print(number);
}
// 1
// 2
// 3
// 4

在循环中迭代时,可变number采用numbers中每个元素的值。

numbers这样的列表也有一个名为forEach的函数,您可以将其调用,从而将前一个循环简化为一行:

numbers.forEach((number) => print(number));
// 1
// 2
// 3
// 4

在这里,您使用了匿名函数(anonymous function)和箭头语法(arrow syntax),稍后将在Functions部分中看到这两者。

最后,像while循环一样,for循环也可以使用continuebreak来控制循环内的流。 例如,如果要跳过打印数字3,则可以在for-in循环中使用continue语句:

for (var number in numbers) {
  if (number == 3) {
    continue;
  }
  print(number);
}
// 1
// 2
// 4

5. switch and enum

Dart还支持switch语句和使用enum的枚举。 有关这两者的更多信息,请查阅Dart文档Dart documentation。 就像您所见过的大多数其他Dart构造一样,它们的工作方式与在其他语言(如C和Java)中的工作方式类似。


Functions

函数使您可以将多个相关的代码行打包到一个包体中,然后可以将其召唤以避免在整个Dart应用程序中重复这些代码行:

函数由返回类型,函数名称,括号中的参数列表以及括在括号中的函数体组成。 您要变成函数的代码放在花括号内。 调用函数时,您传入的参数与函数的参数类型匹配。

注意:在函数上指定返回类型是可选的。 如果不使用返回类型,则从函数返回的值将具有推断的类型,或者如果无法推断该类型,则为dynamic。 这使您可以从函数中返回不同类型的值。 通常,您会希望避免这种情况,而是从函数中指定单个返回类型。

通常,函数是在其他函数外部或Dart类内部定义的,但是您也可以将Dart函数相互嵌套。 例如,您可以在此部分的main中添加函数。

DartPad中编写一个新函数,该函数将仅检查给定的字符串是否为banana

bool isBanana(String fruit) {
  return fruit == 'banana';
}

该函数使用return返回由传递给该函数的参数确定的布尔值。 对于任何给定的输入,此函数将始终返回相同的值。 如果某个函数不需要返回值,则可以将返回类型设置为void,例如对于main函数。

您可以通过传入字符串来调用该函数。 然后,您可以将该调用的结果传递给print

var fruit = 'apple';
print(isBanana(fruit)); // false

您可以更改传递给函数的参数,然后使用新参数再次调用它们:

fruit = 'banana';
print(isBanana(fruit));  // true

调用函数的结果完全取决于传入的参数。

1. Optional Parameters

如果函数的参数是可选的,则可以用方括号括起来:

String fullName(String first, String last, [String title]) {
  return "${title == null ? "" : "$title "}$first $last";
}

如果函数调用中未包含可选参数,则函数体内的参数使用的值为null

然后,您可以调用带有或不带有可选参数的函数:

print(fullName("Joe", "Howard"));
// Joe Howard

print(fullName("Albert", "Einstein", "Professor"));
// Professor Albert Einstein

2. Optional Named Arguments

使用Dart函数,可以在参数列表中使用大括号括起来,以使用可选的命名参数(optional named arguments)

bool withinTolerance({int value, int min, int max}) {
  return min <= value && value <= max;
}

然后,可以通过为参数名称提供冒号来以不同顺序传递参数:

print(withinTolerance(min: 1, max: 10, value: 11)); // false

像可选参数一样,带有可选名称的参数不需要添加到函数调用中,并且相应的参数在函数中将被赋予null in the function值。

3. Default Values

您还可以使用equals将默认值分配给一个或多个参数:

bool withinTolerance({int value, int min = 0, int max = 10}) {
  return min <= value && value <= max;
}

然后,可以在调用函数时保留默认值的参数。

print(withinTolerance(value: 5)); // true

运行您的代码以查看新函数的实际作用。

4. First-Class Functions

Dart支持所谓的first-class functions。 该术语意味着将函数与任何其他数据类型一样对待,也就是说,可以将它们分配给变量,作为参数传递并从其他函数返回。

您可以使用Function类型来指定名为op的参数本身就是一个函数:

int applyTo(int value, int Function(int) op) {
  return op(value);
}

因此,如果您有一个名为square的函数:

int square(int n) {
  return n * n;
}

您可以传递square以将applyTo用作参数:

print(applyTo(3, square)); // 9

您可以将函数分配给变量:

var op = square;

然后,您可以在变量上调用该函数,就好像它是原始函数的别名一样:

print(op(5)); // 25

稍后您将了解有关从其他函数返回函数的更多信息。

5. Anonymous Functions and Closures

到目前为止,您已经看到的功能已被命名为功能,即带有名称的功能。 匿名函数正是您所期望的,没有名称的函数。 您也可以省略将推断出的返回类型。 因此,您只需要一个参数列表和一个用于匿名函数的函数主体。

您可以将匿名函数分配给变量:

到目前为止,您已经看到的函数已被命名为函数,即带有名称的函数。 匿名函数正是您所期望的,没有名称的函数。 您也可以省略将推断出的返回类型。 因此,您只需要一个参数列表和一个用于匿名函数的函数主体。

您可以将匿名函数分配给变量:

var multiply = (int a, int b) {
  return a * b;
};

右侧由参数列表和函数主体组成。

由于multiply现在拥有一个匿名函数,因此您可以像调用其他任何函数一样调用它:

print(multiply(14, 3)); // 42

您之前已经看到了列表和forEach函数的预览。 forEach是使用匿名函数的一个很好的例子。

因此,如果您有一个数字列表:

numbers = [1, 2, 3];

您可以在列表上调用forEach并传入匿名函数,该函数将元素三倍并打印出三倍的值:

numbers.forEach((number) { 
  var tripled = number * 3;  
  print(tripled);
  // 3
  // 6
  // 9
});

您可以从另一个函数返回一个匿名函数:

Function applyMultiplier(num multiplier) {
  return (num value) { 
    return value * multiplier;    
  };
}

这对于生成具有给定行为但输入参数值变化的函数很有用。 因此,这里的返回值是一个函数,该函数采用一个num参数并将其乘以用于生成该函数的倍数。

一个示例是创建一个名为Triple的变量,该变量将其输入乘以3:

var triple = applyMultiplier(3);

您可以使用num的任何一种来调用Triple

print(triple(6)); // 18
print(triple(14.0)); // 42

Dart中的匿名函数充当闭包,这意味着它们在自身外部定义的“闭包”变量。 例如,上面的applyMultiplier函数的返回值是一个闭包,该闭包可以访问在其他地方定义的multiplier变量,在这种情况下,位于applyMultiplier本身的参数列表中。

6. Arrow Syntax

Dart函数(命名的或匿名的)仅由一行代码组成时,您可以使用Dart箭头语法(arrow syntax)来简化函数主体。

例如,上面的multiply变为:

var multiply = (int a, int b) => a * b;

您已经删除了大括号和return语句。

您还可以将箭头语法与applyMultiplier结合使用:

Function applyMultiplier(num multiplier) =>
  (num value) => value * multiplier;

您在这里使用了两次箭头语法:第一个是表示applyMultiplier函数的返回值,第二个是在返回值本身之内,这是一个匿名函数。

在上面使用forEach的示例中,您不能使用箭头语法,因为您传递给forEach的函数的主体具有多行代码。

查看Dart官方文档official Dart documentation以了解Dart的中级和高级部分:

  • Iterables and Generators
  • Exceptions
  • Runes and symbols
  • Asynchronous Dart with isolates, streams, futures and async/await

后记

本篇主要讲述了Dart 入门,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容