Dart入门之语法深入

1.流程控制语句

if语句
  • 根据特定表达式是否为true来有条件的执行另一个语句
  • if语句有两种形式:一种是带有else分支,另一种则是没有


for循环语句
  • 标准for循环:
    for(initializer; condition; expression) statement
  • 如果遍历对象实现了Iterable接口,则可以使用forEach()方法
  • List和Set等实现了Iterable接口的类还支持for-in形式的遍历
var collection = [0, 1, 2];
//for loop
for(int i=0; i<collection.length; ++i){
  print(collection[i]);
}

//forEach
collection.forEach((v)=> print(v));

//for-in
for(var x in collection){
  print(x);
}


while do-while语句
  • while循环在执行之前先判断条件是否满足
  • do-while循环限制性循环代码再判断条件
//while 
int x = 6;
while(x > 0){
  print(x--);
}

//do-while
int y=6;
do{
  print(y--);
}while(y > 0);


switch语句
  • switch语句提供了一种更方便的方法来实现深层次嵌套的if-else逻辑
  • 每个非空case语句必须有一个break语句,同时还可以通过continue、throw、return来结束非空case语句
  • 没有case语句匹配时,可以使用default语句匹配默认情况
  • case语句内可以有局部变量,仅当前语句内可见
String grade = 'B';
switch(grade){
  case 'A':
    print('优秀');
    break;
  case 'B':
    print('良好');
    break;
  case 'C':
    print('及格');
    break;
  default:
    print('未知成绩');
    break;
}


break continue语句
  • break语句用于结束最近的while、do while、for或者switch语句,并将程序的执行权传递给紧接在被终止语句之后的语句
  • continue语句导致最近的循环语句的档次迭代提前结束
int max = 10;
for(var i=0; i<max; ++i){
  if(i == 5){
    break;
  }else{
    print(i);
  }
}

int max = 10;
for(var i=0; i<max; ++i){
  if(i == 5){
    continue;
  }else{
    print(i);
  }
}


assert语句
  • assert(condition, optionMessage);
  • 当condition执行为false,中断正常执行
  • 断言只在检查模式下运行有效,如果在生产模式运行,则断言不会执行
String urlStr = 'https://www.163.com';
assert(urlStr.startWith('https'));
assert(urlStr.startWith('https'), 'URL($urlStr) should start with "https".');




2.异常

  • Dart代码能够Throw和Catch异常。异常是一些代码未知的错误情况。如果异常没有被捕获,则异常会被抛出,最终导致代码终止执行。

  • Dart中所有异常为 非检查异常。方法不一定声明他们所抛出的异常,并且你也不需要捕获任何异常。

  • Dart提供了Exception和Error类型,以及一些子类型。也可以实现自己的异常类型。Dart可以抛出任何非null对象为异常,不仅仅是实现了Exception或者Error的对象。

  • 抛出异常

throw FormatException('Expected at least 1 section');
  • 抛出任意类型对象(不建议)
throw 'Out of Llamas');
  • 捕获异常可以避免异常继续传递,捕获异常给你一个处理该异常的机会
try{
  breedMoreLlamas();
} on OutOfLlamasException{
  buyMoreLlamas();
}
  • 对于可以抛出多重类型异常的代码,可以指定多个捕获语句
try{
  breedMoreLlamas();
}on OutOfLlamasException{
  buyMoreLlamas();
}on Exception catch(e){
  print('Unknown exception:$e');
}catch(e, s){
  print('Something really unknown:$e');
  print('Stack trace:\n $s');
}
  • 要确保某些代码不管有没有出现异常都需要执行,可以使用一个finally语句来实现。如果没有catch语句来捕获异常,则在执行完finally语句后,异常被抛出。
try{
  breedMoreLlamas();
}finally{
  cleanLlamaStalls();
}
  • 定义的finally语句在任何匹配的catch语句之后执行
try{
  breedMoreLlamas();
}catch(e){
  print('Error:$e');
}finally{
  cleanLlamaStalls();
}




3.类

Dart是一个面向对象编程语言,同时支持基于Mixin的继承机制。每个对象都是一个类的实例,所有的类都继承与Object。基于Mixin的继承意味着每个类(Object除外)都只有一个超类,一个类的代码可以在其他多个类继承中重复使用。

3.1 使用类的成员

  • 对象的成员 由函数和数据(即方法和实例变量)组成,使用(.)来访问对象的实例变量和方法【使用(?.)代替(.)可以避免因为左边表达式为null而导致的问题】
var p = Point(2, 2);
//为实例变量y赋值
p.y = 3;

//获取y的值
assert(p.y == 3);

//调用变量p的distanceTo()方法
num distance = p.distanceTo(Point(4, 4));


3.2 使用构造函数

  • 可以使用构造函数来创建一个对象。构造函数的命名方式可以为类名类名.标识符的形式。【从Dart2开始,new关键字是可选的】
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x':1, 'y':2});


3.3 实例变量

  • 所有未初始化的实例变量,其值均为null
  • 所有实例变量均会隐式的声明一个Getter方法,非final类型的实例变量还会隐式的声明一个Setter方法。
  • 如果你在声明一个实例变量的时候就将其初始化,那么该实例变量的值就会在对象实例创建的时候被设置,该过程会在构造函数以及他的初始化器列表执行之前。
class Point{
  num x; //声明实例变量x并初始化为null
  num y; //声明实例变量y并初始化为null
  num z = 0; //声明实例变量z并初始化为0
}

void main(){
  var point = Point();
  point.x = 4; //使用x的Setter方法
  assert(point.x == 4); //使用x的Getter方法
  assert(point.y == null); //默认值为null
}


3.4 构造函数

  • 声明一个与类名一样的函数 即可声明一个构造函数
class Point{
  num x, y;
  Point(num x, num y){
    this.x = x;
    this.y = y;
  }
}
  • 对于大多数编程语言来说,在构造函数中为实例变量赋值的过程都是类似的,而Dart则提供了一种特殊的语法糖来简化该步骤
class Point{
  num x, y;
  //在构造函数体执行前用于设置x和y的语法糖
  Point(this.x, this.y);
}
3.4.1 默认构造函数
  • 如果你没有声明构造函数,那么Dart会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。
3.4.2 构造函数不被继承
  • 子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数。
3.4.3 命名式构造函数
  • 可以为一个类声明多个命名式构造函数来表达更明确的意图
  • 在子类中提供一个与父类命名构造函数名字一样的命名构造函数,则需要在子类中显式的声明。
class Point{
  num x, y;
  Point(this.x, this.y);
  
  //命名式构造函数
  Point.origin(){
    x = 0;
    y = 0;
  }
}
3.4.4 重定向构造函数
  • 有时候类中的构造函数会调用类中其他的构造函数,该重定向构造函数没有函数体,只需在函数签名后使用(:)指定需要重定向到的其他构造函数即可。
class Point{
  num x,y;
  //该类的主构造函数
  Point(this.x, this.y);
  
  //委托实现给主构造函数
  Point.alongXAxis(num x) : this(x, 0);
}
3.4.5 常量构造函数
  • 如果类生成的对象不会变的,那么可以在生成这些对象时就将其变为编译时常量。你可以在类的构造函数前加上const关键字并确保所有实例变量均为final,来实现该功能
  • 常量构造函数创建的实例并不总是常量
class ImmutablePoint{
  final num x, y;

  const ImmutablePoint(this.x, this.y);

  static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}

void main(){
  var a = const ImmutablePoint(1, 1);
  var b = const ImmutablePoint(1, 1);
  var c = ImmutablePoint(1, 1);
  assert(identical(a, b)); //正常
  assert(identical(a, c)); //报错
}
3.4.6 工厂构造函数
  • 使用factory关键字表示类的构造函数将会令该构造函数变为工厂构造函数,这意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象
class Logger{
  final String name;
  //_cache变量是库私有的,因为在其名字前面有下划线
  static final Map<String, Logger> _cache = <String, Logger>{};
  
  factory Logger(String name){
    return _cache.putIfAbsent(name, ()=>Logger._internal(name));
  }

  Logger._internal(this.name);
}
3.4.7 初始化列表
  • 在构造函数体执行之前初始化实例变量
  • 初始化列表表达式 = 右边的语句不能使用this关键字
//使用初始化列表在构造函数体执行前设置实例变量
Point.fromJson(Map<String, num> json)
  :x = json['x'], y = json['y']{
    print('In Point.fromJson():($x, $y)');
}
3.4.8 调用父类构造函数
  • 默认情况下,子类的构造函数会调用父类的匿名无参构造函数,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用顺序如下:
  1. 初始化列表
  2. 父类的无参构造函数
  3. 当前类的构造函数
  • 如果父类没有匿名无参构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用(:)指定。
class Person{
  String firstName;
  Person.fromJson(Map data){
    print('in Person');
  }
}

class Employee extends Person{
    Employee.fromJson(Map data):super.fromJson(data){
        print('in Employee');
    }
}

void main(){
  var emp = Employee.fromJson({});
  //打印 in Person
  //打印 in Employee
}


3.5 方法

3.5.1 实例方法
  • 对象的实例方法可以访问实例变量和this
import 'dart:math';
class Point{
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other){
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx*dx + dy*dy);
  }
}

void main(){
  var p1 = Point(2, 2);
  var p2 = Point(4, 4);
  print(p1.distanceTo(p2));
}
3.5.2 Getter和Setter

Getter和Setter是一堆用来读写对象属性的特殊方法,实例对象的每一个属性都有一个隐式的Getter方法,如果为非final属性的话还会有一个Setter方法,可以使用get和set关键字为额外的属性添加Getter和Setter方法

class Rectangle{
  num left, top, width, height;
  
  Rectangle(this.left, this.top, this.width, this.height);

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

void main(){
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
3.5.3 抽象方法
  • 定义一个接口方法而不去做具体实现,让实现它的类去实现该方法,抽象方法只能存在于抽象类中。
abstract class Doer{
  //定义实例变量和方法等等...
  
  void doSomething(); //定义一个抽象方法
}

class EffectiveDoer extends Doer{
  void doSomething(){
    //提供一个实现,所以在这里该方法不再是抽象的...
  }
}


3.6 抽象类

  • 使用关键字abstract标识类可以让该类成为抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。
  • 抽象类常常会包含抽象方法
//该类被声明为抽象的,因为它不能被实例化
abstract class AbstractContainer{
  //定义构造函数、字段、方法等...
  
  void updateChildren(); //抽象方法
}


3.7 隐式接口

  • 每一个类都隐式的定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其他接口。一个类可以通过关键字implements来实现一个或多个接口,并实现每个接口定义的API
//Person类的隐式接口中包含greet()方法
class Person{
  final _name; //_name变量同样包含在接口中,但它只是库内可见的
  Person(this._name); //构造函数不在接口中
  String greet(String who) => '你好$who,我是$_name'; //greet()方法在接口中
}
//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())); //你好小芳,你知道我是谁吗?
}


3.8 扩展类

  • 使用extends关键字来创建一个子类,并可使用super关键字引用一个父类
  • 子类可以重写父类的实例方法、Getter和Setter方法
class Television{
  void turnOn(){
    _illuminateDisplay();
  }
}

class SmartTelevision extends Television{
  @Override
  void turnOn(){
    super.turnOn();
    _bootNetworkInterface();
    _upgradeApps();
  }
}
  • 重写运算符
< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
- % >>

注:!= 操作符并不是一个可被重写的操作符。表达式e1!=e2仅仅是!(e1==e2)的一个语法糖

  • 如果调用了对象上不错在的方法或实例变量,将触发noSuchMethod方法,你可以重写noSuchMethod方法来追踪和记录这一操作
class A{
  //除非你重写noSuchMethod,否则调用一个不存在的成员会导致NoSuchMethodError
  @Override
  void noSuchMethod(Invocation invocation){
    print('你尝试使用一个不存在的成员:'+'${invocation.memberName}');
  }
}


3.9 枚举

枚举是一种特殊的类型,用于定义一些固定数量的常量值

  • 使用关键字enum来定义枚举类型
  • 每一个枚举值都有一个名为index成员变量的Getter方法,该方法将会返回以0位基准索引的位置值
  • 使用枚举类的values方法获取一个包含所有枚举值的列表
  • 在switch语句中使用枚举,但是需要注意的是必须处理枚举值的每一种情况


3.10 Mixin

Mixin是一种在多重继承中复用某个类中代码的方法模式

  • 定义一个类继承自Object并且不为该类定义构造函数,这个类就是Mixin类,除非你想让该类与普通类一样可以被正常的使用,否则可以使用关键字mixin替代class让其成为一个单纯的Mixin类
  • 使用with关键字并在其后跟上Mixin类的名字来使用Mixin模式
abstract class Animal{}
abstract class Bird extends Animal{}
mixin Walker{
  void walk(){
    print('I am walking');
  }
}

mixin Flyer{
  void fly(){
    print('I am flying');
  }
}

class Parrot extends Bird with Walker, Flyer{}

void main(){
  Parrot parrot = new Parrot();
  parrot.walk();
  parrot.fly();
}


3.11 类变量和方法

使用关键字static可以声明类变量或类方法

  • 静态变量(即类变量)常用于声明类范围内所属的状态变量和常量。静态变量在其首次被使用的时候才被初始化。
  • 静态方法(即类方法)不能被一个类的实例访问,同样的,静态方法内也不能使用this




4.泛型

4.1 为何使用泛型

  • 正确指定泛型类型可以生成更好的代码
var names = List<String>();
names.addAll(['Andy', 'Simon', 'Lee']);
names.add(42); //Error
  • 使用泛型可以减少重复代码量
abstract class Cache<T>{
  T getByKey(String key);
  void setByKey(String key, T value);
}

4.2 使用集合字面量

  • List、Set以及Map字面量也可以是参数化的。定义参数化的List只需在中括号前添加<type>;定义参数化的Map只需要在大括号前添加<keyType, valueType>:
var names = <String>['A', 'B', 'C']; //list
var uniqueNames = <String>{'A', 'B', 'C'}; //set
var pages = <String, String>['A' : '页面',]; //map

4.3 使用类型参数化的构造函数

  • 构造方法时也可以使用泛型,在类名后用尖括号(<...>)将一个或多个类型包裹
var nameSet = Set<String>.from(names);
var views = Map<int, View>();
  • Dart的泛型类型是固化的,这意味着即便在运行时也会保持类型信息
    【注:Java中的泛型是类型擦除的,你可以判断对象是否为List,但不可以判断对象是否为List<String>】
var names = List<String>();
names.addAll(['A', 'B', 'C']);
print(names is List<String>); //true

4.4 限制参数化类型

  • 使用泛型的时候可以通过extends关键字限制泛型的类型范围
class Foo<T extends SomeBaseClass>{
  //具体实现...
  String toString() => "'Foo<$T>'的实例";
}

class Extender extends SomeBaseClass{...}

var someBaseClassFoo = Foo<SomeBaseClass>(); //OK
var extenderFoo = Foo<Extender>(); //OK

var foo = Foo<Object>(); //Error

4.5 泛型方法

T first<T>(List<T> ts){
  //处理一些初始化工作或错误检测...
  T tmp = ts[0];
  //处理一些额外的检查...
  return tmp;
}

方法first<T>的泛型T可以在如下地方使用:

  • 函数的返回值类型(T)
  • 参数的类型(List<T>)
  • 局部变量的类型(T tmp)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容