Dart 学习笔记

dart-logo

阅读 language-tour 时做的一些笔记。

Important concepts

  • Dart 中一切皆对象,除了 null 之外,所有对象都继承自 Object
  • Dart 是强类型语言,但是类型标注是可选的,因为 Dart 中存在类型推断的机制,使用 var 关键字后编译器可以推断出具体的类型
  • 当开启了空安全(dart ≧ v2.12)之后,对于可为空的值必须明确指出(用 ?),可为空的值转换成不为空的值时使用 !
  • 当需要接收任意对象时使用 Object?
  • Dart 支持泛型,比如 List<int>
  • Dart 支持顶层函数和局部函数,同样也支持顶层变量
  • 标识符可以以字母或者 _ 开头,Dart 中 _ 开头的方法表示私有方法
  • Dart 中也分为表达式 expressions 和声明 statements,表达式有返回值而声明无返回值
  • Dart 工具提供了两种类型的问题:警告和错误。警告表示代码可能无法正确执行,错误分为编译期错误和运行时错误,编译期错误会导致程序无法执行,而运行时错误是代码执行过程中抛出的异常。

Variables

var name = 'Bob';

变量保存了引用,上面的例子中,name 变量包含了一个引用,该引用指向了一个值为 Bob 的 String 对象。

当使用 var 关键字时,变量的类型可以自动被推断出来,当然你也可以显式指出对象的类型。除此之外,如果你不想限定变量的类型,则可以使用 Obejct 或者 dynamic

默认值

如果没有开启空安全 (dart ≧ v2.12),则所有未被初始化的变量都会被初始化为 null,哪怕是数字型的变量,因为 Dart 中一切都是对象。但是,如果开启了空安全,则所有不可为空的变量会被要求先初始化才能使用。

Late 变量

Dart 2.12 之后添加了 late 修饰符,主要有两种用途:

  • 声明不可为空的顶层变量或者成员变量而不直接初始化值
  • 延迟初始化变量(使用到时才初始化)

第一种情况很好理解,因为如果开启了空安全,则顶层变量和成员变量必须要在声明的同时进行初始化,否则编译期无法保证空安全,所以引进 late 之后表明我们不想立马初始化该变量,但是它会在稍后被初始化。

第二种情况则适用于,一个变量对于程序来说是非必须的,或者性能消耗比较大,则可以用 late 修饰,这样,只有在使用到该变量时程序才会初始化它。比如:

// 如果该变量未被使用,则 _readThermometer() 不会被调用
late String temperature = _readThermometer(); // 延迟初始化

final 和 const

final 修饰的变量只能被赋值一次,并且 final 修饰的顶层变量会在第一次被使用时初始化。

final name = 'Bob'; // final 修饰的变量可以没有类型标注
final String nickname = 'Bobby';

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

const 变量是编译期常量(同时也是 final 的)。如果 const 修饰的是成员变量,则需要用 static const

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

finalconst 的不同之处在于,final 修饰的对象不能被修改,但是其属性可以被修改;而 const 修饰的对象和属性都不能被修改,它们是不可变的

Build-in types

Numbers

  • int, 不大于 64 位
  • double, 64 位浮点数

int 和 double 都是 num 的子类,num 类型的数据包含了基本的操作符,比如加减乘以及 abs(), ceil(), floor(), 位运算等。

Strings

  • Dart 中使用 String 可以用单引号或者双引号。不过,在单引号中的 String 对象中需要对 ' 进行转义。
  • 我们可以使用 ${expression} 在 String 中引用变量或者表达式。
  • 我们可以用 == 比较两个 String 的值是否一致。
  • 我们可以使用三个单引号或者双引号来创建多行 String。
  • 我们可以在 String 值之前加 r 来表示原始类型的 String。

Booleans

Dart 中使用 bool 表示布尔类型的值。

Lists

Dart 中数组是用 List 表示的。可以用如下的方式创建数组:

var list = [1, 2, 3];
var list = [
  'Car',
  'Boat',
  'Plane', // Dart 中允许添加 trailing comma,这样可以防止复制粘贴出错
];

Dart 2.3 之后添加了扩展运算符 (...) 和空值敏感的扩展运算符 (...?):

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

Dart 中还有提供了集合 if集合 for 的操作:

var nav = [
  'Home',
  'Mall',
  'Mine',
  if (promoActive) 'Outlet'
];

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

关于其它常见的 API 见 collections

Sets

Dart 中同样用 Set 类型表示无序且唯一的集合。

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

Set 和 List 一样,支持扩展运算符、集合 if 和集合 for 的操作。

Maps

Map 是包含了 key 和 value 集合,其中 key 必须唯一。Dar 中的 Map 同样使用花括号 {} 表示,而且优先级比集合 Set 更高:

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
}; // 自动推断出 map 的类型为 Map<String, String>

// 也可以通过这种方式创建
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var names = {}; // 这种方式创建的是 map 而不是 set

// 访问 map 的语法 [],其实类似调用方法,具体获取到的值是不确定的
// 报错:Const variables must be initialized with a constant value
const lightRed = Colors.red[200];

Runes and grapheme clusters

Dart 中,使用 Runes 表示字符串的 Unicode 字符集。我们可以使用 characters 包下的类来操作字符。

import 'package:characters/characters.dart';
...
var hi = 'Hi ✡️';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}\n'); // 使用 characters 才能正常打印 emoji

Dart 中的字符串使用 UTF-16 编码,如果要用字符串表示 Unicode 字符则需要使用 \uXXXX 的语法。

print('\u{1f606}'); // 😆

Symbols

Dart 中使用 Symbol 对象表示操作符或者标识符,用 # + 修饰符表示。

Functions

Dart 中一切都是对象,函数的类型用 Function 表示,而且函数也可以作为变量或者作为方法的参数进行传递。

void say(String content) {
  print(content);
}

// Dart 中只有单个表达式的函数可以使用缩写,用 => 代替 { }
bool isEvenNumber(int num) => num > 0 && num % 2 == 0;

参数

Dart 中的函数除了可以有普通的参数,还可以是具名参数或者可选位置参数。如果使用了 Sound null safety (sdk ≧ 2.12),且没有指明默认值,则这两种类型参数都必须是可为空的,即参数后跟 ?

具名参数

具名参数 (Named parameters) 是可选的参数。

// 定义函数
void enableFlags({bool? bold, int? index}) {}

// 也可以给具名函数的参数添加默认值
void enableFlags({bool? bold = true}) {}

// 用 required 标记具名参数中必须有的参数
void enableFlags({required bool? bold}) {}

// 调用函数,无参数
enableFlags()

// 调用时如果有参数名则必须写参数名称
enableFlags(
  bold: true,
  hidden: false
);
可选位置参数

可选位置参数 (Optional positional parameters) 和普通的参数唯一的不同之处是它是可选的😅。用 [] 圈起来的参数即可选位置参数。比如:

// title 是必须的位置参数,而 subtitle 是可选的
void say(String title, [String? subtitle]) {
  print(title);
  if (subtitle != null) print(subtitle);
}

// 可选位置参数也可以有默认值
void sing(String song, [String? instrument = 'Piano']) { }

main() 函数

每个应用都有一个 main() 函数作为应用的入口。main() 函数通常返回空,并且可以有数组作为参数。

void main(List<String> arguments) {
  print(arguments);
}

匿名函数

Dart 中的匿名函数和其它语言中类似,可以有多个参数或者没有参数,后跟方法体,形式如下:

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

如果只有单个的表达式或者只有返回值,可以使用箭头表达式:

([[Type] param1[, …]]) => expression; // 如果是赋值表达式,则该值就是返回值

Lexical scope

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

Lexical closures

词法闭包也叫函数闭包,和 JS 中的闭包概念类似,即定义在函数中的函数,同时使得该函数能够访问其它函数作用域中的变量。

Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);
  print(add2(3)); // 这里 add2 访问了 makeAdder 中定义的值,也即把 2 保存了下来
  print(makeAdder(10)(3));
}

Operators

Dart 中支持的操作符表,见 Operators

Cascade notation

// cascade notation,使用级联表达式使得属性赋值变得更简单
var paint = Paint()
  ..color = 'Black'
  ..strokeCap = 'Round'
  ..strokeWidth = 5.0;
print(paint);

Control flow statements

switch and case

// Dart 中的 case 语法有 fall through 机制
// 但是如果定义了 case 内容而没有 break 则会报错
var command = 'CLOSED';
switch (command) {
  case 'EARLY_CLOSE': // fall through to 'CLOSED'
  case 'CLOSED':
    var youCantAccess = 1; // Dart 中的 case 自带作用域
    print('bye bye');
    continue afterClosed; // 可以使用 label 绕过无 break 的限制

  afterClosed:
  case 'CLOSED_AFTER':
    // print(youCantAccess); // undefined

    print('see you tomorrow');
    break;
}

Exceptions

Dart 中所有异常都是 unchecked exceptions,意味着 Dart 不会强制你去捕捉异常。

Catch

Dart 中的 try catch 语法:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 特定类型的异常
  buyMoreLlamas();
} on Exception catch (e, s) { // 第二个参数为错误栈
  // 其它类型的异常
  print('Unknown exception: $e');
} catch (e) {
  // 所有其它未指定的错误
  print('Something really unknown: $e');
}

Classes

Dart 中一切皆对象,除了 null 之外,所有对象都是某个类的实例。除了可以继承类之外,Dart 还提供了一种基于 mixin 的继承,也就是说可以通过 with 关键字继承某个没有构造器的类来扩展功能,还可以使用扩展函数

Using class members

所有对象都由成员变量和方法构成,使用 . 的语法访问对象的属性或者方法,使用 ?. 访问可为空对象的属性和方法。

Using constructors

Dart 中创建构造器除了可以使用类名,也可以使用 类名.构造器名() 的形式。

class DummyClass {
  late int i;
  
  // 默认构造函数
  DummyClass(this.i);
  
  // 我们可以为构造函数定义名称,这种构造器被称为「具名构造器」
  DummyClass.create(int i, String message) {
    print(message);
    this.i = i;
  }
  
  // 在「具名构造器」后为成员变量赋值
  DummyClass.ten() : i = 10 { // :后的部分称为 initializer list
    print('Ten ${this.i}'); // print: Ten 10
  }
}

Getting an object’s type

我们可以使用 runtimeType 获得对象的类型 Type

var dummy = DummyClass.create(1);
print('The type of a is ${dummy.runtimeType}');

Instance variables

  • 所有未初始化可为空的实例变量都会被初始化为 null。
  • 所有实例变量都会默认自带 getter 函数,非 final 的实例变量和不带初始化器的 late final 实例变量会自动生成 setter 函数。
  • 非延迟初始化的实例变量的值会在对象创建之后,构造器和初始化器列表执行之前,就被赋值。可为空的赋值为 null,不可为空的赋值为初始值。
  • 实例变量可以是 final 的,但是必须在构造器或者初始化列表中进行赋值。

Constructors

Dart 中类的构造器和其它语言类似:

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

不过,Dart 提供了一种语法糖的写法:

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);
}
具名构造器

我们可以通过具名构造器 (Named constructors) 为一个类实现多个构造器。不过具名构造器不能被继承,如果想在子类中使用和父类相同的构造器,只能为子类单独实现。

class Weather {
  var humidity = '';

  Weather.display(this.humidity);
}

class Shower extends Weather{
  Shower.display(String humidity) : super.display(humidity);
}
构造器初始化列表

除了调用父类构造器之外,我们还可以在构造器方法体执行之前初始化成员变量,称为 Initializer list

Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}
构造器初始化顺序

默认情况下,子类会先调用父类中的默认构造函数(未定义的话会生成一个无参构造器),如果存在构造器初始化列表 (Initializer list) 则会先调用它们。所以,当使用默认构造函数创建对象时,构造器的初始化顺序为:

  1. 构造器初始化列表
  2. 父类无参构造器
  3. 子类无参构造器

另外,如果父类未定义默认构造器,则子类实现构造器时必须先调用父类的某个具名构造器。

重定向构造器
class Point {
  double x, y;

  Point(this.x, this.y);

  // 使用 this 关键字重定向到默认构造器
  Point.alongXAxis(double x) : this(x, 0);
}
常量构造器

当你想要创建的对象是编译期常量时,可以使用 const 修饰构造器(比如用于创建注解):

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

  final double x, y; // 记得所有的变量都要是 final 的

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

使用常量构造器 (Constant Constructors) 初始化对象时,使用相同的值的对象只会在第一次创建时被初始化,也就是说相同值的对象只会初始化一次

工厂构造器

使用工厂模式创建对象。

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

  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

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

Methods

Getters and setters
class Rectangle {
  double left, top, width, height;

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

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

Abstract classes

Dart 中的抽象类同样无法被直接实例化,如果需要实例化可以通过工厂构造器

abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

Implicit interfaces

Dart 中同样使用 implement 关键字实现一个或多个接口。

class Point implements Comparable, Location {...}

Extending a class

使用 extends 继承父类,使用 super 引用父类。

Extension methods

和 Kotlin 类似,在 Dart 中我们同样可以通过扩展函数扩充函数库。

Enumerated types

Dart 中的枚举类和 Java 中类似。

enum Color { red, green, blue }

枚举类有以下限制:

  • 无法继承、实现或者混用 (mixin) 枚举类
  • 无法直接实例化

Adding features to a class: mixins

Dart 中还提供了另外一种复用代码的方式 Mixins。我们可以把可复用的代码放到 mixin 类中。子类使用 with 关键字进行关联。

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

class Musician with Musical {
  // ···
}

class Maestro extends Person with Musical {
  Maestro(String maestroName) : super(maestroName) {
    canPlayPiano = true;
  }
}

除此之外,我们还可以限制 mixin 的使用范围 (on),以及像接口一样使用逗号 (,) 分割继承多个 mixin:

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer, DancerPerformer {
  // ...
}

Class variables and methods

Dart 中的类同样可以使用静态变量和常量,以及静态方法。

class DummyClass {
  static const a = 1;
  static var b = 2;
  static void f() { }
}

Generics

Dart 中的泛型(参数化类型)和 Java 以及 Kotlin 等语言非常相似,使用 <> 表示泛型。

为什么使用泛型

使用泛型一般有两个目的:

  • 指明泛型类型之后,减少代码出错。比如在集合中加入不该加入的值。
  • 使用泛型减少复制粘贴的代码。比如持有某个对象的类,当需要改变持有的对象而其它部分不发生变化时,如果使用了泛型就不用重新复制一个新的类。

Using collection literals

即在使用集合时定义集合类型,形式如 <type>[] or <type>{} or <keyType, valueType>{}

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};

泛型集合及其类型

Java 中,由于泛型存在类型擦除,所以是无法确定集合的确切类型的。但是,Dart 中的泛型是 reified 的,也就是说在运行时也能得到集合的类型信息。

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

泛型参数类型的限制

Dart 中限制泛型参数边界同样使用 extends 关键字。

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

class Extender extends SomeBaseClass {...}

Libraries and visibility

Dart 中,我们可以通过 importlibrary 指示符创建模块化的、可共享的代码库。另外,即使不使用 library 指示符,每个 dart app 都是一个 library。

导入一个 library 的语法:

import 'dart:html'; // html 是 dart 内建的库,所以使用 dart 作为命名空间

其它库可以使用文件路径或者 package: 命名空间:

import 'package:test/test.dart'; // 导入一些三方库,比如通过 pub 包管理工具发布的三方库

指定库的前缀

如果两个库有相同的名称,可以通过 as 指定前缀解决冲突。

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

部分导入

如果只使用到库的一部分,可以使用部分导入的语法,通过 showhide 做到。

// 只导入 foo
import 'package:lib1/lib1.dart' show foo;

// 导入除了 foo 意外的部分
import 'package:lib2/lib2.dart' hide foo;

实现 libraries

Create Library Packages

Asynchrony support

Dart 中有很多返回 FutureStream 对象的函数,我们可以使用它们编写出异步的代码。除此之外,Dart 还提供了 asyncawait 关键字,让你可以像 JS 一样,更方便地写出一些更具易读性的异步代码。

常见的用法如下:

Future<String> lookUpVersion() async => '1.0.0';

// 在方法末尾添加 async 关键字,说明方法执行耗时操作,返回值通常是 Future
Future checkVersion() async {
  var version = '';
  try {
    version = await lookUpVersion();
  } catch (e) {
    // React to inability to look up the version
  }
}

如果 async 方法无有意义的返回值,可以返回 Future<void>

Generators

Dart 中的 Generator 函数和 ES6 中相似,如果你想要延迟生成一系列值,可以考虑使用生成器。Dart 中的 Generator 函数分为两种:

  • 同步生成器:返回 Iterable 对象
  • 异步生成器:返回 Stream 对象
// 同步生成器
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

// 异步生成器
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);
  }
}

Callable classes

我们可以通过给某个类实现 call() 方法,使得该类可以像方法一样被调用。

class WannabeFunction {
  String call(String a, String b, String c) {
    print('WannabeFunction.call()');
    return '$a $b $c!';
  }
}

var wf = WannabeFunction(); // 初始化该类对象,然后可以像方法一样调用它们
wf('Hi', 'there,', 'gang') // 实际调用的是 call() 方法

Isolates

与其它语言的并发机制不同,Dart 中并没有采用共享状态的并发机制 (shared-state concurrency),而是使用了 isolates。所有的代码都运行在自己的 isolate 中,每个 isolate 都有自己的内存堆,保证了相互独立性。

更多资料见:Isolates

Typedefs

Dart 中,一切皆是对象,函数也是对象,但是函数对象的信息在运行时往往会被丢失,比如下面这个例子:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) : compare = f;
}

int sort(Object a, Object b) => 0;

SortedCollection sc = SortedCollection(sort);

// 只知道 compare 是 Function,但是其具体类型被丢失了
assert(sc.compare is Function);

typedef 就是为了解决上面这个问题而出现的,我们可以给方法类型一个别名,这样,当方法被赋值到一个变量上时就能保留其类型信息:

// 函数的信息被保存在 Compare 中
typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare; // 用于接收我们定义的函数

  SortedCollection(this.compare);
}

int sort(Object a, Object b) => 0;

SortedCollection sc = SortedCollection(sort);
assert(sc.compare is Compare); // 现在可以确定函数的具体类型了

typedef 也可以使用泛型:

typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

assert(sort is Compare<int>); // True!

Metadata

通过元信息注解,我们可以为类和方法提供额外的信息。Dart 中自带的注解有 @deprecated@override,除此之外,你也可以自定义注解,只要在类的构造器上使用 const 关键字就可以了:

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

Comments

Dart 支持三种类型的注释:单行注释、多行注释和文档注释。

/// 文档注释,使用 [] 给成员属性或者方法添加链接,比如查看 [main] 方法
/// 虽然 Dart 中也能用 /** */ 作为文档注释,但是编辑器会提醒你用 /// 代替
void main() {
  // 单行注释
  print('Howdy!');
  /*
   * 多行注释
   */
   print('Hola!');
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 俗话说 ‘工欲善其事必先利其器’ 想要撸flutter app 而不懂 dart 那就像一个不会英语的人在和英国人...
    奔跑的痕迹阅读 833评论 0 1
  • Dart语法预览 重要概念: 所有能够使用变量引用的都是对象, 每个对象都是一个类的实例。在 Dart 中 甚至连...
    xmb阅读 447评论 0 2
  • 一个基本的Dart程序 下面的代码中使用了很多Dart最基本的特性: 重要概念 所有你能够赋值给一个变量的都是一个...
    YZune阅读 14,498评论 2 14
  • 1、变量声明及命名规则 1、简介 flutter中文网:https://flutterchina.club/get...
    Y__W阅读 811评论 0 1
  • 声明 本笔记是我自己在学习Dart语言基础的时候做的笔记。有些代码只是为了演示用法,不要纠结逻辑。 本笔记是站在一...
    蜗牛学开车阅读 954评论 0 6