序言

对于任何一个语言的程序员来说,在任意一门语言中定义一个函数都不会是一个难事,但是我们任然需要去学习dart的函数定义,不是因为我们不会,而是也许可以更好

基本语法

你所知道,或者不知道的函数定义方式
下面3个函数,干的都是一个事,但写法却有较大差异。

bool isEven(int a) {
  return a % 2 == 0;
}
isEven2(a) {
  return a % 2 == 0;
} 

isEven3(a) => a % 2 == 0;


isEven是最常规的写法,它明确的声明了参数类型,具有很强的类型约束。
isEven2省略了类型,包括入参和返回值,但他仍然可以在特定类型下正常运行。但不利于他人调用
isEven3在isEven2的基础上使用了速记语法又叫箭头语法,当方法体仅有一个表达式时,它可以看起来很简洁

在箭头语法中下面的写法是不可以的,箭头语法的方法体只能是表达式
在这里插入图片描述

函数的参数

函数的参数分为三类,参数可以有任意个required positional parameters(必须参数),后面可以有Named parametersoptional positional parameters,但不可以都有。
这是最常规的参数定义

int sum(int a, int b) {
  return a + b;
}

他的参数全是必须参数

Named parameters 命名参数

命名参数使用{param1, param2, …}的形式定义在这里插入图片描述

  • 可以指定默认值,
  • 也可以使用?定义可选参数,
  • 调用时必须使用参数名:值的方式指定参数值

如果你希望某个参数在被调用时必传,需要使用required关键字
在这里插入图片描述
被标记为required的参数,任然可以设置允许为空,此时调用时任然必穿,但可以传null

  printSum(a : 1, b: 2, c: null);


void printSum({int a = 0, int? b, required String? c}) {
  print('$a + $b = ${a + (b ?? 0)}');
}

在函数定义时,命名参数必须在必须参数之后定义,但在传参时可以在任何位置
在这里插入图片描述

Optional positional parameters 可选位置参数

可选位置参数使用[param1, param2, …]的形式声明,该参数必须设置默认值或者允许null

在这里插入图片描述
默认值只能是编译时常数。

The main() function

每个应用程序都必须有一个顶级main()函数,用作应用程序的入口点。main()函数返回void并具有可选的List参数作为参数,但是不可以同时存在2个main方法。
Dart中是不支持方法重载的。

void main() {
  print('hello world');
}

void main(List<String> args) {
  print(args);
}

Functions as first-class objects

函数也是个类对象
回顾一下我们之前用过的可迭代容器的foreach方法,他的参数就是函数类型

var list = <int>[];
list.forEach(action)

下面是官方foreach的实现代码
在这里插入图片描述
我们参照他的写法来实现类似的
在这里插入图片描述
这应该比较容易理解哈,我在kotlin就喜欢这么写,dart语法不太一样,但功能类似。

也可以将函数声明为一个变量
在这里插入图片描述
此时onClick的类型如下
在这里插入图片描述

Function types

在dart中可以使用函数类型,声明方式如下:

  setOnClickListener3(OnClickListener(onClick));
class OnClickListener {
  void Function(String param) onClick;
  OnClickListener(this.onClick);
}

void Function(String param) onClick;

在定义函数类型时,可以省略必选参数和可选位置参数的名称,但命名参数的名称不可省略
在这里插入图片描述

Anonymous functions 匿名函数

我们前面也用到了

  setOnClickListener3(OnClickListener((s, [a, b]) => print('$s $a')));

OnClickListener是个类对象,他的构造参数是函数类型,此时我们创建了一个匿名函数给他。

Lexical scope 词法范围

dart中函数也是对象,所以对象中可以有对象,函数中也可以有函数
在这里插入图片描述
在上面的例子中,我们在main函数中声明了print1,在print1中声明了print2, 在print2函数中我们可以调用所有父函数的局部变量,但在main函数中不可以跨层级调用print2函数。

Lexical closures闭包

简单来说,就是每个函数都是独立运行的,不受其他调用者的影响。
在这里插入图片描述

Tear-offs 方法抽取

直接看例子,文字已经不好说明了

var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();

在这里插入图片描述
下面是一个更偏向实际的例子

// 传统方式
button.onClick((event) {
  myHandler.handleClick(event);
});

// 使用 tear-off(更简洁)
button.onClick(myHandler.handleClick);

个人理解就是我们不需要造壳子。

Testing functions for equality 函数相等测试

  1. 顶层函数:相同函数总是相等
  2. 静态方法:相同静态方法总是相等
  3. 实例方法(闭包):
    相同实例的相同方法 → 相等
    不同实例的相同方法 → 不相等
void foo() {} // A top-level function(顶层函数)

class A {
  static void bar() {} // A static method(静态方法)
  void baz() {} // An instance method(实例方法)
}

void main() {
  Function x;

  // 1. 比较顶层函数
  x = foo;            // tear-off:获取函数引用
  assert(foo == x);   // ✅ 相等:相同的顶层函数
  
  // 2. 比较静态方法  
  x = A.bar;          // tear-off:获取静态方法引用
  assert(A.bar == x); // ✅ 相等:相同的静态方法
  
  // 3. 比较实例方法(闭包)
  var v = A(); // Instance #1 of A(实例1)
  var w = A(); // Instance #2 of A(实例2)
  var y = w;   // y 和 w 引用同一个实例(实例2)
  x = w.baz;   // tear-off:获取实例2的baz方法
  
  // 这些闭包引用相同的实例(实例2),所以相等
  assert(y.baz == x); // ✅ 相等:y 和 w 是同一实例
  
  // 这些闭包引用不同的实例,所以不相等
  assert(v.baz != w.baz); // ✅ 不相等:v 和 w 是不同的实例
}

Return values 返回值

在dart中所有函数都有返回值,void也算,未明确指定的情况下会返回null

foo() {}

assert(foo() == null);

可以返回多个值,通过record

(String, int) foo() {
  return ('something', 42);
}

Setter&Getter

每个属性访问(顶级、静态或实例)都是对getter或setter的调用。变量隐式创建一个getter,如果它是可变的,则创建一个setter。这就是为什么当您访问属性时,您实际上是在后台调用一个小函数。读取属性调用getter函数,编写属性调用setter函数,即使在属性被声明为变量的情况下也是如此。

如果我们想要为一个变量手动声明set or get方法,参考下面的代码

// Defines a variable `_secret` that is private to the library since
// its identifier starts with an underscore (`_`).
String _secret = 'Hello';

// A public top-level getter that
// provides read access to [_secret].
String get secret {
  print('Getter was used!');
  return _secret.toUpperCase();
}

// A public top-level setter that
// provides write access to [_secret].
set secret(String newMessage) {
  print('Setter was used!');
  if (newMessage.isNotEmpty) {
    _secret = newMessage;
    print('New secret: "$newMessage"');
  }
}

void main() {
  // Reading the value calls the getter.
  print('Current message: $secret');

  /*
  Output:
  Getter was used!
  Current message: HELLO
  */

  // Assigning a value calls the setter.
  secret = 'Dart is fun';

  // Reading it again calls the getter to show the new computed value
  print('New message: $secret');

  /*
  Output:
  Setter was used! New secret: "Dart is fun"
  Getter was used!
  New message: DART IS FUN
  */
}

如果你觉得上面的复杂,可以看看我自己写的

在这里插入图片描述
这段代码中name字段被定义为私有,禁止外部访问,通过set和get关键字定义了面向外部的set,get函数。
通过这样的方式,可以实现权限控制,参数有效性控制,内容二次加工等需求。

Generators

当你需要延迟生成一系列值时,可以考虑使用生成器函数。Dart内置支持两种生成器函数:

同步生成器:返回一个可迭代对象。 Iterable

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

异步生成器:返回一个流对象。Stream

Stream<int> naturalsToStream(int n) async* {
  int k = 0;
  while (k < n) {
    print(k);
    yield k++;
  }
}

Stream的使用方式
在这里插入图片描述
当递归生产值时,需要使用yield*

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

External functions 外部函数

指方法的申明和实现分开的,比如和其他语言互操作,有点类似java中调用c的native关键字。

声明方式如下

external void someFunc(int i);

小结

还是学到了不少东西哈。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐