Dart 语言学习笔记
Part 1:概览
- Dart是一门纯OOP语言。所有的类都继承于Object类。null也是对象,变量仅仅存储对象的引用
- Dart是强类型语言,即使Dart可以进行类型推断(使用var申明变量类型)
Part 2:基本语法
1. 注释(和C几乎一样)
- 单行注释
// 这是单行注释,不用多说诶,懂的都懂
- 多行注释
/*
这是多行注释,和C一模一样
*/
- 文档注释
文档注释可以是多行注释,也可以是单行注释,文档注释以 /// 或者 /** 开头。在连续行上使用 /// 与多行文档注释具有相同的效果。
/// Feeds
2. Dart的内置类型
-
numbers
-
概览
int和double都是num的子类,num中定义了一些基本的算术运算符(+,-,etc.),此外还定义了abs()、ceil()等方法(位运算符,例如>>定义在int中)
-
int
· 整数值,长度不超过64位,允许的取值范围为 -253 ~ 253-1,具体的取值范围依赖于不同的平台。
· 整形支持传统的位运算操作。
-
double
64位双精度浮点数字。
如果一个数字包含了小数点,那么它就是浮点型。
-
和字符串之间的相互转换
-
string–>数字
var one = int.parse('1');
-
数字–>string
String oneAsString = 1.toString()
-
-
-
strings
-
UTF-16编码的字符序列
-
通过单引号或者双引号创建(单双引号交替使用来避免引号冲突, 这一点和Python中是一样的)
-
在字符串中使用 ${ } 来调用字符串的表达式, 也可直接使用 $ 来格式化输出字符(这里需要注意的是,如果需要格式化输出调用了方法的字符串,如下例中的 s.isEmpty , 则必须使用${ } 来进行格式化输出。)
s = 'hhhhey'; print("s is empty or not: ${s.isEmpty}");
-
字符串拼接
-
可以通过将多个字符串放在一起对字符串进行拼接
//默认不添加任何间隔符 s = 'He''is''a''girl'; //中间添加间隔符(空格) s2 = 'He' 'has' 'a dream';
-
可以用 + 将多个字符串连成一个(这样默认中间不添加任何间隔符)
-
-
使用三个引号创建多行字符串
var s = """ 这个 是一个多行字符串 """;
-
在字符串引号前加上 r 表示不做任何转义
var s = r"he \t is girl";
-
-
booleans
- 仅有两个对象:true和false
- 只能 显式地 检查布尔值
-
lists(也被称为arrays)
-
创建一个List
var lst = [1, 2, 3]; // 这里lst会被推断为List<int>
-
下标从 0 开始
-
注意:Dart中的数组是类型唯一性的,也就是说,一个数组中的元素的数据类型必须相同。
-
可以使用 .length 方法获取数组的长度
-
list.generate(参考资料)
-
扩展操作符与空感知操作符
-
扩展操作符
用于将一个数组中的所有元素插入到另外一个数组中。
void main() { var lst1 = [1, 2, 3, 4]; List<int> lst2 = [99, 98, 97]; List<int> lst3 = [0, ...lst1, ...lst2]; print(lst3); } //结果:[0, 1, 2, 3, 4, 99, 98, 97]
-
空感知扩展操作符
扩展操作符右边可能为null,则使用空感知扩展操作符(…?),由此避免产生异常。如果操作符右边为空,则跳过该数组。
void main() { var list; var lst2 = [0, ...?list]; print(lst2); } //结果:[0]
-
-
Dart中引入了集合中的 if 和 for操作构建集合
-
if 操作
void main() { bool judge = 2 > 3 ? true : false; var lst = ['home', 'is', if (judge) 'far']; print(lst); } //结果:[home, is]
-
for操作
-
-
-
sets
-
Set 用来表示集合。该类型拥有离散数学上集合的特征,即:无序、不重复
-
创建一个集合对象
// exp1 var set1 = <int>{3, 99}; //exp2 Set<String> set2 = {"hello", "World"};
-
使用 .add 和 .addAll添加元素(都仅有一个参数)
// 使用 .add添加单个元素 var set1 = {4, 5, 6}; set1.add(90); // 使用 .addall()添加可迭代对象 // exp1 var set1 = {4, 5, 6}; var set2 = {10, 11, 12}; set1.addAll(set2); // exp2 List<int> lst = [-1, -2, -3]; set1.addAll(lst);
-
注意
- 在声明Set对象的时候,赋值给Set的值可以是重复的(会有警告但是不会有报错),Set会自动去除重复的多余元素。
-
-
maps
-
类似于Python中的字典,Dart中的Map以键值对(key-value)的形式进行存储。其特性、创建、引用均和Python中的字典相同,在此不再赘述。
-
注意
- 一个Map中的Key只能出现一次,但value可以出现很多次。
var gaifts = { 'first': 'hat', 'second': 'book', 'third': 'book' };
-
若欲创建一个不可变的map对象,只需要在Map对象前添加const关键字
var a = const { 'first': 'hat', 'second': 'book' };
-
-
runes
-
在Dart中,Rune类型用来表示UTF-32字符串,并可以将文字转换成符号表情或含有特定含义的文字。
var clapping = ' \u{1f600}'; print(clapping);
-
-
symbols
-
Symbol 表示在Dart程序中申明的运算符或者标识符。(但在实际的Dart开发中,很少会用到symbol这个类型)
Symbol a = #new_mark; print(a);
-
-
dynamic(注意!!慎用)
严格意义上来说,dynamic并不是一个类型。在dart中,使用dynamic申明的变量在使用的过程中可以被赋予不同类型的值,其类型也会随之变化。但在实际开发中,需要慎用dynamic,因为我们无法确定dynamic在何时是何种类型。
dynamic a = "hhhh"; print(a.runtimeType.toString()); a = 2432; print(a.runtimeType.toString());
-
查看变量的类型
// 使用如下语句查看dart中变量中的类型 print(object.runtimeType.toString());
-
final 和 const
-
final 和 const 用于定义常量,如下例
// e.g.1 const a = 3; const String str = 'ddd'; // e.g.2 final b = 4; final double f = 4.9; // e.g.3 var foo = const []; foo = [1, 2, 3] // 这里不会报错
这里由const和final定义的a和b在后面是不能被修改的。其类型可以自己申明或被自动推断
-
若使用 const 修饰类中的变量,则必须加上static关键字,即static const
-
注意!!:没有使用const或者final修饰的变量是可以被修改的,即使这些变量之前引用过const常量(如e.g.3)
-
常量可以使用常用的方法及函数。
-
-
附注
- 在Dart中,未初始化的变量拥有一个默认的初始化值:null。在Dart中一切皆为对象。
- 在Dart中,并没有任意类型间的强制类型转换,但是特定类型之间可以进行转换。
3. 运算符
-
算术运算符
-
+:加(二元运算符)
-
-:减(二元运算符)
-
-表达式:负号(一元运算符)
var a = 4; print(-a);
-
*:乘(二元运算符)
-
/:除(二元运算符),结果可能是非整数
-
~/:整除(二元运算符)
-
%:取模
-
Dart支持如 --var 这样的自增和自减操作
-
-
关系运算符
- 均为二元运算符
- ==:相等
- !=:不等
- >:大于
- <:小于
- >=:大于等于
- <=:小于等于
-
类型判断运算符
-
as:判断某变量是否属于某种类型,如果是,返回该变量的值;否,则抛出异常
var a = 233 print(a as int); // 正常输出233 print(a as double); // 抛出异常
-
is:如果对象是指定类型则返回 true
// exp1 var a = 213; print(a is int); //返回true
-
is!:如果对象是指定类型则返回 false
// exp2 var b = 233; print(a is! int); //返回false
-
-
赋值运算符
-
可以使用 “=” 进行赋值,也可以使用 "??="来为值为null的变量赋值。
// 将 value 赋值给a a = value; // 当 b 为 null 时,将 value 赋值给b b ??= value;
-
-
逻辑运算符
Dart 中的逻辑运算符和C语言中的逻辑运算符相同,此处不在赘述,仅作简要阐述。
bool a = true; bool b = false; // 将a取反 ! a; // 逻辑或 a || b; // 逻辑与 a && b;
-
位运算符
- & :按位与
- | : 按位或
- ^ :按位异或
- ~ 表达式 :按位对表达式进行取反
- << :位左移
- > > :位右移
-
级联运算符
-
在 Dart 中,“…” 被称为级联运算符,它的作用是让你可以快速多次调用同一个对象的多个方法或成员变量
// e.g.1 class Dog { late String name; late int age; late String gender; // 构造函数 Dog(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } String woof() { return "${name} has woofed"; } String eat() { return "${name} has eat some food."; } void sleep() { print("${name} has sleep."); return null; } } void main(List<String> args) { // exp 1 Dog("fippy", 2, 'female') ..woof() ..sleep() ..eat(); }
exp 1 相当于:
// exp 2 var fippy = Dog("fippy", 2, 'female'); fippy.woof(); fippy.sleep(); fippy.eat();
注意:
- 一定要注意级联表达式中的分号位置
- 级联运算符是可以进行嵌套的
- 很多Dart教程中有提及返回值为void的方法是不能使用级联运算符的。这种说法并不准确。反例如上例的sleep方法
- 严格来说,级联运算符并非Dart中的一个运算符,而是一个特有的语法
-
-
一些特殊的运算表达式
-
??
-
使用规则:var1 ?? var2
-
用于判定是否为空,如果var1为空,则返回var2
String person_name(var name) { return name ?? "Guest"; } void main(List<String> args) { print(person_name("Admin")); }
-
-
? :
-
和C语言中的 ? : 表达式相同
var name = 2 > 3 ? "Bob" : "Jim"; print(name);
-
-
?.
-
条件访问成员,左侧的操作对象不能为空,举例如下
// 如果左侧的fippy为空,那么返回null,否则返回age fippy?.age
-
-
-
附注
-
可以使用括号来提高运算表达式的可读性
//exp1 if ((n % i == 0) && (d % i == 0))... //exp2 if (n % i == 0 && d % i == 0)... // 两种方式效果相同,但exp1的可读性比exp2的可读性要高
-
4. 流程控制
-
条件分支if
-
和 C语言 中的 if-else 用法相似,但有一个不同点是,Dart中的if语句必须是一个布尔值,不能是其他类型,如下例
void main(List<String> args) { String a = "panda"; if (a == 'dog') { print("it's a dog"); } else if (a == 'panda') { print("it's a panda"); } else { print("it's something else."); } }
-
-
循环
-
for
-
基础使用
Dart 中可以像C语言中的for循环一样进行使用,如下
List<int> a = [4, 3, 1]; for (var i = 0; i < a.length; i++) { print(a[i]); }
-
for循环的高级用法
如果要遍历的对象实现了Iterable接口(
那么事情就简单起来了),就可以使用forEach和for-in进行迭代。PS:Dart中的内置类型:Map、Set、List都是可以直接使用这两种方法的。(和JS有些类似)-
forEach
List<int> a = [4, 3, 1]; a.forEach((element) { print(element); });
-
for-in
for (var element in a) { print(element); }
-
-
-
while
while循环会在执行循环体之前先判断条件是否为真,和C语言中的while循环类似,此处不再赘述
-
do-while
do-while循环会在每次执行完循环体之后再判断是否继续执行循环,和C语言中的do-while循环类似,此处不再赘述
-
break和continue
-
break
在循环体中使用break可以中断当前循环并直接跳出循环
-
continue
在循环中使用continue语句可以跳过本次循环直接执行下一次循环
-
-
-
Switch语句
-
和C语言中的switch语句用法相似,如下
void main(List<String> args) { var command = 'forward'; switch (command) { case 'forward': print('forward'); break; case 'back': print('back'); break; default: print("something else"); break; } }
-
要注意的是,每一个case后面都需要接一个break语句
-
Dart 中支持空的case语句,如下
void main(List<String> args) { var command = 'back'; switch (command) { case 'forward': case 'back': // command为forward或者back都会执行 print('forward or back'); break; default: print("something else"); break; } }
-
Dart中可以实现continue + label的形式从一个case跳转到另一个case,如下:
void main(List<String> args) { var command = 'forward'; switch (command) { case 'forward': print('forward'); // continue 语句 continue back; back: case 'back': print('back'); break; default: print("something else"); break; } }
-
5. 函数
-
概览
Dart是一门面向对象的语言,在Dart中,函数也是对象(属于Function类型)。Dart可以将函数作为参数传入另一个函数。此外,Dart的函数写法与C语言很相似。通常写法如下:
// 自定义函数 int plus(int a, int b) { return a + b; } // main函数 void main(List<String> args) { int sum = plus(2, 3); print(sum); }
-
函数参数
Dart中的函数参数类型有两大种:可选参数和必传参数,而可选参数分为可选位置参数和可选命名参数。Dart中的必传参数往往在可选参数之前。
-
可选参数
-
可选位置参数
可选位置参数部分使用 "[ ]"包裹。如下
int plus(int c, [int a = 0, int b = 0]) { return a + b + c; } void main(List<String> args) { int sum = plus(2, 3, 3); print(sum); }
-
可选命名参数
可选命名参数部分使用 “{ }” 包裹。如下:
int plus(int c, {int a = 0, int b = 0}) { return a + b + c; } void main(List<String> args) { int sum = plus(2, a: 3); print(sum); }
-
-
必传参数
在Dart中,必传参数使用 "required"修饰符(在1.22版本之前是@required),如下:
int plus({required int a, int b = 2}) { return a + b; } void main(List<String> args) { int sum = plus(a: 3); print(sum); }
注意:使用required关键字必须在 可选参数部分 使用。
-
-
返回值
所有的函数都会有一个返回值,如果没有人为地指定返回值,那么函数体会隐式地添加 “return null;”
-
箭头表达式 (这个点比较重要,在工程性项目比如Flutter开发中会频繁使用)
如果函数体只有一行,那么可以使用 “=>” 进行简写。如下
void out(int number) => print(number); void main(List<String> args) { out(1); }
-
匿名函数
匿名函数即没有函数名的函数,Dart中的匿名函数和JS中的匿名函数相似,Dart中的匿名函数格式如下:
([[Type] param1[,...]]) { // 函数体 }
举例如下:
void main(List<String> args) { List<int> lst = [3, 4, 1, 903, 4372]; // forEach内的即为匿名函数 lst.forEach((element) { print(element); }); }
6. 异常
-
概览
异常是指程序运行中发生的意外错误,如果没有捕获异常,会导致程序终止执行。Dart可以抛出并捕获异常。但有一点需要注意:Dart中的所有异常都是未检查的异常。这就是说,Dart并不需要声明它们可能抛出的异常,也不强制捕获任何异常。Dart中可以将任何非null对象作为异常抛出。此外,开发者也可以定义自己的异常。
-
assert
assert语句表示断言,用于判断某一语句是否满足条件。
如果assert语句判断结果为false,则会中断正常的程序执行流程。反之,若为true,则继续执行后续的程序。如下例:
// e.g.1 void main(List<String> args) { bool judge = true; assert(!judge); print('yes'); }
注意:
- 在开发时,assert语句仅仅在调试模式中才有效,在直接运行,或者开发环境中,是无效的。e.g.1中,若直接运行,控制台会输出yes,但运行调试模式,会抛出异常而结束,不会输出yes。
- assert的判断条件是任何可以转化为bool类型的对象(包括函数)
-
抛出异常
// e.g.1 抛出任意异常 throw 'Out of date';
注意:抛出异常的时候应该尽量使用Error或Exception类型的异常
-
捕获异常
捕获异常是为了增加程序的健壮性,防止因为抛出异常过多导致程序崩溃。在Dart中通常使用try on catch 进行异常捕获。如下例
// e.g.1:使用on或者catch来指定异常类型 void main(List<String> args) { try { var a; int b = a; } on Error { print(Error); } } // e.g.2:,使用on来指定异常类型,使用catch来捕获异常对象,且两者可以同时使用 try { // ··· } on Exception catch (e) { print('Exception details:\n $e'); // 可以为catch方法指定两个参数,第一个参数为抛出的异常对象,第二个参数是栈信息StackTrace对象。 } catch (e, s) { print('Exception details:\n $e'); print('Stack trace:\n $s'); }
此外,还可以用 rethrow 语句使得在捕获后重新抛出异常(使得这个异常可以被程序其他部分调用)
-
finally
如果有想在任何情况下都执行的代码(不论是否有异常,都执行代码),需要使用到finally语句。finally语句会在异常处理后执行。如下例:
try { //.... } catch { print("error caught"); } finally { print("Always execute"); }
Part 3:面向对象(OOP)
Dart支持mixin继承机制(关于mixin继承机制,请参考这篇文章:mixin继承机制),所有类都继承自Object类。且Dart是单继承。这里默认大家对OOP至少有基本的认识。
1. 成员变量
在Dart中,我们使用 点操作符 来引用对象的变量和方法。在成员变量前加下划线,表示私有变量。如下例:
class Point {
late int x;
late int y;
}
void main(List<String> args) {
Point p = new Point();
// 使用 点操作符 引用对象的变量和方法
p.x = 1;
p.y = 3;
}
2. 构造函数
构造函数主要用来在创建对象时初始化对象,构造函数的函数名必须要和类名相同,如下例:
class Point {
late int x;
late int y;
// 构造函数
Point(int x, int y) {
// 这里this指代了当前类的实例
this.x = x;
this.y = y;
}
}
注意:
- 如果你没有声明构造函数,那么 Dart 会⾃动⽣成⼀个⽆参数的构造函数并且该构造函数会调⽤其⽗类的⽆参数构造⽅法。
3. 抽象类
使用abstract修饰符定义的类称为抽象类,抽象类只能被继承,不能被实例化。Dart中的抽象类可以用来定义接口或部分接口实现。抽象类举例如下:
abstract class Animal {
late String name;
// 抽象方法
sleep();
eat();
// 普通方法
print_name() {
print("The name is $name");
}
Animal(String name) {
this.name = name;
}
}
4. 枚举类
枚举类是一种特殊的类,使用enum关键字进行定义,作用是用来表示相同类型的一组常量值。举例如下:
// 定义一个枚举类
enum Color { red, greeen, yellow, white }
void main(List<String> args) {
assert(Color.red.index == 0);
print(Color.values.runtimeType.toString());
Color color = Color.greeen;
// 可以在switch语句中使用枚举类型(因为每个元素都具有相同的类型)
switch (color) {
case Color.greeen:
print("ahhh, it's green");
break;
default:
print('this is default');
break;
}
}
注意:
1. 枚举类型不能被子类化、继承或实现。
2. 枚举类型不能被显式实例化。
5. 继承
继承是指子类继承父类的特征和行为。在Dart中,使用extends关键字来创建一个子类,使用super关键字来指代继承的父类。如下例:
class Person {
late String name;
late int age;
void run() {
print("Person $name is running.");
}
void eat() {
print("Person $name is eating");
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// 创建一个engineer类,继承自Person
class engineer extends Person {
// 使用父类Person的构造函数
engineer(String name, int age) : super(name, age);
// 对父类的run方法进行覆盖
void run() {
print("Engineer $name is running");
// 使用super调用父类的方法或变量
super.run();
}
void show_age() {
print("Engineer $name is $age years old.");
}
}
void main(List<String> args) {
var Bill = engineer('Bill', 32);
Bill.run();
Bill.show_age();
}
6. Mixin
Dart是单继承的,若欲实现多继承,则需要借助Mixin机制了。Dart使用with关键字来实现Mixin。如下:
class BackEndEngineer {
void Code() {
print("BackEndEngineer is coding.");
}
}
class FrontEndEngineer {
late String name;
void Design() {
print("$name is designing.");
}
FrontEndEngineer(String name) {
this.name = name;
}
}
// 使用with实现mixin继承
class FullStackEngineer = FrontEndEngineer with BackEndEngineer;
void main(List<String> args) {
var John = FullStackEngineer('John');
John.Design();
John.Code();
}
上例中 FullStackEngineer继承了FrontEndEngineer和BackEndEngineer,拥有两个类的特性、成员变量、方法。
注意:
-
若要继承的父类A中含自定义的构造函数,那么A不能放在with后参与mixin继承,但with前的父类可以有自定义的构造函数。如下例:
class BackEndEngineer { late String name; void Code() { print("$name is coding."); } // 这里有构造函数 BackEndEngineer(String name) { this.name = name; } } class FrontEndEngineer { late String name; void Design() { print("$name is designing."); } FrontEndEngineer(String name) { this.name = name; } } // 这里使用mixin则会报错 class FullStackEngineer = FrontEndEngineer with BackEndEngineer; void main(List<String> args) { var John = FullStackEngineer('John'); John.Design(); John.Code(); }
7. 元数据
元数据又称中介数据、中继数据,主要用来描述数据的属性信息。元数据注释以字符@开头。在Dart中,已经内置了3种元数据注解,此外,也可以自己定义元数据注解。更多关于元数据的讲解,可以参考这篇文章:Dart 元数据 (ps:这篇文章中的@required,在比较新的Dart版本中已经简化为required,具体参考前文)
-
@deprecated
表示标注的数据已经过时
-
@override
表示被标注的数据是覆盖父类的方法
-
@proxy
可以用来在编译时避免错误警告
(ps:有关泛型和异步编程的内容将在未来进行更新)