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 for
或 for...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 循环关键字break
和continue
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
关键字在变量和方法前进行定义 - 静态成员不能访问非静态成员,而非静态成员可以访问静态成员,这是因为静态会常驻内存,而非静态成员会不一定常驻内存。 所以有静态 成员时其非静态成员未心已经实例化了,反之一样。
- 类中的常量需要使用
static
和const
来声明 - 实例化的类不能访问静态成员
pvoid main() {
SuperMan.fight(); // <-- 直接调用,不用实例化
}
class SuperMan {
static void fight() {
print("啊打打打打打打...");
print("KO");
}
}
输出
啊打打打打打打...
KO6.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
, TS
和PHP
一样,在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.
57.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只能是num9.1 为什么要有泛型?泛型有什么用?
泛型可以解决类型的空间复杂度,如果声明一个费用列表,如果不给这个列表加以类型限制,那么这个列表可能是任意类型, 这就导致无法预测列表空间中的每一元素中可能是任意类型,而我们只是期望一组数字类型而已. 而泛型就是解决类型的的空间复杂度用的。
9.2 以及用在什么地方?
在
dart
中可以用的复合类型中,比如以上的list
和map
, 也可以用在类和方法中,如: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);
中根据参数的类型给推导出来了。