VSCode
编辑器来学习Dart
语言, 那么我们需要配置一下Dart
环境,我们安装了几个插件:
首先我们需要再VXCode
中创建一个文件,取名01-hello_dart
,然后添加如下内容
main(List<String> args) {
// list参数是接收在命令行输入的参数
print(args); //Dart语言中使用print函数来打印
}
变量类型 变量名称 = 赋值;
String name = 'coderwhy';
int age = 18;
double height = 1.88;
print('${name}, ${age}, ${height}'); // 拼接方式后续会讲解
String content = 'Hello Dart';
content = 'Hello World'; // 正确的
content = 111; // 错误的, 将一个int值赋值给一个String变量
声明格式:var/dynamic/const/final 变量名称 = 赋值;
var name = 'coderwhy';
name = 'kobe';
print(name.runtimeType); // String
String
类型的, 所以赋值其它类型的数据是不可以的,只能赋值String
类型的数据dynamic是一个明确类型,不属于类型推倒
,声明的数据类型就是dynamic
类型(相当于Any),在通常情况下不会使用dynamic,因为类型的变量会带来潜在的危险dynamic name = 'coderwhy';
print(name.runtimeType); // String
name = 18;
print(name.runtimeType); // int
//如果int类型的数据调用String的方法,在编译期间不会报错,但是在运行期间就会Crach
final name = 'coderwhy';
name = 'kobe'; // 错误做法
const age = 18;
age = 20; // 错误做法
String getName() {
return 'coderwhy';
}
main(List<String> args) {
const name = getName(); // 错误的做法, 因为要执行函数才能获取到值
final name = getName(); // 正确的做法
}
class Person {
const Person();
}
main(List<String> args) {
final a = const Person();
final b = const Person();
print(identical(a, b)); // true,
// 在Dart2.0之后, const可以省略
const p1 = const Person("why");
const p2 = const Person("why");
const p3 = const Person("lilei");
print(identical(p1, p2));//true
print(identical(p2, p3));//false
}
class Person {
final String name;
const Person(this.name);
//我们可以使用const来修饰,构造方法,通过传递的属性来控制是否创建同一个对象
}
const或则new
,实质上我们创建对象之前都是有new
关键字的final m = new Person();
也是可以创建对象的identical()
方法比较两个对象是否相等int
,浮点数用double
就行了。int
和double
可表示的范围并不是固定的,它取决于运行Dart的平台。// 1.整数类型int
int age = 18;
int hexAge = 0x12;
print(age);
print(hexAge);
// 2.浮点类型double
double height = 1.88;
print(height);
// 字符串和数字转化
// 1.字符串转数字
var one = int.parse('111');
var two = double.parse('12.22');
print('${one} ${one.runtimeType}'); // 111 int
print('${two} ${two.runtimeType}'); // 12.22 double
// 2.数字转字符串
var num1 = 123;
var num2 = 123.456;
var num1Str = num1.toString();
var num2Str = num2.toString();
var num2StrD = num2.toStringAsFixed(2); // 保留两位小数
print('${num1Str} ${num1Str.runtimeType}'); // 123 String
print('${num2Str} ${num2Str.runtimeType}'); // 123.456 String
print('${num2StrD} ${num2StrD.runtimeType}'); // 123.46 String
// 布尔类型
var isFlag = true;
print('$isFlag ${isFlag.runtimeType}');
var message = 'Hello Dart';
// 错误的写法
if (message) {
print(message)
}
// 1.定义字符串的方式
var s1 = 'Hello World';
var s2 = "Hello Dart";
// 可以使用三个单引号或者双引号表示多行字符串:
// 2.表示多行字符串的方式
var message1 = '''
哈哈哈
呵呵呵
嘿嘿嘿''';
// 2.字符串和表达式进行拼接
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);
List / Set / Map
。// List定义
// 1.使用类型推导定义
var letters = ['a', 'b', 'c', 'd'];
print('$letters ${letters.runtimeType}');
// 2.明确指定类型
List<int> numbers = [1, 2, 3, 4];
print('$numbers ${numbers.runtimeType}');
Flutter 升级到 2.2 后发现使用 new List()
来创建一个新的集合 的方式 显示成弃用了,替换方案如下:
List<String> list = [];
List<String> list = List.empty(growable: true);
//growable 为 false 是为 固定长度列表,为 true 是为 长度可变列表
创建给定长度的列表:
要创建具有给定长度的可增长列表,对于可为空元素类型,只需在创建后立即分配长度:
List<int> list = []..length = 3;
对于不可为空的元素类型,替代方法如下:
List<int> growableList = List<int>.filled(3, 0, growable: true);
// Set的定义
// 1.使用类型推导定义
var lettersSet = {'a', 'b', 'c', 'd'};
print('$lettersSet ${lettersSet.runtimeType}');
// 2.明确指定类型
Set<int> numbersSet = {1, 2, 3, 4};
print('$numbersSet ${numbersSet.runtimeType}');
// Map的定义
// 1.使用类型推导定义
var infoMap1 = {'name': 'why', 'age': 18};
print('$infoMap1 ${infoMap1.runtimeType}');
// 2.明确指定类型
Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'};
print('$infoMap2 ${infoMap2.runtimeType}');
// 获取集合的长度
print(letters.length);
print(lettersSet.length);
print(infoMap1.length);
// 添加/删除/包含元素
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));
// List根据index删除元素
numbers.removeAt(3);
print('$numbers');
// Map的操作
// 1.根据key获取value
print(infoMap1['name']); // why
// 2.获取所有的entries
print('${infoMap1.entries} ${infoMap1.entries.runtimeType}'); // (MapEntry(name: why), MapEntry(age: 18)) MappedIterable>
// 3.获取所有的keys
print('${infoMap1.keys} ${infoMap1.keys.runtimeType}'); // (name, age) _CompactIterable
// 4.获取所有的values
print('${infoMap1.values} ${infoMap1.values.runtimeType}'); // (why, 18) _CompactIterable
// 5.判断是否包含某个key或者value
print('${infoMap1.containsKey('age')} ${infoMap1.containsValue(18)}'); // true true
// 6.根据key删除元素
infoMap1.remove('age');
print('${infoMap1}'); // {name: why}
dart中是没有函数的重载的,所以不存在函数名相同,但是参数不同的方法
返回值 函数的名称(参数列表) {
函数体
return 返回值
}
int sum(num num1, num num2) {
return num1 + num2;
}
sum(num1, num2) {
return num1 + num2;
}
//Effective Dart建议对公共的API, 使用类型注解, 但是如果我们省略掉了类型, 依然是可以正常工作的
//但是通常我们不推荐省略函数的返回值类型, 因为这样会导致代码的可读性变差
//另外如果函数体只有一个表达式语句,我们可以使用箭头语法(注意这里只能是一个表达式,不能是一个语句)
sum(num1, num2) => num1 + num2;
{param1, param2, ...}
// 命名可选参数
printInfo1(String name, {int age, double height}) {
print('name=$name age=$age height=$height');
}
// 调用printInfo1函数
printInfo1('why'); // name=why age=null height=null
printInfo1('why', age: 18); // name=why age=18 height=null
printInfo1('why', age: 18, height: 1.88); // name=why age=18 height=1.88
printInfo1('why', height: 1.88); // name=why age=null height=1.88
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("可选参数方法");
}
[param1, param2, ...]
// 定义位置可选参数
printInfo2(String name, [int age, double height]) {
print('name=$name age=$age height=$height');
}
// 调用printInfo2函数
printInfo2('why'); // name=why age=null height=null
printInfo2('why', 18); // name=why age=18 height=null
printInfo2('why', 18, 1.88); // name=why age=18 height=1.88
注意:只有可选参数才可以有默认值, 必须参数不能有默认值
printInfo5(String name, {int age = 18, double height = 1.88}) {
print('name=$name age=$age height=$height');
}
main(List<String> args) {
// 1.将函数赋值给一个变量
var bar = foo;
print(bar);
// 2.将函数作为另一个函数的参数
test(foo);
// 3.将函数作为另一个函数的返回值
var func =getFunc();
func('kobe');
}
// 1.定义一个函数
foo(String name) {
print('传入的name:$name');
}
// 2.将函数作为另外一个函数的参数
test(Function func) {
func('coderwhy');
}
// 3.将函数作为另一个函数的返回值
getFunc() {
return foo;
}
// 封装test函数, 要求: 传入一个函数
// void test(Function foo) {
// foo("why");
// }
//使用typedef来顶一个类型
typedef Calculate = int Function(int num1, int num2);
//这样写是的代码可读性变差
// void test(int foo(int num1, int num2)) {
// foo(20, 30);
// }
void test(Calculate calc) {
calc(20, 30);
}
(参数列表) {函数体};
test(() {
print("匿名函数被调用");
return 10;
});
就近原则
var name = 'global';
main(List<String> args) {
// var name = 'main';
void foo() {
// var name = 'foo';
print(name);
}
foo();
}
var num = 7;
print(num / 3); // 除法操作, 结果2.3333..
print(num ~/ 3); // 整除操作, 结果2;
print(num % 3); // 取模操作, 结果1;
main(List<String> args) {
// 1.??=
// 当原来的变量有值时, 那么??=不执行
// 原来的变量为null, 那么将值赋值给这个变量
// var name = null;
// name ??= "lilei";
// print(name);
// ??
// ??前面的数据有值, 那么就使用??前面的数据
// ??前面的数据为null, 那么就使用后面的值
var name = null;
var temp = name ?? "lilei";
print(temp);
}
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();
}
不支持非空即真或者非0即真,必须有明确的bool类型
//基本循环
for (var i = 0; i < 5; i++) {
print(i);
}
//for in遍历List和Set类型
var names = ['why', 'kobe', 'curry'];
for (var name in names) {
print(name);
}
while
和do-while
和其他语言一致break
和continue
用法也是一致每一个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('其他方向');
}
}
class 类名 {
类型 成员名;
返回值类型 方法名(参数列表) {
方法体
}
}
会省略this,但是有命名冲突时,this不能省略;
class Person {
String name;
eat() {
print('$name在吃东西');
}
}
main(List<String> args) {
// 1.创建类的对象
var p = new Person(); // 直接使用Person()也可以创建
// 2.给对象的属性赋值
p.name = 'why';
// 3.调用对象的方法
p.eat();
}
从Dart2开始,new关键字可以省略
没有明确指定构造方法时
,将默认拥有一个无参的构造方法
。默认的构造方法将会失效
,不能使用
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';
}
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同于
Person(this.name, this.age);
// 父类应用指向子类对象
// Object和dynamic
// Object调用方法时, 编译时会报错
// dynamic调用方法时, 编译时不报错, 但是运行时会存在安全隐患
// Object obj = "why";
// print(obj.substring(1));
// 明确声明
// dynamic obj = 123;
// print(obj.substring(1));
但是在开发中, 我们确实希望实现更多的构造方法,怎么办呢?
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);
在之后的开发中, 我们也可以利用命名构造方法,提供更加便捷的创建对象方式:
// 新的构造方法
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);
main(List<String> args) {
var p = Person("why");
}
class Person {
final String name;
final int age;
//这种初始化变量的方法, 我们称之为初始化列表(Initializer list)
Person(this.name, {int age}): this.age = age ?? 10 {
// this.age = 10;
}
//赋值时只能写一个赋值语句,但是上面的初始化列表方法可以写一些运算符来赋值,比如三目运算符 计算赋值
Person(this.name, {this.age = 10}){
// this.age = 10;
}
}
main(List<String> args) {
var p = Person("why");
print(p.age);
}
class Person {
String name;
int age;
// Person(this.name): age = 0;
// 构造函数的重定向
Person(String name): this._internal(name, 0);
Person._internal(this.name, this.age);
}
identical(对象1, 对象2)
函数来判断两个对象是否是同一个对象:main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // false
}
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)); // true
const p3 = Person("why");
const p4 = Person("why");
print(identical(p3, p4));//true
}
class Person {
final String name;
const Person(this.name);
}
Dart提供了factory关键字, 用于通过工厂去获取对象
main(List<String> args) {
final p1 = Person.withName("why");
final p2 = Person.withName("why");
print(identical(p1, p2));
}
// class Person {
// final String name;
// final String color = "red";
// const Person(this.name);
// const Person(this.color);
// }
// 普通的构造函数: 会自动返回创建出来的对象, 不能手动的返回
// 工厂构造函数最大的特点: 可以手动的返回一个对象
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);
}
setter
和getter
了main(List<String> args) {
final p = Person();
// 直接访问属性
p.name = "why";
print(p.name);
// 通过getter和setter访问
p.setName = "lilei";
print(p.getName);
}
class Person {
String name;
// setter
set setName(String name) => this.name = name;
// getter
String get getName => name;
//上面是采用=>语法 简写, 下面是不是简写的
set setName(String name) {
this.name = name;
}
String get getName {
return this.name;
}
}
减少我们的代码量
,也是多态的使用前提。
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';
}
}
我们知道,继承是多态使用的前提。
所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
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;
}
}
// 注意一:继承自抽象类后, 必须实现抽象类的抽象方法
// class Rectangle extends Shape {
// @override
// int getArea() {
// return 100;
// }
// }
工厂构造方法使用 external关键字来修饰,该关键字的作用:将方法的声明和方法的实现分离
interface/protocol
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
print('超人在奔跑');
}
@override
fly() {
print('超人在飞');
}
}
重新实现
(无论这个类原来是否已经实现过该方法)。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");
}
}
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("去上课");
}
}
枚举在开发中也非常常见, 枚举也是一种特殊的类, 通常用于表示固定数量的常量值。
枚举最主要的 还是为了代码的 安全
枚举使用关键字enum
来定义:
main(List<String> args) {
print(Colors.red);
}
enum Colors {
red,
green,
blue
}
main(List<String> args) {
print(Colors.red.index);
print(Colors.green.index);
print(Colors.blue.index);
print(Colors.values);
}
enum Colors {
red,
green,
blue
}
// 创建List的方式
var names1 = ['why', 'kobe', 'james', 111];
print(names1.runtimeType); // List
// 限制类型
var names2 = <String>['why', 'kobe', 'james', 111];//最后一个报错
List<String> names3 = ['why', 'kobe', 'james', 111];// 最后一个报错
// 创建Map的方式
var infos1 = {1: 'one', 'name': 'why', 'age': 18};
print(infos1.runtimeType); // _InternalLinkedHashMap
// 对类型进行显示
Map<String, String> infos2 = {'name': 'why', 'age': 18}; // 18不能放在value中
var infos3 = <String, String>{'name': 'why', 'age': 18}; // 18不能放在value中
如果我们需要定义一个类, 用于存储位置信息Location, 但是并不确定使用者希望使用的是int类型,还是double类型, 甚至是一个字符串, 这个时候如何定义呢?
一种方案是使用Object类型, 但是在之后使用时, 非常不方便
另一种方案就是使用泛型.
使用Object定义的方式
main(List<String> args) {
Location l1 = Location(10, 20);
print(l1.x.runtimeType); // Object
}
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); // int
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType); // String
}
}
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);
// 错误的写法, 类型必须继承自num
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType);
}
class Location<T extends num> {
T x;
T y;
Location(this.x, this.y);
}
最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。
main(List<String> args) {
var names = ['why', 'kobe'];
var first = getFirst(names);
print('$first ${first.runtimeType}'); // why String
}
T getFirst<T>(List<T> ts) {
return ts[0];
}
在Dart中,你可以导入一个库来使用它所提供的功能。
库的使用可以使代码的重用性得到提高,并且可以更好的组合代码。
Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明
import语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下
import '库所在的uri';
// import 'dart:io';
// import 'dart:isolate';
// import 'dart:async';
// import 'dart:math';
// 1.系统的库: import 'dart:库的名字';
import 'dart:math';
main(List<String> args) {
final num1 = 20;
final num2 = 30;
print(min(num1, num2));
}
//dart:前缀表示Dart的标准库,如dart:io、dart:html、dart:math
import 'dart:io';
//当然,你也可以用相对路径或绝对路径的dart文件来引用
import 'lib/student/student.dart';
//Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
import 'package:flutter/material.dart';
只导入库中某些内容
,或者刻意隐藏库里面某些内容
,可以使用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();
library关键字
通常在定义库时,我们可以使用library关键字给库起一个名字。
但目前我发现,库的名字并不影响导入,因为import语句用的是字符串URI
library math;
在之前我们使用student.dart作为演练的时候,只是将该文件作为一个库。
在开发中,如果一个库文件太大,将所有内容保存到一个文件夹是不太合理的,我们有可能希望将这个库进行拆分,这个时候就可以使用part关键字了不过官方已经不建议使用这种方式了:
mathUtils.dart
文件part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart
文件part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart
文件:
part of "utils.dart";
part "mathUtils.dart";
part "dateUtils.dart";
test_libary.dart
文件import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
官方不推荐使用part
关键字,那如果库非常大,如何进行管理呢?
dart
文件作为库文件,使用export
关键字在某个库文件中单独导入mathUtils.dart
文件part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart
文件part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart
文件:library utils;
export "mathUtils.dart";
export "dateUtils.dart";
test_libary.dart
文件import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
_
是我们区分 公有和私有的一种方式