Dart语言基础1 2025-06-11 周三

Dart 简介

变量

  • 用 var 来定义变量,而不用显式指定它们的类型。由于其支持类型推断,因此大多数变量的类型会由它们的初始化内容决定:
var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
  'tags': ['saturn'],
  'url': '//path/to/saturn.jpg',
};
  • 延迟初始化变量: 如果你确定变量在使用之前已设置,但 Dart 推断错误的话,可以将变量标记为 late 来解决这个问题:
late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}
  • 如果你不打算更改一个变量,可以使用 final 或 const 修饰它
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

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

导入 (Import)

// Importing core libraries
import 'dart:math';

// Importing libraries from external packages
import 'package:test/test.dart';

// Importing files
import 'path/to/my_other_file.dart';

枚举类型 (Enum)

枚举(enum)支持定义 方法、字段 和 构造函数,可以看做特殊的类,推荐使用。

  • 下面这个简单的枚举示例定义了一组行星类别:
enum PlanetType { terrestrial, gas, ice }
  • 下面是一个增强型枚举的示例,定义了一组行星类的常量实例,即太阳系的行星:
/// Enum that enumerates the different planets in our solar system
/// and some of their properties.
enum Planet {
  mercury(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  venus(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
  // ···
  uranus(planetType: PlanetType.ice, moons: 27, hasRings: true),
  neptune(planetType: PlanetType.ice, moons: 14, hasRings: true);

  /// A constant generating constructor
  const Planet({
    required this.planetType,
    required this.moons,
    required this.hasRings,
  });

  /// All instance variables are final
  final PlanetType planetType;
  final int moons;
  final bool hasRings;

  /// Enhanced enums support getters and other methods
  bool get isGiant =>
      planetType == PlanetType.gas || planetType == PlanetType.ice;
}
  • 你可以这样使用 Planet 枚举:
final yourPlanet = Planet.earth;

if (!yourPlanet.isGiant) {
  print('Your planet is not a "giant planet".');
}

扩展类(继承)

Dart 支持单继承。

class Orbiter extends Spacecraft {
  double altitude;

  Orbiter(super.name, DateTime super.launchDate, this.altitude);
}

Mixins

  • Mixin 是一种在多个类层次结构中重用代码的方法。
mixin Piloted {
  int astronauts = 1;

  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}
  • 现在你只需使用 Mixin 的方式继承这个类就可将该类中的功能添加给其它类。
class PilotedCraft extends Spacecraft with Piloted {
  // ···
}

接口和抽象类

  • 所有的类都隐式定义成了一个接口。因此,任意类都可以作为接口被实现。
class MockSpaceship implements Spacecraft {
  // ···
}
  • 你可以创建一个被任意具体类扩展(或实现)的抽象类。抽象类可以包含抽象方法(不含方法体的方法)。
abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

异步

  • 使用 asyncawait 关键字可以让你避免回调地狱 (Callback Hell) 并使你的代码更具可读性。
const oneSecond = Duration(seconds: 1);
// ···
Future<void> printWithDelay(String message) async {
  await Future.delayed(oneSecond);
  print(message);
}

运算符Operators

  • 基本的加减乘除和其他语言一样,取商和取余操作带来方便。
assert(5 ~/ 2 == 2); // 结果是整型; 取商
assert(5 % 2 == 1); // 余数
  • 自增自减,关系运算符,赋值运算,逻辑运算,位运算等都和C++差不多。

类型判定运算符

  • 类型检查:is和is!
if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}
  • 使用as进行类型转换,类型不匹配会抛出异常,可以考虑用try catch结构,但是会影响性能,应尽量少用
void main() {
  Object? obj = 42;

  try {
    String str = obj as String; // 可能抛出CastError
    print(str.length);
  } catch (e) {
    print('类型转换失败: $e');
  }
}
  • 使用条件类型转换(is + 类型提升),替代as
void main() {
  Object? obj = 42; // 假设这是一个未知类型的对象

  if (obj is String) {
    // 类型提升:Dart自动将obj视为String类型
    print(obj.length); // 安全访问String属性
  } else {
    print('obj不是String类型');
  }
}
T? safeCast<T>(dynamic value) {
  if (value is T) return value;
  return null;
}

void main() {
  Object? obj = 42;
  String? str = safeCast<String>(obj);
  print(str); // 输出:null
}
  • 对可空类型使用as? 如果类型不匹配,返回null而不是抛出异常。(这个应该作用as的主要使用方式)
void main() {
  Object? obj = 42;

  // 如果obj不是String类型,str为null
  String? str = obj as? String;
  print(str); // 输出:null

  // 安全访问属性
  print(str?.length); // 输出:null
}
  • 集合操作使用whereType<T>():过滤并转换符合条件的元素。避免使用as
void main() {
  List<Object?> mixedList = ['a', 1, null, true];

  // 只保留String类型的元素
  List<String> strings = mixedList.whereType<String>().toList();
  print(strings); // 输出:[a]
}

条件表达式

  • 如果赋值是根据布尔值, 考虑使用 ?:。
var visibility = isPublic ? 'public' : 'private';
  • 如果赋值是基于判定是否为 null, 考虑使用 ??。
String playerName(String name) => name ?? 'Guest';

级联运算符 (..)

能看懂就行,平时还是不要用了,鸡肋功能,意义不大

querySelector('#confirm') // 获取对象。
  ..text = 'Confirm' // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

上面的代码等价于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

内建类型Built-in types

数值类型的分类

  • num:抽象基类,所有数值类型的父类。
  • int:整数类型,范围为 -2⁶³ 到 2⁶³-1。
  • double:双精度浮点数,符合 IEEE 754 标准。
void main() {
  // 整数
  int age = 25;
  int maxValue = 2147483647; // 2³¹-1(在32位系统上)
  
  // 浮点数
  double pi = 3.14159;
  double price = 9.99;
  
  // num类型(可接受int或double)
  num count = 10;       // int类型
  num temperature = 25.5; // double类型
  
  // 科学计数法
  double largeNumber = 1.23e5; // 1.23 × 10⁵ = 123000
}

对应于后台Java的number类型,客户端用num进行对接是最合适,避免因为类型不匹配导致异常。

数值格式化

  • 使用NumberFormat类(需导入intl包):
import 'package:intl/intl.dart';

void main() {
  double price = 1234.5678;
  
  // 货币格式化
  String formattedPrice = NumberFormat.currency(symbol: '¥', decimalDigits: 2).format(price);
  print(formattedPrice); // ¥1,234.57
  
  // 自定义格式
  String customFormat = NumberFormat('#,##0.00').format(price);
  print(customFormat); // 1,234.57
}

对于货币,日期等特殊格式,可以考虑用这个。

精度问题

  • 在 Dart 中,double.toString() 方法可能会因为浮点数精度问题导致输出过长的字符串。这是 IEEE 754 双精度浮点数格式的固有特性,不仅限于 Dart,几乎所有编程语言都会遇到这个问题。
void main() {
  double d = 0.1;
  print(d.toString()); // 输出:0.100000000000000055511151231257827021181583404541015625
}
  • 使用 toStringAsFixed() 控制小数位数,将浮点数格式化为指定小数位数的字符串,自动四舍五入。(这是显示数字最常用的方法)
void main() {
  double d = 0.1;
  print(d.toStringAsFixed(2)); // 输出:0.10
}

String

  • 使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";
  • 使用 r 前缀,可以创建 “原始 raw” 字符串:
var s = r"In a raw string, even \n isn't special.";

Boolean

  • Dart 使用 bool 类型表示布尔值。 Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。

  • Dart 的类型安全意味着不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是应该像下面这样,明确的进行值检查:

// 检查空字符串。
var fullName = '';
assert(fullName.isEmpty);

// 检查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);

// 检查 null 值。
var unicorn;
assert(unicorn == null);

// 检查 NaN 。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

List

  • 几乎每种编程语言中最常见的集合可能是 array 或有序的对象集合。 在 Dart 中的 Array 就是 List对象

  • Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引。

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

list[1] = 1;
assert(list[1] == 1);

Set

  • 在 Dart 中 Set 是一个元素唯一且无序的集合。下面是通过字面量创建 Set 的一个简单示例:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
  • 要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量:
var names = <String>{};
// Set<String> names = {}; // 这样也是可以的。
// var names = {}; // 这样会创建一个 Map ,而不是 Set 。

Map

  • 通常来说, Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
  • 如果 Map 中不包含所要查找的 key,那么 Map 返回 null:
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

Rune

  • 在 Dart 中, Rune 用来表示字符串中的 UTF-32 编码字符。

  • Unicode 码点:每个 Unicode 字符对应一个唯一的数字(如 A → U+0041,😀 → U+1F600)。

  • UTF-16 编码:Dart 字符串使用 UTF-16 编码,基本字符(如 ASCII)用 1 个代码单元(16 位)表示,复杂字符(如表情符号)用 2 个代码单元(32 位)表示。

  • rune:本质是一个整数,代表 Unicode 码点的值(如 0x1F600 表示 😀)。

  • 字符串与 rune 互转

void main() {
  // 字符串 → rune
  String emoji = '😀';
  List<int> runes = emoji.runes.toList();
  print(runes); // [128512](十进制的0x1F600)
  
  // rune → 字符串
  String fromRune = String.fromCharCode(0x1F600); // 或 String.fromCharCode(128512)
  print(fromRune); // 😀
  
  // 多个rune → 字符串
  String multipleRunes = String.fromCharCodes([0x1F600, 0x1F601]);
  print(multipleRunes); // 😀😁
}
  • 遍历字符串中的 rune
void main() {
  String str = 'Hello 😀 你好';
  
  // 方式1:使用runes属性
  str.runes.forEach((rune) {
    print(String.fromCharCode(rune)); // 正确输出每个字符
  });
  
  // 方式2:使用for-in循环(自动处理rune)
  for (var char in str) {
    print(char); // 自动处理复杂字符
  }
}
  • 处理包含复杂字符的字符串长度
void main() {
  String str = 'A😀B';
  
  // 错误:基于UTF-16代码单元的长度
  print(str.length); // 4(A占1个,😀占2个,B占1个)
  
  // 正确:基于rune的长度
  print(str.runes.length); // 3(A、😀、B各占1个rune)
}
  • 比较和排序 rune
void main() {
  String str1 = 'A';
  String str2 = 'B';
  
  // 比较Unicode码点
  int comparison = str1.runes.first.compareTo(str2.runes.first);
  print(comparison); // -1(A的码点小于B)
  
  // 排序字符串(按Unicode码点)
  List<String> chars = ['B', 'A', '😀'];
  chars.sort((a, b) => a.runes.first.compareTo(b.runes.first));
  print(chars); // [A, B, 😀]
}
  • 复杂场景可使用第三方库(如 characters 包)简化操作:
import 'package:characters/characters.dart';

void main() {
  String str = 'A😀B';
  int length = str.characters.length; // 3(正确)
}

Symbol

  • 在 Dart 中,Symbol 是一种特殊的对象,用于在运行时表示标识符(如类名、方法名、变量名)。它主要用于反射(reflection)、元编程(metaprogramming)和代码生成工具。

  • 创建 Symbol

void main() {
  // 方式1:使用#语法
  Symbol methodSymbol = #myMethod;
  Symbol fieldSymbol = #name;
  
  // 方式2:使用Symbol构造函数(不推荐,性能较差)
  Symbol dynamicSymbol = Symbol('runtimeName');
  
  print(methodSymbol); // Symbol("myMethod")
}
  • 反射(dart:mirrors)仅在 JIT 模式下可用(如开发环境),AOT 编译(如生产环境)会移除相关代码。
import 'dart:mirrors';

class Person {
  String name = 'Alice';
  
  void greet() {
    print('Hello!');
  }
}

void main() {
  Person person = Person();
  InstanceMirror mirror = reflect(person);
  
  // 通过Symbol访问字段
  Symbol nameSymbol = #name;
  dynamic name = mirror.getField(nameSymbol).reflectee;
  print(name); // Alice
  
  // 通过Symbol调用方法
  Symbol greetSymbol = #greet;
  mirror.invoke(greetSymbol, []); // 输出:Hello!
}
  • 将常用的 Symbol 定义为常量,避免重复创建:
const Symbol nameSymbol = #name;
const Symbol ageSymbol = #age;
  • 在 json_serializable 等代码生成工具中,使用 Symbol 定义 JSON 字段名:
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  @JsonKey(name: #full_name) // 等价于 name: 'full_name'
  String fullName;
  
  User(this.fullName);
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
  • 自定义元数据
class MyAnnotation {
  final Symbol value;
  
  const MyAnnotation(this.value);
}

@MyAnnotation(#myField)
class MyClass {
  // ...
}

void main() {
  // 通过反射获取注解(需要dart:mirrors)
  ClassMirror mirror = reflectClass(MyClass);
  InstanceMirror annotation = mirror.metadata.first;
  Symbol symbolValue = annotation.reflectee.value;
  print(symbolValue); // Symbol("myField")
}

函数基础

函数是一等对象

  • 一个函数可以作为另一个函数的参数
void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);
  • 同样可以将一个函数赋值给一个变量
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名函数

可以创建没有名字的函数,这种函数被称为 匿名函数, 有时候也被称为 lambda 或者 closure 。 匿名函数可以赋值到一个变量中

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

词法闭包

  • 闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量。
  • 函数可以封闭定义到它作用域内的变量。 接下来的示例中, makeAdder() 捕获了变量 addBy。 无论在什么时候执行返回函数,函数都会使用捕获的 addBy 变量。
/// 返回一个函数,返回的函数参数与 [addBy] 相加。
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 创建一个加 2 的函数。
  var add2 = makeAdder(2);

  // 创建一个加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

返回值

所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句。

foo() {}
assert(foo() == null);

命名可选参数

定义函数时,使用 {param1, param2, …} 来指定命名参数:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

位置可选参数

  • 将参数放到 [] 中来标记参数是可选的:
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
  • 下面是不使用可选参数调用上面方法 的示例:
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
  • 下面是使用可选参数调用上面方法的示例:
assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

在定义方法的时候,可以使用 = 来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。

/// 设置 [bold] 和 [hidden] 标志 ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold 值为 true; hidden 值为 false.
enableFlags(bold: true);

参考文章

Dart中文网
W3Cschool翻译

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容