第三章 函数、闭包与高阶函数

2613 字
13 分钟
第三章 函数、闭包与高阶函数

第三章 函数、闭包与高阶函数#

目标:掌握 Dart 函数的声明方式、命名参数(C++ 没有的重要特性)、闭包机制,以及 Dart 独有的级联运算符。这些是读懂 Flutter 代码的基础。


3.1 函数声明#

基本形式#

// 完整写法
int add(int a, int b) {
return a + b;
}
// 箭头函数:函数体只有一个表达式时可以简写
int add2(int a, int b) => a + b;

💡 C++ 对比:

int add(int a, int b) { return a + b; }
// C++ 没有箭头函数语法糖(lambda 不算,写法不同)

void 函数#

void greet(String name) {
print('Hello, $name!');
}
// 箭头写法也行
void greet2(String name) => print('Hello, $name!');

返回类型可省略(但不推荐)#

add(int a, int b) => a + b; // 能跑,但返回类型不明确

⚠️ Dart 的 linter 通常会警告你加上返回类型。建议总是写返回类型。

🔗 Lumi-Hub 实例#

ws_service.dart 中两种写法都有:

// 箭头函数:简单的一行逻辑
String _assistantMsgId(String msgId) => '${msgId}_ai';
// 完整函数:复杂逻辑
String _mergeAssistantContent(String existing, String incoming) {
if (incoming.isEmpty) return existing;
if (existing.isEmpty) return incoming;
if (existing.endsWith(incoming)) return existing;
final maxOverlap = min(existing.length, incoming.length);
for (var overlap = maxOverlap; overlap > 0; overlap--) {
if (existing.endsWith(incoming.substring(0, overlap))) {
return existing + incoming.substring(overlap);
}
}
return existing + incoming;
}

💡 经验法则:一行能写完就用 =>,否则用花括号 {}


3.2 🔑 参数:位置参数、可选参数、命名参数#

这是 Dart 和 C++ 差异最大的地方之一。Dart 有三种参数传递方式:

① 必选位置参数(和 C++ 一样)#

int add(int a, int b) => a + b;
add(1, 2); // ✅ 必须按顺序传

② 可选位置参数 []#

String greet(String name, [String? title]) {
if (title != null) {
return 'Hello, $title $name!';
}
return 'Hello, $name!';
}
greet('Lumi'); // ✅ Hello, Lumi!
greet('Lumi', 'Captain'); // ✅ Hello, Captain Lumi!

💡 用方括号 [] 把可选参数包起来。可选参数可以有默认值。

③ 🔑 命名参数 {}(重点!)#

void connect({
required String host, // required = 必须传
int port = 8765, // 有默认值 = 可以不传
String? accessKey, // 可空 = 可以不传
}) {
print('连接 $host:$port, key=$accessKey');
}
// 调用时用 参数名: 值 的形式
connect(host: '127.0.0.1'); // ✅ port 用默认值 8765
connect(host: '192.168.1.1', port: 9090); // ✅ 指定 port
connect(port: 9090, host: '10.0.0.1'); // ✅ 顺序可以随意
connect(host: '10.0.0.1', accessKey: 'secret123'); // ✅ 传入可选参数

💡 命名参数是 Flutter/Dart 的灵魂。C++ 没有命名参数。

C++ 调用 connect("host", 8765, nullptr) 时,你必须记住每个参数的顺序。

Dart 的 connect(host: '...', port: 8765) 自文档化,一眼就知道每个参数是什么。

何时用命名参数 vs 位置参数?#

场景推荐原因
参数 ≤ 2 个且含义明显位置参数add(1, 2) 显然是两个加数
参数 ≥ 3 个命名参数避免参数顺序混乱
有很多可选配置命名参数Flutter Widget 的标准做法
构造函数命名参数Flutter 几乎全用命名参数

🔗 Lumi-Hub 实例#

ws_service.dartuploadFile 方法——混合使用位置参数和命名参数:

Future<Map<String, dynamic>> uploadFile(
String filePath, { // ← 位置参数
void Function(double progress)? onProgress, // ← 命名参数(可空回调)
}) async {
// ...
onProgress?.call(0); // 安全调用:onProgress 为 null 时跳过
// ... 分片上传循环 ...
onProgress?.call((index + 1) / totalChunks); // 上报进度
// ...
onProgress?.call(1); // 上传完成
return attachment;
}

调用端(chat_screen.dart):

final attachment = await ws.uploadFile(
filePath, // 位置参数:文件路径
onProgress: (progress) { // 命名参数:进度回调
setState(() {
_uploadProgress = progress.clamp(0, 1);
});
},
);

💡 注意 onProgress 的类型:void Function(double progress)?

  • void Function(double) —— 这是一个函数类型
  • ? —— 可空(调用者可以不传)
  • 调用时用 onProgress?.call(...) 安全调用

sendMessage 的默认参数值:

void sendMessage(String text, {
List<Map<String, dynamic>> attachments = const [], // ← const 空列表作为默认值
}) {
// ...
}

💡 Dart 中默认参数值必须是编译时常量const)。 所以不能写 = [](运行时创建的 List),必须写 = const []


3.3 🔑 闭包#

闭包 = 函数 + 它捕获的外部变量。Dart 的所有函数都是闭包。

基础闭包#

Function makeCounter() {
var count = 0; // 这个变量被闭包"捕获"了
return () {
count++;
return count;
};
}
var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 3 —— count 一直活着,没有被销毁

💡 C++ 对比:

auto makeCounter() {
int count = 0;
return [count]() mutable { // 必须显式写 [count] 捕获列表
return ++count;
};
}

Dart 不需要写捕获列表——自动捕获所有引用变量

闭包作为参数#

var numbers = [3, 1, 4, 1, 5, 9];
// 传一个闭包给 sort
numbers.sort((a, b) => a.compareTo(b));
// 传一个闭包给 where
var big = numbers.where((n) => n > 3).toList();

💡 上一章学的 .map() / .where() / .fold() 的参数全部都是闭包。

常见闭包写法#

// 无参数
() => print('hello')
// 一个参数
(x) => x * 2
// 多个参数
(a, b) => a + b
// 带类型标注(更清晰)
(int a, int b) => a + b
// 多行闭包
(String name) {
var greeting = 'Hello, $name!';
print(greeting);
return greeting;
}

🔗 Lumi-Hub 实例:嵌套函数(闭包的一种形式)#

ws_service.dartuploadFile 中定义了一个嵌套函数 sendInit

Future<Map<String, dynamic>> uploadFile(String filePath, { ... }) async {
// ... 外部变量 ...
final fileName = ...;
final mimeType = ...;
final sizeBytes = ...;
final sha256Hex = ...;
// ↓ 嵌套函数:自动捕获了上面的 fileName、mimeType 等变量
Future<Map<String, dynamic>> sendInit() {
return _sendAndAwaitResponse(
'FILE_UPLOAD_INIT',
{
'file_name': fileName, // ← 捕获外部变量
'mime_type': mimeType, // ← 捕获外部变量
'size_bytes': sizeBytes, // ← 捕获外部变量
'sha256': sha256Hex, // ← 捕获外部变量
},
timeout: const Duration(seconds: 15),
);
}
// 调用嵌套函数(支持重试)
Map<String, dynamic> initResp;
try {
initResp = await sendInit(); // 第一次尝试
} catch (e) {
// ... 超时时重试 ...
initResp = await sendInit(); // 第二次尝试(复用同一个函数)
}
}

💡 嵌套函数的好处:

  1. 避免重复代码sendInit 被调用了两次(正常 + 重试),不用复制粘贴
  2. 自动捕获上下文:不用把 fileNamemimeType 等作为参数传进去
  3. 作用域隔离sendInit 只在 uploadFile 内部可见

3.4 🔑 高阶函数#

高阶函数 = 接收函数作为参数,或返回函数的函数。

函数作为参数#

void repeat(int times, void Function(int) action) {
for (var i = 0; i < times; i++) {
action(i);
}
}
repeat(3, (i) => print('第 $i 次'));
// 输出:第 0 次、第 1 次、第 2 次

函数作为返回值#

Function(int) multiplier(int factor) {
return (int value) => value * factor;
}
var double = multiplier(2);
var triple = multiplier(3);
print(double(5)); // 10
print(triple(5)); // 15

typedef —— 给函数类型起别名#

当函数类型很长时,用 typedef 让代码更可读:

// 定义函数类型别名
typedef ProgressCallback = void Function(double progress);
typedef JsonMap = Map<String, dynamic>;
// 使用别名
Future<JsonMap> uploadFile(
String filePath, {
ProgressCallback? onProgress, // ← 比 void Function(double)? 更清晰
}) async {
// ...
}

💡 C++ 对比:

using ProgressCallback = std::function<void(double)>;

几乎一样的思路,Dart 用 typedef,C++ 用 using

🔗 Lumi-Hub 实例#

ws_service.dart 中函数作为参数的典型用法:

// onProgress 就是一个高阶函数参数
Future<Map<String, dynamic>> uploadFile(
String filePath, {
void Function(double progress)? onProgress,
}) async {
// ...
onProgress?.call(0); // 开始:0%
for (var index = 0; index < totalChunks; index++) {
// ... 上传逻辑 ...
onProgress?.call((index + 1) / totalChunks); // 过程中
}
onProgress?.call(1); // 完成:100%
return attachment;
}

chat_screen.dart 中到处都是回调函数参数:

_TopBar(
// ...
onCancelSelection: _toggleSelectionMode, // ← 传方法引用
onDeleteSelected: () => _deleteSelectedMessages(ws), // ← 传闭包
onOpenSidebar: isCompact
? () => Scaffold.of(scaffoldContext).openDrawer()
: null, // ← 条件传闭包或 null
)

💡 方法引用 vs 闭包

  • onCancelSelection: _toggleSelectionMode —— 直接传方法引用(等价于传函数指针)
  • onDeleteSelected: () => _deleteSelectedMessages(ws) —— 传一个闭包(因为需要额外传 ws 参数)

3.5 🔑 级联运算符 ..#

级联运算符让你对同一个对象连续调用多个方法,无需重复写变量名。

基本用法#

// 不用级联
var list = <int>[];
list.add(1);
list.add(2);
list.add(3);
// 用级联 —— 更简洁
var list2 = <int>[]
..add(1)
..add(2)
..add(3);

级联的本质#

.. 的返回值是对象本身,而不是方法的返回值。

var sb = StringBuffer()
..write('Hello') // write() 返回 void,但 .. 返回 sb 本身
..write(', ')
..write('World!');
print(sb.toString()); // Hello, World!

💡 C++ 中也有类似的模式叫方法链(Method Chaining),但需要每个方法返回 this

builder.setHost("127.0.0.1").setPort(8765).build();

Dart 的级联不需要方法特殊设计——任何方法都可以用 .. 级联。

?.. 空安全级联#

List<int>? list;
list
?..add(1) // 如果 list 为 null,整条链都不执行
..add(2)
..add(3);

🔗 Lumi-Hub 实例#

ws_service.dart_handleHistoryResponse——经典的级联清空+填充:

if (offset == 0) {
_messages
..clear() // 先清空
..addAll(loaded); // 再填入新数据
}

不用级联的话要写成:

if (offset == 0) {
_messages.clear();
_messages.addAll(loaded);
}

chat_screen.dart 中的 Set 级联操作:

_selectedMessageIds
..clear()
..addAll(messageIds);

💡 级联最适合对一个对象做多步初始化或配置的场景。


3.6 匿名函数 vs 具名函数 vs 方法引用#

Dart 中函数有三种”身份”:

// 1. 具名函数(顶级或嵌套)
void greet() => print('hello');
// 2. 匿名函数(闭包/lambda)
var greet2 = () => print('hello');
// 3. 方法引用(把实例方法/静态方法当作值传递)
var items = [3, 1, 2];
items.sort(Comparable.compare); // 传静态方法引用

方法引用 —— 简化回调#

当回调函数的签名和某个现有方法完全匹配时,可以直接传方法引用:

// 匿名函数写法
numbers.forEach((n) => print(n));
// 方法引用写法(更简洁)
numbers.forEach(print); // print 的签名刚好是 void Function(Object?)

🔗 Lumi-Hub 实例#

chat_screen.dart 中的方法引用:

_TopBar(
onCancelSelection: _toggleSelectionMode, // 方法引用
// 等价于:
// onCancelSelection: () => _toggleSelectionMode(),
)
_scroll.addListener(_onScrollMaybeLoadOlder); // 方法引用作为监听器

3.7 本章小结#

概念C++Dart重要程度
函数声明int f(int)相同,多了 => 箭头写法
命名参数{required String host, int port = 80}🔑🔑🔑
可选位置参数重载实现[int? count]🔑
默认参数值= value= const value(必须编译时常量)🔑
闭包[captures](){} 需要显式捕获() {} 自动捕获🔑
嵌套函数Lambda 可以,具名函数不行✅ 支持嵌套具名函数🔑
高阶函数std::function函数即一等公民🔑
typedefusingtypedef Fn = void Function(int)🔑
级联 ..❌(需方法链设计)原生支持,对任何对象生效🔑
方法引用函数指针直接传方法名

🎯 关键要点#

  1. 命名参数是 Dart/Flutter 的灵魂——几乎所有 Widget 构造函数都用命名参数。记住 required 表示必传,? 或默认值表示可选。
  2. 闭包自动捕获——不需要像 C++ 那样写捕获列表。但要注意:捕获的是变量的引用,不是值的拷贝。
  3. 嵌套函数是一种强大的闭包形式——用于避免重复代码(如重试逻辑)。
  4. 级联 .. 让对同一对象的多次操作更紧凑——在初始化和配置场景中特别有用。
  5. 默认参数值必须是 const——所以 = [] 要写成 = const []

📖 下一章:第 4 章 面向对象:Dart 的类体系 —— 构造函数语法糖、factory、mixin 等 Dart 独有的 OOP 特性。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

第三章 函数、闭包与高阶函数
https://firefly-7a0.pages.dev/posts/learn_dart/03_functions/
作者
lonelystar
发布于
2026-03-17
许可协议
CC BY-NC-SA 4.0
最后更新于 2026-03-17,距今已过 43 天

部分内容可能已过时

评论区

Profile Image of the Author
LonelyStar
Hello, I'm LonelyStar.
公告
欢迎来到我的博客!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
119
分类
11
标签
346
总字数
226,548
运行时长
0
最后活动
0 天前

目录