函数

函数是Dart程序中的核心组件,用于封装可重用的代码块,执行特定任务。

函数定义与调用

  • 理论: 在Dart中,函数由返回类型、函数名、参数列表(可选)和函数体组成。如果没有明确指定返回类型,Dart函数默认返回dynamic类型,但最佳实践是明确指定。如果函数不返回任何值,其返回类型应为void。
  • 语法:
返回类型 函数名(参数列表) {
  // 函数体
  return 表达式; // 如果有返回值
}
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

// 1. 无参数,返回 void 的函数
void greet() {
  print('Hello, Dart!');
}

// 2. 带参数,返回 String 的函数
String sayHello(String name) {
  return 'Hello, $name!';
}

// 3. 带多个参数,返回 int 的函数
int add(int a, int b) {
  return a + b;
}

// 4. 函数的调用
void main() { // Dart 程序的入口函数
  greet(); // 调用 greet 函数

  String message = sayHello('Alice'); // 调用 sayHello 函数,并接收返回值
  print(message);

  int sum = add(10, 20); // 调用 add 函数
  print('Sum: $sum');

  // 注意:可以不指定返回类型,但强烈不推荐
  // funcWithoutReturnType(param) { return 'Value: $param'; }
  // print(funcWithoutReturnType(123));
}
  • 注意事项:
    • main()函数是Dart应用程序的入口点。
    • 始终明确声明函数的返回类型,即使是void,这有助于代码的可读性和静态分析。
    • 函数参数可以有类型注解,这同样是最佳实践。

拓展: 单行函数

对于只包含一个表达式的函数,Dart提供了一种简洁的语法,称为“箭头函数”或“表达式体(expression body)”。

  • 语法:
返回类型 函数名(参数列表) => 表达式;
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器
int multiply(int a, int b) => a * b; // 单行函数示例
void main() {
  print('Product: ${multiply(5, 4)}');
}

可选参数

Dart允许函数具有可选参数。可选参数可以是命名可选参数或定位可选参数,但一个函数不能同时拥有两者作为可选参数。

命名可选参数

  • 理论: 命名参数用花括号{}包裹。当调用函数时,可以通过参数名: 值的形式来指定这些参数,且顺序不重要。这大大增加了函数调用的可读性,特别是当函数有多个参数时。
  • 语法:
返回类型 函数名({类型 参数名1, 类型 参数名2}) { ... }

可以通过required关键字标记某个命名参数为必须的,即调用时必须提供该参数。

  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本 (支持 `required` 关键字)
// 运行环境: Dart VM 或支持Dart的Web浏览器
void printPersonDetails({String name, int age, String city}) {
  print('Name: $name, Age: $age, City: $city');
}
// 使用 required 关键字的命名参数
void createProduct({required String name, required double price, String description}) {
  print('Product: $name, Price: $price${description == null ? '' : ', Desc: $description'}');
}
void main() {
  // 调用 printPersonDetails 函数
  printPersonDetails(name: 'Charlie', age: 30, city: 'New York');
  printPersonDetails(age: 25, name: 'David'); // 顺序不重要,city 将为 null
  printPersonDetails(city: 'London', name: 'Eve'); // age 将为 null
  printPersonDetails(); // 所有参数都为 null
  // 调用 createProduct 函数
  createProduct(name: 'Laptop', price: 1200.0);
  createProduct(name: 'Keyboard', price: 75.0, description: 'Mechanical keyboard');
  // 注意:createProduct(price: 50.0); // 错误:必须提供 name
}
  • 注意事项:
    • 命名可选参数的默认值为null,除非提供了默认值或用required关键字标记。
    • 在Dart 2.12+版本中,使用required关键字可以强制要求调用者提供命名参数。

定位可选参数

  • 理论: 定位可选参数用方括号[]包裹。它们必须出现在必需参数之后。调用函数时,如果不提供这些参数,它们的值默认为null。它们的顺序与定义时一致。
  • 语法:
返回类型 函数名(类型 必需参数1, [类型 可选参数1, 类型 可选参数2]) { ... }
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

void printLocation(String country, [String state, String city]) {
  print('Country: $country');
  if (state != null) {
    print('State: $state');
  }
  if (city != null) {
    print('City: $city');
  }
}

void main() {
  // 调用 printLocation 函数
  printLocation('USA'); // state 和 city 为 null
  printLocation('USA', 'California'); // city 为 null
  printLocation('USA', 'California', 'San Francisco');
  // 注意:printLocation('USA', 'San Francisco'); // 错误:会把 'San Francisco' 赋给 state
}
  • 注意事项:
    • 定位可选参数必须在所有必需参数之后。
    • 定位可选参数在调用时,其位置决定了其对应关系。
    • 定位可选参数的默认值为null,除非显式提供了默认值。

默认参数值

  • 理论: 您可以为可选参数(无论是命名还是定位)指定默认值。如果调用者不提供该参数,则使用默认值。
  • 语法:
// 命名可选参数带默认值
返回类型 函数名({类型 参数名1 = 默认值, 类型 参数名2 = 默认值}) { ... }
// 定位可选参数带默认值
返回类型 函数名([类型 参数名1 = 默认值, 类型 参数名2 = 默认值]) { ... }

注意: 默认值必须是编译时常量。

  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

// 命名可选参数带默认值
void configureApp({String theme = 'light', int fontSize = 16}) {
  print('App Theme: $theme, Font Size: $fontSize');
}

// 定位可选参数带默认值
void sendMessage(String message, [String recipient = 'Everyone', int priority = 1]) {
  print('Message: "$message" to $recipient with priority $priority');
}

void main() {
  // 命名可选参数调用
  configureApp(); // 使用所有默认值
  configureApp(theme: 'dark'); // 覆盖 theme,fontSize 使用默认值
  configureApp(fontSize: 18, theme: 'blue'); // 覆盖所有参数

  print('\n---');

  // 定位可选参数调用
  sendMessage('Meeting at 3 PM'); // recipient 和 priority 使用默认值
  sendMessage('Urgent task', 'Team Lead'); // priority 使用默认值
  sendMessage('Feedback needed', 'Product Team', 5); // 覆盖所有可选参数
}
  • 注意事项:
    • 默认值有助于减少函数重载的需求,并使API更易于使用。
    • 默认值只能是编译时常量(如数字、字符串、bool、null,或常量表达式)。

匿名函数与箭头函数

  • 理论:
    • 匿名函数: 没有名字的函数。它们常用于将函数作为参数传递给其他函数,或者在需要一个简单函数而又不想单独命名时使用。
    • 箭头函数: 特指只有一个表达式体的匿名函数。它是匿名函数的一种简写形式。
  • 语法:
// 匿名函数 (lambda)
(参数列表) {
  // 函数体
  return 表达式; // 如果有返回值
};

// 箭头函数 (匿名函数的简写形式)
(参数列表) => 表达式;
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

void main() {
  // 1. 匿名函数示例 (作为变量赋值)
  var printMessage = (String msg) {
    print('Anonymous Message: $msg');
  };
  printMessage('Hello from anonymous function!'); // 调用匿名函数

  // 2. 匿名函数作为参数传递 (常见于列表的 forEach 方法)
  List<int> numbers = [1, 2, 3, 4, 5];
  numbers.forEach((number) {
    print('Number: $number');
  });

  // 3. 箭头函数示例 (作为变量赋值)
  var multiply = (int a, int b) => a * b;
  print('Product: ${multiply(6, 7)}');

  // 4. 箭头函数作为参数传递 (常见于列表的 map, where, sort 方法)
  var doubledNumbers = numbers.map((number) => number * 2).toList();
  print('Doubled numbers: $doubledNumbers'); // [2, 4, 6, 8, 10]

  var evenNumbers = numbers.where((number) => number % 2 == 0).toList();
  print('Even numbers: $evenNumbers'); // [2, 4]
}
  • 注意事项
    • 匿名函数非常灵活,是Dart函数式编程风格的基础。
    • 箭头函数只能包含一个表达式作为函数体。

高阶函数

  • 理论: 高阶函数是指满足以下至少一个条件的函数
    1. 接受一个或多个函数作为参数。
    2. 返回一个函数。 Dart中的函数是“一等公民”,这使得高阶函数非常容易实现,并且在处理集合(如ListMap)时非常有用。
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

// 1. 接受一个函数作为参数
void executeOperation(int a, int b, Function(int, int) operation) {
  print('Result: ${operation(a, b)}');
}

// 2. 返回一个函数
Function(int) makeMultiplier(int factor) {
  return (int number) => number * factor;
}

void main() {
  // 示例 1: 接受函数作为参数
  executeOperation(10, 5, (a, b) => a + b); // 传递一个匿名加法函数
  executeOperation(10, 5, (a, b) => a - b); // 传递一个匿名减法函数

  // 定义一个独立的函数作为参数传递
  int customMultiply(int x, int y) {
    return x * y;
  }
  executeOperation(4, 3, customMultiply);

  print('\n---');

  // 示例 2: 返回一个函数
  var doubler = makeMultiplier(2);  // doubler 现在是一个接受 int 并返回 int * 2 的函数
  var tripler = makeMultiplier(3);  // tripler 现在是一个接受 int 并返回 int * 3 的函数

  print('Doubled 5: ${doubler(5)}');   // 输出 10
  print('Tripled 5: ${tripler(5)}');   // 输出 15
}
  • 注意事项:
    • 高阶函数是函数式编程的核心概念,可以使代码更加简洁、模块化和可读。
    • Dart中的List.forEach, List.map, List.where, Future.then等都是高阶函数的典型应用。

函数作为一等公民

  • 理论: 在Dart中,函数被视为“一等公民(first-class objects)”。这意味着函数可以像普通变量一样被赋值给变量、作为参数传递给其他函数、或者作为其他函数的返回值。
  • 示例: 前面的高阶函数示例已经充分展示了函数作为一等公民的特性。这里再举一个简单例子。
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

// 定义一个普通函数
int addNumbers(int a, int b) {
  return a + b;
}

void main() {
  // 1. 函数可以赋值给变量
  var calculator = addNumbers; // calculator 现在是一个引用了 addNumbers 函数的变量
  print('Using calculator: ${calculator(10, 20)}'); // 调用通过变量引用的函数

  // 2. 函数可以存储在数据结构中 (例如 List 或 Map)
  List<Function> operations = [];
  operations.add(addNumbers);
  operations.add((a, b) => a - b); // 添加一个匿名函数

  print('First operation: ${operations[0](5, 2)}'); // 调用列表中的第一个函数
  print('Second operation: ${operations[1](5, 2)}'); // 调用列表中的第二个函数
}
  • 注意事项:
    • “一等公民”是编程语言中一个非常重要的特性,它赋予了函数极大的灵活性。
    • 理解这一特性有助于更好地使用Dart的异步编程(Future, Stream)和集合方法。

闭包

  • 理论: 闭包(Closure)是一个函数(可以是一个匿名函数)以及它被创建时所能访问的“包围”它的词法作用域中的变量的组合。这意味着即使外部函数已经执行完毕,闭包仍然可以访问和操作外部作用域中的变量。
  • 示例:
// SDK 版本: Dart 2.12.0 或更高版本
// 运行环境: Dart VM 或支持Dart的Web浏览器

// 外部函数
Function counterGenerator() {
  int count = 0; // 这个变量属于 counterGenerator 的词法作用域

  // 返回一个匿名函数,这个匿名函数就是闭包
  return () {
    count++; // 闭包访问并修改了外部函数的 count 变量
    return count;
  };
}

void main() {
  // 创建第一个计数器闭包
  var counter1 = counterGenerator();
  print('Counter 1: ${counter1()}'); // 1
  print('Counter 1: ${counter1()}'); // 2
  print('Counter 1: ${counter1()}'); // 3

  // 创建第二个独立的计数器闭包
  var counter2 = counterGenerator();
  print('Counter 2: ${counter2()}'); // 1 (独立的 count 变量)
  print('Counter 1: ${counter1()}'); // 4 (counter1 自己的 count 继续递增)
}
  • 注意事项:
    • 闭包允许您创建具有“私有”状态的函数,因为外部函数的局部变量只能被内部闭包访问。
    • 在事件处理、回调函数、以及实现某些设计模式(如工厂模式)中,闭包非常有用。
    • 重要概念: 闭包记住的是它被定义时的环境,而不是执行时的环境。