cv.cm

编码 · 编程语言

Dart 入门教程

阅读约 9 分钟

简短回答

Dart 是 Google 推出的静态类型、面向对象语言,因 Flutter 应用框架而广为人知。它具备健全的空安全,可编译为原生代码以及 JavaScript 或 WebAssembly,开发时支持快速热重载。本页依次讲解环境搭建、类型、空安全、流程控制、函数、集合、类、异步代码和错误处理。

Dart 是什么,用来做什么

Dart 是 Google 开发的、面向客户端优化的编程语言。它是带类型推断的静态类型语言,拥有健全的空安全,既能编译为原生机器码(用于移动端、桌面和服务器),也能编译为 JavaScript 或 WebAssembly(用于 Web)。开发时它运行在带热重载的虚拟机上,修改后几秒内就能在运行中的应用里看到效果。

大多数人学 Dart 是为了用 Flutter 开发应用。Flutter 是 Google 的 UI 工具包,一套代码即可构建 Android、iOS、Web、Windows、macOS 和 Linux 应用。Dart 也可用于命令行工具和后端服务。

安装 Dart 并运行 Hello, world

从 dart.dev 安装 Dart SDK(或安装自带 Dart 的 Flutter)。每个 Dart 程序都从顶层的 main 函数开始执行。

// hello.dart
void main() {
  print('Hello, world!');
}
dart run hello.dart
# Hello, world!
dart compile exe hello.dart   # builds a native executable
dart create my_app            # creates a new console project

本页的所有示例都可以在 DartPad 中试运行,它是 dartpad.dev 上基于浏览器的编辑器。

变量与基本类型

用 var 让编译器推断类型,用 final 声明只赋值一次的变量,用 const 声明编译期常量。你也可以始终显式写出类型。

void main() {
  var name = 'Dart';        // inferred as String
  int year = 2011;          // explicit type
  final now = DateTime.now(); // set once, at run time
  const pi = 3.14159;       // compile-time constant
  double ratio = 2;         // int literal becomes 2.0
  bool ok = year > 2000;
  print('$name since $year, pi=${pi.toStringAsFixed(2)}, ok=$ok, ratio=$ratio');
  // Dart since 2011, pi=3.14, ok=true, ratio=2.0
  print(now.year > 2000);   // true
}
类型示例说明
int42整数;在原生平台上为 64 位
double3.1464 位浮点数
num7 或 7.5int 和 double 的父类型
booltrue条件中只允许 true 和 false
String'hi'UTF-16;支持 $x 和 ${expr} 插值
dynamic任意类型关闭静态检查;大多数代码中应避免使用

在 Web 上,int 值用 JavaScript 数字表示,因此非常大的整数可能丢失精度。

健全空安全

从 Dart 3 起,所有 Dart 代码都使用健全空安全。String 类型的变量永远不会为 null;若要允许为空,写成 String?。编译器会检查每一处使用,因此空值错误在程序运行之前就能发现。

int lengthOf(String? s) {
  // return s.length;         // compile error: s might be null
  if (s == null) return 0;
  return s.length;            // promoted to String after the check
}
void main() {
  String? nickname;
  print(nickname?.length);    // null (null-aware access)
  print(nickname ?? 'guest'); // guest (default if null)
  nickname ??= 'dee';         // assign only if null
  print(lengthOf(nickname));  // 3
  late String config;         // initialized later, checked at run time
  config = 'loaded';
  print(config);              // loaded
}

流程控制

Dart 有常见的 if、for、while 和 do-while,以及可遍历任意可迭代对象的 for-in 循环。从 Dart 3 起,switch 支持模式匹配,并且可以作为表达式使用。

void main() {
  var scores = [72, 95, 48];
  for (var s in scores) {
    if (s >= 90) {
      print('$s excellent');
    } else if (s >= 50) {
      print('$s pass');
    } else {
      print('$s fail');
    }
  }
  for (var i = 0; i < 3; i++) {
    print('i = $i');
  }
  var day = 6;
  var kind = switch (day) {
    6 || 7 => 'weekend',
    >= 1 && <= 5 => 'weekday',
    _ => 'invalid',
  };
  print(kind); // weekend
}

函数

函数是一等对象。参数可以是位置参数、放在 [] 中的可选位置参数,或放在 {} 中的命名参数。命名参数默认可选,除非标记为 required。箭头语法 => 是只返回一个表达式的函数的简写。

int add(int a, int b) => a + b;
String greet(String name, {String greeting = 'Hello', required bool loud}) {
  var text = '$greeting, $name';
  return loud ? text.toUpperCase() : text;
}
String label(String text, [int? width]) =>
    width == null ? text : text.padRight(width, '.');
void main() {
  print(add(2, 3));                              // 5
  print(greet('Ana', loud: false));              // Hello, Ana
  print(greet('Ana', greeting: 'Hi', loud: true)); // HI, ANA
  print(label('id', 5));                         // id...
  var twice = (int x) => x * 2;                  // anonymous function
  print([1, 2, 3].map(twice).toList());          // [2, 4, 6]
}

集合:List、Set、Map 与记录

核心集合有 List(有序)、Set(元素唯一)和 Map(键值对)。集合字面量支持 if、for 和展开运算符 ...。Dart 3 新增的记录(Record)无需声明类即可把多个值组合在一起。

void main() {
  var nums = [3, 1, 2];               // List<int>
  nums.add(4);
  nums.sort();
  print(nums);                        // [1, 2, 3, 4]
  var tags = {'dart', 'flutter', 'dart'};
  print(tags.length);                 // 2
  var ages = {'Ann': 31, 'Bo': 25};   // Map<String, int>
  ages['Cy'] = 40;
  print(ages['Bo']);                  // 25
  var showAll = false;
  var menu = ['Home', if (showAll) 'Admin', for (var n in nums) 'Item $n'];
  print(menu);  // [Home, Item 1, Item 2, Item 3, Item 4]
  (String, int) pair = ('Ann', 31);   // a record
  var (who, age) = pair;              // destructuring
  print('$who is $age');              // Ann is 31
}

类与 async/await

Dart 是面向对象的:每个值都是对象。类支持带初始化形参(this.x)的构造函数、命名构造函数、getter、mixin 和接口。异步代码使用 Future 和 Stream 配合 async 与 await,使 Flutter 应用在等待文件或网络时保持响应。

class Point {
  final double x, y;
  const Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;     // named constructor
  double get sum => x + y;           // getter
  @override
  String toString() => 'Point($x, $y)';
}
Future<String> fetchUser() async {
  await Future.delayed(Duration(milliseconds: 100));
  return 'ana';
}
Future<void> main() async {
  print(Point(1, 2).sum);            // 3.0
  print(Point.origin());             // Point(0.0, 0.0)
  var user = await fetchUser();
  print('user: $user');              // user: ana
}

错误处理

用 throw 抛出异常,用 try、on、catch 和 finally 处理。用 on SomeType 捕获特定类型。Dart 区分 Exception(程序可以处理的预期问题)和 Error(程序缺陷,例如断言失败或越界错误),后者通常不应捕获。

int parseAge(String s) {
  var n = int.tryParse(s);            // null instead of throwing
  if (n == null || n < 0) {
    throw FormatException('Not a valid age: $s');
  }
  return n;
}
void main() {
  for (var input in ['42', 'abc']) {
    try {
      print(parseAge(input));
    } on FormatException catch (e) {
      print('Error: ${e.message}');
    } finally {
      print('checked $input');
    }
  }
}
// 42 / checked 42 / Error: Not a valid age: abc / checked abc

一个完整的小程序:购物车合计

这个程序用类表示商品,用映射保存购物车,并打印小票。它综合运用了类、映射、字符串格式化和集合方法。

class Product {
  final String name;
  final double price;
  const Product(this.name, this.price);
}
void main() {
  const apple = Product('Apple', 0.5);
  const bread = Product('Bread', 2.25);
  final cart = <Product, int>{apple: 6, bread: 2};
  var total = 0.0;
  cart.forEach((product, qty) {
    final line = product.price * qty;
    total += line;
    print('${product.name.padRight(6)} x$qty  ${line.toStringAsFixed(2)}');
  });
  final count = cart.values.reduce((a, b) => a + b);
  print('$count items, total ${total.toStringAsFixed(2)}');
}
// Apple  x6  3.00
// Bread  x2  4.50
// 8 items, total 7.50

接下来学什么

  • dart.dev 上的 Dart 语言导览和教程。
  • Effective Dart:官方的风格与设计指南。
  • 如果想开发应用,请阅读 flutter.dev 上的 Flutter 文档。
  • pub.dev:Dart 和 Flutter 的包仓库。

常见问题

Dart 只能用于 Flutter 吗?

不是。Dart 是通用语言,可以开发命令行工具、服务器和 Web 应用。Flutter 只是学习它最热门的理由。

Dart 是编译型还是解释型?

两种模式都有。开发时 Dart 在虚拟机上以即时编译方式运行并支持热重载;发布时则提前编译为原生机器码,Web 端则编译为 JavaScript 或 WebAssembly。

Dart 中的 final 和 const 有什么区别?

final 变量只赋值一次,可以在运行时赋值。const 值必须在编译期已知,并且是深度不可变的,因此 Flutter 可以复用常量 widget。

Dart 和 Java 或 JavaScript 相似吗?

它的 C 风格语法和类对 Java、C# 和 JavaScript 开发者来说很熟悉。与 JavaScript 不同的是,Dart 是静态类型语言,并具备健全的空安全。

Dart 中的 ?? 运算符有什么作用?

a ?? b 在 a 不为 null 时返回 a,否则返回 b。与之相关的 a ??= b 仅在 a 为 null 时才把 b 赋给 a。

全部编码教程