Flutter入门02----Dart语法

VSCode环境搭建

  • 在学习Dart语法时,本人使用的是VSCode编辑器,下载官方网址为:https://code.visualstudio.com/
  • 其次安装Flutter,Dart,Code Runner三个插件,如下所示:


    1.png
  • 然后创建一个文件夹,用VSCode打开,就可以进行Dart语法演练了;

main函数

  • Dart语言的入口是main函数,并且必须显示的进行定义;
  • Dart的入口函数main是没有返回值的;
  • 传递给main函数的命令行参数,是通过List完成的;
  • 代码展示如下:


    2.png

声明变量

  • Dart中声明变量有两种方式:
    • 明确的声明;
    • 类型推导(var/final/const)
  • 案例代码如下:
main(List args) {
  //1.明确的声明
  String name = "zyc";

  //2.类型推导(var/final/const)
  //2.1 var声明变量
  var age = 20;

  //2.2 final声明常量
  final weight = 2.55;

  //2.3 const声明常量
  const address = "南京";

  //final与const的区别
  // const date1 = DateTime.now();
  final date2 = DateTime.now();

  final p1 = Person("zyc");
  final p2 = Person("zyc");

  print(identical(p1, p2)); //false

  const s1 = Student("123");
  const s2 = Student("123");

  print(identical(s1, s2)); //true

}

class Person {
  String name;

  Person(String name) {
    this.name = name;
  }
}

class Student {
  final String name;

  const Student(this.name);
}
  • String name = "zyc":明确声明初始化变量name;
  • var age = 20:定义变量age并初始化,为整型变量,赋值其他数据类型会报错;
  • final weight = 2.55:定义常量weight并初始化,为浮点型常量,不可再次赋值修改;
  • const address = "南京":定义常量address并初始化,为String类型,不可再次赋值修改;
  • const必须赋值,常量值(在编译期间需要有一个确定的值);
  • final可以通过计算/函数获取一个值(运行期间来确定一个值),DateTime.now()是运行时获取当前时间,所以赋值给const常量会报错;
  • 定义一个Person类,然后创建两个Person对象,入参相同,用final修饰,这两个对象不是同一个对象identical函数可用来检测两个对象是否是同一个对象;
  • 定义一个Student类,然后创建两个Student对象,入参相同,用const修饰,这两个对象是同一个对象

数据类型

  • int,double
  • bool
  • String
  • 集合类型:List/Set/Map
  • String类型的代码案例:
main(List args) {
  //1.定义字符串
  var str1 = 'abc';
  var str2 = "abc";
  var str3 = """
  abc
  ass
  sss
  """;
  //2.字符串拼接
  var name = "zyc";
  var age = 31;
  var height = 188;

  var message1 = "my name is ${name},age is ${age},height is ${height}";
  var message2 = "name is ${name},type is ${name.runtimeType}";

  print(message1); //my name is zyc,age is 31,height is 188
  print(message2); //name is zyc,type is String
}
  • 定义初始化字符串可以使用单引号双引号三引号
  • 字符串引用时使用${字符串变量名},这与Shell脚本语言的语法类似,{}可加可不加,在确定边界时必须加上,否则会出现语法错误;
  • name.runtimeType:是获取字符串变量name的运行时类型
  • 集合类型的代码案例:
main(List args) {
  //1.数组List
  var names = ["abc", "sss", "asd"];
  names.add("fff");
  names.remove("sss");
  names.removeAt(1);

  //2.集合Set
  var movies = {"大话西游", "2012", "大白鲨"};
  movies.add("长津湖");

  //3.映射map
  var info = {"name": "zyc", "age": 20, "height": 175};
  bool isNameKey = info.containsKey("name");
  print(isNameKey); 
  print(info.keys); //所有key
  print(info.values); //所有value
}

函数

函数的定义
  • 先上案例代码:
main(List args) {
  print(sum(10, 30));
}

//1.返回值类型可以省略,开发中不推荐这么写
int sum(int num1, int num2) {
  return num1 + num2;
}
  • 函数返回值类型可以省略,开发中不推荐这么写;
函数的参数
  • Dart中不支持函数的重载;
  • 函数的参数可分为两类:
    • 必选参数:必须传,不存在默认值;
    • 可选参数:位置可选参数 ,命令可选参数,存在默认值;
  • 案例代码如下:
main(List args) {
  sayHello1("zyc");

  sayHello2("zyc", 18, 175.5);

  sayHello3("zyc", age: 31, height: 175);
  sayHello3("wangwu", height: 172);
  sayHello3("zhuliu", age: 33);
  sayHello3("nima", height: 144, age: 55);
}

//1.必选参数:必须传
void sayHello1(String name) {
  print(name);
}

//Dart中没有函数的重载
//2.可选参数:位置可选参数 / 命名可选参数
//可选参数可以支持有默认值
//2.1.位置可选参数:[int age,double height]
//实参和行参在进行匹配时,是根据位置匹配的
void sayHello2(String name, [int age = 22, double height = 123.4]) {
  print(name);
  print(age);
  print(height);
}

//2.2.命名可选参数 {int age,double height }
void sayHello3(String name, {int age = 32, double height = 56.9}) {
  print(name);
  print(age);
  print(height);
}
  • void sayHello2(String name, [int age = 22, double height = 123.4]):其中[int age = 22, double height = 123.4]位置可选参数[],参和行参在进行匹配时,是根据位置匹配的,可以设置默认值,这种语法在Shell脚本语言中也存在;
  • void sayHello3(String name, {int age = 32, double height = 56.9}):其中{int age = 32, double height = 56.9}命令可选参数{},可以设置默认值,在进行函数调用时,命令可选参数必须写参数名,且支持不用传所有的命令可选参数;
函数是一等公民
  • 函数是一等公民意思就是说函数可以作为另一个函数的参数或者返回值,有点函数式编程的味道;
  • 案例代码如下:
main(List args) {
  //1.函数作为参数
  test1(shit);

  //2.匿名函数: (参数列表) {函数体}
  test2(() {
    print("匿名函数被调用");
    return 10;
  });

  //3.箭头函数:函数体只有一行代码
  test1(() => print("箭头函数被调用"));

  test3((num1, num2) {
    return num1 + num2;
  });

  test4((num1, num2) {
    return num1 + num2;
  });

  var demo1 = demo();
  print(demo1(30, 40));
}

//函数可以作为参数
void test1(Function foo) {
  foo();
}

void test2(Function foo) {
  var result = foo();
  print(result);
}
//4.明确函数类型
typedef Calculate = int Function(int num1, int num2);

void test3(int foo(int num1, int num2)) {
  var result = foo(20, 30);
  print(result);
}

void test4(Calculate cal) {
  print(cal(100, 200));
}

//5.函数作为返回值
Calculate demo() {
  return (num1, num2) {
    return num1 * num2;
  };
}

void shit() {
  print("shit函数被调用");
}
  • void test1(Function foo):test1函数的参数是一个函数Function,此函数是没有类型的,也就是说任意一个函数都可以作为test1函数的参数传进来;
  • void test2(Function foo)在函数调用是传入参数是一个匿名函数,即没有函数名称,只有函数实现体,匿名函数的格式为:(参数列表) {函数体},使用匿名函数比较简便,不用我们再去定义一个函数了;
  • 箭头函数:函数体只有一行代码,格式为:() => 函数体
  • Function关键字没有明确函数类型,在大多数情况下我们需要明确函数的类型,void test3(int foo(int num1, int num2)),test3函数就明确了传参函数的类型 int返回值,两个int入参,test3在调用时传入上述类型的函数实现体;
  • void test3(int foo(int num1, int num2))这种定义函数类型的方式比较麻烦,可使用typedef关键字来定义函数的类型,譬如typedef Calculate = int Function(int num1, int num2),最后传参只要传Calculate即可
  • Calculate demo():demo函数返回是一个Calculate类型的函数;

运算符

  • 针对运算我们只介绍Dart中特有的运算符;
赋值运算符
  • ??=
    • 当原来的变量有值时,那么??=不执行;
    • 当原来的变量为null,那么将值赋值给这个变量;
  • ??
    • ??前面的数据有值,那么就使用??前面的数据;
    • ??前面的数据为null,那么就使用??后面的数据;
  • 案例代码如下:
main(List args) {
  //??=
  //当原来的变量有值时,那么??=不执行
  //当原来的变量为null,那么将值赋值给这个变量
  var name1 = "zyc";
  name1 ??= "shit";
  print(name1); //zyc

  var age = null;
  age ??= 100;
  print(age); //100

  //??
  //??前面的数据有值,那么就使用??前面的数据
  //??前面的数据为null,那么就使用??后面的数据
  var name2 = "SF";
  var temp2 = name2 ?? "SFFF";
  print(temp2); //SF

  var name3 = null;
  var temp3 = name2 ?? "SFFF";
  print(temp3); //SFFF
}
级联运算符
  • ..
  • 案例代码如下:
main(List args) {
  var p = Person();
  p.name = "SF";
  p.run();
  p.eat();

  //..级联运算符
  var p1 = Person()
    ..name = "SF"
    ..eat()
    ..run();
}

class Person {
  String name;

  void run() {
    print("run");
  }

  void eat() {
    print("eat");
  }
}

类与对象

  • Dart是面向对象的开发语言,所以类与对象是相当重要的;
构造函数
  • 先看代码案例:
main(List args) {
  var p = Person("SF", 27);

  var p1 = Person.withNameAgeHeight("zyc", 31, 175.0);

  var p2 = Person.fromMap({"name": "zyc", "age": 31, "height": 175.2});
  print(p2);

  //Object和dynamic的区别
  //Object在调用方法,编译时会报错;
  //dynamic在调用方法时,编译时不会报错,但是运行时会存在安全隐患
  // Object obj1 = "zyc";
  // print(obj1.substring(1));

  //明确的类型声明
  // dynamic obj2 = "zyc";
  // print(obj2.substring(1));
  
  //dynamic obj3 = 123;
  //print(obj3.substring(1)); //运行时会报错
}

class Person {
  String name;
  int age;
  double height;

  //构造函数
  // Person(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }
 
  //语法糖
  Person(this.name, this.age);

  //命名构造函数
  Person.withNameAgeHeight(this.name, this.age, this.height);

  Person.fromMap(Map map) {
    this.name = map["name"];
    this.age = map["age"];
    this.height = map["height"];
  }

  @override
  String toString() {
    return "$name $age $height";
  }
}
  • 创建Person类,默认构造函数为Person(),若自定义构造函数Person(String name, int age),那么默认构造函数为Person()就会被覆盖,不能使用了;
  • 自定义构造函数Person(String name, int age) 还有一种语法糖写法即Person(this.name, this.age)
  • 命名构造函数,在原来基础上新增属性,这时就要用到命名构造函数,例如Person.withNameAgeHeight(this.name, this.age, this.height)Person.fromMap(Map map) 都是自定义的;
  • Objectdynamic的区别:
    • Object是所有类的基类,其修饰变量,对象在调用方法,编译时会报错;
    • dynamic其修饰变量,对象在调用方法,编译时不会报错,但是运行时会存在安全隐患;
初始化列表
  • 与C++的语法类似;
  • 初始化列表的使用只能在构造函数中;
  • 代码案例:
main(List args) {
  var p = Person("SF");
  print(p.age);
}

class Person {
  //被final修饰的变量为常量 只能被赋值一次
  final String name;
  final int age;

  //初始化列表与C++语法类似
  //创建对象时,若传入age,那么就使用传入的age,如果没有传入age,那么使用默认值,age为可选参数
  Person(this.name, {int age}) : this.age = age ?? 10 {
  //在执行此大括号的代码时,对象已经初始化完毕了
  //必须保证在执行此大括号的代码之前,final修饰的name与age必须已经初始化
  //所以下面代码报错
  //this.age = 10;
  }

  //存在局限性 
  //Person(this.name, {this.age = 10});
}
  • Person(this.name, {int age}) : this.age = age ?? 10:初始化列表,在执行大括号中的代码之前,完成对象的初始化;
重定向构造函数
  • 在一个构造函数中,去调用另一个构造函数,注意⚠️是在冒号后面使用this进行调用;
  • 案例代码如下:
main(List args) {
  var p = Person("SF");
  print(p.age); //22
}

class Person {
  String name;
  int age;

  //调用自定义构造函数Person._internal
  Person(String name) : this._internal(name, 22);

  Person._internal(this.name, this.age);
}
常量构造函数
  • 在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法;
  • 默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象,但是若在构造函数前加上const进行修饰,那么可以保证同一个参数,创建出来的对象是相同的,这样的构造函数称之为常量构造函数
  • 案例代码如下:
main(List args) {
  const p1 = Person("zyc");
  const p2 = Person("zyc");
  
  //p1与p2是同一个对象
  print(identical(p1, p2)); 
}

class Person {
  final String name;

  const Person(this.name);
}
  • 注意常量构造函数,只存在一个final属性;
  • 拥有常量构造函数的类中,所有的成员变量必须是final修饰的;
工厂构造函数
  • Dart提供了factory关键字,用于通过工厂去获取对象;
  • 普通的构造函数,会默认返回创建出来的对象,而工厂构造函数,需要手动返回一个对象
  • 案例代码:
main(List args) {

  final p1 = Person.withName("zyc");
  final p2 = Person.withName("zyc");

  print(identical(p1, p2)); //true
}

class Person {
  //对象属性
  String name;
  String color;

  //类属性
  static final Map _nameCache = {};
  static final Map _colorCache = {};

  //工厂构造函数
  //1.根据key值从缓存中获取对象,存在直接返回,不存在
  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);
}
  • final修饰变量,保证变量不被胡乱修改;
setter与getter方法
  • 案例代码:
main(List args) {
  final p = Person();

  //直接访问属性
  p.name = "zyc";
  print(p.name);

  //通过setter与getter访问属性
  p.setName = "SF";
  print(p.getName);

  p.setAge = 31;
  print(p.age);
}

class Person {
  String name;
  int age;

  //setter
  set setName(String name) {
    this.name = name;
  }

  set setAge(int age) => this.age = age;

  //getter
  String get getName {
    return name;
  }

  int get getAge => age;
}
  • setter与getter方法中使用了setget关键字,格式如代码所示;
  • setter与getter方法中可以使用箭头函数,更加简便;
类的继承
  • Dart中继承使用extends关键字,子类中使用super访问父类;
  • 父类中的所有成员变量和方法都会被继承,但是构造方法除外;
  • 子类的构造方法在执行前,会隐含调用父类的无参数默认构造函数(没有参数且与类同名的构造方法);
  • 如果父类没有无参数默认构造函数,则子类的构造函数必须在初始化列表中通过super显式调用父类的某个构造函数;
  • 案例代码:
main(List args) {}

class Animal {
  int age;

  Animal(this.age);
}

class Person extends Animal {
  String name;
  
  //必须完成父类属性age的初始化 在初始化列表中完成
  Person(this.name, int age) : super(age);
}
抽象类
  • abstract关键字修饰的类,称之为抽象类
  • 在Dart中没有具体实现的方法,称之为抽象方法
  • 抽象方法必须存在于抽象类中;
  • 案例代码:
main(List args) {
  //抽象类不能实例化
  //final s = Shape();

  //Map是系统的一个抽象类
  //Map能实例化 是因为Map内部实现了一个工厂构造函数 external factory Map()
  final map = Map();
  print(map.runtimeType);
}

abstract class Shape {
  void getArea();
}

//继承抽象类的子类 必须实现抽象类中定义的抽象方法
class Rectanle extends Shape {
  @override
  void getArea() {
    print("画矩形");
  }
}
  • 抽象类不能实例化;
  • 继承抽象类的子类 必须实现抽象类中定义的抽象方法,否则会报错;
external关键字详解

说道抽象类abstract,就不得不说一下external关键字,external关键字估计用到人很少,在看源码的时侯经常可以看到,如下:

class Object {
  const Object();
  external bool operator ==(other);
  external int get hashCode;
  external String toString();
  @pragma("vm:entry-point")
  external dynamic noSuchMethod(Invocation invocation);
  external Type get runtimeType;
}
  • 可以看到Object类里有很多方法都是用external声明,并且这些方法没有具体实现;
  • 但我们看到class不是abstract class,为什么方法可以不用实现呢?这就是external的作用。
    Tips:external只声明方法,声明的方法需要由外部去实现,通常是由底层sdk根据不同平台(vm、web等)实现;若外部没实现,则会返回null;
1、external作用
  • external修饰的方法具有一种实现方法声明和实现分离的特性。
    关键在于它能实现声明和实现分离,这样就能复用同一套对外API的声明,然后对应不同平台的多套实现;这样不管是dart for web 还是dart for vm,对于上层开发而言都是同一套API;
  • external声明的方法由底层sdk根据不同平台实现,class不用声明为abstract class,所以class可直接实例化;
2、external声明方法实现
@patch
class 类名 {
  ...
  @patch
  external声明的方法名
  ...
}

external声明的方法,通过@patch注解实现结构如上
比如Object里各种external声明方法的实现如下

@patch
class Object {
  ...
  @patch
  bool operator ==(Object other) native "Object_equals";

  static final _hashCodeRnd = new Random();

  static int _objectHashCode(obj) {
    var result = _getHash(obj);
    if (result == 0) {
      // We want the hash to be a Smi value greater than 0.
      result = _hashCodeRnd.nextInt(0x40000000);
      do {
        result = _hashCodeRnd.nextInt(0x40000000);
      } while (result == 0);
      _setHash(obj, result);
    }
    return result;
  }

  @patch
  int get hashCode => _objectHashCode(this);
  

  @patch
  String toString() native "Object_toString";

  @patch
  @pragma("vm:exact-result-type", "dart:core#_Type")
  Type get runtimeType native "Object_runtimeType";
  ...
}

隐式接口

  • 在Dart中接口比较特殊,没有一个专门的关键字来声明接口;
  • 默认情况下,定义的每个类都相当于默认也声明了一个接口,可称之为隐式接口,可以由其他类来实现,因为Dart不支持多继承;
  • 在开发中,我们通常将用于给别人实现的类 声明为抽象类;
  • 案例代码:
main(List args) {
  
}

class Animal {
  void eat() {
    print("eat");
  }
}

class Runnner {
  void run() {
    print("run");
  }
}

class Flyer {
  void fly() {
    print("fly");
  }
}

//当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman extends Animal implements Runnner, Flyer {

  @override
  void eat() {
    // TODO: implement eat
    super.eat();
  }

  @override
  void run() {
    // TODO: implement run
  }

  @override
  void fly() {
    // TODO: implement fly
  }
}
  • 当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法;
混入mixin
  • 当前类 实现 隐式接口类,隐士接口类中的方法已实现,这时当前类不想再实现隐式接口类中的方法,可使用混入语法;
  • 定义可混入的类时,不能用class关键字,而是使用mixin关键字;
  • 当前类使用with进行混入,使用混入时可以使用super关键字;
  • 案例代码:
main(List args) {
  final sm = Superman();
  sm.run();
  sm.fly();
  sm.eat();
}

class Animal {
  void eat() {
    print("Animal eat");
  }
}

mixin Runnner {
  void run() {
    print("Runnner run");
  }
}

mixin Flyer {
  void fly() {
    print("Flyer fly");
  }
}

//当将一个类当作接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman extends Animal with Runnner, Flyer {
  @override
  void eat() {
    // TODO: implement eat
    super.eat();
  }

  @override
  void run() {
    // TODO: implement run
    super.run();
    print("Superman run");
  }
}
  • 使用混入时,Superman可以实现Runnner中的方法,也可以不用实现Runnner中的方法,实现时可以使用super关键字,调用Runnner中的实现;
类成员和方法 => 静态static成员和方法
  • 上面我们在类中定义的成员与方法都属于实例对象的,在开发中,我们也需要定义类级别的成员与方法
  • 类成员与方法的定义声明使用static关键字;
  • 类成员与方法 通过类名来调用;
  • 案例代码:
main(List args) {
  final p = Person();
  p.name = "zyc";
  p.eat();

  Person.color = "yellow";
  print(Person.color);
  Person.run();
}

class Person {
  //成员属性 对象属性
  String name;

  //静态成员属性 类属性
  static String color;

  void eat() {
    print("Person eat");
  }

  static void run() {
    print("Person run");
  }
}
枚举
  • 使用enum关键字 定义枚举;
  • 案例代码:
main(List args) {
  final color = Colors.red;

  switch (color) {
    case Colors.red:
      print("红色");
      break;
    case Colors.green:
      print("灰色");
      break;
    case Colors.blue:
      print("蓝色");
      break;
    default:
  }
  //获取枚举的所有值
  print(Colors.values);
  //获取枚举值的index
  print(Colors.red.index);
}

enum Colors { red, green, blue }
泛型
  • 待补充;
扩展Extension
  • 给指定的类扩展方法;
  • Extension是在Dart语言2.6.0版本才支持的;
import 'package:Fluter01/day01/shared/SFSizeFit.dart';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String message = "Hello World";
    final result = message.sf_split(" ");

    print(result);
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("基础widget")),
        body: Center(
          child: Container(
            width: 200,
            height: 200,
            color: Colors.red,
          ),
        )
    );
  }
}

extension StringSplit on String {
  List sf_split(String split) {
    return this.split(split);
  }
}
库的使用
  • 在Dart中,你可以导入一个库来使用它所提供的功能;

  • 库的使用可以使代码的重用性得到提高,并且可以更好的组合代码;

  • Dart中任何一个Dart文件都是一个库,即使你没有使用关键字library声明;

  • 库的导入:

    • 系统库的导入;
    • 自定义库的导入;
    • 第三方库的导入;
  • 案例代码- 系统库的导入:

import 'dart:async';
import 'dart:math';

main(List args) {
  final num1 = 10;
  final num2 = 30;
  //min函数 属于 dart:math系统库
  print(min(num1, num2));
}
  • 案例代码 - 自定义库的导入:
  • 创建的自定义库的代码如下:
3.png
import 'Utils/math_utils.dart' as SFUtils;

main(List args) {
  //通过库的别名来调用函数方法
  print(SFUtils.sum1(10, 20));
}

int sum1(int num1, num2) {
  print("...");
  return num1 + num2;
}
import 'Utils/math_utils.dart' show sum1;

main(List args) {
  //通过库的别名来调用函数方法
  print(SFUtils.sum1(10, 20));
}
  • 可使用as关键字给自定义库取个别名,主要是解决冲突的问题;
  • 在默认情况下,导入一个库时,是导入这个库中的所有内容;
    • show:可指定要导入的内容;
    • hide:隐藏某个要导入的内容,导入其他内容;
  • 若库文件太多,这样导入的代码写的太多,可以单独创建一个文件A.dart,使用export关键字将库文件导入A.dart中,这样只要导入A.dart文件即可,如下所示:
    4.png
import 'Utils/utils.dart';

main(List args) {
  print(dateFormat());
  print(sum1(10, 20));
}
  • Dart第三方库的网站:https://pub.dev
  • 第三库的导入使用,如下所示:


    5.png
  • 第三方库的使用,如下所示:
import 'package:http/http.dart' as http;

main(List args) async {
  var url = Uri.https('www.googleapis.com', '/books/v1/volumes', {'q': '{http}'});
  var response = await http.get(url);
  if (response.statusCode == 200) {
    var jsonResponse = convert.jsonDecode(response.body) as Map;
    var itemCount = jsonResponse['totalItems'];
    print('Number of books about http: $itemCount.');
  } else {
    print('Request failed with status: ${response.statusCode}.');
  }
}

你可能感兴趣的:(Flutter入门02----Dart语法)