乐趣区

Flutter系列之Dart函数类与运算符

编程语言虽然千差万别,但归根结底,设计思想无非是表示信息与处理信息

在 Flutter 系列之 Dart 语言概述中已经介绍了 Dart 如何表示信息,本篇将介绍 Dart 是如何处理信息的

作为一门真正面向对象的编程语言,Dart 将处理信息的过程抽象为了对象,而函数、类与运算符则是抽象中最重要的手段

函数

函数是一段用来独立完成某个功能的代码片段,而 Dart 中所有类型都是对象类型,函数也不例外,即函数也是对象,它的类型为 Function。

void main() {
  Function check = isEmptyStr;
  printStr('',check);
  printStr('Hello World!',check);
}

bool isEmptyStr(String str){// 判断字符串是否为 null
  return str.isEmpty;
}

void printStr(String str,Function check){// 用 check 函数来判断 String 是否为 null
  if (check(str)) {print('$str is null');
  }else {print('$str is not null');
  }
}
 is null
Hello World! is not null

上面代码中,首先定义了一个判断字符串是否为 null 的函数 isEmptyStr,并把它传给了另一个打印输出的函数 printStr,即函数也可以被定义为变量,甚至可以作为参数传递给其他函数

日常开发中,经常会遇到一个函数中需要传递多个参数的情况,这种情况下 Dart 与其他语言如 c、c++、java 等的做法是不同的

c、c++、java 等的做法是提供函数的重载,即函数同名但参数不同,而 Dart 认为重载会导致混乱,因此不支持重载,而是提供了可选参数和可选命名参数

具体方式为,在申明函数时:

·给参数添加 {},以 paramName:value 的方式指定调用参数,即可选命名参数

·给参数加 [],则意味着这些参数可以忽略,即可选参数

不仅如此,在使用这两种方式定义函数时,还可以给参数设置默认值

注意:可选参数和可选命名参数不可一起使用,可选参数和可选命名参数在申明时需放在其他参数后面,且可选命名参数调用时与申明的顺序无关,但可选参数调用时与申明的顺序有关

void main() {_ptionalNamedParameter('Tom',2,sex:'男');
  _ptionalNamedParameter('Tom',2,sex:'男',age:5);
  _ptionalNamedParameter('Tom',2);
  _ptionalNamedParameter('Tom',2,age:25);
  _ptionalNamedParameter('Tom',2,age:18,sex:'女');

  _ptionalParameter('Tom',2,'男');
  _ptionalParameter('Tom',2,'男',5);
  _ptionalParameter('Tom',2);
  //_ptionalParameter('Tom',2,25);// 错误
  //_ptionalParameter('Tom',2,18,'女');// 错误
}

bool isNotEmptyStr(String str) {return str.isNotEmpty;}

void printStr(String str,Function check){// 用 check 函数来判断 String 是否为 null
  if (check(str)) {print('$str is null');
  }else {print('$str is not null');
  }
}

// 可选命名参数:在需要定义为可选命名参数时给参数加 {},并且可以给可选命名参数设置默认值,必须定义在其他参数后面,参数顺序可变,不可与可选参数同用
void _ptionalNamedParameter(String name, int classes, {String sex, int age = 18}) {print('_ptionalNamedParameter:name is $name,classes = $classes,sex is $sex,age = $age');
}

// 可选参数:在需要定义为可选参数时给参数加 [],并且可以给可选参数设置默认值,必须定义在其他参数后面,参数顺序不可变,不可与可选命名参数同用
void _ptionalParameter(String name, int classes, [String sex = '男', int age]) {print('_ptionalParameter:name is $name,classes = $classes,sex is $sex,age = $age');
}
_ptionalNamedParameter:name is Tom,classes = 2,sex is 男,age = 18
_ptionalNamedParameter:name is Tom,classes = 2,sex is 男,age = 5
_ptionalNamedParameter:name is Tom,classes = 2,sex is null,age = 18
_ptionalNamedParameter:name is Tom,classes = 2,sex is null,age = 25
_ptionalNamedParameter:name is Tom,classes = 2,sex is 女,age = 18
_ptionalParameter:name is Tom,classes = 2,sex is 男,age = null
_ptionalParameter:name is Tom,classes = 2,sex is 男,age = 5
_ptionalParameter:name is Tom,classes = 2,sex is 男,age = null

类是特定类型的数据和方法的集合,也是创建对象的模板

类的定义及初始化

Dart 是面向对象的语言,每个对象都是一个类的实例,都继承自顶层类型 Object

注意:Dart 中并没有 public、protected、private 等关键字,取而代之的是只要在申明变量和方法时,在其前面加上“_”即可作为 private 使用,如果不加“_”,则默认为 public。不过,“_”的限制范围并不是类访问级别的,而是库(package)访问级别的

void main() {Student student = new Student('Jack', '男', age:18);
  student.printStudentInfo();
  var s=Student('Tom', '男');// 省略 new 关键字
  s.printStudentInfo();
  Student.studentId = 1;
  Student.printStudentId();}

class Student {
  String name;
  String sex;
  int age;
  static int studentId = 0;

  // 语法糖,相当于在函数体内:this.name=name;this.sex=sex;this.age=age;
  Student(this.name, this.sex, {this.age});

  void printStudentInfo() => print('name is $name, sex is $sex, age is $age');

  static void printStudentId() => print('studentId is $studentId');
}
name is Jack, sex is 男, age is 18
name is Tom, sex is 男, age is null
studentId is 1

上面示例中,定义了三个成员变量,并通过构造函数语法糖对三个成员变量进行初始化操作;定义了一个静态成员变量,并在初始化时赋值默认值,并定义了两个方法分别打印三个成员变量及一个静态成员变量的值

有的时候类的实例化需要根据参数提供多种初始化方式。除了可选命名参数和可选参数之外,Dart 还提供了命名构造函数的方式,使得实例化过程语义更清晰

注意:Dart 支持初始化列表。在构造函数真正执行之前,可以先给实例变量赋值,甚至可以重定向之另一个构造函数

void main() {Student student = new Student.nameInfo('Tom');
  student.printStudentInfo();}

class Student {
  String name;
  String sex;
  int age;

  Student(this.name, this.sex) : age = 18; // 初始化变量 age

  Student.nameInfo(String name) : this(name, '男'); // 重定向构造函数

  void printStudentInfo() => print('name is $name, sex is $sex, age is $age');
}
name is Tom, sex is 男, age is 18

上面示例中,Student 中有两个构造函数:Student 和 Studnet.nameInfo,其中,Studnet.nameInfo 将成员变量的初始化重定向到了 Student 中,而 Student 则在初始化列表中为 age 赋值默认值

复用

在面向对象的编程语言中,将其他类的变量与方法纳入本类中进行复用的方式一般有两种:继承和接口,而 Dart 也是面向对象的编程语言,因此,在 Dart 中,复用的方式也是继承和接口

继承:子类由父类派生,会自动获取父类的成员变量和方法实现,子类可以根据需要覆写构造函数及父类方法

接口:子类获取到的仅仅是接口的成员变量符号和方法符号,需要重新实现成员变量,以及方法的申明和初始化,否则编译器会报错

以下示例会对继承和接口的区别进行说明

void main() {Android android = new Android();
  android
    ..brandName = '华为'
    ..phoneModel = 'P30 Pro'
    ..edition = 'EMUI9.1.1';// 级联运算符,相当于 android.brandName = '华为';android.phoneModel = 'P30 Pro';android.edition = 'EMUI9.1.1';android.printInfo();

  IOS ios = new IOS();
  ios
    ..phoneModel = 'iPhone XR'
    ..edition = 'iOS 12';// 级联运算符,相当于 phone.phoneModel = 'iPhone XR';phone.edition = 'iOS 12';ios.printInfo();}

class Phone {
  String phoneModel; // 手机型号
  String edition; // 手机版本

  void printInfo() =>
      print('This Phone phoneModel is $phoneModel, and edition is $edition');
}

//Android 继承自 Phone
class Android extends Phone {
  String brandName; // 品牌
  @override
  void printInfo() {
    // 覆写了 printInfo 实现
    print('This Phone brandName is $brandName, phoneModel is $phoneModel, and edition is $edition');
  }
}

//IOS 实现了 Phone 接口
class IOS implements Phone {
  // 成员变量需要重新申明
  String phoneModel;
  String edition;
  // 方法需要重新实现及初始化
  void printInfo() => print('This is IOS Phone, phoneModel is $phoneModel, and edition is $edition');
}
This Phone brandName is 华为, phoneModel is P30 Pro, and edition is EMUI9.1.1
This is IOS Phone, phoneModel is iPhone XR, and edition is iOS 12

以上代码为 Android 通过继承 Phone 的方式添加了成员变量,并覆写了 printInfo()的实现;IOS 通过接口实现的方式,覆写了 Phone 的变量定义及方法实现;而从以上代码也能看出,接口并没有为我们带来实际好处

注意:Dart 和其他编程语言一样,也不支持多继承

其实,Dart 除了继承和接口外,还给我们提供了另一种机制来实现类的复用,即“混入”(Mixin):混入鼓励代码重用,可以认为是具有实现方法的接口

要使用混入,只需要 with 关键字即可

void main() {IOS ios = new IOS();
  ios
    ..phoneModel = 'iPhone XR'
    ..edition = 'iOS 12';// 级联运算符,相当于 phone.phoneModel = 'iPhone XR';phone.edition = 'iOS 12';ios.printInfo();}

class Phone {
  String phoneModel; // 手机型号
  String edition; // 手机版本

  void printInfo() =>
      print('This Phone phoneModel is $phoneModel, and edition is $edition');
}

class IOS with Phone {}
This Phone phoneModel is iPhone XR, and edition is iOS 12

如上示例可知:通过混入,一个类里可以以非继承的方式使用其他类中的变量和方法,即通过混入,可以解决单继承问题

运算符

Dart 除了和其他大部分编程语言的运算符一样外,还提供了几个额外的运算符,用于简化处理变量实例缺失(null)的情况

?. 运算符:假设 Phone 类中有个 printInfo() 方法,phone 是 Phone 类的一个可能为 null 的实例,phone 调用成员方法的安全方法,可以简化为 phone.?printInfo(), 表示如果 phone 为 null,则跳过 printInfo() 方法

??= 运算符:如果变量 a 为 null,则给 a 赋值 value,负责跳过。a??=value

?? 运算符:如果 a 不为 null,返回 a 的值,否则返回 b 的值。a??b

void main() {
  Phone phone;
  phone?.printInfo();// 由于 phone 为 null,因此不会执行 printInfo() 方法
  Test();}

class Phone {
  String phoneModel; // 手机型号
  String edition; // 手机版本

  void printInfo() =>
      print('This Phone phoneModel is $phoneModel, and edition is $edition');
}

void Test(){
  int age;
  print('age=$age');
  print('age=${age??16}');//?? 运算符,age 为 null,返回 16
  age??=18;//??= 运算符,age 为 null,给 age 赋值为 18
  print('age=$age');
  print('age=${age??20}');//?? 运算符,age 不为 null,返回 18
  age??=22;//??= 运算符,age 不为 null,返回 18
  print('age=$age');
}
age=null
age=16
age=18
age=18
age=18

在 Dart 中,一切都是对象,连运算符也是对象成员方法的一部分

对于系统的运算符,一般情况下只支持基本数据类型和标准库中提供的类型。而对于用户自定义的类,如果想支持基本操作,比如比较大小、相加相减等,则需要用户自己来定义关于这个运算符的具体实现

Dart 提供了运算符的覆写机制,我们不仅可以覆写方法,还可以覆写或者自定义运算符

operator 是 Dart 的关键字,与运算符一起使用,表示一个类成员运算符方法

void main() {Vector v = new Vector(5, 3);
  Vector v2 = new Vector(1, 2);
  Vector v3 = new Vector(4, 1);
  Vector v4 = v - v2;
  print('x = ${v4.x}, y = ${v4.y}');
  print(v3 == (v - v2));
}

class Vector {
  num x, y;

  Vector(this.x, this.y);

  // 自定义相减运算符
  Vector operator -(Vector v) {return Vector(x - v.x, y - v.y);
  }

  // 覆写相等运算符
  bool operator ==(dynamic v) {return (x == v.x && y == v.y);
  }
}
x = 4, y = 1
true

文章已同步更新至微信公众号,欢迎关注

退出移动版