现在位置: 首页 > Dart 教程 > 正文

Dart 函数

函数是组织代码的基本单元,它将一段可复用的逻辑封装起来,需要时调用即可。

Dart 的函数系统非常灵活,支持命名参数、可选参数、箭头函数和高阶函数等特性。


函数定义与返回值

函数由返回类型、函数名、参数列表和函数体组成。

实例

// 定义一个简单的函数
// String 是返回类型,greet 是函数名,(String name) 是参数列表
String greet(String name) {
  return '你好,$name!欢迎来到 RUNOOB。';
}

// 没有返回值的函数使用 void
void printWelcome() {
  print('=== RUNOOB Dart 教程 ===');
}

void main() {
  printWelcome();

  // 调用函数并接收返回值
  String message = greet('小明');
  print(message);
}
=== RUNOOB Dart 教程 ===
你好,小明!欢迎来到 RUNOOB。

返回值

每个函数都有一个返回值。

如果没有显式 return,函数隐式返回 null(但空安全下这会导致类型不匹配,所以通常用 void 表示无返回值)。

实例

// 返回 int 类型的函数
int add(int a, int b) {
  return a + b;
}

// 使用箭头语法简写单表达式函数
int multiply(int a, int b) => a * b;

// void 表示没有有意义的返回值
void log(String msg) {
  print('[RUNOOB 日志] $msg');
}

void main() {
  print('3 + 5 = ${add(3, 5)}');
  print('3 × 5 = ${multiply(3, 5)}');
  log('函数学习完成');
}
3 + 5 = 8
3 × 5 = 15
[RUNOOB 日志] 函数学习完成

命名参数与可选参数

Dart 的参数分为两种:必选参数(required)和可选参数。

可选参数又分为命名参数(named)和位置参数(positional)。

命名参数(Named Parameters)

命名参数用花括号 {} 包裹,调用时通过参数名传递,顺序可以任意。

实例

// 花括号 {} 中的是命名参数,默认是可选的
// required 关键字标记为必填
String createUser({
  required String name,    // 必填的命名参数
  int age = 0,             // 可选,默认值为 0
  String? email,           // 可选,可以为 null
  bool isVip = false,      // 可选,默认值为 false
}) {
  var info = '用户名: $name, 年龄: $age';
  if (email != null) {
    info += ', 邮箱: $email';
  }
  if (isVip) {
    info += ' [VIP用户]';
  }
  return info;
}

void main() {
  // 命名参数:通过参数名传递,顺序无关
  print(createUser(name: 'runoob', age: 10));
  print(createUser(name: 'admin', email: 'admin@runoob.com', isVip: true));
  // 必填参数不能省略
  // createUser();  // 错误:缺少必填参数 name
}
用户名: runoob, 年龄: 10
用户名: admin, 邮箱: admin@runoob.com [VIP用户]

位置参数(Positional Parameters)

位置参数用方括号 [] 包裹,调用时按顺序传递。

实例

// 方括号 [] 中的是可选位置参数
String buildUrl(String host, [String path = '/', int port = 80]) {
  return 'http://$host:$port$path';
}

void main() {
  // 只传必填参数,可选参数使用默认值
  print(buildUrl('www.runoob.com'));

  // 传 2 个参数
  print(buildUrl('www.runoob.com', '/dart'));

  // 传 3 个参数
  print(buildUrl('localhost', '/api', 8080));
}
http://www.runoob.com:80/
http://www.runoob.com:80/dart
http://localhost:8080/api

命名参数 vs 位置参数:如何选择

场景推荐方式原因
参数很多(3个以上)命名参数调用时参数名自文档化,不易传错顺序
参数含义不明显命名参数true/false 这类值不说明用途,加名字更清晰
参数很少且含义明确位置参数如 add(1, 2),简洁明了
Flutter Widget 构建命名参数Flutter 标准风格

一个函数不能同时使用位置可选参数和命名可选参数。你只能选一种。


默认参数值

可选参数可以指定默认值,当调用者没有传该参数时使用默认值。

实例

// 所有可选参数都设置了默认值
String formatMessage(
  String content, {
  String prefix = '[RUNOOB]',
  String suffix = '',
  bool uppercase = false,
}) {
  var result = '$prefix $content $suffix';
  return uppercase ? result.toUpperCase() : result;
}

void main() {
  // 全部使用默认值
  print(formatMessage('Dart 教程更新了'));

  // 覆盖部分默认值
  print(formatMessage('重要通知', prefix: '[公告]', suffix: '!!!'));

  // 覆盖默认值 + 开启大写
  print(formatMessage('error', prefix: '[错误]', uppercase: true));
}
[RUNOOB] Dart 教程更新了
[公告] 重要通知 !!!
[错误] ERROR

默认值必须是编译时常量。也就是说,默认值不能是 DateTime.now() 或某个函数的返回值(除非该函数是 const 的)。如果需要在运行时确定默认值,可以在函数体内用 ?? 处理。


箭头函数与匿名函数

当函数体只有一条表达式时,可以用箭头语法(=>)简写。

匿名函数是没有名字的函数,通常作为参数传递给其他函数。

实例

void main() {
  var numbers = [1, 2, 3, 4, 5];

  // 匿名函数(完整写法)
  numbers.forEach((number) {
    print('RUNOOB 数字: $number');
  });

  print('---');

  // 箭头函数(单表达式简写)
  var doubled = numbers.map((n) => n * 2);
  print('翻倍: $doubled');

  // 箭头函数在函数定义中的使用
  // 当函数体只有一个 return 语句时,可以用 => 简写
  int square(int x) => x * x;
  print('5 的平方: ${square(5)}');

  // 存储匿名函数到变量
  var sayHello = (String name) => '你好,$name!';
  print(sayHello('runoob'));
}
RUNOOB 数字: 1
RUNOOB 数字: 2
RUNOOB 数字: 3
RUNOOB 数字: 4
RUNOOB 数字: 5
---
翻倍: (2, 4, 6, 8, 10)
5 的平方: 25
你好,runoob!

高阶函数与闭包

高阶函数是指可以接收函数作为参数、或将函数作为返回值的函数。

闭包是指函数可以访问其词法作用域中的变量,即使该函数在作用域外被调用。

高阶函数

实例

// 高阶函数:接收一个函数作为参数
List<int> filterList(
    List<int> items, bool Function(int) predicate) {
  return items.where(predicate).toList();
}

// 高阶函数:返回一个函数
Function makeMultiplier(int factor) {
  // 返回一个闭包:记住了外部的 factor
  return (int n) => n * factor;
}

void main() {
  var numbers = [10, 15, 20, 25, 30];

  // 传入一个匿名函数作为筛选条件
  var bigNumbers = filterList(numbers, (n) => n > 18);
  print('RUNOOB 大于18的数: $bigNumbers');

  // 获取返回的函数
  var doubleIt = makeMultiplier(2);
  var tripleIt = makeMultiplier(3);

  print('5 × 2 = ${doubleIt(5)}');
  print('5 × 3 = ${tripleIt(5)}');
}
RUNOOB 大于18的数: [20, 25, 30]
5 × 2 = 10
5 × 3 = 15

闭包(Closure)

闭包是一个函数对象,它可以访问其词法作用域中的变量,即使该函数在原始作用域之外被调用。

实例

// 闭包典型应用:创建计数器
Function makeCounter() {
  int count = 0;  // 这个变量被返回的函数"捕获"
  return () {
    count++;
    return count;
  };
}

void main() {
  // counterA 和 counterB 各自拥有独立的 count
  var counterA = makeCounter();
  var counterB = makeCounter();

  print('RUNOOB 计数器 A: ${counterA()}');  // 1
  print('RUNOOB 计数器 A: ${counterA()}');  // 2
  print('RUNOOB 计数器 B: ${counterB()}');  // 1(独立的计数)
  print('RUNOOB 计数器 A: ${counterA()}');  // 3
}
RUNOOB 计数器 A: 1
RUNOOB 计数器 A: 2
RUNOOB 计数器 B: 1
RUNOOB 计数器 A: 3

闭包的关键在于:内部函数"记住"了外部函数的变量,即使外部函数已经执行完毕。每次调用 makeCounter() 都会创建一个全新的 count 变量和闭包,所以多个计数器互不影响。