在构建任何健壮的应用程序时,理解数据的本质都是第一步。无论是开发一个简单的计数器,还是构建复杂的金融系统,我们都必须精确地处理数据。与我们在 C++ 或 Java 等其他静态语言中遇到的情况类似,Dart 语言中的每个变量都有一个明确的关联数据类型。这不仅有助于编译器捕获潜在的错误,还能确保我们的程序逻辑清晰且可预测。
在 Dart 中,数据类型不仅仅是存放数据的容器,它们定义了我们可以在应用程序中表示和操作的值的种类。通过正确使用这些类型,我们可以优化内存使用,提高代码的可维护性,并避免运行时崩溃。
在这篇文章中,我们将深入探讨 Dart 编程语言的核心数据类型。我们将通过大量的实战代码示例,详细分析每种类型的特性,并分享一些在实际开发中非常有用的最佳实践。
目录
Dart 数据类型全景图
Dart 的类型系统非常强大且表达力强。为了让你对全局有一个清晰的认识,我们可以将 Dart 的主要数据类型归纳如下表所示。建议你先快速浏览一下,随后我们将逐一攻克它们。
关键字
n
—
int, double, num, BigInt
String
bool
List
Set
Map
Runes
Symbol
Null
接下来,让我们深入细节,看看如何在实战中使用它们。
1. 数字类型:处理数值的核心
在 Dart 中,数字类型是我们处理数学运算和数值计算的基础。Dart 提供了多种数值类型来满足不同的计算需求,从普通的整数计数到高精度的科学计算。
核心数值类型
我们可以将 Dart 中的数字分为以下几类:
- int: 用于表示整数。在 Web 平台上,它的值可以是任意大小的整数(模拟);而在原生平台(如移动端或桌面端)上,它通常表示 64 位整数,范围在 $-2^{63}$ 到 $2^{63} – 1$ 之间。
- double: 用于表示 64 位精度的浮点数(小数),符合 IEEE 754 标准。当你需要处理小数(如价格、坐标)时,这是你的首选。
- num: 这是一个抽象类型。它是 int 和 double 的父类。如果你希望一个变量既可以存储整数又可以存储小数,可以使用 num。
- BigInt: 用于处理非常大的整数,远远超出普通 int 的范围。当我们处理加密算法、哈希值或极大数量的统计时,它会非常有用。
实战演练:声明与使用
让我们通过代码来看看如何声明和使用这些数值类型。
#### 声明整数 (int)
// 声明一个普通的整数变量
int age = 25;
// Dart 2.12+ 引入了空安全
// 这里的 ‘int?‘ 表示 count 可以是一个整数,也可以是 null
int? count;
// 使用 ‘var‘ 关键字(类型推断)
// Dart 编译器会自动识别 2024 为整数
var year = 2024;
// 尝试修改 age
age = 26; // 这是允许的
// age = 26.5; // 报错!类型 ‘double‘ 不能赋给类型 ‘int‘
#### 声明小数 (double)
// 显式声明一个双精度浮点数
double pi = 3.1415;
// 一个可空的 double 变量
double? percentage;
// 再次强调类型推断
var temperature = 36.6; // Dart 自动将其识别为 double
// 在 Dart 中,如果一个数字包含小数点,它就是 double
// 即使它是 10.0,它也是 double,而不是 int
#### 使用 num 实现灵活存储
当我们不确定具体的数值类型,或者需要兼容两者时,可以使用 num。这是 Dart 中一个很棒的设计,体现了面向对象的灵活性。
num value = 10;
print("当前值: $value"); // 输出: 10 (类型推断为 int)
// 这是允许的,因为 num 同时支持 int 和 double
value = 10.5;
print("修改后的值: $value"); // 输出: 10.5 (类型变为 double)
// 如果我们将 value 初始化为 double,它就不能再变回 int 吗?
// 是的,一旦变量被赋值,其具体类型(int 或 double)就固定了。
// 如果我们需要在运行时改变类型,num 是最佳选择。
#### 使用 BigInt 处理超大整数
在处理天文数字或加密密钥时,普通的 int 可能溢出,这时我们需要 BigInt。
// 直接解析字符串创建 BigInt
var bigNumber = BigInt.parse(‘123456789012345678901234567890‘);
// 也可以使用位运算创建
var anotherBigNumber = BigInt.two.pow(100); // 2 的 100 次方
print("大数相加结果: ${bigNumber + anotherBigNumber}");
综合示例:数值运算
让我们看一个更完整的例子,演示数值之间的转换和运算。
void main() {
// 1. 基础定义
int num1 = 2;
double num2 = 1.5;
// 2. 打印原始值
print("整数值: $num1");
print("浮点数值: $num2");
// 3. 执行加法运算
// 注意:int + double 结果为 double(这是 Dart 的隐式转换规则)
var sum = num1 + num2;
print("两数之和: $sum");
// 4. 字符串转数字 (常见的实际操作)
String priceString = "99.8";
// 使用 parse 方法将字符串转为 double
// 注意:如果字符串格式不对,程序会抛出异常,后续我们会讲如何处理
double price = double.parse(priceString);
print("商品价格: $price");
// 5. 类型转换与保留小数
double gallons = 10.5;
int liters = gallons.toInt(); // 直接截断小数
print("转换后的整数值: $liters");
// 使用 toStringAsFixed 保留小数位
double currency = 123.45678;
print("保留两位小数: ${currency.toStringAsFixed(2)}"); // 输出: 123.46
}
2. 字符串:处理文本的艺术
在几乎所有的应用程序中,字符串都是最常用的数据类型。Dart 中的字符串是一系列 UTF-16 编码的字符序列。我们可以使用单引号或双引号来创建字符串,这一点非常灵活。
字符串的声明与转义
// 使用单引号或双引号完全取决于你的喜好
String str1 = "Geeks for Geeks";
String str2 = ‘Dart is Awesome‘;
// 如果字符串中包含引号,我们需要使用反斜杠进行转义
String sentence = ‘It\‘s a wonderful day!‘;
字符串插值与拼接
在现代开发中,直接拼接字符串(使用 + 号)虽然直观,但在处理复杂逻辑时显得笨重。Dart 提供了强大的字符串插值功能,这能让你的代码更整洁。
void main() {
String name = "Alex";
int age = 30;
// 使用 + 拼接 (老派做法,有时效率不如直接插值)
String message = "Hello, " + name + ". You are " + age.toString() + " years old.";
print(message);
// 使用字符串插值 (推荐做法,Dart 的优雅之处)
// 使用 $ 符号引用变量,使用 ${} 引用表达式
String newMessage = "Hello, $name. Next year, you will be ${age + 1}.";
print(newMessage);
}
多行字符串
当我们需要处理大段文本或 SQL 语句时,Dart 的多行字符串显得尤为方便。使用三个引号即可实现。
void main() {
// 使用单引号的三引号或多引号的三引号均可
String multiLineText = ‘‘‘
这是一个多行字符串。
它可以跨越多行,不需要使用
。
并且它还会保留空格和缩进。
‘‘‘;
print(multiLineText);
// 实际应用:写一个 SQL 查询
String query = """
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name DESC
""";
}
3. 布尔值:逻辑控制的基石
布尔值极其简单,只有 INLINECODE6aed33a7 和 INLINECODEecaffab0 两个值。但不要小看它,它是所有逻辑判断、循环控制和流程分支的核心。
布尔值的声明与判断
在 Dart 中,不像 JavaScript 那样会自动将非零数字或非空字符串视为 INLINECODE958d9153。Dart 进行了严格的类型检查。INLINECODE0fb52183 语句中的条件必须明确是一个布尔表达式。
void main() {
// 显式声明布尔值
bool isValid = true;
bool isComplete = false;
// 使用布尔值进行条件判断
if (isValid) {
print("数据有效!");
}
// 1 表示真,0 表示假?
// int number = 1;
// if (number) { ... } // 在 Dart 中这是错误的!编译器会报错
// 必须显式比较:
int number = 1;
if (number > 0) {
print("数字大于0"); // 这是正确的写法
}
// 布尔值来源于比较运算
String str1 = ‘Coding is ‘;
String str2 = ‘Fun‘;
bool areStringsEqual = (str1 == str2);
print("字符串是否相等: $areStringsEqual");
}
实用见解:使用 isEmpty 进行检查
在实际开发中,检查字符串或集合是否为空是高频操作。我们应该尽量利用 Dart 内置的布尔属性,而不是自己写逻辑。
String name = "";
// 不推荐
if (name.length == 0) {
print("名字是空的");
}
// 推荐 (更直观)
if (name.isEmpty) {
print("名字是空的");
}
4. 集合:List, Set 和 Map
在实际业务中,我们很少只处理单个数据,更多时候处理的是一组数据。Dart 提供了三种强大的集合类型。
List:有序的列表
List 类似于数组,它存储有序的元素。你可以通过索引(从 0 开始)来访问列表中的元素。在 Dart 中,List 既可以是固定长度的,也可以是可增长长度的。
void main() {
// 定义一个字符串列表
// 是泛型,表示这个列表只能放 String
List fruits = ["Apple", "Banana", "Orange"];
// 访问第一个元素
print("第一个水果: ${fruits[0]}");
// 添加新元素
fruits.add("Grape");
// 删除元素
fruits.remove("Banana");
// 使用 ... 扩展运算符 (Spread Operator) - Dart 的神技
// 它可以将一个列表中的元素插入到另一个列表中
List moreFruits = ["Pineapple", ...fruits];
print(moreFruits);
}
Set:唯一性的保证
Set 是无序的集合,并且它最重要的特性是:元素唯一。如果你尝试向 Set 中添加一个已经存在的元素,操作会被忽略。这在处理去重场景时非常有用。
void main() {
// 定义一个数字 Set
var numbers = {1, 2, 3, 4, 4}; // 这里的重复 4 会被自动忽略
print(numbers); // 输出: {1, 2, 3, 4}
// 将 List 转换为 Set 以去除重复元素
List inputList = ["A", "B", "A", "C", "B"];
var uniqueItems = inputList.toSet();
print("去重后的集合: $uniqueItems");
}
Map:键值对的映射
Map 用于存储键值对。字典中通过唯一索引找单词,Map 中通过唯一键找对应的值。它是构建配置对象、JSON 数据模型的最常用类型。
void main() {
// 定义一个 Map,键是 String,值可以是任意类型
Map user = {
"name": "张三",
"age": 28,
"isAdmin": true
};
// 访问 Map 中的值
print("用户名: ${user[‘name‘]}");
// 更新值
user[‘age‘] = 29;
// 检查键是否存在
if (user.containsKey(‘email‘)) {
print("Email: ${user[‘email‘]}");
} else {
print("该用户未填写邮箱");
}
}
5. 特殊类型:Runes, Symbols 和 Null
除了上述常用的类型,Dart 还有一些用于特定场景的类型。
Runes (Unicode)
虽然普通 String 可以处理大部分文本,但如果你需要处理 Emoji 表情符号、特殊的颜文字或复杂的 Unicode 字符(如 𐍈),你可能需要使用 Runes。不过在 Dart 3+ 中,官方引入了更方便的 characters 包来处理这些问题。
Symbol
Symbol 通常用于 Dart 的反射机制(通过 mirror 包),用于获取类、方法或变量的标识符。在普通的 Flutter 或业务开发中,我们很少直接使用它,但在构建框架或底层库时,它是不可或缺的。
// Symbol 的用法示例
Symbol className = #MyClass;
print(Symbol); // 输出: Symbol("MyClass")
Null:空安全
Null 类型表示“空”或“无”。Dart 的空安全机制要求我们在定义变量时明确指出该变量是否可以为 null,这极大地减少了运行时的空指针错误。
// 可空变量
String? nickname; // 默认值为 null
// 非空变量必须初始化
String name = "Dart";
// 空值合并运算符 ??
// 表示如果左边为空,则使用右边的值
String finalName = nickname ?? "访客";
print("欢迎你, $finalName"); // 输出: 欢迎你, 访客
总结与最佳实践
通过这篇文章,我们已经涵盖了 Dart 中所有的核心数据类型。掌握这些类型是成为熟练的 Dart/Flutter 开发者的基石。让我们回顾一下几个关键要点:
- 类型推断:尽量使用
var来简化代码,但在类型不明确或为了代码可读性时,显式声明类型是更好的选择。 - 空安全:这是 Dart 的现代化特性。请充分利用 INLINECODE39a3ea85、INLINECODE030fcefe(非空断言,慎用)、
??(空值合并)等操作符来写出更安全的代码。 - 字符串处理:优先使用字符串插值(
$variable)而不是拼接操作符,这样代码更易读。 - 集合的选择:如果你需要索引访问,用 INLINECODE6e338de5;如果你需要去重,用 INLINECODEc1bc6f70;如果你需要键值对查找,用
Map。
你现在可以尝试在本地编写这些代码,或者在实际项目中应用这些数据类型的技巧。熟练掌握它们,将让你的 Dart 代码更加健壮、优雅且高效。