Dart之变量与常量

📅 2026/7/14 22:40:31 👁️ 阅读次数 📝 编程学习
Dart之变量与常量

Dart之变量与常量

一、前言

什么是变量呢?

你可以先把它想象成超市里的储物柜。每个储物柜都有编号,可以放东西,也可以把里面的东西拿出来换成别的东西。

代码里的变量也很像这样:它有名字,有类型,有存放的数据。程序运行时,我们可以通过变量名找到这份数据,也可以在允许的情况下修改它。

常量则像一个封存好的柜子。东西放进去之后,就不希望再被改变。它适合保存那些一旦确定就不应该随便变化的值,比如最大数量、固定配置、颜色值等。

这一篇我们就把 Dart 中的变量与常量系统讲清楚。

二、一句话理解

变量是可以存放并在一定条件下修改数据的命名容器;常量是赋值后不希望再改变的数据。

简单说:

  • 变量:能变的数据容器。
  • 常量:确定后不再变化的数据。
  • var:让 Dart 帮你推断类型。
  • final:运行时确定一次,之后不能改。
  • const:编译时就确定,之后不能改。
  • late:稍后再初始化,但使用前必须有值。

三、什么是变量

变量可以理解为存储数据的容器。

一个变量通常包含三部分:

组成 含义 示例
类型 这个变量能放什么数据 intString
名字 以后通过什么名称找到它 ageuserName
当前真正存放的数据 18Tom
void main() {int age = 18;String userName = 'Tom';print(age);print(userName);
}

这里的 ageuserName 就是变量。

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 推断 ageint,根据 'Dart' 推断 nameString

注意:var 只是在声明时帮你推断类型,不代表类型可以随便变。

void main() {var age = 18;// age = 'Tom'; // 错误:age 已经被推断为 int
}

可以把 var 理解成:你先不写类型,但 Dart 会帮你补上类型。

四、Dart 常见声明方式

Dart 中常见的声明方式包括 vardynamicfinalconstlate

这几个关键字很容易混淆,所以必须放在一起比较。

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 中,常量通常通过 constfinal 表达。

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 的区别

finalconst 是这篇文章最重要的区别之一。

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

finalconst 都不能再次赋值,但它们确定值的时间不同。

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:延迟初始化,使用前必须赋值。

常量的核心是不可变。finalconst 都能表达不可变意图,但 final 偏运行时确定,const 偏编译时确定。

最后要记住:变量不是只会“存值”这么简单,它还涉及类型、安全性、可读性和作用范围。把变量与常量用对,代码会更稳定,也更容易维护。