Skip to Content

可空类型(Nullable)

T? 表示「有值或没有」。非可空类型编译期保证非 null,判空只在可空类型上有意义。基础写法(??、?.、if val)见运算符与条件分支,本页则重点介绍类型层面的规则。

解包四手段

可空值要拿回 T,只有四条路:?? 给默认、! 断言、?. 链式短路、判空流收窄:

func FindUser(id: int) -> string? { if id == 1 { return "甲"; } return null; } val name = FindUser(1); print(name ?? "无名"); print(FindUser(2) ?? "无名");
甲 无名

! 断言宣告「此处必有值」,运行期为 null 时报 MS6045。?? 可以连起来给多级兜底:

val merged = name ?? FindUser(2) ?? "双无"; print(merged); var nick: string? = "在手"; print(nick!.Length); print(nick == null);
甲 2 False

判空流收窄

== null 判断不只是产生 bool,它产生「null 状态事实」,沿控制流传播。守护式写法(判空分支以 return / fail / crash 结束)之后,编译器把值当非空对待:

func GuardStyle(id: int) -> string { val name = FindUser(id); if name == null { return "匿名"; } return $"找到 {name.Length} 字"; } print(GuardStyle(1), GuardStyle(2));
找到 1 字 匿名

守护之后的 name.Length 合法,不需要 ?. 或 !。正向式 if name != null { } 的块内同样收窄。&& 左侧的收窄在右侧可见(name != null && name.Length > 0)。

算术传播

可空值参与算术,结果也是可空:任一侧为 null,整个表达式结果就为 null:

val a: int? = 2; val b: int? = null; print(a + 1, b ?? -1); print(a + b);
3 -1 null

比较的结果恒是 bool(null 参与 == / != 判断合法),但排序比较(< <= > >=)不接受任何可空操作数,null 没有次序,先 ?? 给默认值再比。

收窄的传播与失效

收窄沿控制流传播,也在特定操作后失效。常用形态的完整清单:

  • && 链:左侧建立的收窄在右侧可见(name != null && name.Length > 0 整条合法)。
  • 三元真臂:name != null ? name.Length : 0 的真臂内收窄。
  • 字段路径:user.nick != null 之后 user.nick.Length 直接可用。
  • 重赋值失效:o = expr 之后按 expr 的类型重算状态。
  • 调用毒化:判空之后、读之前若发生任何函数调用,字段路径的收窄全部失效,裸名局部不受影响。
var s: string? = "值"; if s != null && s.Length > 0 { print("长名", s.Length); } val t = s != null ? s.Length : 0; print(t);
长名 1 1

空合并赋值与空条件索引

??= 只在左侧为 null 时赋值;?[i] 把空条件推广到索引:

var who: string? = null; who ??= "默认名"; print(who); var arr: int[]? = [1, 2, 3]; print(arr?[1] ?? -1); arr = null; print(arr?[1] ?? -1);
默认名 2 -1

装箱标量的保守面

当前装箱标量的收窄比较保守,装箱标量(int? / float? / bool? / decimal?)判空后不收窄:while n != null 体内,n 仍是 int?,序数比较照样被拒,先 ?? 落地再算:

var n: int? = 3; while n != null { val cur = n ?? 0; print("轮", cur); if cur <= 1 { n = null; } else { n = cur - 1; } }
轮 3 轮 2 轮 1
  • 被 lambda 或 spawn 捕获的变量、泛型参数、向量与元组叶段同样不收窄。
  • 字符串与自定义类的引用可空不受此限,判空收窄全量生效。
  • 可空元组本身照常收窄:判空分支内可以直接解构,见元组。

注意

  • 赋 null 给非空类型的任何位置都报错。
  • 非可空值与 null 的比较也会直接报错,判空只对可空形有意义;
  • 收窄的事实源只有三个:== null / != null 判断与 is T 测试,重新赋值按新值的类型重算。
  • 判空收窄后多余的 ?? 会报 MS3240 警告(右分支永不执行)。
  • 排序比较不接受任何可空操作数(null 侧无序),先 ?? 再比。

可空函数类型

函数类型的 ? 缀在返回类型上:func(int) -> int? 是「返回 int? 的函数」。

要「可能为 null 的函数类型」,须给整个函数类型加括号写 (func(int) -> int)?,可空函数调用前照常判空收窄:

func Maybe(n: int) -> int? { if n > 0 { return n; } return null; } func On(n: int) -> int { return n; } func main() -> int { val f1: func(int) -> int? = Maybe; print(f1(5) == 5); val f2: (func(int) -> int)? = null; print(f2 == null); val f3: (func(int) -> int)? = On; if (f3 != null) { print(f3(7)); } return 0; }
True True 7
最后更新于 2026年10月9日