掌握如何让函数更好地调用

2024-08-27 14:32
文章标签 函数 调用 掌握 更好

本文主要是介绍掌握如何让函数更好地调用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一篇文章中我们从整体过了一遍 Dart 相关的基本语法,相信大家心中对 Dart 有了一个基本的认识。那么这篇文章来深入研究一下 Dart 的另一个重要语法:函数。这篇文章主要会涉及到:函数命名参数、可选参数、参数默认、闭包函数、箭头函数、Function 函数对象源码分析等。

1. 函数参数

在 Dart 函数参数是一个比较重要的概念,此外它涉及到概念的种类比较多,比如位置参数、命名参数、可选位置参数、可选命名参数等等。函数总是有一个所谓形参列表,虽然这个参数列表可能为空,比如getter 函数就是没有参数列表的。此外在 Dart 中函数参数大致可分为两种:位置参数和命名参数,来一张图理清它们的概念关系。

<image src="https://images.gitbook.cn/32e6c2e0-83d8-11ea-b6fa-0f8087ec57b4" width=100%>

1.1 位置参数

位置参数可以必需的,也可以是可选。

  • 无参数
//无参数类型:这是不带函数参数或者说参数列表为空
String getDefaultErrorMsg() => 'Unknown Error!';
//无参数类型-等价于上面函数形式,同样是参数列表为空
get getDefaultErrorMsg => 'Unknown Error!';
  • 必需位置参数
//必需位置参数类型:这里的 exception 是必需的位置参数
String getErrorMsg(Exception exception) => exception.toString();
  • 可选位置参数
//注意: 可选位置参数是中括号括起来表示,例如[String error]
String getErrorMsg([String error]) => error ?? 'Unknown Error!';
  • 必需位置参数和可选位置参数混合
//注意: 可选位置参数必须在必需位置参数的后面
String getErrorMsg(Exception exception, [String extraInfo]) => '${exception.toString()}---$extraInfo';
1.2 命名参数

命名参数始终是可选参数。为什么是命名参数,这是因为在调用函数时可以任意指定参数名来传参。

  • 可选命名参数
  //注意: 可选命名参数是大括号括起来表示,例如 {num a, num b, num c, num d}num add({num a, num b, num c, num d}) {return a + b + c + d;}//调用main() {print(add(d: 4, b: 3, a: 2, c: 1));//这里的命名参数就是可以任意顺序指定参数名传值,例如 d: 4, b: 3, a: 2, c: 1}
  • 必需位置参数和可选命名参数混合
//注意: 可选命名参数必须在必需位置参数的后面
num add(num a, num b, {num c, num d}) {return a + b + c + d;
}
//调用
main() {print(add(4, 5, d: 3, c: 1));//这里的命名参数就是可以任意顺序指定参数名传值,例如 d: 3, c: 1,但是必需参数必须按照顺序传参。
}

注意:可选位置参数和可选命名参数不能混合在一起使用,因为可选参数列表只能位于整个函数形参列表的最后。

void add7([num a, num b], {num c, num d}) {//非法声明,想想也没有必要两者一起混合使用场景。所以...
}
1.3 关于可选位置参数 [num a, num b] 和可选命名参数 {num a, num b} 使用场景

可能问题来了,啥时候使用可选位置参数,啥时候使用可选命名参数呢?

这里给个建议:

  • 首先,参数是非必需的也就是可选的,如果可选参数个数只有一个建议直接使用可选位置参数 [num a, num b];
  • 如果可选参数个数是多个的话建议用可选命名参数 {num a, num b}。因为多个参数可选,指定参数名传参对整体代码可读性有一定的增强。
1.4 参数默认值(针对可选参数)

首先,需要明确一点,参数默认值只针对可选参数才能添加的。可以使用 = 来定义命名和位置参数的默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为 null。

  • 可选位置参数默认值
num add(num a, num b, num c, [num d = 5]}) {//使用=来赋值默认值return a + b + c + d;
}
main() {print(add(1, 2, 3));//有默认值参数可以省略不传,实际上求和结果是:1 + 2 + 3 + 5(默认值)print(add(1, 2, 3, 4));//有默认值参数指定传入 4,会覆盖默认值,所以求和结果是:1 + 2 + 3 + 4
}
  • 可选命名参数默认值
num add({num a, num b, num c = 3, num d = 4}) {return a + b + c + d;
}
main() {print(add(100, 100, d: 100, c: 100));    
}

2. 匿名函数(闭包、lambda)

在 Dart 中可以创建一个没有函数名称的函数,这种函数称为“匿名函数”,或者 lambda 函数、闭包函数。但是和其他函数一样,它也有形参列表,可以有可选参数。

(num x) => x;//没有函数名,有必需的位置参数 x
(num x) {return x;}//等价于上面形式
(int x, [int step]) => x + step;//没有函数名,有可选的位置参数 step
(int x, {int step1, int step2}) => x + step1 + step2;没有函数名,有可选的命名参数 step1、step2
2.1 闭包在 Dart 中的应用

闭包函数在 Dart 用的特别多,单从集合中操作符来说就有很多。

main() {List<int> numbers = [3, 1, 2, 7, 12, 2, 4];//reduce 函数实现累加,reduce 函数中接收的 (prev, curr) => prev + curr 就是一个闭包print(numbers.reduce((prev, curr) => prev + curr));//还可以不用闭包形式来写,但是这并不是一个好的方案,不建议下面这样使用。plus(prev, curr) => prev + curr;print(numbers.reduce(plus));
}
//reduce 函数定义E reduce(E combine(E value, E element)) {//combine 闭包函数Iterator<E> iterator = this.iterator;if (!iterator.moveNext()) {throw IterableElementError.noElement();}E value = iterator.current;while (iterator.moveNext()) {value = combine(value, iterator.current);//执行 combine 函数}return value;}

3. 箭头函数

在 Dart 中还有一种函数的简写形式,那就是“箭头函数”。箭头函数是只能包含一行表达式的函数,会注意到它没有花括号,而是带有箭头的。箭头函数更有助于代码的可读性,类似于 Kotlin 或 Java 中的 lambda 表达式 -> 的写法。

main() {List<int> numbers = [3, 1, 2, 7, 12, 2, 4];print(numbers.reduce((prev, curr) {//闭包简写形式return prev + curr;}));print(numbers.reduce((prev, curr) => prev + curr)); //等价于上述形式,箭头函数简写形式
}

4. 局部函数

在 Dart 中还有一种可以直接定义在函数体内部的函数,可以把称为局部函数或者内嵌函数。我们知道函数声明可以出现顶层,比如常见的 main 函数等等。局部函数的好处就是从作用域角度来看,它可以访问外部函数变量,并且还能避免引入一个额外的外部函数,使得整个函数功能职责统一。

//定义外部函数 fibonacci
int fibonacci(int n) {//定义局部函数 lastTwoList<int> lastTwo(int n) {if(n < 1) {return <int>[0, 1];  } else {var p = lastTwo(n - 1);return <int>[p[1], p[0] + p[1]];}}return lastTwo(n)[1];
}

5. 顶层函数和静态函数

在 Dart 中有一种特别的函数,我们知道在面向对象语言中比如 Java,并不能直接定义一个函数的,而是需要定义一个类,然后在类中定义函数。但是在 Dart 中可以不用在类中定义函数,而是直接基于 dart 文件顶层定义函数,这种函数我们一般称为“顶层函数”。最常见就是 main 函数了。而静态函数就和 Java 中类似,依然使用 static 关键字来声明,然后必须是定义在类的内部的。

//顶层函数,不定义在类的内部
main() {print('hello dart');
}class Number {static int getValue() => 100;//static 修饰定义在类的内部。
}

6. main 函数

每个应用程序都有一个顶级的 main() 函数,它作为应用程序的入口点。main() 函数返回 void,所以在 Dart 可以直接省略 void,并有一个可选的列表参数作为参数。

//你一般看到的 main 是这样的
main() {print('hello dart');
}
//实际上它和 Java 类似可以带个参数列表
main(List<String> args) {print('hello dart: ${args[0]}, ${args[1]}');//用 dart command 执行的时候: dart test.dart arg0 arg1 =>输出:hello dart: arg0, arg1    
}

7. Function 函数对象

在 Dart 中一切都是对象,函数也不例外,函数可以作为一个参数传递。其中 Function 类是代表所有函数的公共顶层接口抽象类。Function 类中并没有声明任何实例方法。但是它有一个非常重要的静态类函数 apply。

该函数接收一个 Function 对象 function,一个 List 的参数 positionalArguments,以及一个可选参数 Map<Symbol, dynamic> 类型的 namedArguments。

大家似乎明白了什么?知道为啥 Dart 中函数支持位置参数和命名参数吗?

没错就是它们两个参数功劳。实际上,apply() 函数提供一种使用动态确定的参数列表来调用函数的机制,通过它我们就能处理在编译时参数列表不确定的情况。

abstract class Function {external static apply(Function function, List positionalArguments,[Map<Symbol, dynamic> namedArguments]);//可以看到这是 external 声明,我们需要找到对应的 function_patch.dart 实现int get hashCode;bool operator ==(Object other);
}

在 SDK 源码中找到 sdk/lib/_internal/vm/lib/function_patch.dart 对应的 function_patch 的实现:

@patch
class Function {// TODO(regis): Pass type arguments to generic functions. Wait for API spec.//可以看到内部私有的 _apply 函数,最终接收两个 List 原生类型的参数 arguments、names 分别代表着我们使用函数时//定义的所有参数 List 集合 arguments(包括位置参数和命名参数)以及命名参数名 List 集合 names,不过它是委托到 native 层的 Function_apply C++ 函数实现的。static _apply(List arguments, List names) native "Function_apply";@patchstatic apply(Function function, List positionalArguments,[Map<Symbol, dynamic> namedArguments]) {//计算外部函数位置参数的个数  int numPositionalArguments = 1 + // 默认同时会传入 function 参数,所以默认 +1(positionalArguments != null ? positionalArguments.length : 0);//位置参数的集合不为空就返回集合长度否则返回 0//计算外部函数命名参数的个数    int numNamedArguments = namedArguments != null ? namedArguments.length : 0;;//命名参数的集合不为空就返回集合长度否则返回 0//计算所有参数个数总和: 位置参数个数 + 命名参数个数int numArguments = numPositionalArguments + numNamedArguments;//创建一个定长为所有参数个数大小的 List 集合 argumentsList arguments = new List(numArguments);//集合第一个元素默认是传入的 function 对象arguments[0] = function;//然后从 1 的位置开始插入所有的位置参数到 arguments 参数列表中arguments.setRange(1, numPositionalArguments, positionalArguments);//然后再创建一个定长为命名参数长度的 List 集合List names = new List(numNamedArguments);int argumentIndex = numPositionalArguments;int nameIndex = 0;//遍历命名参数 Map 集合if (numNamedArguments > 0) {namedArguments.forEach((name, value) {arguments[argumentIndex++] = value;//把命名参数对象继续插入到 arguments 集合中names[nameIndex++] = internal.Symbol.getName(name);//并把对应的参数名标识存入 names 集合中});}return _apply(arguments, names);//最后调用_apply 函数传入所有参数对象集合以及命名参数名称集合}
}

不妨再来瞅瞅 C++ 层中的 Function_apply 的实现:

DEFINE_NATIVE_ENTRY(Function_apply, 0, 2) {const int kTypeArgsLen = 0;  // TODO(regis): Add support for generic function.const Array& fun_arguments =Array::CheckedHandle(zone, arguments->NativeArgAt(0));//获取函数的所有参数对象数组 fun_argumentsconst Array& fun_arg_names =Array::CheckedHandle(zone, arguments->NativeArgAt(1));//获取函数的命名参数参数名数组 fun_arg_namesconst Array& fun_args_desc = Array::Handle(zone, ArgumentsDescriptor::New(kTypeArgsLen, fun_arguments.Length(),fun_arg_names));//利用 fun_arg_names 生成对应命名参数描述符集合//注意:这里会调用 DartEntry 中的 InvokeClosure 函数,传入了所有参数对象数组 fun_arguments 和 fun_arg_names 生成对应命名参数描述符集合
//最后返回 resultconst Object& result = Object::Handle(zone, DartEntry::InvokeClosure(fun_arguments, fun_args_desc));if (result.IsError()) {Exceptions::PropagateError(Error::Cast(result));}return result.raw();
}

8. 总结

到这里有关 Dart 中的函数就说完,有了这篇文章相信大家对Dart 函数应该有个全面的了解了。下一篇文章我们将一起探讨有关 Dart 集合中的那些事。

这篇关于掌握如何让函数更好地调用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1111936

相关文章

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

一分钟带你上手Python调用DeepSeek的API

《一分钟带你上手Python调用DeepSeek的API》最近DeepSeek非常火,作为一枚对前言技术非常关注的程序员来说,自然都想对接DeepSeek的API来体验一把,下面小编就来为大家介绍一下... 目录前言免费体验API-Key申请首次调用API基本概念最小单元推理模型智能体自定义界面总结前言最

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

redis防止短信恶意调用的实现

《redis防止短信恶意调用的实现》本文主要介绍了在场景登录或注册接口中使用短信验证码时遇到的恶意调用问题,并通过使用Redis分布式锁来解决,具有一定的参考价值,感兴趣的可以了解一下... 目录1.场景2.排查3.解决方案3.1 Redis锁实现3.2 方法调用1.场景登录或注册接口中,使用短信验证码场

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学