
一、前言
什么是变量呢?
你可以先把它想象成超市里的储物柜。每个储物柜都有编号,可以放东西,也可以把里面的东西拿出来换成别的东西。
代码里的变量也很像这样:它有名字,有类型,有存放的数据。程序运行时,我们可以通过变量名找到这份数据,也可以在允许的情况下修改它。
常量则像一个封存好的柜子。东西放进去之后,就不希望再被改变。它适合保存那些一旦确定就不应该随便变化的值,比如最大数量、固定配置、颜色值等。
这一篇我们就把 Dart 中的变量与常量系统讲清楚。
二、一句话理解
变量是可以存放并在一定条件下修改数据的命名容器;常量是赋值后不希望再改变的数据。
简单说:
- 变量:能变的数据容器。
- 常量:确定后不再变化的数据。
var:让 Dart 帮你推断类型。final:运行时确定一次,之后不能改。const:编译时就确定,之后不能改。late:稍后再初始化,但使用前必须有值。
三、什么是变量
变量可以理解为存储数据的容器。
一个变量通常包含三部分:
| 组成 | 含义 | 示例 |
|---|---|---|
| 类型 | 这个变量能放什么数据 | int、String |
| 名字 | 以后通过什么名称找到它 | age、userName |
| 值 | 当前真正存放的数据 | 18、Tom |
void main() {int age = 18;String userName = 'Tom';print(age);print(userName);
}
这里的 age 和 userName 就是变量。
3.1 变量的定义
变量的定义可以这样理解:
变量 = 类型 + 名字 + 值
例如:
int count = 10;
这行代码表示:定义一个名叫 count 的变量,它的类型是 int,当前值是 10。
变量的价值在于:我们不用每次都写死具体数据,而是可以通过变量名重复使用这份数据。
void main() {int price = 20;int count = 3;print(price * count); // 60
}
3.2 变量的特点
变量有四个重要特点:命名规则、类型限制、可变性、作用域与生命周期。
先继续用储物柜类比:
- 命名规则:储物柜要有编号,变量也要有名字。
- 类型限制:小柜子放不下大件物品,变量也受数据类型限制。
- 可变性:柜子里的东西可以换,变量的值也可能被重新赋值。
- 作用域与生命周期:柜子有使用范围和使用时间,变量也有可见范围和存在时间。
void main() {int age = 18;age = 20;print(age); // 20
}
这里 age 一开始是 18,后来改成了 20,这就是变量的可变性。
但变量不是想怎么改就怎么改。类型确定后,通常不能随便换成另一种不兼容类型。
void main() {int age = 18;// age = 'Tom'; // 错误:int 变量不能存字符串
}
类型的存在,是为了让代码更安全。
3.3 显式声明
显式声明就是开发者直接写出变量类型。
void main() {int age = 18;double price = 19.9;String name = 'Dart';bool isLogin = true;
}
这种写法非常清楚:读者一眼就能看到变量是什么类型。
显式声明适合:
- 希望代码更直观时。
- 变量类型对理解逻辑很重要时。
- 初学阶段训练类型意识时。
3.4 隐式声明
隐式声明就是不直接写具体类型,让 Dart 根据右侧的值推断类型。
最常见的是 var。
void main() {var age = 18;var name = 'Dart';print(age.runtimeType); // intprint(name.runtimeType); // String
}
这里 Dart 会根据 18 推断 age 是 int,根据 'Dart' 推断 name 是 String。
注意:var 只是在声明时帮你推断类型,不代表类型可以随便变。
void main() {var age = 18;// age = 'Tom'; // 错误:age 已经被推断为 int
}
可以把 var 理解成:你先不写类型,但 Dart 会帮你补上类型。
四、Dart 常见声明方式
Dart 中常见的声明方式包括 var、dynamic、final、const、late。
这几个关键字很容易混淆,所以必须放在一起比较。
4.1 var
var 用于让 Dart 自动推断变量类型。
void main() {var message = 'hello';var count = 10;print(message.runtimeType); // Stringprint(count.runtimeType); // int
}
var 的特点:
- 编译期推断类型。
- 推断后类型不能随便改变。
- 适合右侧值很明确的场景。
void main() {var name = 'Tom';name = 'Jerry'; // 正确,仍然是 String// name = 18; // 错误,不能换成 int
}
4.2 dynamic
dynamic 表示动态类型,变量可以在运行时存放不同类型的值。
void main() {dynamic value = 10;print(value);value = 'hello';print(value);value = true;print(value);
}
dynamic 的特点:
- 类型检查更宽松。
- 可以存放不同类型的值。
- 错误可能推迟到运行时出现。
void main() {dynamic value = 10;// 编译阶段可能不报错,但运行时可能出问题// print(value.length);
}
dynamic 不是不能用,而是要谨慎用。处理接口返回、JSON、动态数据时它有价值,但普通业务变量尽量写明确类型或使用 var。
4.3 final
final 表示变量只能被赋值一次。
void main() {final name = 'Dart';// name = 'Flutter'; // 错误,final 变量不能再次赋值print(name);
}
final 的值可以在运行时确定。
void main() {final now = DateTime.now();print(now);
}
DateTime.now() 要等程序运行时才能得到结果,所以这里适合用 final。
final 也可以先声明,后赋值一次。
void main() {final String token;token = 'abc123';print(token);
}
但一旦赋值,就不能再次修改。
4.4 const
const 表示编译时常量。
void main() {const maxCount = 100;const appName = 'Dart Demo';print(maxCount);print(appName);
}
const 的值必须在编译时就能确定。
void main() {const count = 10;// const now = DateTime.now(); // 错误,运行时才能确定
}
const 适合固定不变的配置值,比如:
const int maxRetryCount = 3;
const String defaultUserName = '游客';
一句话区分:
final:运行时确定一次。const:编译时就确定。
4.5 late
late 表示延迟初始化。
有些变量逻辑上不能为空,但它的值无法在声明时立刻给出,就可以使用 late。
void main() {late String name;name = 'Dart';print(name);
}
如果 late 变量在赋值前就被使用,会在运行时报错。
void main() {late String name;// print(name); // 运行时报错:还没有初始化
}
late 适合:
- 初始化依赖后续逻辑。
- 对象创建后再注入值。
- Flutter 中某些生命周期后才能得到值的场景。
但不要滥用 late。如果可以在声明时给值,优先直接初始化。
4.6 五种声明方式对比
| 写法 | 是否可重新赋值 | 类型是否固定 | 何时确定值 | 适合场景 |
|---|---|---|---|---|
| 显式类型 | 通常可以 | 固定 | 声明或后续赋值 | 类型需要清楚表达 |
var |
可以 | 推断后固定 | 编译期推断 | 右侧值明确 |
dynamic |
可以 | 不固定 | 运行时 | 动态数据 |
final |
不可以 | 固定 | 运行时可确定 | 只赋值一次 |
const |
不可以 | 固定 | 编译时确定 | 编译时常量 |
late |
取决于搭配 | 固定 | 延迟到使用前 | 稍后初始化 |
如果不确定怎么选,可以先记住:
- 能明确类型就明确类型。
- 右侧值很明显可以用
var。 - 不想被改用
final。 - 编译时就固定用
const。 - 延迟初始化才用
late。 - 需要动态类型才用
dynamic。
五、什么是常量
常量是一种赋值后不再变化的数据。
变量像普通储物柜,东西可以换;常量像封存柜,放进去之后就不应该再改。
5.1 常量的定义
在 Dart 中,常量通常通过 const 或 final 表达。
void main() {final userId = 'u001';const maxCount = 100;print(userId);print(maxCount);
}
严格来说:
const是编译时常量。final是只能赋值一次的变量,常被用来表达运行时不可变值。
所以很多初学者会把它们都称为“不可变数据”,但它们确定值的时机不一样。
5.2 常量的特点
常量的核心特点是不可变。
void main() {const appName = 'Demo';// appName = 'New Demo'; // 错误
}
不可变的好处是:
- 减少意外修改。
- 让代码意图更明确。
- 方便维护固定配置。
命名上,很多团队会对全局常量使用大写加下划线。
const int MAX_RETRY_COUNT = 3;
但在 Dart 官方风格中,普通变量、常量通常也常见小驼峰写法。
const int maxRetryCount = 3;
项目里最重要的是保持统一。
5.3 final 和 const 的区别
final 和 const 是这篇文章最重要的区别之一。
void main() {final now = DateTime.now(); // 正确// const now2 = DateTime.now(); // 错误
}
为什么?
因为当前时间只有运行时才知道,编译时不知道。
再看 const:
void main() {const count = 10;const message = 'hello';
}
这些值在编译时就已经确定,所以可以用 const。
对比表:
| 维度 | final | const |
|---|---|---|
| 是否能再次赋值 | 不能 | 不能 |
| 值何时确定 | 运行时 | 编译时 |
| 是否一定是编译时常量 | 不是 | 是 |
| 典型场景 | 时间、接口结果、构造后确定 | 固定配置、字面量常量 |
一句话记忆:运行时才知道用 final,编译时就知道用 const。
六、全局变量与局部变量
变量还要看作用域。
作用域就是变量能被访问的范围。
6.1 全局变量
定义在函数外部的变量,可以被同一个库中的多个位置访问。
String appName = 'Dart Demo';void printAppName() {print(appName);
}void main() {printAppName();
}
全局变量的特点:
- 生命周期相对更长。
- 可访问范围更大。
- 使用方便,但容易被多个地方修改。
全局变量不是不能用,但要控制数量。尤其是可变全局变量,容易让代码状态变复杂。
6.2 局部变量
定义在函数或代码块内部的变量,通常只能在当前范围内使用。
void main() {int count = 10;print(count);
}
count 只在 main() 函数内部可见。
代码块也会影响变量作用域。
void main() {if (true) {String message = 'hello';print(message);}// print(message); // 错误:message 只在 if 代码块中可见
}
局部变量的好处是影响范围小,阅读和维护都更容易。
6.3 生命周期
生命周期是变量从创建到不再使用的过程。
局部变量通常在进入函数或代码块时创建,在离开后就不再可用。
全局变量通常存在时间更长。
初学阶段可以这样记:
- 作用域回答“哪里能用它”。
- 生命周期回答“它能活多久”。
七、常见误区
7.1 以为 var 是动态类型
var 不是动态类型。
void main() {var age = 18;// age = 'Tom'; // 错误
}
var 只是让 Dart 推断类型,推断后类型仍然固定。
真正的动态类型是 dynamic。
7.2 混淆 final 和 const
final 和 const 都不能再次赋值,但它们确定值的时间不同。
void main() {final now = DateTime.now(); // 正确// const now2 = DateTime.now(); // 错误
}
运行时才知道的值,用 final;编译时就知道的值,用 const。
7.3 滥用 late
late 能解决延迟初始化问题,但也可能把错误推迟到运行时。
void main() {late String token;// print(token); // 使用前未初始化会报错
}
如果能直接初始化,就不要为了“看起来高级”而使用 late。
7.4 到处使用可变全局变量
全局变量访问方便,但可变全局变量容易让状态来源变得混乱。
int count = 0;void add() {count++;
}
小项目里可能没问题,但项目变大后,很多地方都能改 count,就很难追踪问题。
能用局部变量解决的问题,优先使用局部变量。
八、总结
这一篇我们系统讲了 Dart 中的变量与常量。
变量是可以存放数据的命名容器,它有类型、名字和值。变量具有命名规则、类型限制、可变性、作用域和生命周期。
Dart 中常见声明方式包括:
- 显式类型:直接写出类型,清楚直观。
var:编译期自动推断类型,推断后类型固定。dynamic:运行时动态类型,灵活但风险更高。final:只能赋值一次,值可以运行时确定。const:编译时常量,编译时必须确定。late:延迟初始化,使用前必须赋值。
常量的核心是不可变。final 和 const 都能表达不可变意图,但 final 偏运行时确定,const 偏编译时确定。
最后要记住:变量不是只会“存值”这么简单,它还涉及类型、安全性、可读性和作用范围。把变量与常量用对,代码会更稳定,也更容易维护。