dart语法

195 阅读12分钟
  1. dart入口函数是
void main() {}
  • 入口函数main函数必须定义
  • 入口函数main函数是没有return的
  • 传递给main函数的参数,应该通过List<String>完成
  • 定义字符串时,用单引号或双引号
  • 每行语句必须用分号结尾
  1. 变量类型
  • 变量类型 变量名称 赋值
  • var->不限制变量类型,但是赋值后后期不能改变类型
  • dynamic->不限制变量类型,赋值后后期仍可以改变类型
  • final->定义常量,可以动态获取,比如说接收方法的返回值
  • const->定义常量,不能动态获取,值必须是在编译期间就定下来
  • const可以放在赋值语句的右边,共享对象,提高性能
final a = const Person();
  • 获取当前变量的类型name.runtimeType
  1. 数字类型
  • 整型int, 浮点型double
  • 整型转浮点型, 先转为字符串
  int a = 18;
  double b = 3.14159;
  double c = double.parse(a.toString());
  int d = int.parse(b.toStringAsFixed(0));
  print('${c}:${c.runtimeType}');  print('${d}:${d.runtimeType}');
  1. 布尔值
  • 不能判断非0为真,或是非空为真,只有true == true
var message = 'Hello Dart';
  // 错误的写法
  if (message) {
    print(message)
  }
  1. 字符串 三个单引号或双引号表示多行
  var s1 = 'hello world';  
  var s2 = "hello dart";
  var s3 = 'hello \'world';
  var s4 = "hello'world";
  var s5 = '''
哈哈哈
呵呵呵
嘿嘿嘿''';
print('i want to say $s1')

${}用于在和字符串拼接,如果只是一个标识符而不用进行计算可以省略{} 6. 集合的类型

  • List 列表
    以下两种定义方式都是List:
  var letters = ['a', 'b', 'c', 'd'];
  print('$letters : ${letters.runtimeType}');//JSArray<String>
  List<int> numbers = [1,2,3,4,5];
  print('$numbers : ${numbers.runtimeType}');//JSArray<int>
  • Set 集合
    以下两种定义方式都是Set:(set与list的区别,set是无序并且不重复的)
var lettersSet = {'a', 'b', 'c', 'd'};
print('$lettersSet ${lettersSet.runtimeType}');//_LinkedHashSet<String>
Set<int> numbersSet = {1, 2, 3, 4};
print('$numbersSet ${numbersSet.runtimeType}');//_LinkedHashSet<int>
  • Map 映射
    以下两种定义方式都是Map:
var infoMap1 = {'name': 'why', 'age': 18};
print('$infoMap1 ${infoMap1.runtimeType}');
Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'};
print('$infoMap2 ${infoMap2.runtimeType}');
  1. 集合的常见操作
  • 所有集合都支持的获取长度的属性 .length
  • Set/List添加/删除/包含/相互转换
  List<int> num1 = [1,2,3,4,5];
  Set<int> num2 = {1,2,3,4,5};
  //增
  num1.add(6);
  num2.add(6);
  //删
  num1.remove(1);
  num2.remove(1);
  //是否包含
  print(num1.contains(2));  
  print(num2.contains(2));
  //由于List是有序的,可以根据索引删除
  num1.removeAt(3);
  //转换
  num1.toSet();
  num2.toList();
  • Map的操作
 Map<String,Object> user = {
    'height':188,
    'age':20,
    'sex':'male'
    };
//   根据key获取value
  print(user['sex']);
//   获取所有entries
  print('${user.entries}:${user.entries.runtimeType}');
//   获取所有keys
  print('${user.keys}:${user.keys.runtimeType}');
//   获取所有value
  print('${user.values}:${user.values.runtimeType}');
//   判断是否包含某个key或value
  print('${user.containsKey('age')},${user.containsValue(20)}');
// 根据key删除元素
  user.remove('age');
  print(user);
  1. 函数的基本定义
返回值 函数名称(参数列表){
    函数体
    return 返回值
}

返回值类型可以省略
如果函数中只有一个表达式,也可以使用箭头语法

sum(num1, num2) => num1 + num2;
  1. 函数的参数
  • 命名可选参数{param1, param2, ...}
void main() {
 printInfo1('why');  //name=why age=null height=null
 printInfo1('why',age:18);//name=why age=18 height=null
 printInfo1('why',height:188);//name=why age=null height=188
 printInfo1('why',height:188,age:20);//name=why age=20 height=188
}
printInfo1(String name, {int age, double height}) {
  print('name=$name age=$age height=$height');
}

可以指定某个参数是必传的(需要在flutter中使用,引用flutter/material.dart或其他)

printInfo3(String name, {int age, double height, @required String address}) {
  print('name=$name age=$age height=$height address=$address');
}
  • 位置可选参数[param1, param2, ...]
void main() {
 printInfo1('why');  //name=why age=null height=null
 printInfo1('why',18);//name=why age=18 height=null
 printInfo1('why',188);//name=why age=18 height=null
 printInfo1('why',18,188);//name=why age=18 height=188
}
printInfo1(String name, [int age, double height]) {
  print('name=$name age=$age height=$height');
}
  • 参数默认值(只有可选参数才能有默认值,必传参数不能)
void main(){
  printInfo4('lili');
}
printInfo4(String name,{int age = 18, double height = 1.88}){
  print('name=$name age=$age height=$height');
}
  • 函数是一等公民
    面向对象的语言中,类是一等公民,因为类是最重要的
    意味着你可以将函数赋值给一个变量,也可以将函数作为另一个函数的参数或者返回值来使用
void main(){
  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;
}
//4. 如何定义传入参数的要求
typedef Calculate = int Function(int num1, int num2);
void test(Calculate calc) {
  calc(20, 30);
}
  • 匿名函数的使用
main(List<String> args) {
  // 1.定义数组
  var movies = ['盗梦空间', '星际穿越', '少年派', '大话西游'];

  // 2.使用forEach遍历: 有名字的函数
  printElement(item) {
    print(item);
  }
  movies.forEach(printElement);

  // 3.使用forEach遍历: 匿名函数1(箭头函数的方法体只能有一行)
  movies.forEach((item) {
    print(item);
  });
  movies.forEach((item) => print(item));
  
  //4.使用forEach遍历: 匿名函数2(匿名函数)
  movies.forEach((item){
    print(item);
  });
}
//返回匿名函数
var demo1 = demo();
  print(demo1(20, 30));
  Calculate demo() {
  return (num1, num2) {
    return num1 * num2;
  };
}
  • 词法作用域 以{}来决定作用域范围,优先使用自己作用域里的变量,如果没有找到,则一层层向外找
var name = 'global';
main(List<String> args) {
  // var name = 'main';
  void foo() {
    // var name = 'foo';
    print(name);
  }

  foo();
}
  • 词法闭包,可以访问词法范围内的变量
main() {
  makeAdder(num addBy) {
    return (num i) {
      return i + addBy;
    };
  }
  var adder2 = makeAdder(2);
  print(adder2(10)); // 12
  print(adder2(6)); // 8

  var adder5 = makeAdder(5);
  print(adder5(10)); // 15
  print(adder5(6)); // 11
}
  • 返回值 每个函数都要返回一个值,如果没有指定返回值,则隐式添加函数体return null
  1. 运算符
  • ~/整除
  • ??=赋值操作 当变量为null时使用后面的内容进行赋值num ??= 123
  • ??条件运算符,跟上一个的区别是??只是判断,是三目运算符的一种简写 ??=是赋值
  var name = null;
  name ?? "lilei";

  var name2 = null;
  name2 ??= "lilei";
  print(name);//null
  print(name2);//lilei
  • ..对一个对象进行连续操作
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();
}
class 类名{
    类型 成员名;
    返回值类型 方法名(参数列表){
        方法体
    }
}
class Person {
  String name;

  eat() {
    print('$name在吃东西');
  }
}

在方法中使用属性时,可以省略this,但是有命名冲突时,要加上this
从dart2开始,new关键字也可以省略

//var p = new Person();也可以
  var p = Person(); 

  // 2.给对象的属性赋值
  p.name = 'why';

  // 3.调用对象的方法
  p.eat();
  1. 类的构造方法
  • 普通构造方法
    当类中没有明确指定构造方法时,将默认拥有一个无参的构造方法,当有了自己的构造方法,默认的构造方法会失效
main() {
  var p = Person('lili',18); 
  print(p);
}
class Person {
  String name;
  int age;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  //等同于
  //Person(this.name, this.age);

  @override
  String toString() {
    return 'name=$name age=$age';
  }
}
  • 命名构造方法
    当我们希望实现更多的构造方法时,由于不支持函数重载,这时我们就要使用命名构造方法
main() {
  var p1 = Person(); 
  print(p1);//name= age=0
  var p2 = Person.withArgments('tina',19);
  print(p2);//name=tina age=19
  var p3 = new Person.fromMap({'name': 'kobe', 'age': 30});
  print(p3);//name=kobe age=30
}
class Person {
  String name;
  int age;

  Person() {
    name = '';
    age = 0;
  }
	// 命名构造方法
  Person.withArgments(String name, int age) {
    this.name = name;
    this.age = age;
  }
  Person.fromMap(Map<String, Object> map) {
    this.name = map['name'];
    this.age = map['age'];
  }
  @override
  String toString() {
    return 'name=$name age=$age';
  }
}
  • 初始化列表
//在构造函数中定义default值
 var p = Person("why");
class Person {
  final String name;
  final int age;

  // Person(this.name, {int age}): this.age = age ?? 10 {}
//多个参数用逗号分隔
Person(this.name, {String time,String test}): this.time = time ?? DateTime.now().toString(),this.test = test??'Colors.blue' {}
 
  // 保留
  Person(this.name, {this.age = 10});
  
  //以上两种方式当赋值是常量时都可以,但当默认值是需要运行才能获得的值时只能用上面那种
Person(this.name, {String time}): this.time = time ?? DateTime.now().toString() {}
//x
Person(this.name, {this.time = DateTime.now().toString()});
}
class Point {
  final num x;
  final num y;
  final num distance;

  // 错误写法 如果distance的类型不是final则这种写法也可以
  // Point(this.x, this.y) {
  //   distance = sqrt(x * x + y * y);
  // }

  // 正确的写法
  Point(this.x, this.y) : distance = sqrt(x * x + y * y);
}
  • 在一个构造方法中调用另一个构造方法(重定向构造函数)(作用后面补充TODO)
class Person {
  String name;
  int age;

  Person(this.name, this.age);
  Person.fromName(String name) : this(name, 0);
}

  • 常量构造函数
    在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法, 一般构造函数写法:
main(List<String> args) {
  var p1 = Person('why');
  var p2 = Person('why');
//   identical 判断是否是同一个对象
  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
}

class Person {
  final String name;

  const Person(this.name);
}

注意: ① 拥有常量构造方法的类中,所有的成员变量必须是final修饰的
② 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字 如果是将结果赋值给const修饰的标识符时,const可以省略.

  • 工厂构造方法
    意义是可以指定返回的对象,举例:我们希望name相同时返回同一个对象,color相同时返回同一个对象,那么我们判断list中是否有这个key,如果有就返回list[key],如果没有就创建
    利用factory关键字,通过工厂去获取对象
// 在普通的构造函数中不需要写返回值,因为会默认返回对象 但是工厂构造函数需要自己手动返回一个对象
main(List<String> args) {
  final p1 = Person.withName("why");
  final p2 = Person.withName("why");
  print(identical(p1, p2));
}

// 只要name相同 返回同一个对象 只要color相同 返回同一个对象 
// 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);
}


  1. setter和getter
    类内定义的属性,默认是可以被外界直接访问的,如果我们要监控类的属性被访问的过程,就可以使用setter和getter(就算设置了getter和setter,直接访问属性d.color也可以)
main(List<String> args) {
  final d = Dog("黄色");
  d.setColor = "黑色";
  print(d.getColor);
}

class Dog {
  String color;

  String get getColor {
    return color;
  }
  set setColor(String color) {
    this.color = color;
  }

  Dog(this.color);
}
//简写
set setName(String name) => this.name = name;
get getName => name;
  1. 类的继承
    继承可以减少代码量,也是多态的使用前提
    继承使用extends关键字,子类中使用super来访问父类
    父类中所有成员变量和方法都会被继承,但是构造方法除外
    子类可以拥有自己的成员变量,并且可以对父类方法进行重写
main(List<String> args) {
  var p = new Person();
  p.age = 18;
  p.run();
  print(p.age);
}

class Animal {
  int age;

  run() {
    print('在奔跑ing');
  }
}

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';
  }
}

void main(){
  var p1 = Person('lili',12);
  p1.run();
}

使用父类的参数

class Animal{
  int age;

  Animal(this.age);
}

class Person extends Animal{
  String name;

  Person(this.name,int age):super(age);
}
  1. 抽象类(todo 后期理解)
    继承是多态使用的前提,当定义很多通用的调用接口时,我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
    但父类本身可能并不需要对某些方法进行具体的实现,所有父类中定义的方法,我们可以定义为抽象方法
    抽象方法就是没有具体实现的方法(没有方法体)
    注意 抽象方法必须存在于抽象类中
    抽象类就是使用abstract声明的类
abstract class Shape {
  getArea();
}

class Circle extends Shape {
  double r;

  Circle(this.r);

  @override
  getArea() {
    return r * r * 3.14;
  }
}

class Reactangle extends Shape {
  double w;
  double h;

  Reactangle(this.w, this.h);

  @override
  getArea() {
    return w * h;
  }
}

void main(){
  var c1 = Circle(2);
  print(c1.getArea());
  var r1 = Reactangle(2,2);
  print(r1.getArea());
}

① 抽象类不能实例化
② 抽象类中的抽象方法必须被子类实现,抽象类中的已经被实现方法,可以不被子类重写
如果抽象类要实例化,那么可以在抽象类中实现工厂类,如Map,具体使用不明白TODO

 final p = Shape2();
abstract class Shape2{
  // 可以没有方法的实现 只有声明
  void getArea();

  String getInfo(){
    return '形状';
  }

  factory Shape2(){
    return null;
  }
}
  1. 隐式接口
    dart中没有一个专门的关键字来声明接口,定义的每个类都相当于默认也声明了一个接口,可以由其他类来实现(因为dart不支持多继承)
    当我们将一个类当作接口使用时 那么实现这个接口的类 必须实现这个接口中的所有方法
abstract class Runner {
  run();
}

abstract class Flyer {
  fly();
}

class SuperMan implements Runner, Flyer {
  @override
  run() {
    print('超人在奔跑');
  }

  @override
  fly() {
    print('超人在飞');
  }
}

void main(){
  var c1 = SuperMan();
  c1.fly();
  c1.run();
}
  1. mixin混入
    在通过implements实现某个类时,类中的所有方法都必须被重新实现(无论这个类原来是否实现过该方法)
    在某种情况下,一个类可能希望直接复用之前类的原有实现方法,应该怎么做
    ① 使用继承,但是dart只支持单继承,那么只能复用一个类的实现
    ② 使用Maxin混入的方式
    除了通过class定义类之外,还可以通过mixin关键字来定义一个类,通过mixin定义的类用于被其他类混入使用,通过with关键字来进行混入
main(List<String> args) {
  var superMan = SuperMain();
  superMan.run();
  superMan.fly();
}

mixin Runner {
  run() {
    print('在奔跑');
  }
}

mixin Flyer {
  fly() {
    print('在飞翔');
  }
}

// implements的方式要求必须对其中的方法进行重新实现
// class SuperMan implements Runner, Flyer {}

class SuperMain with Runner, Flyer {

}
  1. 类成员和方法
    之前的成员和方法都属于对象级别的,有时我们也需要定义类级别的成员和方法,这时我们用static关键字来定义
main(List<String> args) {
  var stu = Student();
  stu.name = 'why';
  stu.sno = 110;
  stu.study();

  Student.time = '早上8点';
  // stu.time = '早上9点'; 错误做法, 实例对象不能访问类成员
  Student.attendClass();
  // stu.attendClass(); 错误做法, 实现对象不能访问类方法
}

class Student {
  String name;
  int sno;

  static String time;

  study() {
    print('$name在学习');
  }

  static attendClass() {
    print('去上课');
  }
}
  1. 枚举类型
    枚举也很常见,是一种特殊的类,通常用于表示固定数量的常量值
    枚举通过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
}

不能子类化,混合或实现枚举,也不能显式实例化一个枚举(枚举某个场景的所有情况)

  1. 泛型
    List使用时的泛型写法
// 创建List的方式
  var names1 = ['why', 'kobe', 'james', 111];
  print(names1.runtimeType); // List<Object>

  // 限制类型
  var names2 = <String>['why', 'kobe', 'james', 111]; // 最后一个报错
  List<String> names3 = ['why', 'kobe', 'james', 111]; // 最后一个报错

Map使用时的泛型写法

 // 创建Map的方式
  var infos1 = {1: 'one', 'name': 'why', 'age': 18}; 
  print(infos1.runtimeType); // _InternalLinkedHashMap<Object, Object>

  // 对类型进行显示
  Map<String, String> infos2 = {'name': 'why', 'age': 18}; // 18不能放在value中
  var infos3 = <String, String>{'name': 'why', 'age': 18}; // 18不能放在value中

类定义的泛型
如果我们需要定义一个类,用于存储位置信息location,但是并不确定使用者希望使用的是int类型还是double类型,甚至是一个字符串,这个时候该如何定义呢?
① 使用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);
}

如果我们使用时希望类型只能时num类型

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 = [12, 'kobe'];
  var first = getFirst(names);
  print('$first ${first.runtimeType}'); // 12 int
}

T getFirst<T>(List<T> ts) {
  return ts[0];
}
  1. 库的导入
import 'dart:io';//dart标准版
import 'lib/student/student.dart';//相对路径引入
import 'package:flutter/material.dart';//第三方库
import 'lib/student/student.dart' show Student, Person;//shou显示某个成员,屏蔽其他
import 'lib/student/student.dart' hide Person;//hide隐藏某个成员,显示其他
import 'lib/student/student.dart' as Stu;//当各个库命名冲突时,可以使用as关键字来使用命名空间
export xxx //导出文件
//_下划线是区分公共还是私有的唯一方式