Dart之数据类型

Dart之数据类型

一、前言

学习 Dart 数据类型时,可以先想象一个超市。

超市里有饮料、蔬菜、水果、零食、日用品。不同商品不能全部随便塞进同一个容器里:鸡蛋需要防摔的盒子,饮料需要瓶子,散装糖果需要袋子,冷冻食品需要冷柜。

代码里的数据也是一样。

数字、文字、真假、列表、集合、键值对、函数,它们都有各自更适合的“容器”。这个容器在编程里就叫数据类型。

数据类型不是为了增加记忆负担,而是为了告诉 Dart:这份数据是什么、能做什么操作、应该如何存储和检查。

这一篇我们系统梳理 Dart 中常见的数据类型。

二、一句话理解

Dart 数据类型就是数据的分类规则,它决定一个值能表示什么内容、能使用哪些方法、能参与哪些操作。

简单说:

  • int 适合整数。
  • double 适合小数。
  • String 适合文字。
  • bool 适合真假。
  • List 适合有顺序的一组数据。
  • Set 适合去重的一组数据。
  • Map 适合键值对数据。

先知道每种类型解决什么问题,再去记它的方法,学习会轻很多。

三、Dart 数据类型总览

Dart 中的数据类型可以先分成四类:

分类 常见类型 主要作用
基本数据类型 numintdoubleboolStringdynamic 表示数字、真假、文本和动态值
集合类型 ListSetMap 存放一组数据
特殊类型 FunctionenumSymboltypedef 表示函数、枚举、符号和类型别名
类型转换 toString()toInt()toDouble() 在不同类型之间转换

这张表是本文的地图。接下来我们一类一类展开。

四、基本数据类型

基本数据类型是最常见、最基础的一组类型。写 Dart 时几乎每天都会用到。

4.1 数字类型 num

num 是 Dart 数字类型的父类型,它下面有两个常见子类型:intdouble

void main() {num a = 10;num b = 12.5;print(a);             // 10print(a.runtimeType); // intprint(b);             // 12.5print(b.runtimeType); // double
}

虽然变量声明的是 num,但 Dart 会根据具体值判断它实际是 int 还是 double

可以这样理解:

  • num 是数字的大箱子。
  • int 是整数小盒子。
  • double 是小数小盒子。

num 适合你只关心“这是数字”,但暂时不关心它是整数还是小数的场景。

4.2 整数类型 int

int 用于表示整数。

void main() {int age = 18;int count = 100;print(age);print(count);
}

整数没有小数部分,适合表示年龄、数量、页码、索引、次数等。

Dart 里的 int 支持平台相关的整数范围。通常开发中不需要一开始就背范围,但要知道:如果数字特别大,可以使用 BigInt

void main() {BigInt bigNumber = BigInt.parse('1200000213231241431212133322222223');print(bigNumber);print(bigNumber.runtimeType);
}

如果需要随机整数,可以使用 dart:math 里的 Random

import 'dart:math';void main() {Random random = Random();print(random.nextInt(100)); // 生成 0 到 99 的随机整数
}

4.3 浮点数类型 double

double 用于表示小数。

void main() {double price = 19.9;double score = 98.5;print(price);print(score);
}

即使写成 4,如果变量类型是 double,输出时也会表现为小数形式。

void main() {double value = 4;print(value);             // 4.0print(value.runtimeType); // double
}

double 还能表示一些特殊值:

void main() {print(double.infinity);         // Infinityprint(double.negativeInfinity); // -Infinityprint(double.nan);              // NaN
}

使用 double 时要知道一个重要点:浮点数可能出现精度问题。

void main() {double value = 0.1 + 0.2;print(value); // 0.30000000000000004
}

这不是 Dart 算错了,而是浮点数在计算机中的表示方式决定的。涉及金额、精确小数时,要格外谨慎。

4.4 数字类型基本操作

数字类型可以做加、减、乘、除、整除、取余。

void main() {num a = 4;int b = 5;print(a + b);  // 9print(a - b);  // -1print(a * b);  // 20print(a / b);  // 0.8print(a ~/ b); // 0print(a % b);  // 4
}

几个容易忽略的点:

  • / 的结果是 double
  • ~/ 是整除,结果是整数。
  • % 是取余,常用于判断是否整除。

数字也可以做类型转换:

void main() {int a = 4;double b = 5.1;print(a.toDouble()); // 4.0print(b.toInt());    // 5
}

数字可以比较和检查:

void main() {int a = 4;double b = 7.8;print(a.compareTo(b)); // -1,表示 a 小于 bprint(a.isNegative);   // falseprint(a.isEven);       // trueprint(a.isOdd);        // false
}

常用数字方法还有:

void main() {int a = -10;num b = 5.834;print(a.abs());          // 10print(a.clamp(1, 5));    // 1print(b.ceil());         // 6print(b.floor());        // 5print(b.round());        // 6print(b.truncate());     // 5
}

这些方法可以这样记:

  • abs():求绝对值。
  • clamp():把数限制在一个范围内。
  • ceil():向上取整。
  • floor():向下取整。
  • round():四舍五入。
  • truncate():直接截断小数部分。

4.5 布尔类型 bool

bool 只有两个值:truefalse

void main() {bool isLogin = true;bool isVip = false;print(isLogin);print(isVip);
}

它常用于条件判断。

void main() {bool hasTicket = true;if (hasTicket) {print('可以入场');} else {print('不能入场');}
}

可以把 bool 理解成代码里的开关:开是 true,关是 false

4.6 字符串类型 String

String 用于表示文本。

void main() {String name = 'Dart';String language = "Flutter";print(name);print(language);
}

Dart 字符串可以用单引号,也可以用双引号。

字符串支持插值,也就是把变量嵌入字符串中。

void main() {String name = 'Dart';print('Hello, $name');
}

如果表达式比较复杂,可以使用 ${}

void main() {int a = 3;int b = 5;print('结果是:${a + b}');
}

多行字符串可以使用三个引号。

void main() {String slogan = '''
I love Dart
I love Flutter
''';print(slogan);
}

要注意:Dart 中的字符串是不可变的。所谓修改字符串,本质上通常是创建一个新的字符串。

4.7 动态类型 dynamic

dynamic 表示这个变量可以存放任意类型的值。

void main() {dynamic value = 5;print(value);value = 'hello, Dart';print(value);
}

看起来很灵活,但也有代价:编译器不会帮你做严格类型检查,错误可能留到运行时才暴露。

void main() {dynamic value = 10;value = 'hello';print(value.toUpperCase());
}

如果你很确定数据结构,优先使用明确类型。dynamic 更适合处理临时不确定的数据,比如外部接口返回、动态 JSON、迁移旧代码等。

五、集合类型

集合类型用于存放一组数据。

如果基本类型像一个个商品,那么集合类型就像购物袋、收纳盒和标签柜。

5.1 列表 List

List 是有顺序的集合,允许重复元素。

void main() {List<int> numbers = [1, 2, 2, 5, 9];List<String> skills = ['Dart', 'Flutter', 'Android'];print(numbers);print(skills);
}

List 的特点:

  • 有顺序。
  • 可以通过下标访问。
  • 允许重复。
  • 适合表示一串有序数据。
void main() {List<String> names = ['Tom', 'Jerry', 'Lucy'];print(names[0]); // Tomnames.add('Lily');print(names);
}

如果你关心“第几个元素是什么”,通常应该先想到 List

5.2 集合 Set

Set 是不重复的集合。

void main() {Set<int> numbers = {1, 2, 2, 2, 4, 5};print(numbers); // {1, 2, 4, 5}
}

Set 的特点:

  • 不允许重复元素。
  • 适合去重。
  • 适合判断某个元素是否存在。
void main() {Set<String> tags = {'Dart', 'Flutter', 'Dart'};print(tags); // {Dart, Flutter}print(tags.contains('Dart')); // true
}

如果你关心“有没有重复”“是否包含某个值”,通常可以考虑 Set

5.3 映射 Map

Map 是键值对集合。

void main() {Map<String, int> scores = {'小明': 5,'小红': 2,'小敏': 7,};print(scores);
}

Map 的特点:

  • 由 key 和 value 组成。
  • key 必须唯一。
  • 可以通过 key 快速找到 value。
void main() {Map<String, int> scores = {'Tom': 90,'Jerry': 85,};print(scores['Tom']); // 90scores['Lucy'] = 100;print(scores);
}

如果你关心“通过一个名字找到对应数据”,通常应该想到 Map

5.4 List、Set、Map 怎么选

三种集合很容易混在一起,可以这样判断:

类型 适合场景 关键特点
List 商品列表、消息列表、步骤列表 有顺序、可重复
Set 标签去重、唯一 ID 集合 不重复
Map 用户信息、配置项、成绩表 key-value

简单记法:

  • 要顺序,用 List
  • 要去重,用 Set
  • 要通过 key 查值,用 Map

六、特殊类型

特殊类型不是每天都像 Stringint 那样频繁出现,但它们在 Dart 里也很重要。

6.1 函数类型 Function

在 Dart 中,函数也可以作为值传递。

void hello() {print('hello, Dart!');
}void main() {Function fn = hello;fn();
}

这意味着我们可以把函数存到变量里,也可以把函数作为参数传递。

更推荐的写法是尽量写清楚函数类型。

void main() {int Function(int, int) add = (int a, int b) {return a + b;};print(add(3, 5));
}

明确函数类型之后,参数和返回值都会更清楚。

6.2 枚举类型 enum

enum 用于定义一组固定的命名常量。

enum Color {red,green,yellow,blue,
}void main() {Color myColor = Color.yellow;print(myColor);
}

枚举适合表示有限选项,比如颜色、状态、方向、订单状态。

enum OrderStatus {pending,paid,shipped,finished,
}

相比直接使用字符串,枚举更安全。

String status = 'paidd'; // 拼错了也可能不容易发现
OrderStatus orderStatus = OrderStatus.paid;

6.3 符号类型 Symbol

Symbol 用于表示 Dart 代码中的标识符。

void main() {Symbol symbol = #hello;print(symbol); // Symbol("hello")
}

普通业务代码里 Symbol 不算高频,它更多出现在反射、元编程等场景中。

初学阶段不需要深挖它,只要知道:#hello 表示一个符号,不是字符串。

6.4 类型别名 typedef

typedef 用于给类型起一个别名。

最简单的例子:

typedef MyInt = int;void main() {MyInt value = 10;print(value);print(value.runtimeType);
}

不过实际开发中,typedef 更常用于函数类型别名。

typedef Calculate = int Function(int a, int b);int add(int a, int b) {return a + b;
}void main() {Calculate fn = add;print(fn(3, 5));
}

这样写可以让复杂函数类型更容易阅读。

七、类型转换

类型转换就是把一种类型的数据变成另一种类型。

比如把整数变成小数,把数字变成字符串,把字符串解析成数字。

7.1 隐式转换

某些情况下,Dart 会自动完成类型转换。

最常见的是把 int 赋值给 double

void main() {double value = 4;print(value);             // 4.0print(value.runtimeType); // double
}

这里 4 原本是整数,但赋值给 double 变量时,会转换成 4.0

不过不要以为 Dart 会随便帮你转。Dart 仍然是强类型语言,很多转换需要你明确写出来。

7.2 显式转换

显式转换就是开发者主动调用方法转换类型。

void main() {int a = 4;double b = 4.5;print(a.toDouble()); // 4.0String text = a.toString();print(text);             // 4print(text.runtimeType); // Stringprint(b.toInt()); // 4
}

常见转换方法:

方法 作用
toString() 转成字符串
toInt() 转成整数
toDouble() 转成小数
int.parse() 把字符串解析成整数
double.parse() 把字符串解析成小数

字符串转数字示例:

void main() {String ageText = '18';String priceText = '19.9';int age = int.parse(ageText);double price = double.parse(priceText);print(age);print(price);
}

如果字符串格式不正确,解析会报错。更稳的方式是使用 tryParse()

void main() {String text = 'abc';int? value = int.tryParse(text);print(value); // null
}

tryParse() 解析失败时返回 null,比直接抛异常更适合处理用户输入。

八、常见误区

8.1 以为 dynamic 更方便就到处用

dynamic 的确灵活,但它会减少类型检查。

void main() {dynamic value = 10;value = true;value = 'hello';
}

这段代码能运行,但如果后续调用了不存在的方法,错误可能到运行时才出现。

建议是:能明确类型就明确类型,不确定时再使用 dynamic

8.2 混淆 List、Set、Map

这三个集合类型不是谁替代谁,而是解决不同问题。

  • List 解决顺序问题。
  • Set 解决去重问题。
  • Map 解决映射查找问题。

写代码前先问自己:我需要顺序、唯一性,还是通过 key 找 value?

8.3 忽略 double 精度问题

浮点数不适合直接用于所有精确计算。

void main() {print(0.1 + 0.2); // 0.30000000000000004
}

如果涉及金额、计费、精确小数,要考虑更稳妥的处理方式,比如按分存整数,或使用专门的高精度方案。

8.4 把类型转换想得太随意

不是所有值都能安全转换。

void main() {int value = int.parse('abc'); // 会报错
}

处理外部输入时,优先使用 tryParse()

void main() {int? value = int.tryParse('abc');print(value); // null
}

九、总结

这一篇我们系统梳理了 Dart 中的数据类型。

可以把数据类型理解成数据的容器:不同数据需要放进不同容器里,容器决定了数据能做什么操作。

最常见的基础类型包括:

  • num:数字类型的父类型。
  • int:整数。
  • double:小数。
  • bool:真假。
  • String:文本。
  • dynamic:动态类型。

集合类型包括:

  • List:有顺序、可重复。
  • Set:不重复。
  • Map:键值对。

特殊类型包括:

  • Function:函数也是值。
  • enum:固定选项集合。
  • Symbol:符号。
  • typedef:类型别名。

最后,类型转换分为隐式转换和显式转换。写 Dart 时,不要只看值本身,还要关注它的类型。类型选对了,后面的代码才会更清晰、更安全、更容易维护。