1. VSCode配置
- 首先我们是使用
VSCode 编辑器来学习Dart 语言, 那么我们需要配置一下Dart 环境,我们安装了几个插件:
- Dart和Flutter插件是为Flutter开发准备的
- Code Runner可以点击右上角的按钮让我快速运行代码
2. Main函数
2.1 main函数
首先我们需要再VXCode 中创建一个文件,取名01-hello_dart ,然后添加如下内容
main(List<String> args) {
print(args);
}
使用终端运行打印参数结果如下: 
2.2 Main函数分析
- Dart语言的入口也是main函数,并且必须显示的进行定义
- Dart的入口函数main是没有返回值的;
- 传递给main的命令行参数,是通过List完成的
- 从字面值就可以理解List是Dart中的集合类型
- 其中的每一个String都表示传递给main的一个参数;
- 定义字符串的时候,可以使用单引号或双引号;
- 每行语句必须使用分号结尾,很多语言并不需要分号,比如Swift、JavaScript;
3. 定义变量
3.1明确声明(Explicit)
String name = 'coderwhy';
int age = 18;
double height = 1.88;
print('${name}, ${age}, ${height}');
- 注意: 定义的变量可以修改值, 但是不能赋值其他类型
String content = 'Hello Dart';
content = 'Hello World';
content = 111;
3.2 类型推倒
声明格式:var/dynamic/const/final 变量名称 = 赋值;
3.2.1 var
var name = 'coderwhy';
name = 'kobe';
print(name.runtimeType);
- 这里可以dart是可以推导出name是
String 类型的, 所以赋值其它类型的数据是不可以的,只能赋值String 类型的数据
3.2.1 dynamic
- Dart中的
dynamic是一个明确类型,不属于类型推倒 ,声明的数据类型就是dynamic 类型(相当于Any),在通常情况下不会使用dynamic,因为类型的变量会带来潜在的危险
dynamic name = 'coderwhy';
print(name.runtimeType);
name = 18;
print(name.runtimeType);
3.2.2 final和const
- final和const都是用于定义常量的, 也就是定义之后值都不可以修改
final name = 'coderwhy';
name = 'kobe';
const age = 18;
age = 20;
- final和const的区别?
- const在赋值时, 赋值的内容必须是在编译期间就确定下来的(必须赋值,常量值)
- final在赋值时, 可以动态获取, 比如赋值一个函数(运行期间来确定一个值)
String getName() {
return 'coderwhy';
}
main(List<String> args) {
const name = getName();
final name = getName();
}
- const放在赋值语句的右边,可以共享对象,提高性能:
class Person {
const Person();
}
main(List<String> args) {
final a = const Person();
final b = const Person();
print(identical(a, b));
const p1 = const Person("why");
const p2 = const Person("why");
const p3 = const Person("lilei");
print(identical(p1, p2));
print(identical(p2, p3));
}
class Person {
final String name;
const Person(this.name);
}
- 在Dart2.0之后我们创建对象时候,可以省略
const或则new ,实质上我们创建对象之前都是有new 关键字的final m = new Person(); 也是可以创建对象的 identical() 方法比较两个对象是否相等- const修改对象构造方法 常量构造器 可以共享对象
- 如果构造器使用const来修饰的话, 那么该类中的所有成员变量都需要使用final来修饰
- 根据属性来创建对象, 属性传递一样,生成的对象也一样,如果属性传递的值不一样那么生成的对象也不一样, 这个是Dart底层给我们做的优化,但是严格意义上来讲并不是单利
4 .数据类型
4.1 数字类型
- 对于数值来说,我们也不用关心它是否有符号,以及数据的宽度和精度等问题。只要记着整数用
int ,浮点数用double 就行了。 - 需要说明的是Dart中的
int 和double 可表示的范围并不是固定的,它取决于运行Dart的平台。
int age = 18;
int hexAge = 0x12;
print(age);
print(hexAge);
double height = 1.88;
print(height);
var one = int.parse('111');
var two = double.parse('12.22');
print('${one} ${one.runtimeType}');
print('${two} ${two.runtimeType}');
var num1 = 123;
var num2 = 123.456;
var num1Str = num1.toString();
var num2Str = num2.toString();
var num2StrD = num2.toStringAsFixed(2);
print('${num1Str} ${num1Str.runtimeType}');
print('${num2Str} ${num2Str.runtimeType}');
print('${num2StrD} ${num2StrD.runtimeType}');
4.2 布尔类型
- 布尔类型中,Dart提供了一个bool的类型, 取值为true和false
var isFlag = true;
print('$isFlag ${isFlag.runtimeType}');
- Dart中不能判断非0即真, 或者非空即真
- Dart的类型安全性意味着您不能使用if(非booleanvalue)或assert(非booleanvalue)之类的代码。
var message = 'Hello Dart';
if (message) {
print(message)
}
4.3 字符串类型
- Dart字符串是UTF-16编码单元的序列。您可以使用单引号或双引号创建一个字符串:
var s1 = 'Hello World';
var s2 = "Hello Dart";
var message1 = '''
哈哈哈
呵呵呵
嘿嘿嘿''';
- 字符串和其他变量或表达式拼接: 使用${expression}, 如果表达式是一个标识符, 那么{}可以省略
var name = "why";
var age = 19;
var height = 1.88;
var message1 = "my name is $name, age is $age, height is $height";
var message2 = "name is $name, type is ${name.runtimeType}";
print(message1);
print(message2);
5. 集合类型
- 对于集合类型,Dart则内置了最常用的三种:
List / Set / Map 。
5.1 List / Set / Map 定义
var letters = ['a', 'b', 'c', 'd'];
print('$letters ${letters.runtimeType}');
List<int> numbers = [1, 2, 3, 4];
print('$numbers ${numbers.runtimeType}');
- Set定义
- 其实,也就是把[]换成{}就好了。
- Set和List最大的两个不同就是:Set是无序的,并且元素是不重复的。
var lettersSet = {'a', 'b', 'c', 'd'};
print('$lettersSet ${lettersSet.runtimeType}');
Set<int> numbersSet = {1, 2, 3, 4};
print('$numbersSet ${numbersSet.runtimeType}');
var infoMap1 = {'name': 'why', 'age': 18};
print('$infoMap1 ${infoMap1.runtimeType}');
Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'};
print('$infoMap2 ${infoMap2.runtimeType}');
5.2 集合的常规操作
5.3.1 所有集合都支持的获取长度的属性length
print(letters.length);
print(lettersSet.length);
print(infoMap1.length);
5.3.2 添加/删除/包含
- 对List来说,由于元素是有序的,它还提供了一个删除指定索引位置上元素的方法
numbers.add(5);
numbersSet.add(5);
print('$numbers $numbersSet');
numbers.remove(1);
numbersSet.remove(1);
print('$numbers $numbersSet');
print(numbers.contains(2));
print(numbersSet.contains(2));
numbers.removeAt(3);
print('$numbers');
5.3.3 Map操作
- 由于它有key和value,因此无论是读取值,还是操作,都要明确是基于key的,还是基于value的,或者是基于key/value对的。
print(infoMap1['name']);
print('${infoMap1.entries} ${infoMap1.entries.runtimeType}');
print('${infoMap1.keys} ${infoMap1.keys.runtimeType}');
print('${infoMap1.values} ${infoMap1.values.runtimeType}');
print('${infoMap1.containsKey('age')} ${infoMap1.containsValue(18)}');
infoMap1.remove('age');
print('${infoMap1}');
6. 函数
6.1 函数定义
dart中是没有函数的重载的,所以不存在函数名相同,但是参数不同的方法 - Dart是一种真正的面向对象语言,所以即使函数也是对象,所有也有类型, 类型就是Function,这也就意味着函数可以作为变量定义或者作为其他函数的参数或者返回值.
- 函数的定义方式:
返回值 函数的名称(参数列表) {
函数体
return 返回值
}
int sum(num num1, num num2) {
return num1 + num2;
}
sum(num1, num2) {
return num1 + num2;
}
sum(num1, num2) => num1 + num2;
6.2 函数的参数
- 函数的参数可以分成两类: 必须参数和可选参数, 前面使用的参数都是必须参数.
6.2.1 可选参数-命名可选参数
- 命名可选参数:
{param1, param2, ...} - 命名可选参数传值的时候,必须要带上参数的名称不然会报错
printInfo1(String name, {int age, double height}) {
print('name=$name age=$age height=$height');
}
printInfo1('why');
printInfo1('why', age: 18);
printInfo1('why', age: 18, height: 1.88);
printInfo1('why', height: 1.88);
- 命名可选参数, 可以指定某个参数是必传的
- Dart2.10中@required替换成了required 来标记可选参数
- 如果可选参数没有设置必选标记的话, 那么必须使用?标记,不然会报错
Error: The parameter 'age' can't have a value of 'null' because of its type 'String', but the implicit default value is 'null'.
void printInfo3(String name,
{int? age, double? height, required String address}) {
print('name=$name age=$age height=$height address=$address');
}
void printInfo4(String name, {String age, String, height}) {
print("可选参数方法");
}
6.2.2 可选参数-位置可选参数
- 位置可选参数:
[param1, param2, ...] - 位置可选参数中,实参和形参在进行匹配时,是根据位置的匹配
printInfo2(String name, [int age, double height]) {
print('name=$name age=$age height=$height');
}
printInfo2('why');
printInfo2('why', 18);
printInfo2('why', 18, 1.88);
6.3 参数默认值
- 参数可以有默认值, 在不传入的情况下, 使用默认值
注意:只有可选参数才可以有默认值, 必须参数不能有默认值 - Dart中的main函数就是一个接受可选的列表参数作为参数的, 所以在使用main函数时, 我们可以传入参数, 也可以不传入
printInfo5(String name, {int age = 18, double height = 1.88}) {
print('name=$name age=$age height=$height');
}
6.4 函数一等公民
- 在很多语言中, 函数并不能作为一等公民来使用, 比如Java/OC. 这种限制让编程不够灵活, 所以现代的编程语言基本都支持函数作为一等公民来使用, Dart也支持.
- 这就意味着你可以将函数赋值给一个变量, 也可以将函数作为另外一个函数的参数或者返回值来使用.
main(List<String> args) {
var bar = foo;
print(bar);
test(foo);
var func =getFunc();
func('kobe');
}
foo(String name) {
print('传入的name:$name');
}
test(Function func) {
func('coderwhy');
}
getFunc() {
return foo;
}
typedef Calculate = int Function(int num1, int num2);
void test(Calculate calc) {
calc(20, 30);
}
6.5 匿名函数
- 大部分我们定义的函数都会有自己的名字, 比如前面定义的foo、test函数等等。
- 但是某些情况下,给函数命名太麻烦了,我们可以使用没有名字的函数,这种函数可以被称之为匿名函数( anonymous function),也可以叫lambda或者closure
- 匿名函数格式:
(参数列表) {函数体};
test(() {
print("匿名函数被调用");
return 10;
});
6.6 词法作用域
- dart中的词法有自己明确的作用域范围,它是根据代码的结构({})来决定作用域范围的
优先使用自己作用域中的变量,如果没有找到,则一层层向外查找。 就近原则
var name = 'global';
main(List<String> args) {
void foo() {
print(name);
}
foo();
}
7. 运算符
7.1 除法、整除、取模运算
var num = 7;
print(num / 3);
print(num ~/ 3);
print(num % 3);
7.2 ??=赋值操作和条件运算符
- dart有一个很多语言都不具备的赋值运算符:
- 当变量为null时,使用后面的内容进行赋值。
- 当变量有值时,使用自己原来的值
main(List<String> args) {
var name = null;
var temp = name ?? "lilei";
print(temp);
}
7.3 级联语法
- 某些时候,我们希望对一个对象进行连续的操作,这个时候可以使用级联语法,有点类似链式调用
class Person {
String name;
void run() {
print("${name} is running");
}
void eat() {
print("${name} is eating");
}
void swim() {
print("${name} is swimming");
}
}
main(List<String> args) {
final p1 = Person();
p1.name = 'why';
p1.run();
p1.eat();
p1.swim();
final p2 = Person()
..name = "why"
..run()
..eat()
..swim();
}
8 流程控制
8.1 if和else
- 和其他语言用法一样,但是这里需要注意的是
不支持非空即真或者非0即真,必须有明确的bool类型
8.2 循环操作
for (var i = 0; i < 5; i++) {
print(i);
}
var names = ['why', 'kobe', 'curry'];
for (var name in names) {
print(name);
}
while 和do-while 和其他语言一致break 和continue 用法也是一致
8. 3 switch-case
- 普通的switch使用:
每一个case语句,默认情况下必须以一个break结尾 ,要不然会有穿透情况
main(List<String> args) {
var direction = 'east';
switch (direction) {
case 'east':
print('东面');
break;
case 'south':
print('南面');
break;
case 'west':
print('西面');
break;
case 'north':
print('北面');
break;
default:
print('其他方向');
}
}
9. 类和对象
9.1 类的定义
- 在Dart中,定义类用class关键字
- 类通常有两部分组成:成员(member)和方法(method)。
- 定义类的伪代码如下:
class 类名 {
类型 成员名;
返回值类型 方法名(参数列表) {
方法体
}
}
- 编写一个简单的person类
- 我们在方法中使用属性(成员/实例变量)时,并没有加this
- Dart的开发风格中,在方法中通常使用属性时,
会省略this,但是有命名冲突时,this不能省略;
class Person {
String name;
eat() {
print('$name在吃东西');
}
}
main(List<String> args) {
var p = new Person();
p.name = 'why';
p.eat();
}
9.2 构造方法
9.2.1 普通构造方法
- 我们知道, 当通过类创建一个对象时,会调用这个类的构造方法
- 当类中
没有明确指定构造方法时 ,将默认拥有一个无参的构造方法 。 - 前面的Person中我们就是在调用这个构造方法.
- 当有了自己的构造方法时,
默认的构造方法将会失效 ,不能使用
- 当然,你可能希望明确的写一个默认的构造方法,但是会和我们自定义的构造方法冲突;
- 这是因为
Dart本身不支持函数的重载 (名称相同, 参数不同的方式)
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@override
String toString() {
return 'name=$name age=$age';
}
}
- 另外,在实现构造方法时,通常做的事情就是通过参数给属性赋值
- 为了简化这一过程, Dart提供了一种更加简洁的语法糖形式.
- 上面的构造方法可以优化成下面的写法:
Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(this.name, this.age);
9.2.2 命名构造方法
但是在开发中, 我们确实希望实现更多的构造方法,怎么办呢?
- 因为不支持方法(函数)的重载,所以我们没办法创建相同名称的构造方法。
- 我们需要使用命名构造方法
class Person {
String name;
int age;
Person() {
name = '';
age = 0;
}
Person.withArgments(String name, int age) {
this.name = name;
this.age = age;
}
@override
String toString() {
return 'name=$name age=$age';
}
}
var p1 = new Person();
print(p1);
var p2 = new Person.withArgments('why', 18);
print(p2);
在之后的开发中, 我们也可以利用命名构造方法,提供更加便捷的创建对象方式:
- 比如开发中,我们需要经常将一个Map转成对象,可以提供如下的构造方法
Person.fromMap(Map<String, Object> map) {
this.name = map['name'];
this.age = map['age'];
}
var p3 = new Person.fromMap({'name': 'kobe', 'age': 30});
print(p3);
9.2.3 初始化列表
main(List<String> args) {
var p = Person("why");
}
class Person {
final String name;
final int age;
Person(this.name, {int age}): this.age = age ?? 10 {
}
Person(this.name, {this.age = 10}){
}
}
9.2.4 重定向构造方法
- 在某些情况下, 我们希望在一个构造方法中去调用另外一个构造方法, 这个时候可以使用`重定向构造方法
- 在一个构造函数中,去调用另外一个构造函数(注意:是在冒号后面使用this调用)
main(List<String> args) {
var p = Person("why");
print(p.age);
}
class Person {
String name;
int age;
Person(String name): this._internal(name, 0);
Person._internal(this.name, this.age);
}
9.2.5 常量构造函数
- 在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法.
- 默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象,看下面代码:
- 这里我们使用
identical(对象1, 对象2) 函数来判断两个对象是否是同一个对象:
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2));
}
class Person {
String name;
Person(this.name);
}
- 但是, 如果将构造方法前加
const进行修饰 ,那么可以保证同一个参数,创建出来的对象是相同的 - 这样的构造方法就称之为
常量构造方法 。
main(List<String> args) {
var p1 = const Person('why');
var p2 = const Person('why');
print(identical(p1, p2));
const p3 = Person("why");
const p4 = Person("why");
print(identical(p3, p4));
}
class Person {
final String name;
const Person(this.name);
}
- 常量构造方法的一些注意点:
- 注意点-:拥有常量构造方法的类中,所有的成员变量必须是final修饰的
- 注意二: 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字
- 如果是将结果赋值给const修饰的标识符时,const可以省略.
9.2.6 工厂构造方法
Dart提供了factory关键字, 用于通过工厂去获取对象
- 需求: 传入相同的name和color都能返回相同的对象
main(List<String> args) {
final p1 = Person.withName("why");
final p2 = Person.withName("why");
print(identical(p1, p2));
}
class Person {
String name;
String color;
static final Map<String, Person> _nameCache = {};
static final Map<String, Person> _colorCache = {};
factory Person.withName(String name) {
if (_nameCache.containsKey(name)) {
return _nameCache[name];
} else {
final p = Person(name, "default");
_nameCache[name] = p;
return p;
}
}
factory Person.withColor(String color) {
if (_colorCache.containsKey(color)) {
return _colorCache[color];
} else {
final p = Person("default", color);
_colorCache[color] = p;
return p;
}
}
Person(this.name, this.color);
}
9.3 setter和getter
- 默认情况下,Dart中类定义的属性是可以直接被外界访问的
- 但是某些情况下,我们希望监控这个类的属性被访问的过程,这个时候就可以使用
setter 和getter 了
main(List<String> args) {
final p = Person();
p.name = "why";
print(p.name);
p.setName = "lilei";
print(p.getName);
}
class Person {
String name;
set setName(String name) => this.name = name;
String get getName => name;
set setName(String name) {
this.name = name;
}
String get getName {
return this.name;
}
}
9.4 类的继承
- 面向对象的其中一大特性就是继承,继承不仅仅可以
减少我们的代码量 ,也是多态的使用前提。 - Dart中的继承使用
extends 关键字,子类中使用super 来访问父类。 - 父类中的所有成员变量和方法都会被继承,,但是构造方法除外
main(List<String> args) {
}
class Animal {
int age;
Animal(this.age);
}
class Person extends Animal {
String name;
Person(this.name, int age): super(age);
}
- 子类可以
拥有自己的成员变量 , 并且可以对父类的方法进行重写 :
class Person extends Animal {
String name;
@override
run() {
print('$name在奔跑ing');
}
}
- 子类中可以调用父类的构造方法,对某些属性进行初始化:
- 子类的构造方法在执行前,将隐含调用父类的
无参默认构造方法 (没有参数且与类同名的构造方法) - 如果父类没有
无参默认构造方法 ,则子类的构造方法必须在初始化列表中通过super 显式调用父类的某个构造方法
class Animal {
int age;
Animal(this.age);
run() {
print('在奔跑ing');
}
}
class Person extends Animal {
String name;
Person(String name, int age) : name=name, super(age);
@override
run() {
print('$name在奔跑ing');
}
@override
String toString() {
return 'name=$name, age=$age';
}
}
9.5 抽象类
我们知道,继承是多态使用的前提。
所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
- 什么是 抽象方法? 在Dart中没有具体实现的方法(没有方法体),就是抽象方法
- 抽象方法,必须存在于抽象类中。
- 抽象类是使用abstract声明的类
main(List<String> args) {
final s = Shape();
final map = Map();
print(map.runtimeType);
}
abstract class Shape {
int getArea();
String getInfo() {
return "形状";
}
factory Shape() {
return null;
}
}
- 注意一:抽象类不能实例化.
- 注意二:抽象类中的抽象方法必须被子类实现, 抽象类中的已经被实现方法, 可以不被子类重写.
- 抽象类是不能实例化的,但是系统很多抽象类都实例化了, 是因为使用工厂构造方法来实例化的, 抽象类是不可以使用普通的构造方法来实例化的
工厂构造方法使用 external关键字来修饰,该关键字的作用:将方法的声明和方法的实现分离
9.6 隐式接口
- Dart只支持单继承
- Dart 中没有哪一个关键字是来定义接口,没有关键字
interface/protocol - 默认情况下所有的类都是隐式接口
- 当将一个类当做接口使用时,那么实现这个接口的类,必须实现这个接口中所有方法
- 默认情况下,定义的每个类都相当于默认也声明了一个接口,可以由其他的类来实现(因为Dart不支持多继承)
- 在开发中,我们通常将用于给别人实现的类声明为抽象类
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
print('超人在奔跑');
}
@override
fly() {
print('超人在飞');
}
}
9.7 Mixin混入
- 在通过implements实现某个类时,类中所有的方法都必须被
重新实现 (无论这个类原来是否已经实现过该方法)。 - 但是某些情况下,一个类可能希望直接复用之前类的原有实现方案,怎么做呢?
- 使用继承吗?但是Dart只支持单继承,那么意味着你只能复用一个类的实现
- Dart提供了另外一种方案: Mixin混入的方式
- 除了可以通过class定义类之外,也可以通过mixin关键字来定义一个类
- 只是通过mixin定义的类用于被其他类混入使用,通过with关键字来进行混入
main(List<String> args) {
final sm = SuperMan();
sm.running();
sm.flying();
}
mixin Runner {
void running() {
print("runner running");
}
}
mixin Flyer {
void flying() {
print("flying");
}
}
class Animal {
void eating() {
print("动物次东西");
}
void running() {
print("animal running");
}
}
class SuperMan extends Animal with Runner, Flyer {
@override
void eating() {
super.eating();
}
void running() {
print("SuperMan running");
}
}
- 如果是重名方法优先级 先用自己的 -> 混入的类中的方法–>父类的方法
9.8 类成员和方法
- 前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法
- 在Dart中我们使用static关键字来定义:
main(List<String> args) {
Person.courseTime = "8:00";
print(Person.courseTime);
Person.gotoCourse();
}
class Person {
String name;
static String courseTime;
void eating() {
print("eating");
}
static void gotoCourse() {
print("去上课");
}
}
9.9 枚举类型
枚举在开发中也非常常见, 枚举也是一种特殊的类, 通常用于表示固定数量的常量值。
-
枚举最主要的 还是为了代码的 安全 -
枚举使用关键字enum 来定义:
main(List<String> args) {
print(Colors.red);
}
enum Colors {
red,
green,
blue
}
- 枚举的属性
- index: 用于表示每个枚举常量的索引, 从0开始
- values: 包含每个枚举值的List.
main(List<String> args) {
print(Colors.red.index);
print(Colors.green.index);
print(Colors.blue.index);
print(Colors.values);
}
enum Colors {
red,
green,
blue
}
- 注意一: 您不能子类化、混合或实现枚举。
- 注意二: 不能显式实例化一个枚举
10 泛型
10.1 List和Map的泛型
var names1 = ['why', 'kobe', 'james', 111];
print(names1.runtimeType);
var names2 = <String>['why', 'kobe', 'james', 111];
List<String> names3 = ['why', 'kobe', 'james', 111];
var infos1 = {1: 'one', 'name': 'why', 'age': 18};
print(infos1.runtimeType);
Map<String, String> infos2 = {'name': 'why', 'age': 18};
var infos3 = <String, String>{'name': 'why', 'age': 18};
10.2 类定义的泛型
如果我们需要定义一个类, 用于存储位置信息Location, 但是并不确定使用者希望使用的是int类型,还是double类型, 甚至是一个字符串, 这个时候如何定义呢?
main(List<String> args) {
Location l1 = Location(10, 20);
print(l1.x.runtimeType);
}
class Location {
Object x;
Object y;
Location(this.x, this.y);
}
main(List<String> args) {
Location l2 = Location<int>(10, 20);
print(l2.x.runtimeType);
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType);
}
}
class Location<T> {
T x;
T y;
Location(this.x, this.y);
}
main(List<String> args) {
Location l2 = Location<int>(10, 20);
print(l2.x.runtimeType);
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType);
}
class Location<T extends num> {
T x;
T y;
Location(this.x, this.y);
}
10.3 泛型方法的定义
最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。
main(List<String> args) {
var names = ['why', 'kobe'];
var first = getFirst(names);
print('$first ${first.runtimeType}');
}
T getFirst<T>(List<T> ts) {
return ts[0];
}
11 库的使用
在Dart中,你可以导入一个库来使用它所提供的功能。
库的使用可以使代码的重用性得到提高,并且可以更好的组合代码。
Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明
11.1 库的导入
import语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下
import '库所在的uri';
import 'dart:math';
main(List<String> args) {
final num1 = 20;
final num2 = 30;
print(min(num1, num2));
}
- 常见的库URI有三种不同的形式
- 来自dart标准版,比如dart:io、dart:html、dart:math、dart:core(但是这个可以省略)
- 使用相对路径导入的库,通常指自己项目中定义的其他dart文件
- Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package
import 'dart:io';
import 'lib/student/student.dart';
import 'package:flutter/material.dart';
- 库文件中内容的显示和隐藏
- 如果希望
只导入库中某些内容 ,或者刻意隐藏库里面某些内容 ,可以使用show 和hide 关键字 - **show关键字:**可以显示某个成员(屏蔽其他)
- **hide关键字:**可以隐藏某个成员(显示其他)
import 'lib/student/student.dart' show Student, Person;
import 'lib/student/student.dart' hide Person;
- 库中内容和当前文件中的名字冲突
- 当各个库有命名冲突的时候,可以使用
as关键字 来使用命名空间
import 'lib/student/student.dart' as Stu;
Stu.Student s = new Stu.Student();
11.2 库的定义
library关键字
通常在定义库时,我们可以使用library关键字给库起一个名字。
但目前我发现,库的名字并不影响导入,因为import语句用的是字符串URI
library math;
在之前我们使用student.dart作为演练的时候,只是将该文件作为一个库。
在开发中,如果一个库文件太大,将所有内容保存到一个文件夹是不太合理的,我们有可能希望将这个库进行拆分,这个时候就可以使用part关键字了不过官方已经不建议使用这种方式了:
part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
part of "utils.dart";
part "mathUtils.dart";
part "dateUtils.dart";
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
11.3 export关键字
官方不推荐使用part 关键字,那如果库非常大,如何进行管理呢?
- 将每一个
dart 文件作为库文件,使用export 关键字在某个库文件中单独导入 mathUtils.dart 文件
part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
library utils;
export "mathUtils.dart";
export "dateUtils.dart";
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
|