一.Hello Dart
1.1. 打印 print
print('hello dart');
// command + s 清理运行缓存
1.2. Dart程序分析
- Dart语言的入口也是main函数,并且必须显示的进行定义;
- Dart的入口函数main是没有返回值的;
- 传递给main的命令行参数,是通过List完成的。
从字面值就可以理解List是Dart中的集合类型。
其中的每一个String都表示传递给main的一个参数; - 定义字符串的时候,可以使用单引号或双引号;
- 每行语句必须使用分号结尾,很多语言并不需要分号,比如Swift、JavaScript;
二.定义变量
2.1. 定义变量(Explicit)
/**
声明变量
变量类型 变量名称 = 赋值;
*/
String name = 'xiao ming';
int age = 18;
double height = 1.88;
print('$name, $age, $height');
2.2. 类型推导(Type Inference)
/**
var/dynamic/const/final 变量名称 = 赋值;
const/final 初始化必须赋值
*/
- var
var name = 'name';
name = 'yh';
print(name.runtimeType);//String
- dynamic 动态类型
// dynamic可以改变变量类型,通常情况下不使用dynamic, 因为类型的变化会带来潜在的危险
dynamic age = '180';
print(age.runtimeType);//String
age = 180;
print(age.runtimeType);//int
- final & const
final和const都是用于定义常量的,也就是定义之后不可以修改
final name = 'yh';
// name = 'hy';//错误
const age = 18;
// age = 81;//错误
final和const区别
const在赋值时,赋值的内容必须是在编译期间就确定下来的
final在赋值时,可以动态获取,比如赋值一个函数;final一旦被赋值后就有确定的结果,不会再次赋值
void main(List<String> args) {
const name = getName();//错误的做法,因为要执行函数才能获取到值
final name2 = getName();
}
String getName() {
return 'yh';
}
const修饰赋值语句,只会使用同一份内存,可提高性能:
void main(List<String> args) {
final a = const Person();
final b = const Person();
print(identical(a, b));//true
final c = Person();
final d = Person();
print(identical(c, d));//false
}
class Person {
const Person();
}
三.数据类型
3.1. Int Double
//1.整数类型int
int age = 12;
int hexAge = 0x12;
print('$age,$hexAge');//12,18
//2.浮点型double
double height = 1.88;
//字符串与数字转换
//1.字符串转数字
var one = int.parse('111');
print('$one ${one.runtimeType}');//111 int
//2.数字转字符串
var num1 = 123;
var num2 = 123.456;
var num1Str = num1.toString();
var num2Str = num2.toString();
var num2StrD = num2.toStringAsFixed(1);
print('$num1Str,$num2Str,$num2StrD');//123,123.456,123.5
3.2. bool
注意:Dart中不能判断非0即真,或者非空即真
var isFlag = true;
print('$isFlag,${isFlag.runtimeType}');//true,bool
var message = 'dart';
//错误写法
if (message) {
print(message);
}
3.3. String
Dart字符串是UTF-16编码单元的序列。您可以使用单引号或双引号创建一个字符串:
//单行字符串
var str1 = 'str1';
var str2 = "str2";
//多行字符串
var str3 = '''
1
2
3
''';
//字符串拼接
var str4 = '$str1,$str2,$str3';
print(str4);
}
3.4. 集合类型
- 集合类型的定义
List:数组 / Set:集合 / Map:字典(映射)
var letters = ['a','b','c'];
List<int> numsList = [1,2,3];
print('$letters,${letters.runtimeType},$numsList,${numsList.runtimeType}');
//[a, b, c],List<String>,[1, 2, 3],List<int>
Set<int> numsSet = {1,2,3,4};
print('$numsSet,${numsSet.runtimeType}');
//{1, 2, 3, 4},_CompactLinkedHashSet<int>
Map<String, Object> map = {'name': 'yh', 'height': 1.88};
print('$map,${map.runtimeType}');
//{name: yh, height: 1.88},_InternalLinkedHashMap<String, Object>
- 集合的常见操作
print(letters.length);
print(numsSet.length);
print(objsMap.length);
//list操作
//增,删,包含
numsList.add(5);
numsSet.add(5);
numsList.remove(1);
numsList.removeAt(1);
numsList.contains(4);
print(numsList);//[2, 5]
//map操作
//1.根据key获取value
print(objsMap['name']);//yh
//2.获取所有的entries
print('${objsMap.entries},${objsMap.entries.runtimeType}');//(MapEntry(name: yh), MapEntry(height: 1.88)),MappedIterable<String, MapEntry<String, Object>>
//3.all keys
print(objsMap.keys);//(name, height)
//4.all values
print(objsMap.values);//(yh, 1.88)
//5.contain key || contain value
print('${objsMap.containsKey('name')},${objsMap.containsValue('yh')}');//true,true
//6.remove from key
objsMap.remove('name');
print(objsMap);//{height: 1.88}
四.函数 Function
4.1 函数定义
//函数返回值可以省略
// 返回值 函数的名称(参数列表) {
// 函数体
// return 返回值
// }
void main(List<String> args) {
print(sum(1, 2));
print(sum2(1, 2));
}
int sum(num1, num2) {
return num1 + num2;
}
//注意:这里只能是一个表达式,不能是一个语句
sum2(num1, num2) => num1 + num2;
4.2 函数的参数
可选参数
注意, 只有可选参数才可以有默认值, 必须参数不能有默认值
//命名可选参数: {param1, param2, ...}
//位置可选参数: [param1, param2, ...]
//命名可选参数
optionParamsFun1(String name, {int age = 10, double height = 1.8}) {
print('$name,$age,$height');
}
//调用
optionParamsFun1('yh');
optionParamsFun1('yh',age: 20);
optionParamsFun1('yh',age: 20,height: 1.88);
// yh,10,1.8
// yh,20,1.8
// yh,20,1.88
//位置可选参数
optionParamsFun1(String name, [int age = 10, double height = 1.8]) {
print('$name,$age,$height');
}
optionParamsFun1('yh');
optionParamsFun1('yh', 20);
optionParamsFun1('yh', 20, 1.88);
// yh,10,1.8
// yh,20,1.8
// yh,20,1.88
4.3. 函数是一等公民
函数可以作为参数、返回值
//调用
test(getFunc());
//1.定义一个函数
foo(String name) {
print(name);
}
//2.将函数作为另一个函数的返回值
getFunc() {
return foo;
}
//3.将函数作为另一个函数的参数
test(Function func) {
func("func");
}
4.4. 匿名函数
void main(List<String> args) {
//匿名函数
var movies = ['盗梦空间', '星际穿越', '少年派', '大话西游'];
//1.定义匿名函数
printElement(item) {
print(item);
}
movies.forEach(printElement);
//2.使用forEach遍历:匿名函数
movies.forEach((element) {
print(element);
});
movies.forEach((element) => print(element));
}
4.5. 词法作用域
dart中的词法有自己明确的作用域范围,它是根据代码的结构({})来决定作用域范围的
优先使用自己作用域中的变量,如果没有找到,则一层层向外查找。
var name = 'global';
main(List<String> args) {
// var name = 'main';
void foo() {
// var name = 'foo';
print(name);
}
foo();
}
4.6. 闭包作用域
闭包可以访问其词法范围内的变量,即使函数在其他地方被使用,也可以正常的访问。
void main(List<String> args) {
sum(num m) {
return (num n) {
return m + n;
};
}
var func = sum(10);
print(func(10));//20
}
4.7. 返回值问题
所有函数都返回一个值。如果没有指定返回值,则语句返回null;隐式附加到函数体。
main(List<String> args) {
print(foo()); // null
}
foo() {
print('foo function');
}
五.运算符
5.1.普通运算符
- 除法、整除、取余
var num = 7;
print(num / 3);//除 2.3333
print(num ~/ 3);//整除 2
print(num % 3);//取余 1
- ??=
//当变量不为null时使用变量,当变量为null时,后面的值将被赋值给变量
var name = 'lucy';
name ??= 'sala';
print(name);//lucy
var name2 = null;
name2 ??= 'Toms';
print(name2);//Toms
- ??
//当变量不为null时使用变量,当变量为null时使用后面的值
var name = 'lucy';
print(name ?? 'sala');//lucy
var name2 = null;
print(name2 ?? 'Toms');//Toms
5.2. 级联语法:…
对一个对象进行连续的操作,这个时候可以使用级联语法。
class Person {
String name = 'Tom';
void eat() {
}
void drink() {
}
void sleep() {
}
}
void main(List<String> args) {
final p1 = Person();
p1.name = 'Lucy';
p1.eat();
p1.drink();
p1.sleep();
//级联
final p2 = Person()
..name = "Tom"
..eat()
..drink()
..sleep();
}
5.3. if else
dart不支持非零即真,判断语句中必须有明确的bool类型
var isBool = true;
if (isBool) {
} else {
}
5.4. switch-case
var direction = 'east';
switch (direction) {
case 'east':
print('东方');
break;
case 'west':
print('西方');
break;
default:
print('其他方');
}
//东方
5.5. 循环
for、for in
var list = ['1','2','3'];
for (var i = 0; i < list.length; i++) {
print(list[i]);
}
for (var str in list) {
print(str);
}
//1 2 3
while do-while 与其他语法一致
break continue 与其他语法一致
六.枚举 enum
6.1.枚举的定义
枚举使用 enum关键字 来定义:
void main(List<String> args) {
print(Colors.red);
//枚举属性 index索引,从0开始;values包含所有枚举值的list
print(Colors.red.index);
print(Colors.values);
}
enum Colors {
red,
green,
blue
}
七.类 class
7.1. 类的定义
- 定义类用class关键字
- 类通常有两部分组成:成员(member)和方法(method)。
- dart没有public,private等关键字,使用下划线“_”来区分公有参数私有参数。
class 类名 {
类型 成员名;
返回值类型 方法名(参数列表) {
方法实现
}
}
- Dart2.0后,创建类对象可以省略 关键字new;
类中访问类属性一般省略this,但是有命名冲突时,不可省略。
void main(List<String> args) {
var p1 = Person();
p1.eat();
}
class Person {
String name = 'Tom';
void eat() {
String name = 'lucy';
print(name);//lucy
print(this.name);//Tom
}
}
7.2. 构造方法
- 普通构造方法
Dart为类提供了默认的构造函数,当有了自己的构造函数,默认的构造方法将会失效,不能再使用。
这是因为Dart不支持函数重载
void main(List<String> args) {
var p1 = Person('Tom');
}
class Person {
var name;
Person(String name1) {
this.name = name1;
}
//等同于
Person(this.name);
}
- 命名构造方法
class Person {
var name;
var age;
var height;
//命名构造方法
Person.withArgments(String name, int age) {
this.name = name;
this.age = age;
}
//命名构造方法2
Person.withArgments2();
//命名构造方法,传入map
Person.withMap(Map<String, Object>map) {
this.name = map['name'];
this.age = map['age'];
}
String toString() {
return '$name, $age, $height';
}
}
void main(List<String> args) {
var p = Person.withMap({'name':'Frank','age':18});
print(p);//Frank, 18, null
Person p2 = Person.withArgments2();
print(p2);//null, null, null
}
- 初始化列表 Initializer list
final 初始化时必须赋值
//初始化列表
class Point {
final num x;
final num y;
final num distant;
Point(this.x,this.y) : distant = sqrt(x * x + y * y);
//这种初始化变量的方法, 我们称之为初始化列表(Initializer list)
}
var point = Point(3, 4);
print(point.distant);
- 重定向构造方法
//重定向构造方法
class Person {
String name;
int age;
Person(this.name,this.age);
//重定向
Person.withName(String nameStr) : this(nameStr, 10);
}
- 常量构造方法
- 拥有常量构造方法的类中,所有的成员变量必须是final修饰的.
- 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字
- 如果是将结果赋值给const修饰的标识符时,const可以省略.
class Person {
final String name;
final int age;
const Person(this.name,this.age);
}
Person p = const Person('lucy', 18);
Person p2 = const Person('lucy', 18);
print(identical(p, p2));//true
const Person p = Person('lucy', 18);
const Person p2 = Person('lucy', 18);
print(identical(p, p2));//true
Person p3 = Person('lucy', 18);
Person p4 = Person('lucy', 18);
print(identical(p3, p4));//false
- 工厂构造方法(factory)
- Dart提供了factory关键字, 用于通过工厂去获取对象
- 最大的特点就是可以手动返回一个对象,可以创建单例
class Person {
final String name;
static final Map<String, Person> cache = <String, Person>{};
factory Person(String nameStr) {
if (cache.containsKey(nameStr)) {
final Person? p = cache[nameStr];
return p!;
} else {
final p = Person.withName(nameStr);
cache[nameStr] = p;
return p;
}
}
Person.withName(this.name);
}
Person p3 = Person('lucy');
Person p4 = Person('lucy');
print(identical(p3, p4));//false
- 单例模式
//写法一
class Singleton {
const Singleton._internal();
factory Singleton() => const Singleton._internal();
}
Singleton single = Singleton();
//写法二
class Singleton {
Singleton._privateInstance();
static final Singleton instance = Singleton._privateInstance();
factory Singleton(){
return instance;
}
}
//使用
Singleton single = Singleton();
//写法三
class Singleton {
Singleton._internal();
static final Singleton _instance = Singleton._internal();
static Singleton getInstance() {
return _instance;
}
}
//使用
var instance = Singleton.getInstance();
//写法四
class Singleton {
// 私有化构造方法
Singleton._privateConstructor();
static final Singleton _instance = Singleton._privateConstructor();
//同下 : static Singleton get instance => _instance;
static Singleton get instance { return _instance;}
}
//使用
var instance = Singleton.instance;
//写法五
class Singleton {
Singleton._privateConstructor();
static final Singleton instance = Singleton._privateConstructor();
}
//使用
void mian(){
var instance = Singleton.instance;
var instance2 = Singleton.instance;
print(instance == instance2);
}
7.3 setter和getter
class Person {
var name;
set setName(String nameStr){
name = nameStr;
}
String get getName{
print('getName');
return name;
}
}
Person p = Person();
p.name = '123';
print(p.getName);//getName 123
7.4 继承 extends
面向对象的其中一大特性就是继承,也是多态的使用前提。
- Dart中的继承是单继承;
- Dart中的继承使用extends关键字,子类中使用super来访问父类;
- 父类中的所有成员变量和方法都会被继承,,但是构造方法除外。
子类中可以调用父类的构造方法,对某些属性进行初始化:
- 子类的构造方法在执行前,将隐含调用父类的无参默认构造方法(没有参数且与类同名的构造方法)。
- 如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法。
class Person {
var name;
Person(this.name);
set setName(String nameStr){
name = nameStr;
}
String get getName{
print('getName');
return name;
}
eat() {
}
run(){
print('person run');
}
}
class Student extends Person {
int age;
Student.withAge(String name, int age): this.age = age, super(name);
@override
run() {
// TODO: implement run
super.run();
print('student run');
}
}
Student s = Student.withAge('Tom', 18);
s.name = '123';
print(s.getName);//getName 123
s.run();
//person run
// student run
7.5 抽象类 abstract
注意事项:
- 注意一:抽象类不能实例化.
- 注意二:抽象类中的抽象方法必须被子类实现, 抽象类中的已经被实现方法, 可以不被子类重写.
abstract class Animal {
eat();
run(){
}
}
class Dog extends Animal {
@override
eat() {
// TODO: implement eat
}
}
class SmallDog extends Dog {
@override
run() {
// TODO: implement run
}
}
7.6 隐式接口 implements
Dart中的接口比较特殊, 没有一个专门的关键字来声明接口.
- 默认情况下,定义的每个类都相当于默认也声明了一个接口,可以由其他的类来实现(因为Dart不支持多继承)
- 必须实现抽象类中的所有方法;即在通过implements实现某个类时,类中所有的方法都必须被重新实现(无论这个类原来是否已经实现过该方法):
abstract class Eat {
eat();
}
abstract class Run {
run() {
}
}
class Animal implements Eat,Run {
@override
eat() {
// TODO: implement eat
}
@override
run() {
// TODO: implement run
}
}
7.7 Mixin混入 mixin with
Mixin混入的方式
- 除了可以通过class定义类之外,也可以通过mixin关键字来定义一个类。
- 只是通过mixin定义的类用于被其他类混入使用,通过with关键字来进行混入。
mixin Eat {
eat();
}
mixin Run {
run(){
}
}
class Animal with Eat,Run {
@override
eat() {
// TODO: implement eat
}
// @override
// run() {
// // TODO: implement run
// }
}
7.8 类的成员和方法
- 对象级别的的成员和方法
class Person {
var name;
int age;
eat() {
}
}
- 类级别的成员和方法
class Person {
static var name;
static final age = 18;
static eat() {
}
}
八.泛型
8.1. List和Map泛型
//泛型写法
// List<Object>
//_InternalLinkedHashMap<String, Object>
var list = ['a','b','c',123];
var dic = {'key1':'value1', 'key2':123};
//限制类型
var list2 = <String>['a','b','c'];
var dic2 = <String, String>{'key1':'value1', 'key2':'value2'};
8.2. class泛型
- Object类型
//object
class Person {
Object name;
Object age;
Person(this.name, this.age);
}
Person p = Person('yh',18);
print(p.name.runtimeType);
print(p.age.runtimeType);
- T泛型
//T
class Person<T extends num> {
T age;
T height;
Person(this.age, this.height);
double sum(){
double s = (this.age as int) * (this.height as double);
return s;
}
}
Person p = Person(10,1.88);
print(p.age.runtimeType);// int
print(p.sum());// 18.799999999999997
9.空安全 “?”、“!”
为什么需要空安全?
- 目标:对于空安全而言,我们的目标是让您对代码中的 null 可见且可控,并且确保它不会传递至某些位置从而引发崩溃。
String? == String|Null
- 如果您能确定一条可空的表达式不为空, 您可以在其后添加 !让 Dart 处理为非空。
void main(List<String> arguments) {
int? age = null;
age = 10;
double height = age! * 10.0;
print(height);
print('Hello world!');
}
与swift中的“?”、“!”基本相同。
10.库
在Dart中,库的使用可以使代码的重用性得到提高,并且可以更好的组合代码。
Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明
10.1.库的导入
import语句用来导入一个库,后面跟一个字符串形式的Url来指定表示要引用的库,语法如下:
import '库所在的url';
常见的库URI有三种不同的形式
- 来自dart标准版,比如dart:io、dart:html、dart:math、dart:core(但是这个可以省略)
//dart:前缀表示Dart的标准库,如dart:io、dart:html、dart:math
import 'dart:io';
- 使用相对路径导入的库,通常指自己项目中定义的其他dart文件
//当然,你也可以用相对路径或绝对路径的dart文件来引用
import 'lib/student/student.dart';
- Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package
//Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
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();
10.2. 库的定义
- library关键字
通常在定义库时,我们可以使用library关键字给库起一个名字。
但目前我发现,库的名字并不影响导入,因为import语句用的是字符串URI
library math;
- part关键字
在之前我们使用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 "mathUtils.dart";
part "dateUtils.dart";
test_libary.dart文件
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
- export关键字
官方不推荐使用part关键字,那如果库非常大,如何进行管理呢?
将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入
mathUtils.dart文件
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.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()));
}
最后,也可以通过Pub管理自己的库自己的库,在项目开发中个人觉得不是非常有必要,所以暂时不讲解这种方式。