前言
函数 —— 编程世界的多功能工具
函数就像是一个工具箱里的工具。你有一把螺丝刀(函数),它有一个特定的任务——拧螺丝(执行特定代码)。每次你需要拧螺丝时,你不需要重新发明或制造一把新的螺丝刀,而是直接使用这把现成的螺丝刀。
在Dart中,函数是一等公民,它们能被保存在变量中,能作为参数传递及作为函数的返回值。与所有Dart运行的值一样,函数同样是对象。
操千曲而后晓声,观千剑而后识器。虐它千百遍方能通晓其真意。
一、函数的基本概念
1.1、定义
在Dart中,函数是用于执行特定任务的可重复使用的代码块。一个函数由函数签名和函数体组成。
- 1、函数签名:
- 返回类型:指定函数返回值的
数据类型。 - 函数名:标识函数的
名字。 - 参数列表:传递给函数的
变量列表,可以为空。
- 返回类型:指定函数返回值的
- 2、函数体:
- 包含实际执行的
代码块,可以有多条语句。 - 可以使用
return语句返回值。
- 包含实际执行的
1.2、基本语法
返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
// 函数体
// 可以有多条语句
// 可以使用 return 语句返回值
}
二、函数的参数
2.1、必须参数
必需参数是调用函数时必须提供的参数。
void greet(String name) {
print('Hello, $name!');
}
greet('Alice'); // 输出: Hello, Alice!
2.2、命名参数
- 1、命名参数可以是
必填的或可选的。 - 2、可选命名参数必须排列在一起放置在
参数列表尾部并用{}包裹,并且按名称传递,可以指定默认值但必须是编译时常量。 - 3、任意必填参数都必须
出现在命名参数前面。
void describePerson(String name, {int? age, String occupation = 'unknown'}) {
print('Name: $name, Age: $age, Occupation: $occupation');
}
describePerson('Bob', occupation: 'Engineer', age: 30);
// 输出: Name: Bob, Occupation: Engineer, Age: 30
describePerson('Charlie', occupation: 'Artist');
// 输出: Name: Charlie, Occupation: Artist, Age: null
2.3、位置参数
- 1、位置参数可以是
必填的或可选的。 - 2、可选位置参数必须排列在一起放置在
参数列表尾部并用[]包裹,并且按位置传递,可以指定默认值但必须是编译时常量。 - 3、任意必填参数都必须
出现在可选参数前面。
void describePerson(String name, [String occupation = 'unknown', int? age]) {
print('Name: $name, Occupation: $occupation, Age: $age');
}
describePerson('David', 'Developer', 25);
// 输出: Name: David, Occupation: Developer, Age: 25
describePerson('Eve');
// 输出: Name: Eve, Occupation: unknown, Age: null
2.4、默认参数
可为命名参数和位置参数设置默认值,当未指定参数时,使用函数中指定的默认值。
void describePerson(String name, {String occupation = 'unknown', int age = 0}) {
print('Name: $name, Occupation: $occupation, Age: $age');
}
describePerson('Frank');
// 输出: Name: Frank, Occupation: unknown, Age: 0
三、函数的返回值
3.1、无返回值
无返回值时返回类型为void。
void greet(String name) {
print('Hello, $name!');
}
3.2、返回单一值
函数通过 return 关键字返回一个值。如果没有显式返回值,默认返回 null。
int add(int a, int b) {
return a + b;
}
print(add(3, 4)); // 输出: 7
3.3、返回多个值
虽然 Dart 不直接支持返回多个值,但可以通过返回列表或映射来实现类似的效果。
List<int> addAndMultiply(int a, int b) {
return [a + b, a * b];
}
var result = addAndMultiply(3, 4);
print('Sum: ${result[0]}, Product: ${result[1]}'); // 输出: Sum: 7, Product: 12
四、匿名函数
4.1、定义
匿名函数是没有名字的函数,通常用于作为参数传递给其他函数或立即执行。它们非常适合用于一次性的任务或回调函数。
void executeFunction(void Function() func) {
func();
}
executeFunction(() {
print('This is an anonymous function.');
});
4.2、箭头语法
对于只有一行代码的匿名函数,Dart 提供了更简洁的箭头语法(=>),它自动返回表达式的值。
/// 使用箭头语法
var add = (int a, int b) => a + b;
print(add(3, 4)); // 输出: 7
4.3、参数
匿名函数可以接受参数,并且这些参数可以在函数体内使用。
// 带参数的匿名函数
void greetPeople(List<String> names, void Function(String) greet) {
for (var name in names) {
greet(name);
}
}
greetPeople(['Alice', 'Bob'], (name) {
print('Hello, $name!');
});
4.4、作为返回值
匿名函数还可以作为另一个函数的返回值,这在构建高阶函数时非常有用。
// 返回匿名函数
Function createMultiplier(int multiplier) {
return (int number) => number * multiplier;
}
var doubleIt = createMultiplier(2);
print(doubleIt(5)); // 输出: 10
五、闭包
5.1、定义
闭包是指一个函数对象,它可以记住并访问其创建时的外部作用域中的变量,即使这个函数在其外部作用域之外被调用。换句话说,闭包“捕获”了其定义时的环境状态。
// 创建一个闭包
Function makeCounter() {
var count = 0; // 外部作用域中的变量
return () {
count++; // 访问并修改外部变量
print(count);
};
}
var counter = makeCounter();
counter(); // 输出: 1
counter(); // 输出: 2
counter(); // 输出: 3
5.2、捕获外部变量
闭包的一个关键特性是它可以捕获并保存外部作用域中的变量。这意味着即使外部函数已经返回,闭包仍然可以访问这些变量。
// 捕获多个外部变量
Function createAdder(int base) {
return (int addend) => base + addend;
}
var addTen = createAdder(10);
print(addTen(5)); // 输出: 15
print(addTen(8)); // 输出: 18
5.3、闭包与匿名函数
匿名函数可以成为闭包,当它们引用了外部作用域中的变量时。这种组合使得代码更加简洁和强大。
// 匿名函数作为闭包
void executeWithDelay(void Function() action, int delay) {
Future.delayed(Duration(seconds: delay), action);
}
executeWithDelay(() {
print('Executed after delay');
}, 2); // 两秒后输出: Executed after delay
5.4、应用场景
- 1、回调函数:在
异步操作、事件处理等场景中,闭包常用于定义回调函数。
Future<void> fetchData() async {
print('Fetching data...');
await Future.delayed(Duration(seconds: 2));
print('Data fetched.');
}
void main() async {
fetchData().then((_) {
print('Processing data...');
});
}
- 2、高阶函数:闭包可以作为
参数传递给高阶函数,或者作为高阶函数的返回值。
void performOperation(int a, int b, Function operation) {
print(operation(a, b));
}
performOperation(3, 4, (x, y) => x + y); // 输出: 7
- 3、数据封装:闭包可以用来实现
私有变量和方法,因为外部无法直接访问闭包内部的变量。
class Counter {
int _count = 0;
void Function() get increment => () {
_count++;
print(_count);
};
}
void main() {
var counter = Counter();
var increment = counter.increment;
increment(); // 输出: 1
increment(); // 输出: 2
}
六、高阶函数
6.1、定义
高阶函数是指可以接受函数作为参数或返回值的函数。这种特性使得代码更加简洁和抽象。
void performOperation(int a, int b, Function operation) {
print(operation(a, b));
}
performOperation(3, 4, (x, y) => x + y); // 输出: 7
performOperation(3, 4, (x, y) => x * y); // 输出: 12
6.2、内置高阶函数
Dart 提供了许多内置的高阶函数,如 map、where 和 reduce,用于处理集合数据。
List<int> numbers = [1, 2, 3, 4, 5];
// 使用 map 将每个元素加倍
var doubled = numbers.map((n) => n * 2).toList();
print(doubled); // 输出: [2, 4, 6, 8, 10]
// 使用 where 过滤偶数
var evenNumbers = numbers.where((n) => n % 2 == 0).toList();
print(evenNumbers); // 输出: [2, 4]
// 使用 reduce 计算总和
var sum = numbers.reduce((sum, element) => sum + element);
print(sum); // 输出: 15
七、异步函数
7.1、异步操作
异步编程中,允许编写非阻塞的代码。使用 async 和 await 关键字可以使异步代码看起来像同步代码一样简单。
Future<void> fetchData() async {
print('Fetching data...');
await Future.delayed(Duration(seconds: 2));
print('Data fetched.');
}
void main() async {
fetchData();
print('Doing other work...');
}
7.2、异步流
Stream 类型用于处理一系列异步事件,如文件读取或网络请求。
import 'dart:async';
void listenToStream() {
Stream<int> stream = Stream.periodic(Duration(seconds: 1), (count) => count)
.take(5);
stream.listen((value) {
print('Received value: $value');
}, onDone: () {
print('Stream completed.');
});
}
void main() {
listenToStream();
}
八、回调函数
8.1、定义
回调函数是一个作为参数传递给另一个函数的函数,并在这个函数执行过程中或之后被调用。它允许你指定一段代码,在特定条件满足时执行。回调函数通常用于异步操作、事件处理和数据处理中。
示例代码:
// 简单的回调函数示例
void executeFunction(void Function() callback) {
print('Executing function...');
callback(); // 调用回调函数
}
executeFunction(() {
print('Callback executed!');
});
实际用途
- 1、异步操作:在操作完成后再执行某些代码。
- 2、事件处理:响应用户交互或其他事件。
- 3、数据处理:对数据进行后续处理或验证。
8.2、作为参数传递
可以将一个函数作为参数传递给另一个函数,这样可以在需要的时候调用这个函数。
// 定义一个接受回调函数的函数
void fetchData(String url, void Function(String) onData) {
// 模拟网络请求
print('Fetching data from $url...');
String data = 'Some data';
onData(data); // 当数据准备好时调用回调函数
}
// 使用回调函数处理数据
fetchData('https://example.com', (data) {
print('Received data: $data');
});
8.3、异步操作中的回调
在异步操作中,回调函数通常用于处理操作完成后的工作。Dart 提供了 Future 和 async/await 来简化异步编程。
import 'dart:async';
// 模拟异步操作
Future<void> fetchDataAsync(String url, void Function(String) onData) async {
print('Fetching data from $url...');
await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟
String data = 'Some data';
onData(data);
}
void main() async {
fetchDataAsync('https://example.com', (data) {
print('Received data: $data');
});
}
8.4、错误处理
回调函数不仅可以处理成功的情况,还可以处理错误。通过传递多个回调函数来分别处理成功和失败的情况。
void fetchDataWithErrorHandling(
String url, void Function(String) onData, void Function(String) onError) {
print('Fetching data from $url...');
bool success = false; // 模拟失败情况
if (success) {
String data = 'Some data';
onData(data);
} else {
String error = 'Failed to fetch data';
onError(error);
}
}
void main() {
fetchDataWithErrorHandling(
'https://example.com',
(data) => print('Received data: $data'),
(error) => print('Error: $error'),
);
}
九、递归及递归函数
9.1、概述
- 1、递归:
- 是一种解决计算问题的方法,其中解决方案
取决于同一类问题的更小子集。
- 是一种解决计算问题的方法,其中解决方案
- 2、递归函数:
- 是指
函数调用自身的函数。递归函数必须有一个明确的终止条件,否则会导致无限递归。
- 是指
- 3、详细说明:
- 若每个函数对应一种解决方案, 自己调用自己意味着
解决方案是一致的(有规律的)。 - 每次调用,函数处理的数据会较上次
缩减(子集),而且最后会缩减至无需递归。 内层函数调用(子集处理)完成,外层函数才能算调用完成。- 深入最里层叫做
递 - 从最里层出来叫做
归 - 在递的过程中,外层函数内的
局部变量(以及方法参数)并未消失,归的时候可以用到。
- 若每个函数对应一种解决方案, 自己调用自己意味着
9.2、代码示例
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
void main() {
int num = 5;
print('Factorial of $num is ${factorial(num)}');
}
//详细执行流程说明
//1.初始执行
int factorial(int 5) {
if (n == 0 || n == 1) {
return 1;
} else {
//2.第一次递归调用
return 5*factorial(int 4) {
if (n == 0 || n == 1) {
return 1;
} else {
//3.第二次递归调用
return 4*factorial(int 3) {
if (n == 0 || n == 1) {
return 1;
} else {
//4.第三次递归调用
return 3*factorial(int 2) {
if (n == 0 || n == 1) {
return 1;
} else {
//5.第四次递归调用
return 2*factorial(int 1) {
if (n == 0 || n == 1) {
return 1;
} else {
return n* factorial(n-1);
}
}
}
}
}
}
}
}
}
}
9.3、详细执行说明
-
1、初始调用:
factorial(5); -
2、第一次递归调用:
return 5 * factorial(4); -
3、第二次递归调用:
return 4 * factorial(3); -
4、第三次递归调用:
return 3 * factorial(2); -
5、第四次递归调用:
return 2 * factorial(1); -
6、基本终止条件:
return 1; -
7、回溯计算:
-
factorial(2)返回 2 * 1 = 2。 -
factorial(3)返回 3 * 2 = 6。 -
factorial(4)返回 4 * 6 = 24。 -
factorial(5)返回 5 * 24 = 120。
-
-
8、最终输出:
Factorial of 5 is 120
9.4、小结
- 1、递归函数:
factorial函数通过递归调用自身来计算阶乘。 - 2、终止条件:当
n为0或1时,递归终止。 - 3、回溯计算:每次递归调用的结果被用来计算上一层的值,直到最初的调用完成。
十、总结
Dart 中的函数是一个强大且灵活的工具,支持多种特性,包括参数管理、匿名函数、闭包、高阶函数和异步编程等。通过合理利用这些特性,有助于我们编写出更加简洁、高效、易维护及扩展的代码。
码字不易,记得 关注 + 点赞 + 收藏 + 评论