Void-7's

Dart基础:函数、运算符,抽象类等

Flutter

这里补充上之前的一些知识:

命名参数

sayHello({String name}) {
  print("hello, my name is $name");
}

sayHello2({name: String}) {
  print("hello, my name is $name");
}

main() {
  // 打印 hello, my name is zhangsan
  sayHello(name: 'zhangsan');

  // 打印 hello, my name is wangwu
  sayHello2(name: 'wangwu');
}

可以看到,定义命名参数时,你可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。

命名参数的参数并不是必须的,所以上面的代码中,如果调用sayHello()不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null,在Flutter开发中,你可以使用@required注解来标识一个命名参数,这代表该参数是必须的,不传则会报错,比如下面的代码:

const Scrollbar({Key key,  Widget child})

@required 注解定义在 meta package 中,可以直接导入 package:meta/meta.dart 包使用。

eg:

import 'package:meta/meta.dart';

void say({ String x, var y = 0, var z}) {
  print("x:${x = (x != null) ? x : "hell null"}");
  print(y.toString()+' '+z.toString());
}

void main() {
  String n = 'yes!';
  say(x: n, z: 2);

  String y; //null
  say(x: y,y:5);
}
/* output:
x:yes!
0 2
x:hell null
5 null */

tip: 如果在@required处显示红色波浪错误,则记得在shell中flutter pub get(或者说先ctrl+shit+p打开vscode命令行新建一个dart的项目,会自动进行Pub get操作,这里我IDE用的是vscode

可选的位置参数

使用中括号[]括起来的参数是函数的Optional positional parameters,传参是可选的。可选位置参数只可以放在形参列表的最后:

functionName( param1, [ param2 = 233, param3,... ] )

另外,可以为命名参数或位置参数指定默认值(必须是编译时常量)。没有指定默认值的情况下默认值为 null

Flutter中“万物皆是对象”,包括函数

所以可以把函数赋值给变量或作为参数传递给另一个函数。

匿名函数/Lambda表达式/Closure闭包

大多数方法都是有名字的,比如 main()printElement()。你可以创建一个没有名字的方法,称之为 匿名函数。你可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。

下面代码定义了只有一个参数 item 且没有参数类型的匿名方法。List 中的每个元素都会调用这个函数,打印元素位置和值的字符串:

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

函数返回值

Dart中的函数返回值类型可以省略。所有函数都有返回值。若未指定return,函数返回值为null。

类型判断运算符

asisis! 运算符是在运行时判断对象类型的运算符。

如果你不确定这个对象类型是不是 T,请在转型前使用 is T 检查类型。

if (emp is Person) {
  // 类型检查
  emp.firstName = 'Bob';
}

你可以使用 as 运算符进行缩写:

(emp as Person).firstName = 'Bob';

tip:上述两种方式是有区别的:如果 emp 为 null 或者不为 Person 类型,则第一种方式将会抛出异常,而第二种不会。

赋值运算符

可以使用 = 来赋值,同时也可以使用 ??= 来为值为 null 的变量赋值。

// 将 value 赋值给 a (Assign value to a)
a = value;
// 当且仅当 b 为 null 时才赋值
b ??= value;

算术运算符中的~/是“除并取整"操作

关系运算符x.==(y) 将会返回值,这里不管有没有 y,即 y 是可选的。也就是说 == 其实是 x 中的一个方法,并且可以被重写。

条件表达式表达式 1 ?? 表达式 2:如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。与条件 ? 表达式1 :表达式2类似作用,可以替代if-else语句。

条件访问成员运算符 ?.左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar

严格意义上并非运算符而是语法的级联操作..):

querySelector('#confirm') // 获取对象 (Get an object).
  ..text = 'Confirm' // 使用对象的成员 (Use its members).
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

使用..调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用..调用这个对象的其他方法。上面的代码段与如下代码段等价:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

另外,级联操作可以嵌套。但在返回对象的函数中要谨慎使用避免错误。


类的成员方法

一个类的成员方法是一个函数,为这个类提供某些行为。上面的代码中已经有了一些类的成员方法的定义,这些定义方式跟Java很类似,你可以为某个类的成员变量提供getter/setter方法,如下代码:

class Rectangle {
  num left, top, width, height;

  // 构造方法传入left, top, width, height几个参数
  Rectangle(this.left, this.top, this.width, this.height);

  // right, bottom两个成员变量提供getter/setter方法
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

抽象类

使用关键字abstract标识类使其成为抽象类,其无法被实例化。抽象类常用于声明接口方法。若需要让抽象类同时可被实例化,可以将它定义为工厂构造函数。抽象类中可以有抽象方法非抽象方法,:

abstract class Doer {
  // 抽象方法,没有方法体,需要子类去实现
  void doSomething();
  // 普通的方法
  void greet() {
    print("hello world!");
  }
}

class EffectiveDoer extends Doer {
  // 实现了父类的抽象方法
  void doSomething() {
    print("I'm doing something...");
  }
}

隐式接口

每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:

// A person. The implicit interface contains greet().
// Person 类的隐式接口中包含 greet() 方法。
class Person {
  // _name 变量同样包含在接口中,但它只是库内可见的。
  final _name;

  // 构造函数不在接口中。
  Person(this._name);

  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一个实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是谁吗?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
  print(greetBob(Person('小芸')));
  print(greetBob(Impostor()));
}

如果需要实现多个类接口,可以使用逗号分割每个接口类:

class Point implements Comparable, Location {...}

继承一个类

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

重写类成员

子类可以重写父类的实例方法(包括 操作符)、 Getter 以及 Setter 方法。你可以使用 @override 注解来表示你重写了一个成员

noSuchMethod 方法

如果调用了对象上不存在的方法或实例变量将会触发 noSuchMethod 方法,你可以重写 noSuchMethod 方法来追踪和记录这一行为:

class A {
  // 除非你重写 noSuchMethod,否则调用一个不存在的成员会导致 NoSuchMethodError。
  
  void noSuchMethod(Invocation invocation) {
  print('你尝试使用一个不存在的成员:' +
  '${invocation.memberName}');
  }
}

枚举类型

每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。

使用关键字 enum 来定义枚举类型:

enum Color { red, green, blue }

可以使用枚举类的 values 方法获取一个包含所有枚举值的列表:

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

另外,可以在 Switch 语句中使用枚举,但是需要注意的是必须处理枚举值的每一种情况,即每一个枚举值都必须成为一个 case 子句,不然会出现警告。

使用 Mixin 为类添加功能

关于Mixin作用的简单理解,可以参考https://www.jianshu.com/p/06604077d843

需要注意的是mixin里有方法的具体实现,这样就区别于传统的抽象类了。这样可以避免接口的方法必须在子类实现从而导致的代码冗余。

需要注意的是,如果一个类mixin了不同个mixin类,这些mixin类包含同名函数,此时调用该同名函数会如何执行?

class S {
  fun()=>print('A');
}
mixin MA {
  fun()=>print('MA');
}
mixin MB {
  fun()=>print('MB');
}
class A extends S with MA,MB {}
class B extends S with MB,MA {}

运行如下代码

main() {
    A a = A();
    a.fun();
    B b = B();
    b.fun();
}

输出:

MB
MA

这边就是Mixin线性化的问题,下篇会详细地写一下...