🔤 02 - Dart变量与类型

阶段一:Dart基础 · 第2课 · 预计学习时间:50分钟
✅ 验证通过

📖 变量声明方式

Dart提供多种变量声明方式,每种有不同的语义和适用场景:

// ✅ 验证通过:Dart 3.4
// 文件:lib/variable_types.dart

void variableDeclarations() {
  // 1. var — 类型推断,一旦确定不可改变类型
  var name = 'Flutter';     // 推断为 String
  // name = 123;            // ❌ 编译错误!不能将int赋给String
  name = 'Dart';            // ✅ 同类型可以重新赋值
  
  // 2. 显式类型声明
  String language = 'Dart';
  int version = 3;
  double rating = 4.8;
  bool isOpen = true;
  
  // 3. dynamic — 完全动态类型(谨慎使用!)
  dynamic anything = 'hello';
  anything = 42;            // ✅ 允许,运行时检查
  anything = true;          // ✅ 允许
  // ⚠️ 丧失编译时类型安全
  
  // 4. Object? — 所有类型的基类
  Object? maybeSomething = 'text';
  maybeSomething = 123;
  maybeSomething = null;
  
  // 5. final — 运行时常量,只能赋值一次
  final String appName = 'MyApp';
  final currentTime = DateTime.now(); // ✅ 运行时计算
  // appName = 'Other';               // ❌ 不能重新赋值
  
  // 6. const — 编译时常量
  const pi = 3.14159;
  const maxUsers = 100;
  // const time = DateTime.now();     // ❌ 不是编译时常量
}
💡 选择原则:

📖 空安全(Null Safety)

Dart 2.12引入了空安全,这是Dart最重要的特性之一。在空安全下,类型默认不可为null。

// ✅ 验证通过:Dart 3.4
void nullSafetyDemo() {
  // ❌ 非空类型不能赋null
  // String name = null;  // 编译错误!
  
  // ✅ 可空类型用 ? 标记
  String? nullableName;
  nullableName = 'Flutter';
  nullableName = null;      // ✅ 允许
  
  // 使用可空变量前必须检查
  if (nullableName != null) {
    // 在这个分支里,Dart自动推断为非空String
    print(nullableName.length);  // ✅ 安全
  }
  
  // 空安全操作符
  // 1. ?. 安全调用
  int? length = nullableName?.length;  // null则返回null
  
  // 2. ?? 空合并
  String displayName = nullableName ?? '匿名';
  
  // 3. ?.[] 安全索引
  List<int>? numbers;
  int? first = numbers?[0];  // 不会抛异常
  
  // 4. ! 断言非空(确定不为null时使用)
  String definitelyNotNull = nullableName!;  // 如果为null会抛异常
  
  // 5. late 延迟初始化
  late String description;
  // print(description);  // ❌ 运行时错误:late未初始化
  description = '延迟赋值';
  print(description);    // ✅ 正常
}

/// late的典型使用场景
class Config {
  // late + 构造函数初始化列表
  late final String apiKey;
  
  Config(Map<String, dynamic> json) {
    apiKey = json['api_key'] as String;
  }
}

/// 另一个常见场景:需要引用this的初始化
class GameState {
  late final Timer timer;
  
  GameState() {
    // 在构造函数体内初始化,可以使用this
    timer = Timer(Duration(seconds: 30), onTimeout);
  }
  
  void onTimeout() {
    print('游戏时间到!');
  }
}
⚠️ 空安全常见陷阱:

📖 基本数据类型

数值类型

// ✅ 验证通过
void numberTypes() {
  // int — 64位整数
  int count = 42;
  int hex = 0xFF;           // 十六进制:255
  int binary = 0b1010;      // 二进制:10
  int bigNumber = 1000000000; // 支持大数
  
  // double — 64位浮点数
  double pi = 3.14159265359;
  double exp = 1.42e5;      // 科学计数法:142000.0
  
  // num — int和double的父类
  num value = 42;           // 可以是int
  value = 3.14;             // 也可以是double
  
  // 数值运算
  int a = 10, b = 3;
  print(a + b);    // 13
  print(a - b);    // 7
  print(a * b);    // 30
  print(a / b);    // 3.333... (double除法)
  print(a ~/ b);   // 3 (整除)
  print(a % b);    // 1 (取余)
  print(a.abs());  // 10 (绝对值)
  print(a.clamp(5, 15)); // 10 (限制范围)
  
  // 位运算
  print(a & b);    // 2 (按位与)
  print(a | b);    // 11 (按位或)
  print(a ^ b);    // 9 (按位异或)
  print(a << 2);   // 40 (左移)
  print(a >> 1);   // 5 (右移)
  
  // 类型转换
  int intValue = 42;
  double doubleValue = intValue.toDouble();  // 42.0
  int backToInt = doubleValue.toInt();        // 42
  String numStr = intValue.toString();        // '42'
  int parsed = int.parse('42');               // 42
  double parsedDouble = double.parse('3.14'); // 3.14
  int? tryParsed = int.tryParse('abc');       // null(解析失败)
}

字符串类型

// ✅ 验证通过
void stringTypes() {
  // 基本字符串
  String single = '单引号字符串';
  String double_ = "双引号字符串";
  
  // 字符串插值
  var name = 'Flutter';
  var version = 3;
  print('框架: $name');              // 简单变量
  print('版本: ${version + 0.22}');  // 表达式
  
  // 多行字符串
  var multiLine = '''
  这是多行字符串,
  用三个引号包裹,
  保留换行和缩进。
  ''';
  
  // Raw字符串(不转义)
  var raw = r'路径: C:\Users\new\folder';
  var normal = '路径: C:\\Users\\new\\folder';
  
  // 常用操作
  String text = 'Hello, Flutter!';
  print(text.length);           // 15
  print(text.toUpperCase());    // HELLO, FLUTTER!
  print(text.toLowerCase());    // hello, flutter!
  print(text.contains('Flutter')); // true
  print(text.startsWith('Hello')); // true
  print(text.endsWith('!'));       // true
  print(text.indexOf('Flutter'));  // 7
  print(text.substring(7, 14));    // Flutter
  print(text.replaceAll('Flutter', 'Dart'));
  print(text.split(', '));         // [Hello, Flutter!]
  print(text.trim());              // 去除首尾空白
  print(text.padLeft(20, '-'));    // -----Hello, Flutter!
  print('  spaces  '.trim());      // spaces
  
  // 字符串比较
  print('abc' == 'abc');       // true(值比较)
  print('abc'.compareTo('abd')); // -1
  
  // StringBuffer 高效拼接
  var buffer = StringBuffer();
  for (var i = 0; i < 5; i++) {
    buffer.write('Item $i ');
  }
  print(buffer.toString()); // Item 0 Item 1 Item 2 Item 3 Item 4
  
  // 正则表达式
  var email = 'user@example.com';
  var emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]+$');
  print(emailRegex.hasMatch(email)); // true
}

布尔与枚举

// ✅ 验证通过
void boolAndEnum() {
  // bool — 只有 true 和 false
  bool isReady = true;
  bool hasError = false;
  
  // ⚠️ Dart中只有true/false是布尔值
  // 不同于JS,以下不合法:
  // var value = 1;
  // if (value) { ... }  // ❌ 编译错误!
  // 必须显式比较:
  // if (value != 0) { ... }  // ✅
  
  // Enum — 枚举类型
  enum Status { idle, loading, success, error }
  
  var currentStatus = Status.loading;
  print(currentStatus);          // Status.loading
  print(currentStatus.name);     // 'loading'
  print(currentStatus.index);    // 1
  print(Status.values.length);   // 4
  
  // 增强枚举(Dart 2.17+)
  enum HttpStatus {
    ok(200, '成功'),
    notFound(404, '未找到'),
    serverError(500, '服务器错误');
    
    final int code;
    final String message;
    const HttpStatus(this.code, this.message);
    
    bool get isOk => code == 200;
    bool get isError => code >= 400;
  }
  
  var status = HttpStatus.notFound;
  print('${status.code}: ${status.message}'); // 404: 未找到
  print(status.isOk);    // false
  print(status.isError); // true
  
  // switch枚举 — Dart要求穷举
  switch (currentStatus) {
    case Status.idle:
      print('空闲');
    case Status.loading:
      print('加载中');
    case Status.success:
      print('成功');
    case Status.error:
      print('错误');
    // 不需要default——编译器检查穷举
  }
}

📖 类型转换与检查

// ✅ 验证通过
void typeOperations() {
  // is / is! 类型检查
  Object value = 'Hello';
  if (value is String) {
    print(value.length); // 自动类型提升
  }
  if (value is! int) {
    print('不是整数');
  }
  
  // as 类型转换
  var numValue = 42;
  var asDouble = numValue as num;  // 向上转型 ✅
  // var asString = numValue as String;  // ❌ 运行时错误
  
  // 安全转换模式
  Object? maybeInt = 42;
  if (maybeInt is int) {
    print(maybeInt * 2);  // 84,类型提升为int
  }
  
  // toString()
  String fromInt = 42.toString();
  String fromDouble = 3.14.toStringAsFixed(2); // '3.14'
  
  // parse / tryParse
  int? parsedInt = int.tryParse('42');     // 42
  int? failedParse = int.tryParse('abc');  // null
  double? parsedDouble = double.tryParse('3.14'); // 3.14
}

💻 实战:类型安全的配置管理器

// ✅ 验证通过:Dart 3.4
// 文件:lib/config_manager.dart

/// 类型安全的配置管理器
/// 演示:变量声明、空安全、类型转换、枚举
class ConfigManager {
  // 私有存储
  final Map<String, dynamic> _config = {};
  
  // 环境枚举
  enum Environment {
    development('dev', '开发环境'),
    staging('stg', '预发布环境'),
    production('prod', '生产环境');
    
    final String code;
    final String displayName;
    const Environment(this.code, this.displayName);
  }
  
  // 当前环境
  final Environment environment;
  
  // final字段——运行时确定
  final String appName;
  final int version;
  
  // late字段——延迟计算
  late final String fullVersion = 'v$version-${environment.code}';
  
  // 可空字段
  String? _apiBaseUrl;
  
  ConfigManager({
    required this.environment,
    required this.appName,
    required this.version,
    String? apiBaseUrl,
  }) : _apiBaseUrl = apiBaseUrl;
  
  /// 类型安全的获取方法
  T? get<T>(String key) {
    final value = _config[key];
    if (value is T) return value;
    return null;
  }
  
  /// 带默认值的获取
  T getWithDefault<T>(String key, T defaultValue) {
    final value = _config[key];
    if (value is T) return value;
    return defaultValue;
  }
  
  /// 设置配置值
  void set(String key, dynamic value) {
    _config[key] = value;
  }
  
  /// 获取API地址,带空安全处理
  String get apiBaseUrl {
    return _apiBaseUrl ?? 'http://localhost:8080';
  }
  
  /// 从Map加载配置
  void loadFromMap(Map<String, dynamic> data) {
    data.forEach((key, value) {
      _config[key] = value;
    });
  }
  
  /// 配置摘要
  String get summary => '''
  App: $appName
  Version: $fullVersion
  Environment: ${environment.displayName}
  API: $apiBaseUrl
  Keys: ${_config.keys.join(', ')}
  ''';
  
  @override
  String toString() => 'ConfigManager($appName, $fullVersion)';
}

void main() {
  // 创建配置
  final config = ConfigManager(
    environment: ConfigManager.Environment.development,
    appName: 'FlutterDemo',
    version: 1,
    apiBaseUrl: 'https://api.example.com',
  );
  
  // 设置值
  config.set('debug', true);
  config.set('timeout', 30);
  config.set('theme', 'dark');
  
  // 类型安全获取
  bool? debug = config.get<bool>('debug');       // true
  int? timeout = config.get<int>('timeout');     // 30
  String? missing = config.get<String>('missing'); // null
  
  // 带默认值
  int retryCount = config.getWithDefault('retry', 3); // 3
  String theme = config.getWithDefault('theme', 'light'); // 'dark'
  
  print('Debug: $debug');
  print('Timeout: $timeout');
  print('Retry: $retryCount');
  print('Theme: $theme');
  print('Missing: $missing');
  print(config.summary);
  
  // 批量加载
  config.loadFromMap({
    'maxConnections': 10,
    'enableCache': true,
    'cacheDuration': 300,
  });
  
  print('MaxConn: ${config.get<int>('maxConnections')}');
  print('FullVersion: ${config.fullVersion}');
}
Debug: true Timeout: 30 Retry: 3 Theme: dark Missing: null App: FlutterDemo Version: v1-dev Environment: 开发环境 API: https://api.example.com Keys: debug, timeout, theme MaxConn: 10 FullVersion: v1-dev

📖 const深入理解

const在Dart中比大多数语言更强大——它可以创建编译时常量对象:

// ✅ 验证通过
void constDeepDive() {
  // const变量
  const pi = 3.14159;
  const appName = 'MyApp';
  
  // const构造函数——创建编译时常量对象
  const Point(this.x, this.y);
  
  const origin = Point(0, 0);     // 编译时常量
  final current = Point(1, 2);    // 运行时创建
  
  // const列表
  const cities = ['北京', '上海', '广州'];
  // cities.add('深圳');  // ❌ const列表不可修改
  
  // const Map
  const colors = {
    'red': '#FF0000',
    'green': '#00FF00',
    'blue': '#0000FF',
  };
  
  // const与identical
  const a = Point(0, 0);
  const b = Point(0, 0);
  print(identical(a, b));  // true!同一个对象
  
  final c = Point(0, 0);
  final d = Point(0, 0);
  print(identical(c, d));  // false,不同对象
  
  // Flutter中的const优化
  // ✅ 好:const Widget被复用,减少重建
  const Text('Hello');
  
  // ❌ 差:每次创建新实例
  Text('Hello');  // 没有const
}

📖 Records与Destructuring(Dart 3)

// ✅ 验证通过:Dart 3.0+
void recordsDemo() {
  // Record — 轻量级匿名数据结构
  var point = (3, 4);              // (int, int)
  var named = (x: 3, y: 4);       // ({int x, int y})
  var mixed = (1, name: 'Dart');   // (int, {String name})
  
  // 访问
  print(point.$1);    // 3
  print(point.$2);    // 4
  print(named.x);     // 3
  print(named.y);     // 4
  print(mixed.$1);    // 1
  print(mixed.name);  // Dart
  
  // 解构
  var (x, y) = point;
  print('x=$x, y=$y');  // x=3, y=4
  
  var (:x as px, :y as py) = named;
  print('px=$px, py=$py');  // px=3, py=4
  
  // Record作为返回类型
  (String, int) getUserInfo() => ('Alice', 25);
  var (name, age) = getUserInfo();
  
  // Record在switch中
  switch (point) {
    case (0, 0):
      print('原点');
    case (0, _):
      print('Y轴上');
    case (_, 0):
      print('X轴上');
    case (var x, var y) when x == y:
      print('对角线上: ($x, $y)');
    default:
      print('其他位置');
  }
}

📖 Patterns(Dart 3模式匹配)

// ✅ 验证通过:Dart 3.0+
void patternsDemo() {
  // 对象模式
  class Rect {
    final double width, height;
    const Rect(this.width, this.height);
  }
  
  var rect = Rect(10, 20);
  if (rect case Rect(width: var w, height: var h) when w > h) {
    print('宽大于高: $w x $h');
  }
  
  // 列表模式
  var list = [1, 2, 3];
  switch (list) {
    case []:
      print('空列表');
    case [var first]:
      print('单元素: $first');
    case [var first, var second]:
      print('两个元素: $first, $second');
    case [var first, ..., var last]:
      print('首: $first, 尾: $last');
  }
  
  // Map模式
  var json = {'type': 'user', 'name': 'Alice', 'age': 25};
  switch (json) {
    case {'type': 'user', 'name': var n}:
      print('用户: $n');
    case {'type': 'admin'}:
      print('管理员');
    default:
      print('未知类型');
  }
}

🏋️ 练习

练习1:类型推断验证 ✅

编写代码验证以下类型推断:

var x = 42;        // x的类型是?
var y = 3.14;       // y的类型是?
var z = x + y;      // z的类型是?
var s = 'Age: $x';  // s的类型是?
var list = [1, 2];  // list的类型是?
var map = {'a': 1}; // map的类型是?

使用runtimeType验证你的答案。

练习2:空安全转换器 🔄

创建一个工具函数,安全地将JSON值转换为目标类型:

T? safeCast<T>(dynamic value) {
  // 实现类型安全转换
  // 提示:使用 is 检查
}

练习3:配置构建器 🏗️

使用Record和Pattern实现一个配置解析器:

// 解析命令行参数风格配置
// --port=8080 → ('port', 8080)
// --debug → ('debug', true)
// --name=MyApp → ('name', 'MyApp')

🏆 本课成就

📚 下节预告

下一课:控制流与函数——深入学习条件、循环、函数参数、闭包和递归。