函数
函数是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,除非提供了默认值或用
定位可选参数
- 理论: 定位可选参数用方括号
[]包裹。它们必须出现在必需参数之后。调用函数时,如果不提供这些参数,它们的值默认为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函数式编程风格的基础。
- 箭头函数只能包含一个表达式作为函数体。
高阶函数
- 理论: 高阶函数是指满足以下至少一个条件的函数
- 接受一个或多个函数作为参数。
- 返回一个函数。
Dart中的函数是“一等公民”,这使得高阶函数非常容易实现,并且在处理集合(如
List、Map)时非常有用。
- 示例:
// 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 继续递增)
}
- 注意事项:
- 闭包允许您创建具有“私有”状态的函数,因为外部函数的局部变量只能被内部闭包访问。
- 在事件处理、回调函数、以及实现某些设计模式(如工厂模式)中,闭包非常有用。
- 重要概念: 闭包记住的是它被定义时的环境,而不是执行时的环境。