dart基本语法

177 阅读8分钟

变量与常量的声明


  • var声明变量

    var age = 20;
    // age = "abc"; //错误,上面已经推导age为数字类型
    age = 30;
    
  • final声明常量:可以通过计算/函数来获取一个值(可以在运行期间赋值)

    final height = 1.88;
    //height = 2.00;//错误,常量不能改变
    
  • const声明常量:声明的时候必须赋值,且必须为常量值(编译期间需要该值)

    const address = "北京市";
    //address = "上海市";//错误,常量不能改变
    

数据类型


  • 数字类型
  • 布尔类型,取值true/false。dart中没有非空即真或非零即真
  • 字符串类型,
    • 定义字符串三种方式
      var str1 = 'hello';
      var str2 = "hello";
      var str3 = """"
       hello,
       world
      """;
      
    • 字符串拼接:${}
      var name = "zhangsan";
      var msg = "hello,${name}";
      
      ${}中,若其中包含的是单纯的变量,如上面代码所示,那么{}是可以省略的,即
      var msg = "hello,$name";
      
  • 集合类型
    • list
      var names = ["a","b","c"];
      
    • set
      var movies = {"星际穿越","大话西游","盗梦空间"};
      
    • map
      var info = {
          "name":"zhangsan",
          "age":18
      };
      
    • dynamic
      1.动态任意类型,所有dart对象的基础类型
      2.通过dynamic定义的变量,会关闭类型检查
      3.由于关闭了类型检查,所以有的非法代码会编译通过,但是在运行时可能会崩溃    
      

函数


  • 定义

    int sum(int n1, int n2){
        return n1 + n2;
    }
    

    dart中,函数返回值可以省略,开发中不推荐省略

    sum(int n1, int n2){
        return n1 + n2;
    }
    
  • 参数

    函数的参数分为:必选参数和可选参数,可选参数又可以分为位置可选参数和命名可选参数

    参数默认值:只有可选参数才可以有默认值

    main(List<String> args) {
      //位置可选参数的使用
      sayHello1("zhangsan", 18, 179.0);
      //命名可选参数的使用
      sayHello2("lisi", age:20, height:185.0);
      sayHello2("wangwu", height:180.0);
    }
    
    /**
     * 位置可选参数,调用函数时候,即形参跟实参是根据位置匹配的
     */
    void sayHello1(String name, [int age, double height]){
      print("$name,age is $age, height is $height");
    }
    
    /**
     * 命名可选参数
     */
    void sayHello2(String name,{int age = 18, double height}){
        print("$name,age is $age, height is $height");
    }
    
  • 函数是一等公民

    这意味着可以将函数赋值一个变量,也可以将函数作为另外一个函数的参数或者返回值来使用

    • 函数作为参数
      main(List<String> args) {
        //匿名函数做参数
        test1((){
          print("匿名函数被调用");
        });
      
        //普通函数做参数
        test1(bar);
      
        //箭头函数做参数:dart中箭头函数跟js中的不同,
        //dart的箭头函数函数体只能有一行代码
        test1(()=>print("箭头函数被调用"));
      
        test2((n1,n2){
          return n1+n2;
        });
      }
      
      void test1(Function foo){//此处的函数签名很简单
        foo();
      }
      
      void bar(){
        print("bar was called");
      }
      
      //注意,此处的函数签名与test1要求传入的函数签名不同
      void test2(int foo(int n1, int n2)){
        final ret = foo(1,2);
        print("foo的运算结果是:${ret}");
      }
      
      在上面的代码中,可以将test2中的参数使用typedef进行定义:
      typedef Calculate = int Function(int n1, int n2);
      void test3(Calculate func){
        final ret = func(2,2);
        print('calcluate运算结果是:${ret}');
      }
      
    • 函数作为返回值
      main(List<String> args) {
        final returnedFunc = test();
        print(returnedFunc(3,4));
      }
      
      typedef Calculate = int Function(int n1, int n2)
      Calculate test(){
        return (n1, n2){
          return n1 * n2;
        };
      }
      

运算符(dart中的特殊运算符)


  • ??=

    赋值规则:若左侧变量有值,则不赋值,否则,将右侧值赋值给左侧变量

    main(List<String> args) {
      var name = "zhangsan";
      name ??= "lisi";
      print(name);//zhangsan
    
      var age = null;
      age ??= 18;
      print(age);//18
    }
    
  • ??

    运算结果:若??左侧有值,优先使用左侧值,否则使用右侧的值

    main(List<String> args) {
      var gender = null;
      // gender = gender ?? "male";
      gender = "male" ?? gender;
      print(gender);
    }
    
  • ..(级联运算符),当作链式调用来使用即可

流程控制


  • for...in
    final names = ["zhangsan","lisi","wangwu"];
    for (var name in names) {
      print(name);
    }
    
    


  • 默认地,所有的类都继承自Object
  • 类的构造器
    • 类中若实现了自定义构造函数,则默认构造函数失效

      class Person {
        String name;
        int age;
        Person(String name, int age){
          this.name = name;
          this.age = age;
        }
      }
      

      上面代码中,实现了一个自定义的构造函数,则默认的构造函数失效。 dart提供了一种语法糖,可以代替上面的构造函数写法:

      Person(this.name,this.age);
      
    • 由于dart中不支持函数重载,所以类中不能有多个构造函数。但是我们又需要多个构造函数,如下:

      class Person {
        String name;
        int age;
        double height;
      
        Person(this.name, this.age, this.height);
      }
      

      上面代码中,有时候我们只想通过name和age来实例化一个Person实例。显然,上面的代码无法满足这样的需求。 一种解决方法是,将height修改为命名可选参数:

      Person(this.name, this.age, {this.height});
      

      更好的解决方法是,使用命名构造函数

      main(List<String> args) {
        final p1 = Person("zhangsan", 18);
        print(p1);
      
        final p2 = Person.withAll("lisi", 20, 170);
        print(p2);
      
        final p3 = Person.fromMap({
          "name":"wangwu",
          "age":21,
          "height":175.0
        });
        print(p3);
      }
      
      class Person {
        String name;
        int age;
        double height;
      
        Person(this.name, this.age);
        //命名构造函数
        Person.withAll(this.name,this.age,this.height);
        Person.fromMap(Map<String,dynamic>map){
          this.name = map["name"];
          this.age  = map["age"];
          this.height = map["height"];
        }
      
        @override
        String toString() {
          return "Name:$name, Age:$age, Height:$height";
        }
      }
      
    • 类的初始化列表

      如下代码:

      class Person {
        final String name;//常量
        final int age;//常量
      
        Person(this.name,this.age);
      }
      

      由于常量的特性:初始化的时候必须赋值,所以,如若将代码修改为下面的代码,实例化并访问age的时候,则报错:

      class Person {
        final String name;
        final int age;
      
        // Person(this.name,this.age);
        Person(this.name){
          this.age = 10;
        }
      }
      

      相关报错信息:

      Error: Final field 'age' is not initialized.Try to initialize the field in the declaration or in every constructor. 上面代码中,已经在构造方法中初始化了age,但还是说age没有被初始化。 原因: 当代码执行到花括号内的时候,表示该实例对象已经初始化完毕。但实际上,age并没有被初始化。所以会有上面的错误产生。

      解决方法:使用初始化列表。

      main(List<String> args) {
        final p1 = Person("zhangsan");
        print(p1);
      
        final p2 = Person("lisi",age: 18);
        print(p2);
      }
      
      class Person {
        final String name;
        final int age;
      
        // Person(this.name,this.age);
        // Person(this.name):age=10{
        // }
        Person(this.name, {int age}): this.age = age ?? 10{//初始列表
        }
      
        @override
        String toString() {
          return "${this.name},${this.age}";
        }
      }
      

      为什么不直接使用如下方式来给age赋值呢?

      Person(this.name,{this.age=10});
      

      当然可以使用这种方式赋值,在此只是演示初始化列表的使用。另外,初始化列表的功能比直接使用上面的方式赋值更强大。可选参数不能使用表达式赋值,只能直接赋值。但是初始化列表可以使用表达式赋值。

    • 重定向构造函数

      main(List<String> args) {
        final p = Person("zhangsan");
        print(p.age);
      }
      
      class Person {
        String name;
        int age;
      
        //构造函数重定向
        Person(String name): this._internal(name, 0);
        Person._internal(this.name, this.age);
      }
      
    • 常量构造函数

      用const修饰的构造函数是常量构造函数。通过常量构造函数获得的实例,指向同一块内存。

      main(List<String> args) {
        const p1 = const Person("张三");
        const p2 = const Person("张三");
        print(identical(p1,p2));//true
      }
      
      class Person {
        final String name;
        const Person(this.name);
      }
      

      注意:

      1. 拥有常量构造方法的类中,所有的成员必须是final修饰的。
      2. 为了可以通过常量构造方法创建出相同的对象,不使用new关键字,而使用const关键字。但是若将结果赋值给const修饰的常量的时候,可以省略const。如下:
        main(List<String> args) {
          final p1 = Person("张三");
          final p2 = Person("张三");
          print(identical(p1,p2));//false
        
          const p3 = Person("李四");
          const p4 = Person("李四");
          print(identical(p3,p4));//true
        }
        
    • 工厂构造函数

      普通的构造函数会默认返回创建出来的对象。工厂构造函数需要自己手动返回一个对象。

      需求:如果名字相同,返回同一个对象。如下:

      main(List<String> args) {
        final p1 = Person.withName("zhangsan");
        final p2 = Person.withName("zhangsan");
        print(identical(p1,p2));
      }
      
      class Person {
        String name;
        String color;
      
        static final Map<String, Person> _nameCache = {};
      
        factory Person.withName(String name){
          if (_nameCache.containsKey(name)) {
            return _nameCache[name];
          }
          else{
            final person = Person(name, '');
            _nameCache[name] = person;
            return person;
          }
        }
        Person(this.name, this.color);
      }
      
    • setter和getter(关键字set/get

      main(List<String> args) {
        final p = Person();
        p.setName = "zhangsan";
        print(p.getName);
      }
      
      class Person {
        String name;
        //setter
        set setName(String name){
          this.name = name;
        }
      
        //getter
        String get getName{
          return this.name;
        }
      }
      
    • 继承(关键字:extends)

    • 抽象类的使用

      1.抽象类使用关键字abstract声明;
      2.抽象类中的方法可以有方法体,也可以不实现;
      3.继承抽象类的类,必须实现抽象类中没有实现的抽象方法;
      4.抽象类若没有工厂构造方法,则不能被实例化。如:Map类是一个抽象类,但是它可以被实例化,就是因为它有一个工厂构造方法;
      

隐式接口


  1. dart中,没有关键字可以用来定义接口
  2. 默认地,所有的类都是隐式接口.当一个类被当做接口使用的时候,实现这个接口的类,必须实现其中的所有方法。

如下:

class Running {
      void run(){}
    }

    class Flying {
      void flying(){}
    }
    //将上面两个类当作隐式接口来使用,必须实现其中的所有方法
    class Superman implements Running, Flying {
      @override
      void run() {
        print("superman is runnning");
      }

      @override
      void flying() {
        print("superman is flying");
      }
    }

混入


使用隐式接口的时候,需要在实现的类中完全实现接口中所有的方法,哪怕其中的方法已经在隐式接口(也就是类,普通类中的方法必须有)中实现。这种情况下,就可以使用混入了。

混入语法:

  1. 定义类(隐式接口)的时候需要使用mixin关键字,而非class关键字
  2. 在实现隐式接口的类中,用with关键字来实现混入
main(List<String> args) {
  final superman = Superman();
  superman.fly();
  superman.run();
}

mixin Running {
  void run(){
    print("running...");
  }
}

mixin Flying {
  void fly(){}
}

class Superman with Running, Flying {
  @override
  void fly() {
    print("superman is flying");
  }
}

类属性和类方法(使用static关键字修饰的属性及方法)


dart中,一个dart文件就是一个库文件。

  • 使用系统库。固定格式,如下:

    import "dart:库名称";,如:import "dart:async";

  • 使用自定义库。

    import "自定义库路径";

  • 如果导入的库与当前文件有冲突,比如函数名冲突,这时候可以使用as关键字来给导入的库一个别名。

    import "some/lib/some_lib.dart" as someLib;

    这样在调用相关函数的时候,可以带着别名来调用,以区别于当前文件中的函数。

  • 使用第三方库(类似于node)

    1. 在pubspec.yaml文件中添加响应的依赖库,在进入到该文件所在的目录,执行pub get,即可完成库的安装。
    2. 在使用该库的地方,按如下格式导入即可(以http库为例)。 > import "package:http/http.dart";
  • 默认地,导入一个库的时候,会将库中所有的内容全部导入。如果要导入指定内容,则需要使用show关键字;要隐藏导入的某些内容,使用hide关键字。

    1. 只导入部分内容

      import "some/lib/some_lib.dart" show func1,func2

    2. 隐藏部分内容,其余的全部导入

      import "some/lib/some_lib.dart" hide func1,func2

  • export

    使用场景:假设有一个功能,实现分散在几个dart文件中,在需要时用该功能的地方导入的时候,我们可能需要一个库(文件)一个库的导入,这时我们可以定义一个文件,将需要用的库,使用export关键字将之导出,然后在用到该功能的地方,导入这一个文件即可。

    简言之,上述操作相当于使用了一个公共头文件。

    如下:my_math_lib中包含加减乘除运算的四个库(文件)和一个index文件

    |-- my_math_lib
    |---- division.dart(包含double divide(double n1, double n2)函数)
    |---- multi.dart(包含int multiply(int n1, int n2)函数)
    |---- sub.dart(包含int sub(int n1, int n2)函数)
    |---- sum.dart(包含int sum(int n1, int n2)函数)
    |---- index.dart
    

    index.dart中内容如下:

      export 'division.dart';
      export 'multi.dart';
      export 'sub.dart';
      export 'sum.dart';
    

    在需要使用该库的地方,只需要导入index.dart即可。如下:

     import 'my_math_lib/index.dart';
    
     main(List<String> args) {
       print(sum(4, 2));
     }