Dart语法学习-基础 流程控制、异常、类

Control flow statements

可以使用以下任一方法控制 Dart 代码的流程:

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

可以使用 try-catch 和 throw 影响控制流,如异常中所述:

If and else

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

For loops

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

 使用标准 for 循环进行迭代

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

 Dart 的 for 循环中的闭包捕获索引的值,避免了 JavaScript 中常见的陷阱。

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

for (final c in callbacks) {
  c();
}

 正如预期的那样,输出先是 0,然后是 1。 相反,该示例将在 JavaScript 中打印 2 和 2。

如果你正在迭代的对象是一个 Iterable(比如 List 或 Set)并且你不需要知道当前的迭代计数器,你可以使用 for-in 形式的迭代:

for (final candidate in candidates) {
  candidate.interview();
}

 可迭代类也有一个 forEach() 方法作为另一个选项:

var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3

Log
1
2
3

While and do-while

while (!isDone()) {
  doSomething();
}

do {
  printLine();
} while (!atEndOfPage());

 Break and 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,例如列表或集合,您可能会以不同的方式编写该示例:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
void main() {
  var collection = [1, 2, 3];
  collection.where((element) => element > 1).forEach(print);
}
Log
2
3

 Switch and case

Dart 中的 Switch 语句使用 == 比较整数、字符串或编译时常量。 被比较的对象必须都是同一个类的实例(而不是它的任何子类型),并且该类不能重写 ==。 枚举类型在 switch 语句中运行良好。

通常,每个非空 case 子句都以 break 语句结尾。 结束非空 case 子句的其他有效方式是 continue、throw 或 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 语句,会产生错误:

下面是错误写法:

void main() {
  var command = 'OPEN';
  switch (command) {
    case 'OPEN':
      print(command);
    // ERROR: Missing break
    case 'CLOSED':
      print(command);
      break;
  }
}

 Dart 确实支持空的 case 子句,允许以下形式:

void main() {
  var command = 'CLOSED';
  switch (command) {
    case 'CLOSED': // Empty case falls through.
    case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
      print(command);
      break;
  }
}
Log
CLOSED

 使用一个 continue 语句和一个标签:

void main() {
  var command = 'CLOSED';
  switch (command) {
    case 'CLOSED':
      print(command);
      continue nowClosed;
    // Continues executing at the nowClosed label.

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

    case 'CLOSED_CLOSED_CLOSED':
      print(command);
      break;
  }
}

Log
CLOSED
CLOSED

case 子句可以包含局部变量,这些变量仅在该子句的范围内可见。 

Assert

在开发期间,使用断言语句——assert(condition, optionalMessage);——在布尔条件为假时中断正常执行。 您可以在整个教程中找到断言语句的示例。 这里还有一些:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

 要将消息附加到断言,请添加一个字符串作为断言的第二个参数(可选地使用尾随逗号):

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

 assert 的第一个参数可以是任何解析为布尔值的表达式。 如果表达式的值为真,则断言成功并继续执行。 如果为假,则断言失败并抛出异常(AssertionError)。

断言到底什么时候起作用? 这取决于您使用的工具和框架:

Flutter 在调试模式下启用断言。
仅用于开发的工具,例如 [webdev serve][] 通常默认启用断言。
一些工具,例如 dart run 和 [dart compile js][] 通过命令行标志支持断言:--enable-asserts。
在生产代码中,断言被忽略,并且不评估断言的参数。

Exceptions

 Dart 代码可以抛出和捕获异常。 异常是指示意外发生的错误。 如果异常没有被捕获,引发异常的 isolate 被挂起,通常 isolate 和它的程序被终止。

与 Java 不同,Dart 的所有异常都是未经检查的异常。 方法不会声明它们可能抛出哪些异常,您也不需要捕获任何异常。

Dart 提供了 Exception 和 Error 类型,以及许多预定义的子类型。 当然,您可以定义自己的异常。 然而,Dart 程序可以抛出任何非空对象——而不仅仅是 Exception 和 Error 对象——作为异常。

Throw

下面是抛出或引发异常的示例:

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

 可以抛出任意Object:

throw 'Out of llamas!';

因为抛出异常是一个表达式,所以你可以在 => 语句中抛出异常,也可以在任何其他允许表达式的地方抛出异常:

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

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

 如前面的代码所示,您可以使用 on 或 catch 或两者。 当需要指定异常类型时使用on。(on 可以指定异常类型) 当您的异常处理程序需要异常对象时使用 catch。(需要使用异常对象的话使用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}.');
  }
}

Log
misbehave() partially handled NoSuchMethodError.
main() finished handling NoSuchMethodError.

 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.
}

Classes

Dart 是一种面向对象的语言,具有类和基于混合的继承。 每个对象都是一个类的实例,除 Null 之外的所有类都派生自 Object。 基于 Mixin 的继承意味着虽然每个类(除了顶级类,Object?)都只有一个超类,但是一个类体可以在多个类层次结构中重复使用。 扩展方法是一种在不更改类或创建子类的情况下向类添加功能的方法。

Using class members

对象具有由函数和数据(分别是方法和实例变量)组成的成员。 当你调用一个方法时,你是在一个对象上调用它:该方法可以访问该对象的函数和数据。

使用点 (.) 来引用实例变量或方法:

var p = Point(2, 2);

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

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));
class Point {
  var x;
  var y;

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

void main() {
  var p = Point(2,3);
  print(p.y);
}

Log
3.0

使用 ?.代替 . 当最左边的操作数为空时避免异常: (和kotlin很像)

// If p is non-null, set a variable equal to its y value.
var a = p?.y;

Using constructors

可以使用构造函数创建对象。 构造函数名称可以是 ClassName 或 ClassName.identifier。 例如,以下代码使用 Point() 和 Point.fromJson() 构造函数创建 Point 对象:

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

class Point {
  var x;
  var y;

  Point(double? this.x, double this.y);

  static Point fromJson(Map<String, int> map) {
    return Point(map['x']?.toDouble(), map['y']?.toDouble() ?? 0);
  }
}

void main() {
  var point = Point.fromJson({'x': 1, 'y': 2});
  print(point.x);
  print(point.y);
}

Log
1.0
2.0

 代码具有相同的效果,但在构造函数名称之前使用了可选的 new 关键字:

var p1 = new Point(2, 2);

一些类提供常量构造函数。 要使用常量构造函数创建编译时常量,请将 const 关键字放在构造函数名称之前:

 Constructors

通过创建 与其类同名 的函数来声明构造函数(加上可选的附加标识符,如命名构造函数中所述)。 最常见的构造函数形式,即生成构造函数,创建一个类的新实例:

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

  //与类同名函数
  Point(double x, double y) {
    // See initializing formal parameters for a better way
    // to initialize instance variables.
    this.x = x;
    this.y = y;
  }
}

 this 关键字引用当前实例。

注意:仅当存在名称冲突时才使用它。 否则,Dart 风格会省略 this。

初始化形式参数
将 构造函数 参数分配给 实例变量 的模式非常普遍,Dart 具有初始化形式参数以使其变得容易。

初始化参数也可用于初始化不可为 null 或 final 的实例变量,它们都必须被初始化或提供默认值

class Point {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);
}


void main() {
  var point = Point(2, 3);
  print(point.x);
  print(point.y);
}

Log
2.0
3.0

初始化形式引入的变量是隐式最终的,并且仅在初始化列表的范围内。

默认构造函数

如果不声明构造函数,则会为您提供一个默认构造函数。 默认构造函数没有参数并调用超类中的无参数构造函数。

构造函数不被继承

子类不从它们的超类继承构造函数。 没有声明构造函数的子类只有默认(无参数,无名称)构造函数。

命名构造函数

使用命名构造函数为一个类实现多个构造函数。

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}
const double xOrigin = 2.0;
const double yOrigin = 3.0;

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

void main() {
  var point = Point.origin();
  print(point.x);
  print(point.y);
}

Log
2.0
3.0

 请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。 如果要使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数

 调用非默认超类构造函数

默认情况下,子类中的构造函数调用超类的未命名、无参数构造函数。 超类的构造函数在构造函数主体的开头被调用。 如果还使用了初始化列表,则它会在调用超类之前执行。 综上,执行顺序如下:

  • 初始化列表
  • 超类的无参数构造函数
  • 主类的无参数构造函数

如果超类没有未命名、无参数的构造函数,则您必须手动调用超类中的构造函数之一。 在冒号 (:) 之后、构造函数主体(如果有)之前指定超类构造函数。

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print("in Person");
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson().
  Employee.fromJson(super.data) : super.fromJson() {
    print("in Employee");
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
}

Log
in Person
in Employee
Instance of 'Employee'

因为超类构造函数的参数是在调用构造函数之前计算的,所以参数可以是表达式,例如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}
class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print("in Person");
    data.forEach((key, value) {
      print("key:" + key);
      print("value:" + value.toString());
    });
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson().
  Employee.fromJson(super.data) : super.fromJson() {
    print("in Employee");
  }

  Employee() : super.fromJson({'x': 5}) {
    print("in Employee");
  }
}

void main() {
  var employee = Employee();
  print(employee);
}

Log
in Person
key:x
value:5
in Employee
Instance of 'Employee'

注意:超类构造函数的参数无权访问 this。 例如,参数可以调用静态方法但不能调用实例方法

为避免必须手动将每个参数传递给构造函数的父类调用,您可以使用父类初始化参数将参数转发给指定的或默认的超类构造函数。 此功能不能与重定向构造函数一起使用。 父类初始化器参数与初始化形式参数具有相似的语法和语义:

class Vector2d {
  final double x;
  final double y;

  Vector2d(this.x, this.y);
}

class Vector3d extends Vector2d {
  final double z;

   //将 x 和 y 参数转发给默认的父类构造函数
  // Forward the x and y parameters to the default super constructor like:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

void main() {
  var value = Vector3d(3, 2, 1);
  print(value.x);
  print(value.y);
  print(value.z);
}

Log
3.0
2.0
1.0

如果父类造函数参数位置确定,那么父类初始化器参数参数位置固定,但它们总是可以被命名:

class Vector2d {
  final double x;
  final double y;

  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  final double z;

  // Forward the y parameter to the named super constructor like:
  //将 y 参数转发给命名的父类构造函数
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}

void main() {
  var value = Vector3d.yzPlane(y: 100, z: 200);
  print(value.x);
  print(value.y);
  print(value.z);
}


Log
0.0
100.0
200.0

版本说明:使用超级初始化器参数需要至少 2.17 的语言版本。 如果您使用的是较早的语言版本,则必须手动传入所有超级构造函数参数。

初始化列表

除了调用超类构造函数之外,还可以在构造函数主体运行之前初始化实例变量。 用逗号分隔初始值设定项。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}
class Person {
  String? firstName;
  String? secondName;

  Person.fromJson(Map data)
      : firstName = data['x'],
        secondName = data['y'] {
    print("in Person");
    data.forEach((key, value) {
      print("key:" + key);
      print("value:" + value.toString());
    });
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson().
  Employee.fromJson(super.data) : super.fromJson() {
    print("in Employee");
  }

  Employee() : super.fromJson({'x': 5}) {
    print("in Employee");
  }
}

void main() {
  var employee = Employee.fromJson({'x': 'Z', 'y': 'Y'});
  print(employee.firstName);
  print(employee.secondName);
}

Log
in Person
key:x
value:Z
key:y
value:Y
in Employee
Z
Y

警告:初始化器的右侧无法访问 this。

在开发期间,您可以通过在初始化列表中使用断言来验证输入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

设置最终字段时,初始化列表很方便。

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

Log
3.605551275463989

重定向构造函数

有时构造函数的唯一目的是重定向到同一类中的另一个构造函数。 重定向构造函数的主体是空的,构造函数调用(使用 this 而不是类名)出现在冒号 (:) 之后。

class Point {
  double x, y;

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

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

void main() {
  var point = Point.alongXAxis(100);
  print(point.x);
  print(point.y);
}

Log
100.0
0.0

 常量构造函数

如果你的类产生永不改变的对象,你可以使这些对象成为编译时常量。 为此,定义一个 const 构造函数并确保所有实例变量都是最终的。

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

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

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

void main() {
  var p1 = const ImmutablePoint(2, 2);
  print(p1.x);
  print(p1.y);
  var p2 = const ImmutablePoint(2, 2);
  print(p2.x);
  print(p2.y);
  print(p1 == p2);
}

Log
2.0
2.0
2.0
2.0
true

常量构造函数并不总是创建常量。

工厂构造函数

在实现并不总是创建其类的新实例的构造函数时使用 factory 关键字。 例如,工厂构造函数可能会从缓存中返回一个实例,或者它可能会返回一个子类型的实例。 工厂构造函数的另一个用例是使用无法在初始化列表中处理的逻辑来初始化最终变量。

提示:处理 final 变量的延迟初始化的另一种方法是使用 late final。

Logger 工厂构造函数从缓存返回对象,而 Logger.fromJson 工厂构造函数从 JSON 对象初始化最终变量。

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

void main() {
  var logger = Logger.fromJson({'name':'zy'});
  print(logger.name);
}

Log
zy

 注意:工厂构造函数无法访问它。

像调用任何其他构造函数一样调用工厂构造函数:

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

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

猜你喜欢

转载自blog.csdn.net/zhangying1994/article/details/128900561