Flutter-从入门到项目 05:Dart语法快速掌握(下)

2,721 阅读13分钟

Flutter 专题目录直通车: 这个目录方便大家快速查询你要学习的内容!!!

这一篇继续接 Flutter-从入门到项目 04:Dart语法快速掌握(上) 分析 , 从上一篇文章可以得出大家不太喜欢看语法相关类的文章. 但是没有关系 我还是继续写: 毕竟工欲善其事必先利其器 大家可以先收藏备用

⑦ 函数

Dart是一种面向对象语言,因此即使函数也是对象,也有一个类型 Function 。这意味着 函数可以赋值给变量,也可以作为参数传递给其他函数。您还可以像调用函数一样调用 Dart类 的实例。有关详细信息,请参见: callable-classes

// 函数 类型测试
// void 代表返回值类型
// funcFunc 代表函数名称
// () 参数
void funcFunc(){
}

// 还可以省略 : 没有声明类型也是可以的
funcFunc1(){
}
// 对于仅含有一个表达式的方法,你可以使用一种简写的语法:
funcFunc2() => print("=> 表达式;语法是{ return 表达式 }的简写");

注意:只有一个表达式能够在箭头符(=>)和分号(;)之间出现,语句是不可以这样使用的。比如,你不能把 if 语句放在这两个符号之间,但是一个三元运算符(?:)是可以的。

  • 一个函数可以有两种类型的参数:必要参数可选参数。所有的必要参数都应放在可选参数之前,当必要参数已经全部列出时,才能在后面加入可选参数。

  • 可选参数可以是可选位置参数或者可选命名参数,但不能既是可选位置参数又是可选命名参数。

  • 这两种可选参数都可以定义默认值但是默认值必须是编译时的常量,比如字面值。如果没有为之提供默认值,那么该参数的默认值将会是 null

/// 将 bold 和 hidden 作为你声明的参数的值
funcFunc3({bool bold, bool hidden}) {
  print('$bold,$hidden');  // true,null
}
// 调用
funcFunc3(bold: true);

/// 将 bold 和 hidden 作为你声明的参数的值 默认值分别是 false 和 true
funcFunc4({bool bold = false, bool hidden = true}) {
  print('$bold,$hidden');   // false,true
}
// 可选参数 默认值测试
funcFunc4();


// 可选的位置参数,用[]它们标记为可选的位置参数:
String funcFunc5(String person , String word, [String device]) {
  var result = "$person say : $word"; 
  if (device != null){
    result = "$person say : $device"; 
  }
  print(result);
  return result;
}
// 函数调用
funcFunc5("KC","和谐学习,不急不躁");  // KC say : 和谐学习,不急不躁
funcFunc5("KC","和谐学习,不急不躁","等风来不如追风去"); // KC say : 等风来不如追风去
  • 函数作为一个类对象的功能 -> 将一个函数作为参数传递给另一个函数。 函数式编程思想
// 函数式编程思想
funcFunc6(int num) => print(num);

funcFunc7(){
  var list = [1,2,3,4];
  list.forEach(funcFunc6);
  /**
      flutter: 1
      flutter: 2
      flutter: 3
      flutter: 4
   */
}
  • 匿名函数

大多数函数都能被命名为匿名函数,如 main()printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建 lambda闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。

一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。

下面的代码块包含函数的主体:

 ([[Type] param1[, …]]) { 
     codeBlock; 
  }; 

下面的示例定义了一个具有无类型参数的 匿名函数item,该函数被 list 中的每个 item` 调用,输出一个字符串,该字符串包含指定索引处的值。

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

⑧ 操作符

介绍符号
一元后缀符expr++ expr-- () [] .
一元前缀符-expr !expr ~expr ++expr --expr
乘法类型* / % ~/
加法类型+ -
位操作符<< >>
按位与&
按位异或
按位或|
比较和类型测试>= <= > < as is is!
等价== !=
逻辑与&&
逻辑或||
条件运算符expr1 ? expr2 : expr3
级联运算符..
赋值= *= /= /= ~/= %= += -= <<= >>= &= ^= |=

无论这些 操作符 还是 算数运算符等价和关系操作符类型测试操作符赋值运算符逻辑运算符位操作与移位运算符 与其他语言基本无异! 所以大家瞄一眼就OK

级联 : .. 允许你在单个对象的成员上执行多个操作,具体可见  

⑨ 控制流语句

  • if 和 else
  • for 循环
  • while 和 do-while循环
  • break和continue
  • switch和 case
  • assert

你也可以通过使用 try-catchthrow 来改变控制流,具体说明请见 异常 部分。

这里涉及的语法内容和我们iOS开发基本一致 , 如果你想熟练的同学,可以移步 Flutter 学习

🔟 异常语句

void excFunc(){
  try {
    print("KCFlutter");
  } on Exception catch (e) {
    // 任意一个异常
    print('来了异常: $e');
  } catch (e) {
    // 非具体类型
    print('非具体类型: $e');
  }
}

异常三部曲: throw 抛出异常 + catch 捕获异常 + finally 就是要执行

11 泛型

如果你在API文档寻找基本数组类型或者 List 类型,你将会看到该类型实际上为List<E>,其中<...>标记表示此表为一个泛型类型(或为参数化结构)—— 一种含有正规类型参数的类型。按照惯例,类型变量通常为单字符名称,例如 E,T,S,K,以及V。

  • 比如,如果你打算使用一个仅仅包含字符串的 List,你可以声明它为 List<String>(可理解为“字符串类型组成的List”),通过这种方式,你的程序员同事,以及你的工具(比如Dart编辑器和调试模式下的Dart虚拟机)能检测到将一个非字符串的变量分配到List中很可能是错误的,这里给出一个样例:
// 泛型
void genericsFunc(){
  var names = List<String>();
  names.addAll(['Hank', 'Cooci', 'CC']);
  // names.add(100); // 这里就会报错,因为通过泛型就确定了list 的内容的类型
}
  • 另外一个使用泛型的原因是 为了减少代码的重复泛型可以让你能共享多个类型的一个接口和实现方式 它在调试模式以及静态分析的错误预警中仍然很有优势。举个例子,当你在创建一个接口来缓存一个对象时:
// 泛型可以让你能共享多个类型的一个接口和实现方式,
// 它在调试模式以及静态分析的错误预警中仍然很有优势
abstract class KCObjectCache{
  Object getByKey(String key);
  setByKey(String key,Object value);
}
// 你发现你想要一个字符串专用的接口,所以你创建了另外一个接口:
abstract class KCStringCache{
  String getByKey(String key);
  setByKey(String key,String value);
}
// 接下来,你决定你想要一个这种接口的数字专用的接口...你想到了这个方法.
// 泛型类型可以减少你创建这些接口的困难。取而代之的是,你只需要创建一个带有一个类型参数的接口即可:
// 泛型接下来就牛逼了
abstract class Cache<T>{
  // 在这个代码中,T是一个替代类型,即占位符,你可以将他视为后续被开发者定义的类型。
  T getByKey(String key);
  setByKey(String key,T value);
}

12 类

这个内容也是最重要的额! 平时大家开发也是必须的内容,希望大家好好感受~~☺️

12.1 对象

  • Dart 是一种面向对象的语言,并且支持基于 mixin 的继承方式。
  • Dart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类--Object
  • 基于 mixin 的继承方式具体是指:一个类可以继承自多个父类。
// 类相关测试
class LGPerson {
  int age;
  String name;
  String hobby;
  double height;
}

void classFunc(){
  // 实例变量创建测试
  var person = LGPerson();
  person.age = 18;
  person.name = "Cooci";
  person.height = 182.0;
  person.hobby  = "iOS";

  print(person.runtimeType); // LGPerson
}
  • 使用 .(dot)来调用实例的变量或者方法。

  • 使用 ?. 来确认前操作数不为空, 常用来替代. , 避免左边操作数为 null 引发异常。

  • 使用 runtimeType 方法,在运行中获取对象的类型。该方法将返回 Type` 类型的

12.2 实例化变量(Instance variables)

  • 在类定义中,所有没有初始化的变量都会被初始化为null。

  • 类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法

  var person2 = LGPerson();
  person2.hobby = "Flutter";  // Use the setter method for hobby.
  print(person2.hobby);       // Use the getter method for hobby.
  print("age = ${person2.age}, name = ${person2.name}"); //age = null, name = null

12.3 构造函数(Constructors)

要声明一个构造函数,只需创建一个与类同名的方法(或者加上一个额外的标识符命名构造函数的描述)。构造函数最常见的形式,就是自动生成的构造函数,下面创建一个类的新实例:

class LGStudent {
  int age;
  String name;
  String hobby;
  double height;

  // LGStudent(int age, String name, String hobby){
  //   // height 没有构造赋值
  //   this.age   = age;
  //   this.name  = name;
  //   this.hobby = hobby;
  // }
  // this关键字指向了当前类的实例, 上面的代码可以简化为:
  LGStudent(this.age,this.name,this.hobby);
}

  // 构造函数
  var student1 = LGStudent(18, "KC", "构造函数");
  print("age = ${student1.age}, name = ${student1.name}"); // age = 18, name = KC
  • 如果你不声明一个构造函数,系统会提供默认构造函数。默认构造函数没有参数,它将调用父类的无参数构造函数。: LGStudent ()

  • 子类不继承父类的构造函数。子类只有默认构造函数。(无参数,没有名字的构造函数)。

  • 使用命名构造函数可以为一个类声明多个构造函数,或者说是提供额外的声明:

  // 命名构造函数
  LGStudent.fromMap(Map stuMap){
    age    = stuMap['age'];
    name   = stuMap['name'];
    hobby  = stuMap['hobby'];
    height = stuMap['height'];
  }

  // 初始化列表
  LGStudent.fromMaplist(Map stuMap):
        age = stuMap['age'],
        name = stuMap['name'],
        hobby = stuMap['hobby'],
        height = stuMap['height']{
    // age = 18, name = 酷C, hobby = 大师底层, height = 180.0
    print("age = $age, name = $name, hobby = $hobby, height = $height");
  }

  var stuMap   = {'age': 18,'name': '酷C','hobby': "大师底层",'height': 180.0,};
  var student2 = LGStudent.fromMap(stuMap);
  print("age = ${student2.age}, name = ${student2.name}"); // age = 18, name = 酷C

重定向构造函数

有时一个构造函数的目的只是重定向到同一个类中的另一个构造函数。如果一个重定向的构造函数的主体为空,那么调用这个构造函数的时候,直接在冒号后面调用这个构造函数即可。

  // this关键字指向了当前类的实例, 上面的代码可以简化为:
  LGStudent(this.age,this.name,this.hobby);
  // 重定向构造函数
  LGStudent.rediconstructor(int age, String name, String hobby) : this(age,
      "哭C", hobby);  //  : 后面就是调用上面的构造函数
  var student4 = LGStudent.rediconstructor(20, "KC", '185.');
  print("age = ${student4.age}, name = ${student4.name}, hobby = ${student4
      .hobby}"); // age = 20, name = 哭C, hobby = 185.

静态构造函数

如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。

// 静态构造函数
class LGTeacher{
  final num age;
  final String name;
  const LGTeacher(this.age, this.name);
  static final LGTeacher teacher = LGTeacher(300,"不急不躁");
}

  // 静态构造函数
  var teacher1 = LGTeacher(100, "和谐学习");
  // teacher1.age = 200; // Error: The setter 'age' isn't defined for the class 'LGTeacher'静态无法修改
  teacher1 = LGTeacher(200, "和谐学习");
  print("age = ${teacher1.age}, name = ${teacher1.name}"); //age = 200, name = 和谐学习

  var teacher2 = LGTeacher.teacher;
  // teacher2 = LGTeacher(200, "和谐学习"); // age = 200, name = 和谐学习
  print("age = ${teacher2.age}, name = ${teacher2.name}"); //age = 300, name = 不急不躁

工厂构造函数

当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象:

// 工厂构造函数
class LGCar{
  String name;
  // 普通构造函数
  LGCar.func(this.name);

  static final Map<String, LGCar> _cache = <String, LGCar>{};

  factory LGCar(String name){
    if (_cache.containsKey(name)){
      return _cache[name];
    }else{
      final car = LGCar.func(name);
      _cache[name] = car;
      return car;
    }
  }
}

  // 工厂构造函数
  var car = new LGCar("法拉利");
  print("name = ${car.name}"); // name = 法拉利

12.4 方法

实例方法 : 对象的实例方法可以访问实例变量和 this

setters 和 Getters

是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getterssetters 来创建附加属性,也就是直接使用 getset 关键词:

class LGRectangle {
  num left;
  num top;
  num width;
  num height;

  LGRectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算属性: right and bottom.
  num get right             => left + width;
  set right(num value)      => left = value - width;
  num get bottom            => top + height;
  set bottom(num value)     => top = value - height;
}

// 方法测试
void methodFunc(){
  var rectangle = LGRectangle(10, 20, 100, 300);
  print('right = ${rectangle.right}, bottom = ${rectangle.bottom}');// right = 110, bottom = 320

}

抽象方法

Instancegettersetter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体:

// 抽象方法
abstract class LGDoer {
  // ...定义实例变量和方法...
  void doSomething(); // 定义一个抽象方法。
}

class LGEffectiveDoer extends LGDoer {
  void doSomething() {
    // ...提供一个实现,所以这里的方法不是抽象的...
    print("hello word");
  }
}

12.5 重载操作符

你可以重写在下表中列出的操作符。例如,如果你定义了一个向量类,你可以定义一个 + 方法来加两个向量。

+|[]
> /^[]=
~/&~
>=*==
-%>>

以下是一个类中重写 +- 操作符的例子:

// 重载操作符
class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// Overrides + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// Overrides - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

void vectorFunc() {
  final v = new Vector(2, 3);
  final w = new Vector(2, 2);
  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);
  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);
  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}

如果你重写了 == ,你也应该重写对象中 hashCode 的 getter 方法。对于重写 == 和 hashCode 例子,参见实现 Implementing map keys 。

想要知道更多关于重载的信息,参见 扩展一个类 。

12.6 隐式接口

每个类隐式的定义了一个接口,含有类的所有实例和它实现的所有接口。如果你想创建一个支持类 B 的 API 的类 A,但又不想继承类 B ,那么,类 A 应该实现类 B 的接口。

一个类实现一个或更多接口通过用 implements 子句声明,然后提供 API 接口要求。例如:

// 隐式接口
// 一个 KCPerson ,包含 greet() 的隐式接口。
class KCPerson {
  // 在这个接口中,只有库中可见。
  final _name;
  // 不在接口中,因为这是个构造函数。
  KCPerson(this._name);
  // 在这个接口中。
  String greet(who) => 'Hello, $who. I am $_name.';
}

//  KCPerson 接口的一个实现。
class KCImposter implements KCPerson {
  // 我们不得不定义它,但不用它。
  final _name = "";
  String greet(who) => 'Hi $who. Do you know who I am?';
}

greetBob(KCPerson person) => person.greet('bob');

imposterFunc() {
  print(greetBob(new KCPerson('KC')));
  print(greetBob(new KCImposter()));
}

扩展一个类

使用 extends 创建一个子类,同时 supper 将指向父类:

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

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

子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法  noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。

class A {
  // 如果你不重写 noSuchMethod 方法, 就用一个不存在的成员,会导致 NoSuchMethodError 错误。
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member:' +
          '${mirror.memberName}');
  }
}

你可以使用 @override 注释来表明你重写了一个成员。

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。

@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

关于注释的更多信息,请参 元数据

12.7 枚举类型

枚举类型,通常被称为 enumerationsenums ,是一种用来代表一个固定数量的常量的特殊类。

声明一个枚举类型需要使用关键字 enum

enum LGColor {
  red,
  green,
  blue
}

在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

要得到枚举列表的所有值,可使用枚举的 values 常量。

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

你可以在  switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告:

// 枚举测试
enum LGColor{
  blue,
  green,
  orange
}

// 在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。
void enumFunc(){
  assert(LGColor.blue.index   == 0);
  assert(LGColor.green.index  == 1);
  assert(LGColor.orange.index == 2);

  List<LGColor> colors = LGColor.values;
  print(colors); // [LGColor.blue, LGColor.green, LGColor.orange]

  LGColor color1 = LGColor.blue;
  switch(color1) {
    case LGColor.blue:
      print("blue");
      break;
    case LGColor.green:
      print("green");
      break;
    default:
      print("orange");
  }
}

枚举类型有以下限制

  • 你不能在子类中混合或实现一个枚举。
  • 你不能显式实例化一个枚举。

更多信息,见 Dart Language Specification

13 参考文献

Flutter官方文档: https://flutterchina.club

Dart官方文档: https://dart.dev/guides/language/language-tour

极客学院团队: https://wiki.jikexueyuan.com/project/dart-language-tour/important-concepts.html

Dart语法学习: https://www.jianshu.com/p/9e5f4c81cc7d

Dart 语法: https://www.pipipi.net/dart/dart-syntax.html

下面给大家推荐一个非常好的iOS干货输出点: 逻辑iOS技术号. 共建iOS繁荣生态, 从你的小爱心💖开始