数据类型

通常计算机编程语言里的数据类型可以分为基础数据类型与复合数据类型两大类。Dart的基础数据类型包括:

复合数据类型是基础数据类型的组合。Dart的复合数据类型包括:

Dart还有如下特殊的数据类型:

第2章将讨论函数,第4章将讨论类,第6章将讨论列表(List)、集合(Set)、映射(Map)。

数字

Dart的数字包括64-bit整数 int 和 64-bit浮点数 double。 int的取值范围为 -263 至 to 263 -1。 double是双精度浮点数,遵循 IEEE 754 标准。

Dart是一门OOP编程语言,int和double是num的子类,因此它们有着一些共同的方法,例如:

  • parse(string)
  • abs()
  • ceil()
  • toString()

下面的代码声明了一个整数radius 和一个浮点数 pi

// ch0104_1.dart
void main() {
  int radius = 5;
  double pi = 3.1416;
  print('area: ${radius * radius * pi}'); 
  // Output: area=78.53999999999999
}

除了使用十进制书写整数外,还可以用十六进制。可以使用科学计数法书写浮点数。例如:

// ch0104_2.dart
void main() {
  int radius = 0x10;
  double pi = 0.31416e1;
  print('radius=$radius area=${radius * radius * pi}');
  // Output: radius=16 area=804.2496
}

但是Dart并不直接支持书写八进制数。你可以用 octal 包来实现八进制的书写。

// ch0104_3.dart
// dart pub add octal
import 'package:octal/octal.dart'; 

void main() {
  int decimalValue = octal(123); // 83 in decimal
  print(decimalValue); // Output: 83
  print(decimalValue.toRadixString(8)); // Output: 123
}

字符串(String)

Dart中的String是一个UTF-16值的有序序列,写在一对单引号或双引号里。一个非常nice的功能是字符串插值,即在字符串里使用 ${expr}

// ch0104_4.dart
void main() {
  var str = "It's a beautiful day!";
  var str2 = 'It\'s a beautiful day!'; 
  print(str == str2); // Output: true

  var num = 5;
  print('There are $num apples.'); // Output: There are 5 apples.
  print('There are ${num + 2} oranges.'); // Output: There are 7 oranges.
}

上例中str2 写在一对单引号中,该行中的 \ 为转义符,\'表示一个单引号。

两个字符串写在一起,中间可以使用一个+符号(但通常予以省略), 就表示将这两个字符串顺序拼接在一起,形成一个新的字符串。因此对于多行文本,可以这样写:

  // ch0104_5.dart
  var str =
      'A: Nice to meet you!\n'
      'B: Nice to meet you, too!';

这里的\n是换行符。书写多行文本更方便的做法是将文本写在一对三引号('''""")中,例如:

// ch0104_6.dart
void main() {
  var minAge = 18;
  var query =
      '''
SELECT id, name, age
FROM users
WHERE age >= $minAge
''';
  print(query);
}

布尔(bool)

布尔类型只有2个值,truefalse,且它们都是编译时常量。

// ch0104_7.dart
void main() {
  var isGreater = 43 > 34;
  print('isGreater: $isGreater'); // Output: isGreater: true
  print('2 < -3: ${2 < -3}'); // Output: 2 < -3: false
}

符文(Rune)

符文代表unicode文字系统里的一个符号。unicode为世界上每一个文字或符号分配了一个数字,称之为code point。unicode 最多可支持 1,114,112 个code point,通过 17 个 16 位平面实现,每个平面可支持 65,536 个不同的code point。 unicode常见的编码方式(将code point映射为二进制数)有UTF-8、UTF-16和UTF-32。 Dart采用UTF-16编码,每个code unit为16位(2个字节),每个符文占用1个(0-65,535 code point)或2个code unit(65,536及以上)。对字符编码感兴趣的同学,请继续去了解下ASCII、UTF-8、UTF-16和UTF-32、GBK等。

Dart的String(字符串)本质上是code unit的序列。

// ch0104_8.dart
void main() {
  var s = "笑\u7b11lol😆\u{1f606}"; // 1
  print(s); // 2 Output: 笑笑lol😆😆
  print('len: ${s.length}'); // 3 Output: len: 9

  print('code units: ${s.codeUnits}'); // 4
  // Output: code units: [31505, 31505, 108, 111, 108, 55357, 56838, 55357, 56838]

  print('runes: ${s.runes}'); // 5
  // Output: runes: (31505, 31505, 108, 111, 108, 128518, 128518)

  print(31505.toRadixString(16)); // 6 Output: 7b11
  print(128518.toRadixString(16)); // 7 Output: 1f606
}
  1. 声明变量s,注意这里\u7b11就等于这个字符,\uHHHH表示\u后面是code point的十六进制表示HHHH;如果该十六进制数不是4位,就必须位于一堆花括号{}中,如\u{1f606}就等于😆这个emoji符号;
  2. 打印 s,从输出结果可以看出 s包含了7个符文(字符);
  3. 打印s的长度,结果为9, 可见是code unit的个数,下面一行代码印证了这一点;
  4. 代码s的code point 序列;
  5. 代码s的符文(字符)序列;
  6. 第6-7行代码分别显示31505和128518对应的十六进制表示,以方便分析。

请仔细查看code units 与 runes 并进行对比,这将帮助你理解Rune(符文)和String(字符串)。

使用 characters

下面这个示例同时演示了charactersStringBuffer类的使用。charactersString类添加了一个扩展方法(第5章内容)get charactersStringBuffer是拼接字符串的高效方式。代码中使用的 for-each 循环将在下一章节介绍,这里先了解下。

//ch0104_9.dart
//dart pub add characters
import 'package:characters/characters.dart';

void main() {
  const s = "笑一笑十年少😆"; // 1
  final sb = StringBuffer(); // 2 
  for (var ch in s.characters) { // 3
    sb.write(ch);
    sb.write(' ');
  }
  print(sb);
}

记录(Record)

Dart 3.0 引入了记录这一数据类型。记录是匿名的、不可变的聚合类型,是异质数据的集合。

// ch0104_a.dart
void main() {
  var record = ('class 1', name: 'Alice', id: 1, 'good student'); // 1
  print(record); // 2 Output: (class 1, good student, id: 1, name: Alice)
  print(record.$1); // 3 Output: class 1
  print(record.$2); // Output: good student
  print(record.id); // 4 Ouput: 1
  print(record.name); // Output: Alice

  // record.id = 2; // 5
  // Error: The setter 'id' isn't defined for the class '(String, String, {int id, String name})'.
  // Try correcting the name to the name of an existing setter, or defining a setter or field named 'id'.
  //   record.id = 2;
  //          ^^
}
  1. 声明一个变量record并赋值,如你所见,记录有位置字段和命名字段(如这里的idname);
  2. 打印 record,注意观察字段的显示顺序;
  3. 打印 record 的第1个位置字段record.$1 ;
  4. 打印 record 的命名字段 record.id;
  5. 修改 recordid,遭遇编译错误,这说明了记录是不可变的,同时从错误信息中可以看出,记录是一种特殊的匿名类。

记录类型

记录(Record)没有明确的类型声明,因为它是匿名的,但记录是有形状(Shaple)的。本例中 record的形状为(String, String, {int id, String name});在一对花括号({})里的int id, String name 为命名字段 idname,其类型分别为intString;没有花括号包围的便是位置字段。

// ch01004_b.dart
void main() {
  (int x, int y, int z) point = (1, 2, 3);
  var color = (1, 2, 3);
  print(point); // Output: (1, 2, 3)
  print(point == color); // Output: true

  ({int x, int y, int z}) point2 = (x: 1, y: 2, z: 3);
  var color2 = (r: 1, g: 2, b: 3);
  print(point2); // Output: (x: 1, y: 2, z: 3)
  print(color2); // Output: (b: 3, g: 2, r: 1)
  print(point2 == color2); // Output: false
}

pointcolor具有相同的形状和值,因此他们是相等的,而point2color2的形状不同,自然就不相等。

解构与变量交换

记录的解构与变量交换非常实用。

// ch0104_c.dart
void main() {
  var (x, y) = (1, 2); // 1
  print('x=$x y=$y'); // Output: x=1 y=2

  (y, x) = (x, y); // 2
  print('x=$x y=$y'); // Output: x=2 y=1

  var (:name, :age) = (name: 'Bob', age: 20); // 3
  print('name=$name b=$age'); // Output: name=Bob b=20

  var (x: a, y: b) = (x: 3, y: 4); // 4
  print('a=$a b=$b'); // Output: a=3 b=4
  
  var (who, _, :nickname, fav: _) = (
    'Robert',
    'Naughty boy',
    nickname: 'Bob',
    fav: 'play the guitar',
  ); // 5
  print('who=$who nickname=$nickname'); // Output: who=Robert nickname=Bob
}
  1. 解构记录(1,2)至局部变量xy,这里使用了模式匹配(第3章内容);
  2. 交换xy的值;
  3. 使用冒号(:)语法,解构命名记录 (name: 'Bob', age: 20)至局部变量nameage ;
  4. 解构命名记录 (x: 3, y: 4)至局部变量ab ;
  5. 这是一个综合例子,请运用已学知识自行分析。

枚举(Enum)

枚举是一种用于表示固定数量的常量值的特殊类。使用关键字enum声明个一个枚举。

// ch0104_d.dart
enum Color { red, green, blue }

void main() {
  print(Color.red); // Output: Color.red
  print(Color.blue.name); // Output: blue
  print(Color.blue.index); // Output: 2
}

每个枚举值都有个与之关联的数字,称之为index,该数字从0开始。枚举是一种特殊的类,因此它也可以定义字段(实例变量)和方法,只不过有一些限制,例如枚举的实例变量必须声明为final。第4章会更详细地讨论枚举。

符号(Symbol)

符号对象表示 Dart 程序中声明的操作符或标识符。一般极少用到,在此了解即可。

// ch0104_e.dart
void main() {
  print(#foo); // Output: Symbol("foo")
}

typedef

typedef关键字为数据类型取一个别名。

//ch0104_f.dart
typedef Point = ({int x, int y});
typedef VoidCallback = void Function();
typedef IntList = List<int>;

void main() {
  Point p = (x: 1, y: 2);
  IntList nums = [3, 4, 5];
}

小测验

以下程序的输出是什么?

// ch01004_quiz.dart
void main() {
  var point = (x: 1, y: 2, z: 3);
  ({num x, int y, int z}) point2 = (x: 1, y: 2, z: 3);
  print(point == point2);
}

参考资料