欢迎点赞,转载请注明出处
本节继续介绍Dart一些语法知识点。在阅读之前,你需要具有一些面向对象编程语言的基础,如C++或Java等,因为本节并不会详细地对类这部分进行详细地说明。
函数
- Dart是一种真正面向对象的语言,所以函数也是对象,可以被赋值给变量或者作为其它函数的参数。
- 函数可以有两种形式的参数:必要参数和可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。
- 可选参数分为命名参数和位置参数,可在参数列表中任选其一使用,但两者不能同时出现在参数列表中。
- 当调用函数时,可以使用参数名:参数值的形式来指定命名参数,定义函数时,使用 {param1, param2, …} 来指定命名参数。可以使用 @required注解来标识一个命名参数是必须的参数。
- 使用 [param1, param2, …] 将一系列参数包裹起来作为位置参数。
- 可以用 = 为函数的命名和位置参数定义默认值,默认值必须为编译时常量,没有指定默认值的情况下默认值为 null。
- 前面章节的Hello系列,我们已经看到了每个Dart程序都必须有一个main()顶级函数作为程序的入口,main() 函数返回值为void并且有一个 List 类型的可选参数。
下图示例中,area函数为位置可选参数示例,volume函数为命名可选参数示例。 上图相应的代码如下:
double area (double length , [double width]){
if (width==null) return length*length;
else return length*width;
}
double volume (double length, {double width, double height} ){
if (width==null&&height==null) return length*length*length;
else return length*width*height;
}
void main(){
print("area is ${area(10)}");
print("area is ${area(10,20)}");
print("volumn is ${volume(10)}");
print("volumn is ${volume(10,height:30,width: 20)}");
}
- 可以创建一个没有名字的方法,称之为匿名函数,或Lambda表达式或Closure闭包。
([[ _类型_ ] _参数_ [, …]]) { _函数体_ ; };
for循环中的闭包会自动捕获循环的索引值,所以下列代码中的item参数代表是list数组中的各个元素值:
void main() {
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
}
运算符
Dart运算符与其他大多数编程语言类似,以下列出几个相对特别一些用法的Dart运算符:
- ~/ 除并向下取整;
- ??=或??表达式 为值为null的变量赋值;
- 级联运算符.. 可以在同一个对象上连续调用多个对象的变量或方法;
- 条件访问成员?. 左边的操作对象不能为 null,如果为null则返回null。
异常
Dart异常跟其他编程语言类似,有以下几点特别说明:
- 所有异常都是非必检异常;
- 可以将任何非null对象作为异常抛出,优秀的代码通常会抛出Error 或Exception类型的异常;
- 可以使用on或catch来捕获异常,使用on来指定异常类型,使用catch来捕获异常对象,两者可同时使用;
- 关键字rethrow可以将捕获的异常再次抛出。
试运行下列代码:
void main() {
try {
int firstInput = 1;
int secondInput = 0;
int result = firstInput ~/ secondInput;
print('The result of $firstInput divided by $secondInput is $result');
} on FormatException catch (e) {
print('Exception occurs: $e');
} on IntegerDivisionByZeroException catch (e,s) {
print('Exception occurs: $e');
print('STACK TRACE\n: $s');
} finally {
print('finally called');
}
}
类
Dart 是支持基于mixin继承机制的面向对象语言,所有对象都是一个类的实例,而所有的类都继承自Object类。基于mixin的继承意味着每个除 Object 类之外的类都只有一个超类。Extension方法是一种在不更改类或创建子类的情况下向类添加功能的方式。
- 从Dart 2开始,创建对象时的new关键字是可选的;
- 使用常量构造函数,在构造函数名之前加const关键字;
- 两个使用相同构造函数相同参数值构造的编译时常量是同一个对象;
- 可以使用Object对象的runtimeType属性在运行时获取一个对象的类型;
- 所有未初始化的实例变量其值均为null;
- Dart提供了一种特殊的语法糖来简化构造函数执行:
class Point {
num x, y;
Point(this.x, this.y);
}
上面的代码等价于下面的写法:
class Point {
num x, y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
- 可以为一个类声明多个命名式构造函数来表达更明确的意图,例如下图第4行Point.origin():

- 默认情况下,子类的构造函数会调用父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,这同C++语言特性很像,具体用法我们结合后续章节的例子解释。
- 使用factory关键字标识类的构造函数将会令该构造函数变为工厂构造函数,即单例。
- 可以使用get和set关键字为额外的属性添加Getter和Setter方法,具体用法参考下面的示例,注意get right的写法,right后面是没有括号的,set前是没有返回类型:
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) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
var postion=rect.right; //此时调用get right
rect.right = 12; //此时调用set right(12)
}
- 每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口,不包括构造函数。如果想要创建一个A类支持调用B类的API且不想继承B类,则可以实现B类的接口。我们先看一个implements的错误示例:
上图输出错误信息可以看到,SomeBody类缺少Person.greet和Person.name实现,并提供4种可能的修正方法,如下图代码所示: - Dart继承是单继承,子类可以访问父类中的所有变量和方法,因为Dart中没有private,public,protected的区别,但如果变量名前以下划线命名开头,则该变量名为私有的。
- Dart 2.7 中引入的Extension方法是向现有库添加功能的一种方式,这类似于Objective-C语言的Category机制。
- 每一个枚举值都有一个名为 index 成员变量的 Getter 方法。
- Mixin是一种在多重继承中复用某个类中代码的方法模式。使用with关键字并在其后跟上Mixin类的名字来使用 Mixin模式。定义一个类继承自Object并且不为该类定义构造函数,这个类就是Mixin类,Mixin类不能实例化。可以使用关键字on来指定哪些类可以使用该Mixin类。
至此,我们可以看到扩展一个类,我们可以用implements,extends和with关键字,我们需要给出一个例子,来说明下它们的关联和区别,以加深理解。下图尝试构造出一个精简且有一定含义的示例及注释:

你也可以自己尝试在我示例的基础上进一步修改理解这些概念:
/// Animal抽象类不能被实例化,但可以包含某些方法实现,如run()
abstract class Animal {
String name;
Animal(this.name);
String get noise;
String eat(){
return "eats sth";
}
}
/// Bird继承于Animal抽象类,需要实现noise Getter,并可以复用Animal类的eat方法
class Bird extends Animal {
Bird(String name) : super(name);
String get noise => 'tweet';
}
/// Pikachu类实现Animal接口,需实现name实例字段,noise Getter,以及重定义eat方法
class Pikachu implements Animal {
String name = 'Pikachu';
String get noise => 'pika';
@override
String eat() {
return "eats nothing";
}
}
/// Swimmer为mixin类,不能定义构造函数
mixin Swimmer {
String swim() {
return "can swimming";
}
}
/// 没有默认构造函数的抽象类也可以用于混入
abstract class Flyer {
// 不能直接被派生extends
factory Flyer._() => null;
String fly() {
return "can flying";
}
}
/// Duck类继承于Bird类,并具有Swimmer和Flyer的功能
class Duck extends Bird with Swimmer, Flyer {
Duck(String name) : super(name);
String get noise => 'duckling';
}
main() {
var magpie = new Bird('Magpie');
var pika = new Pikachu();
var bufflehead = new Duck("Bufflehead");
print('${magpie.name} noise is ${magpie.noise}, ${magpie.eat()}.');
print('${pika.name} noise is ${pika.noise}, ${pika.eat()}.');
print('${bufflehead.name} noise is ${bufflehead.noise}, ${bufflehead.eat()}, '
'${bufflehead.swim()}, ${bufflehead.fly()}.');
}
本节对Dart类进行了非常概要性的介绍,你只要先有大致印象即可。更多的知识点的理解和掌握,还是需要在实际编程中不断的积累和总结,可以戳官方这里更系统地学习Dart类的用法。
实验四
- 任务1:定义一个类Counter,为它设计合理的实例变量和方法,将实验三的代码重新组织成类的方式进行实现。
- 任务2:在任务1的基础上,定义类Counter两个派生类WordsCounter和CodeLineCounter,将实验三的代码重新组织进行实现,你也可以修改任务1Counter类的定义与实现方案。
上一篇 Dart变量、类型和流程控制 下一篇 Dart泛型、库、异步和注释