系统化掌握Dart编程之函数

937 阅读7分钟

image.png

前言

函数 —— 编程世界的多功能工具

image.png

函数就像是一个工具箱里的工具。你有一把螺丝刀(函数),它有一个特定的任务——拧螺丝(执行特定代码)。每次你需要拧螺丝时,你不需要重新发明或制造一把新的螺丝刀,而是直接使用这把现成的螺丝刀。

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 提供了许多内置的高阶函数,如 mapwherereduce,用于处理集合数据。

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、异步操作

异步编程中,允许编写非阻塞的代码。使用 asyncawait 关键字可以使异步代码看起来像同步代码一样简单。

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 提供了 Futureasync/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、终止条件:当 n01 时,递归终止
  • 3、回溯计算:每次递归调用的结果被用来计算上一层的值,直到最初的调用完成。

十、总结

Dart 中的函数是一个强大且灵活的工具,支持多种特性,包括参数管理匿名函数闭包高阶函数异步编程等。通过合理利用这些特性,有助于我们编写出更加简洁高效易维护及扩展的代码。

码字不易,记得 关注 + 点赞 + 收藏 + 评论