Skip to main content

02.Dart的基础知识

1 声明变量和常量

1.2 用var声明变量

void main() {
var foo = 'string';
var bar = 0;
var arr = ['e1', 'e2'];
print(foo);
print(bar);
print(arr);
``
> 输入为:
``` bash
string
0
[e1, e2]
ga
不要声明没有初始化值的变量,这样会造成这个变量可以是任意类型。
void main() {
var foo;
foo = 0;
foo = "string";
foo = [];
print(foo);
}

输出结果为:

[]

1.2 指定类型声明变量

void main() {
String str;
int num;
bool boo;
var identifier = new Map();
}

1.3 final关键字的使用

final也是用于声明用的,但只能声明一次。且不能再被赋值.

void main() {
final foo = 1;
foo = 2; // <-- 不能再次赋值了
print(foo);
}

尽管用final只能声明一次, 但对于复合类型的的常来说,其子元素可以被修改操作而不会被禁止,只要复合类型不变就可以了。

void main() {
final bar = new Map();
bar["index"] = 1; // <-- 如果声明的是复合变量,则子元素的操作不会禁止
bar["index"] = "I'm a string"; // <-- 可以对子元素任意操作
print(bar);
}

1.4 声明常量

常量声明只能声明单一类型的值。

void main() {
const num = 1;
const str = "hello world";
const b = true;
}

2 内置数据类型和运算符

tip

dart一共提供了7种数据类型:

  • num(int/double) 数值类型
  • string 字符串类型
  • bool 布尔类型
  • List 列表类型
  • Map 映射类型
  • Symbol 标致类型
  • Runes

2.1 Number类型

void main() {
num foo = 1; // 声明一个数字,可以是整数或浮点数
int bar = -1; // 声明一个整数
double baz = 0.1; // 声明一个浮点数
print(foo.runtimeType); // 打印 int 类型
foo = 0.1;
print(foo.runtimeType); //打印double 类型 说明声明num是可以在 int和double之间转换的
}

2.1.1 Number类型的运算

运算符有: +, - , *, /, ~/, %
其它不论,而~/dart特有的运算符--取整

void main() {
num foo = 1; // 声明一个数字,可以是整数或浮点数
int bar = -1; // 声明一个整数
double baz = 0.1; // 声明一个浮点数
print(foo.runtimeType); // 打印 int 类型
foo = 0.1;
print(foo.runtimeType); // 打印 double 类型 说明声明num是可以在 int和double之间转换的
}

2.1.2 Number类型的一些常用内置方法

void main() {
num foo = -2.1;
print(foo.round()); // 四舍五入
print(foo.ceil()); // 进一取整
print(foo.floor()); //退一取整
print(foo.abs()); //绝对值
}

2.2 字符串类型

2.2.1 字符串的声明

  String foo = "hello world"; // <-- 双引号 单行声明
foo = 'hello world'; // <-- 单引号
foo = '''
line1
line2
...
'''; // <-- 多行, 类似于格式化字符串的意思

2.2.2 字符串的运算符

String foo = "hello world";
print(foo + "!"); // <-- 拼接
print(foo * 2); // <-- 拼接自己2次
print(foo == foo); // <-- 字符串比较
print(foo[0]); // <-- 取下标0的字符

输出

hello world!
hello worldhello world
true

2.2.3 字符串插值表达式

String dart = 'dart';
print("hello, ${dart}"); // <-- 打开 hello,dart

2.2.4 字符串常用属性

String dart = 'dart';
print(dart.length); // 长度: 4
print(dart.isEmpty); // 是否为空: false
print(dart.isNotEmpty); // 是否不为空: true

2.2.5 常用方法

String dart = ' dart ';
print(dart.contains('dart')); // 是否包含 dart
print(dart.toLowerCase()); // 转化为小写
print(dart.trim()); // 去空格
print(dart.trimLeft()); // 去左空格
print(dart.trimRight()); // 去右空格
print(dart.indexOf('d')); // d 所在的位置
print(dart.lastIndexOf('a')); // a所在的倒数位置
print(dart.split('r')); // 从r中拆分为列表
print(dart.endsWith(' ')); // 以最后一个字符是空格
print(dart.startsWith(' ')); //以最前一个字符是空格

2.3 布尔值

bool isOk = true;
print(isOk);

2.4 List数组

2.4.1 声明

var foo = [1, 2, 3, "hello world"];
print(foo[0]); // <-- 打印1
foo[0] = "hello world";
print(foo[0]); // <-- 打印 hello world
var bar = const[1, 2, 3]; // <-- 声明不可变更list
var [0] = 1; // <-- 错误: 不可更改
var baz = <String>[]; // 泛型指定元素的类型

2.4.2 常用属性和常用方法

var foo = [1, 2, 3, "hello world"];
print(foo[0]); // <-- 打印1
foo[0] = "hello world";
print(foo[0]); // <-- 打印 hello world
var bar = const[1, 2, 3]; // <-- 声明不可变更list
var [0] = 1; // <-- 错误: 不可更改

2.5 Map类型

2.5.1 声明

var foo = {"foo": 1, "bar": "hello world", "baz": true}; // <-- 声明map
print(foo); // <-- {foo: 1, bar: hello world, baz: true}
foo["newItem"] = "hello";
print(foo); // <-- {foo: 1, bar: hello world, baz: true, newItem: hello}
var bar = const {"foo": 1}; // <-- 声明不可变的map 声明后变量不可以变更
bar["foo"] = 2; // 错误,不能修改
var baz = <num, String>{}; // <-- 限定类型
baz[1] = "str";

2.5.2 常用属性和方法

var foo = {"foo": 1, "bar": "hello world", "baz": true};
print(foo.length); // <-- 长度属性 3
print(foo.keys); // <-- 所有键名列表 {foo, bar, baz}
print(foo.values); // <-- 所有键值列表 {1, hello world, true}

print(foo.containsKey("foo")); // <-- 是否包含foo键名 true
print(foo.containsValue(1)); // <-- 是否包含1键值 true

foo.forEach((k, v) {
print("key = ${k}, value = ${v}"); // <-- 打印键名和键值
});

2.6 dynamic 动态类型

2.6.1 声明

var foo; // <-- var 方式声明且不初始化值,则默认类型为 dinamic 动态类型
foo = 1; // 可以再次赋值为 int
foo = "str";// 可以再次赋值为 string
foo = true;// 可以再次赋值为 bool
foo = []; // 数组等等
print(foo);

dynamic bar; // <-- 用 dynimac 关键字声明
bar = 1;
bar = "str";
bar = true;
print(bar);

3 运算符

3.1 加减乘除运行符

操作符说明示例
+print(1 + 1), 输出: 2
-print(2 - 1), 输出: 1
*print(2 * 1), 输出: 2
/print(2 / 2), 输出: 1
~/除后取整print(3 ~/ 2), 输出: 1
%除余print(5 % 2), 输出: 1

3.2 递增递减运算符

var foo = 3;
print(foo++); // <-- 先执行再递增: 3
print(foo); // <-- 结果:4
print(++foo); // <-- 先递增再执行: 5

print(foo--); // <-- 先递减再执行: 5
print(++foo); // <-- 先执行再递减: 5

3.3 关系运算符

操作符说明示例
==相等判断print(true == true); 输出: true
!=不等判断print("foo" != "bar"); 输出: true
>大于print( 1 > 2); 输出: false
<小于print( 1 < 2); 输出: true
>=大于或等于print( 1 >= 2); 输出: false
<=小于或等于print( 1 <= 2); 输出: true

3.4 逻辑运算符

操作符说明示例
!print(!false); 输出: true
&&print(true && true); 输出: true
``

3.5 赋值运算符

操作符说明示例
=赋值var foo = 1;
??=复合运算符, 由 ??=组成, 判定是否为空,空则赋值,不为空,则不赋值var bar; bar ??= 1; bar 没有初始化值,则赋值1,反之不处理
+=加等int foo = 0; foo += 1;
-=减等int foo = 0; foo -= 1;
*=乘等int foo = 0; foo *= 2;
/=除等int foo = 4; foo /= 2;
~/=除取整等int foo = 5; foo ~/= 3;
%=取余整等int foo = 5; foo %= 3;

3.6 位运算符

3.6.1 按位与(&)

// 按位与(&)
final value = 0x22; // 100010
final bitmask = 0x0f; // 001111
// 100010
// & 001111
// --------
// 000010
print(value & bitmask ); // 打印为2

3.6.2 按位或(|)

// 按位或(|)
final value = 0x22; // 100010
final bitmask = 0x0f; // 001111
// 100010
// | 001111
// --------
// 101111
print((value | bitmask )); // 打印为47

3.6.3 按位非(~)

// 按位非(~)
final value = 9; // 01001 有符号
// 0 1 0 0 1 +9 二进制 最高位 0 整数 1 负数
// 0 0 1 1 0 补码
// 1 1 0 0 1 取反
// 1 1 0 1 0 加1
// --------
// 1 1 0 1 0 -10
print( value.toRadixString(2) ); // 1001
print( (~value).toRadixString(2) ); //-1010

3.6.4 异或(^)

// 异或(^) 不相同才为1
var foo = int.parse("1010", radix: 2);
var bar = int.parse("0010", radix: 2);
// 1 0 1 0 10
// 0 0 1 0 2
// --------
// 1 0 0 0 8
print(foo ^ bar); // 8

3.6.5 右移(>>)

// 右移(>>)
var foo = int.parse("0010", radix: 2);
// 0010 2的二进制数
// 0001 右移一位 1
print(foo >> 1);

3.6.6 左移(<<)

// 右移(<<)
var foo = int.parse("0010", radix: 2);
// 0010 2的二进制数
// 1000 左移2位 1
print(foo << 2); // 8

3.7 条件表达式

3.7.1 三目表达式 condition ? expr1: expr2

var isOk = true;
String res = isOk ? "i'm Ok" : "It's bad";
print(res);

4 流程控制

tip

基本跟C语言风格一致

4.1 if条件语句

bool conditionA = true;
bool conditionB = true;
if (conditionA) {
// do something
} else if (conditionB) {
// do something
} else {
// do something
}

4.2 forfor...in...循环

var items = [1, 2, 3, 4, 5];
for (var i = 0; i < items.length; i++ ) {
print(items[i]);
}
for (var item in items) {
print(item);
}

4.3 while 循环

  int mux = 0;
while (mux < 5) {
print(mux);
mux++;
}
print("----${mux}------");
do {
print(mux);
mux--;
} while(mux > 0);

4.4 循环关键字breakcontinue

  var continueItem = 2;
var breakItem = 3;
var items = [1, continueItem, breakItem ];

for (var item in items) {
if (item == breakItem) {
print("break\n");
break;
}
print(item);
}
print("----Dividing Line-----");
for (var item in items) {
if (item == continueItem) {
print("continue\n");
continue;
}
print(item);
}

输出

1
2
break

----Dividing Line----- 1 continue

3

### 4.5 `switch...case`流程
不同于`C`风格的`switch`,`dart`在这之上又加了个`continue`跳转标签,表达形式如下:
``` dart
switch(<condition>) {
case <case1>:
// todo ...
continue Tag; <-- 执行成功case1后再执行标签为tag的case2
<Tag>:
case <case2>:
// todo ...
break;
default:
// todo ...
}

var getYouColorToSeeSee = 'black';
switch(getYouColorToSeeSee) {
case 'black':
print("Oh, It's black");
continue anotherColor;
anotherColor:
case 'white':
print("Oh, It's white");
break;
default:
print('I can\'t see anything');
}

输出

Oh, It's black
Oh, It's white

5 方法

5.1 方法的定义

方法的定义范式为:

type functionName(arg1, arg2, ...) {
// todo ...
return result
}
String AreYouOk (String name) {
return "${name}: Are you ok?";
}
String AreYouOkAlias (String name) => "${name}: Are you ok?"; // <-- 如果能用一行搞定的话,也可以用箭头方式声明
print(AreYouOk("雷军"));

输出

雷军: Are you ok?

5.2 可选参数

调用方法时,有些参数可以传有些可以不传,这就是可行参数.

5.2.1 命名可选参数

可选参数可以通过参数命名来指定传入哪个可选参数,如:

String Foo (String arg, {String option1 = "", int option2 = 0 } ) {
return "arg=>${arg}, option1 => ${option1}, option2 => ${option2}";
}

print(Foo("hello", option1: "hello"));

> 输出
``` dart
arg=>hello, option1 => hello, option2 => 0
5.2.2 命名可选参数
String Foo (String arg, [String option1 = "", int option2 = 0 ] ) {
return "arg=>${arg}, option1 => ${option1}, option2 => ${option2}";
}

print(Foo("hello", "hello"));

输出

arg=>hello, option1 => hello, option2 => 0
warning

相较下, 命名可选参数具有更好的可读性

5.3 闭包

  • 闭包是一个方法对象
  • 闭包定义在其它方法的内部
  • 闭包能够访问外部的方法的局部变量并持有其状态,这也是闭包最主要的应用方式
Function foo () {
var total = 0;
return () => {
print(++total)
};
};

var fooIns = foo();
fooIns();
fooIns();
fooIns();

输出

1
2
3
tip

可以看到随着fooIns多次调用,里面的状态仍然保持上一次的操作的结果。就是说闭包在不对外暴露变量的同时,也能保持对上次执行后的状态。 闭包之所以能保持其状态,是因为其内部的声明变量被匿名方法再次访问了,如以上示例, 在调用foo时,变量total被声明了,按理说foo方法执行 完毕后变量total要被销毁掉,但由于在下个匿名方法中被访问,也就是变量在下个调栈(匿名方法)被访问,所以为了保证下个调用栈能访问能,变量 total不能被销毁,这就是为什么这个闭包有它状态的原因。

6 面向对象

6.1 声明对象和属性

void main() {
var personIns = Person();
personIns.name = "foo";
personIns.age = 18;
personIns.introduce();
}

class Person {
String name = "";
int age = 0;

void introduce() {
print("Name is ${name}, age is ${this.age}");

}
}

输出

Name is foo, age is 18
tip

与其它语言不同的是,实例化类时,dart可以用new关键字或省略.

6.2 库的可见性和类的可见性

dart中,一个文件就是一个库,而在这个文件中,如果定义名是收下划线开头的,则这个变量就不可导出。创建一个新的文件为util.dart, 内容如下:

class _bar { } // 不对外暴露
class person {
String _name = ""; // <-- 类成员属性不对外暴露
String name = ""; // <-- 类属性对外暴露
void _introduce() { } // 一样原理
void introduce() { } // 一样原理
}
String foo = ""; // 对外暴露

则导入util.dart库时并使用如:

import 'util.dart'; // 把库导入进来
void main() {
print(foo); // 访问正常
print(_bar); // 不可访问
personIns = person();
print( personIns.name ); // 访问正常
print( personIns._name ); // 访问正常失败
}

6.3 计算属性

计算属性是通过计算得来的,它本身可以看作是一个方法,而最后返回的结果就是属性。与typescript是计算属性的概念是一致的。 如:

void main() {
var feeIns = new Goods();
print(feeIns.totalFee());
}

class Goods {
num price = 10, // 价格
total = 10; // 数量

// 返回总费用
num totalFee() {
return price * total;
}
}
tip

上而定义了商品类,而在获取费用时却使用了方法来获取,一般来说,费用更像是一个属性,可以直接获取,所以这里定义一个代表动作的方法来获取, 感觉很违和。而费用又是通过计算获取的,所以计算属性就是派上用场了。修改后如下:

void main() {
var feeIns = new Goods();
print(feeIns.totalFee);
}

class Goods {
num price = 10, // 价格
total = 10; // 数量

// 总费用
num get totalFee => price * total;
}

而设置计算属性也是一样的,如:

void main() {
var feeIns = new Goods();
print(feeIns.totalFee);
feeIns.totalFee = 70;
print(feeIns.total);
}

class Goods {
final num price = 10; // 这里价格是固定的
num total = 10; // 数量

// 计算属性获取总费用
num get totalFee => price * total;

// 计算属性设置总费用
set totalFee(num newFee) {
total = newFee / price; // 由于价格是固定的,所以为了满足当前的费用,就需要改动商品的数量了
}
}

输出

100
7.0

6.4 构造方法

构造方法就是类名方法。

void main() {
var goodsIns = Goods(10, 10);
print(goodsIns.totalFee);
}

class Goods {
num price = 0;
num total = 0;

Goods(num price, num total) {
this.price = price;
this.total = total;
}

// 计算属性获取总费用
num get totalFee => price * total;

// 计算属性设置总费用
set totalFee(num newFee) {
total = newFee / price;
}
}

输出

100

如果传入的参数后面直接用于初始化类的属性,则可以有参数中直接使用this关键字的语法糖直接赋值给属性。

...
class Goods {
num price;
num total;

Goods(this.price, this.total); // 语法糖简写形式 ... }

### 6.5 常量类
如果一个类中的属性只能被设置一次, 那么这个类就是常量类。 常量类相对于对重复赋值的类来说,可能会执行地更快。而
常量类的属性都是用`final`关键字定义,如:

``` dart
void main() {
const goodsIns = Goods(10, 10);
print(goodsIns.getFee());
}

class Goods {
final num price;
final num total;
const Goods(this.price, this.total);

num getFee() => price * total;
}

输出

100

6.6 初始化列表

初始化列表可以用于初始化类的参数,它执行先于默认构造方法,其本身就是自定义的构造方法。

void main() {
var goodsIns = Goods.withMap({'total': 10});
print(goodsIns.getFee());
}

class Goods {
num price = 10;
final num total;

Goods.withMap(Map map): total = map['total'];

num getFee() => price * total;
}

输出

100

6.7 静态成员

  • 静态成员使用static关键字在变量和方法前进行定义
  • 静态成员不能访问非静态成员,而非静态成员可以访问静态成员,这是因为静态会常驻内存,而非静态成员会不一定常驻内存。 所以有静态 成员时其非静态成员未心已经实例化了,反之一样。
  • 类中的常量需要使用staticconst来声明
  • 实例化的类不能访问静态成员
pvoid main() {
SuperMan.fight(); // <-- 直接调用,不用实例化
}

class SuperMan {
static void fight() {
print("啊打打打打打打...");
print("KO");
}
}

输出

啊打打打打打打...
KO

6.8 对象操作符

6.8.1 对象成员预判符?

适用于对一个对象的成员不确定是否存在,如果在则调用,不在不调用。如果用if来判断要多写几行,而用?则一行就可以了。这与TS是一样。

pvoid main() {
SuperMan?.fight(); // 如果存在则使用
}

class SuperMan {
static void fight() {
print("啊打打打打打打...");
print("KO");
}
}

6.8.2 as类型断言

如果出现一个变量不知其类型,那么就可以用as强制给它断言。如:

void main() {
var man; // 不给任何类型
man = SuperMan();
// 由于man声明时没有指定明确的类型,导致无法推导出变量是什么,通过as能直接指定是什么类型
(man as SuperMan).fight();
}

class SuperMan {
void fight() {
print("啊打打打打打打...");
print("KO");
}
}

6.8.3 is!is类型判断

用于对一个变量类型的判断

void main() {
var man; // 不给任何类型
man = SuperMan();
if (man is SuperMan) { // <-- 是否是Superman
man.fight();
}
}

class SuperMan {
void fight() {
print("啊打打打打打打...");
print("KO");
}
}

6.8.4 联级操作符..

在连续作同一对象的成员时,无论是赋值还是调用,可以起到一种类型链式调用的简写方式。

void main() {
var superMan = SuperMan();
superMan..heroBar = "bar"
.. heroFoo = "foo"
.. fight();
}

class SuperMan {
String heroFoo = "";
String heroBar = "";

void fight() {
print("${heroFoo} VS ${heroBar}");
}
}

输出

foo VS bar

6.9 call方法

如果类实现了call方法,则该类实例化的对象还可以作为方法来调用一次。这有点类似于闭包,有调用一次后的结果还可以再次调用。

void main() {
var superMan = SuperMan();
superMan(); // <-- 调用call 方法

}

class SuperMan {
void call() {
print("The competition is progressing...");
}
}

7 面向对象高级特性

dart的面向对象的继承,抽象类,接口基本和java一致,但与其它编程语言不一样的是,还有个Mixins操作符覆写。

7.1 继承

java, TSPHP一样,在dart通过关键字extends继承父类后能有以下特性:

  • 子类继承父类可见属性和方法
  • 子类能再次定义同名成员的方法来覆盖父类的继承下来的成员。
  • 单继承
void main() {
Parent("foo", 35)
..ohAreYou()
..howOldAreYou();
Child("bar", 5)
..ohAreYou() // <-- 调用父类方法
..howOldAreYou(); // <-- 覆盖父类方法
}

class Parent {
String name;
int age = 0;

Parent(String this.name, int this.age);

void ohAreYou() {
print("${name}, Your dad.");
}

void howOldAreYou() {
print("${age}");
}
}

class Child extends Parent {
Child(String name, int age) : super(name, age);


void ohAreYou() {
print("${name}, Your son.");
}
}

输出

foo, Your dad.
35
bar, Your son.
5

7.1 继承中的构造方法

如果子类不显示声明构造方法且父类有声明无参数的构造方法,则子类实例化时会默认调用父类的构造方法.

void main() {
Parent();
Child(); // <-- 调用父类构造方法
}

class Parent { Parent() { print(this.toString()); } }

class Child extends Parent { }

但如果父类构造方法有参数要求时,子类也要显式实现构造方法,并调用父类构造方法时所以要的参数.
``` dart
void main() {
Parent("hello");
Child(); // <-- 调用父类构造方法
}

class Parent {
Parent(String foo) {
print(foo);
}
}

class Child extends Parent {
Child() : super("hello"); // <-- 父类有构造方法有参数要求时,子类显示声明并调用父类构造方法
}

输出

hello
hello
tip

父类的构造方法在子类构造方法调用父类构造方法的位置开始执行。

7.2 抽象类

抽象类跟java, TS等待面向对象的编程语言的抽象类是一致的,抽类可以看成一个普通类,只不过这个类的属性 构造方法和方法的声明是用描述的方式来声明的, 其目的用于被继承的子类必须实现抽象方法。意思是子类一旦继承了这个抽象类,那么它就必须实现抽象类中描述才行。抽象类通常的使用场景比如工场模式开发。 如何 保证工场中所生产出来的应用类是一致,抽象类就是很好的约束,只要工场模式中所生产的每一个类都继承自同一个抽象类,那么就可以保证每个产出类都有一致的实现。 抽象类的声明关键字是abstract, 如:

void main() {
Altman()
..fight() // 干架
..fly(); // 起飞
}

// 声明超人抽象类 -- 就是说超人的标配标准
abstract class SuperManAbstract {
// 要会飞
void fly () ;

// 要会干架
void fight();
}

// 定义奥特曼超人
class Altman extends SuperManAbstract {

void fight() {
print('奥特曼激光...');
}


void fly() {
print('奥特曼飞行模式...');
}
}

输出

奥特曼激光...
奥特曼飞行模式...

7.2 接口

接口的作用跟抽象类很象,都是用于约束类的实现用的,那为什么有抽象类了还要出个接口类呢? 这是因为接口是用于实现的其关键字是implements而抽象类 则是继承,在dart中只能是单继承,比如实现一个有卡卡罗特的龟派气功和贝吉塔的帅气的超级赛亚人,如果用抽象类的话,那么卡卡罗特是一个抽象类,而 贝吉塔又是一个抽象类,而dart又不可能同时2个类,怎么办呢?答案是,多例继承是不行的,但可以实现多个接口。代码大概是这样子的:

void main() {
SuperSaiyan() // 创造"融合超级赛亚人"1个
..guiPaiQiGong() // 有卡卡罗特的龟派气功
..faceValueOfPrince(); // 有贝吉塔的颜值
}

// 定义卡卡罗特抽象类
abstract class KakarotAbstract {
// 龟派气功
void guiPaiQiGong();
}

// 定义贝吉塔抽象类
abstract class VegitoAbstract {
// 王子的般的颜值
void faceValueOfPrince();
}


// 超级赛亚人 = 卡卡罗特 + 贝吉塔
class SuperSaiyan implements KakarotAbstract, VegitoAbstract{

void guiPaiQiGong() {
print("龟派气功, 发射...");
}


void faceValueOfPrince() {
print("王子的般的颜值, 暴表");
}
}

输出

龟派气功, 发射...
王子的般的颜值, 暴表

7.3 Mixins 多个类继承

  • Mixins有主要用处就是解决dart单例继承的问题,跟C++一样可以有多个父类。类似于龙珠中的融合术,只不过融合的人数不限制,融合多少人,就 有多少人的特性和能力一样。
  • 用于被Mixin的类不能有构造方法且只能继承自Object
  • 通过关键字with连接一个或多个mixin 比如初出江湖的少年,想成为一代高手高手高高手,必须要继承些前辈们的武林绝学才能独步天下笑傲江湖。所以准备下以下武林绝学:
void main() {
JumpingEndJunior()
..duGuJiuJiang()
..taXueWuHeng()
..kuiHuaBaoDian();
}

// 逍遥子-风清扬 class XiaoYouZhi { // 独孤九剑 void duGuJiuJiang() { print("剑法: 独孤九剑,无招胜有招"); } }

// 香帅-楚留香 class ChuLiuXiang { // 踏雪无痕 void taXueWuHeng() { print("轻功: 踏雪无痕"); } }

// 东方不败-葵花宝典 class DongFangBuBai { //葵花宝典 void kuiHuaBaoDian() { print("断子绝孙大法: 欲练此功,必先自宫"); } }

// 声明一个初出江湖的少年,然后得到3个武林神话的传承 - 跳涯少年 class JumpingEndJunior extends XiaoYouZhi with ChuLiuXiang, DongFangBuBai { }

> 输出
``` bash
剑法: 独孤九剑,无招胜有招
轻功: 踏雪无痕
断子绝孙大法: 欲练此功,必先自宫

7.4 操作符覆写

由一个对象去使用操作符,如果想更改操作符原有的定义,那么可以在类的内部对其进行重新地定义。 基本的范式是:

<type> operator <operator> (arg1, arg2, ...) {
return <type>
}

可覆写的操作符如下:

<+|[]
>/^[]=
<=~/&~
>=*<<==
-%>>
void main() {
print(Weight(1) > Weight(2));
}

class Weight {
double weight;

Weight(this.weight);

// 覆写 > 操作操作符
bool operator > (Weight w) {
return weight > w.weight;
}
}
tip

简单理解的话, 可以把对象 + 覆写的操作符看成是一个方法名,而操作符后就是方法的参数了

8 枚举

枚举是一个有穷序列,就是一个开发者声明的有限集合。由于枚举是有限的集合,用于对变量的限定,就是变量只能是集合内的任一元素,从而 确定了变量被限定在什么范围内。

void main() {
Map<Months, String> MonthsMapDesc = {
Months.January: "1月",
Months.February: "2月",
Months.March: "3月",
Months.April: "4月",
Months.May: "5月",
Months.June: "6月",
Months.July: "7月",
Months.August: "8月",
Months.September: "9月",
Months.October: "10月",
Months.November: "11月",
Months.December: "12月",
};
// 以上key就被Months合集给限定住了,key的值只能是month中的一元素
print(MonthsMapDesc[Months.January]);
}
// 声明一个月份合集枚举,那么以这个月份为类型的变量只能是集合内的任一变量
enum Months {
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
}

输出

1

9 泛型

泛型就是把期望的类型当成参数传递过去。然后返回对应的类型。如像列表,map类型当中在没有指定时可以是任意的类型,我们可以看作:

var list = <dynamic>[1, "string", {}]; // 可以是任意类型
var map = <dynamic, dynamic>{"foo": 1, 2: 2}; // 可以是任意的类型的key 和任意类型的value

就是说期望返回值可以是任意类型。但如果给它加上指定类型,就是说,期望它是什么样的类型。如:

var list = <num>[1, 2, 3]; //元素只能是
var map = <String, num>{"foo": 1, "bar": 2}; // key 只能是String, value只能是num

9.1 为什么要有泛型?泛型有什么用?

泛型可以解决类型的空间复杂度,如果声明一个费用列表,如果不给这个列表加以类型限制,那么这个列表可能是任意类型, 这就导致无法预测列表空间中的每一元素中可能是任意类型,而我们只是期望一组数字类型而已. 而泛型就是解决类型的的空间复杂度用的。

9.2 以及用在什么地方?

dart中可以用的复合类型中,比如以上的listmap, 也可以用在类和方法中,如:

  T foo<T>(T arg) {
return arg;
}
print(foo<String>("hello"));
print(foo<num>(1));

输出

hello
1

上面的foo方法的泛型意思是, 期望一个类型,然后参数也必须是这个类型,然后再返回这个期望类型的返回值;

void main() {
Bar<String>("hello").printExpectType();
Bar("hello").printExpectType(); // <-- 这里bar并没有指定期望的类型,但Bar的
// 初始化时检出类型后,作为期望类型的初始化
}

class Bar<T> {
T arg;
Bar(T this.arg);

void printExpectType() {
print(arg.runtimeType);
}
}

输出

String
String
warning

Bar("hello").printExpectType();这里明明没有指定类型,但是它依然能自动推导出来是什么类型,为什么呢?
我们可以的T看成是一个变量类型参数,如果以上在初始化类时,这样Bar<String>就相当于给变量类型参数T赋值为String,但 以上的Bar("hello").printExpectType()并没有看到给类型T赋值的行为?其实是有的,尽管初始化Bar时,没有把类型传递 过去,但,在初始化时的Bar(T this.arg);中根据参数的类型给推导出来了。