Dart基础语法

一、选择开发工具

首先需要安装Dart SDK(https://www.dart-china.org/t/...),SDK安装完成呢后,选择一款编辑工具。这里就先只推荐两款工具 VS Code ,Android Studio。工具下载完成后,安装dart插件,这个时候就可以编写dart代码了。我们先新建一个hello.dart文件,然后在文件中输入下方的代码,点击运行按钮就可以执行代码。

// 在dart中,main方法是代码运行的入口方法,一切都是从main方法开始的。。
void main (){
  // 点击IDE运行按钮 在打印台中会打印出 hello Dart。 接下来的例子都是在该方法下执行的(除特殊说明)。
  print('hello Dart');
}
二、常用的数据类型
  1. num num是数字类型的父类,包含了两个子类 int和double。
  2. String String可以用 '' 或 "" 来创建,也可以使用''' 或者"""来创建可变行的字符串。

    eg.num与String的一些常用操作

    // String -> num
    int one = int.parse('1');
    print(one + 2); // 输出 3 double类型同理
    int one = int.parse('hello');
    print(one + 2); // 报错
    
    // int -> String
    String oneStr = 1.toString();
    print(oneStr); // 输出 1
    
    // double -> String
    String oneStr = 3.1455.toStringAsFixed(2);
    print(oneStr); // 截取两位小数, 输出3.15 四舍五入
    String twoStr = 3.toStringAsFixed(2);
    print(twoStr); // 输出3.00
    
    // 可以使用${表达式}将表达式的值放入字符串中。如果表达式是标识符,{}可省略
    String oneStr = 'hello';
    String twoStr = "dart";
    print('$oneStr $twoStr'); //输出 hello dart
    print('$oneStr ${twoStr.toLowerCase()}'); //输出 hello dart
    print('$oneStr $twoStr.toLowerCase()'); //输出 hello dart.toLowerCase()
  3. bool Dart是强bool类型检查。

    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • if(非bool类型) 报错,assert( 非bool类型)报错,也就是说需要bool类型的,如果我们用int 等非bool类型的值都会报错。

      if (1){} // 报错
  4. List 列表

    var list_1 = new List(); // 非固定长度 new可以省略
    
    // 添加元素
    list_1.add('1');
    
    // 添加多个元素
    list_1.addAll(['1', '2']);
    
    // 获取list长度
    print(list_1.length); // 输出2
    
    // 获取list最后一个元素
    print(list_1.last); //输出 2
    
    // 获取list第一个元素
    print(list_1.first); // 输出 1
    
    // 查询某位置的元素,index从0开始
    print(list_1[0]); // 输出 1
    
    // 根据某个元素获取对应的index
    print(list_1.indexOf("1")); // 输出 0
    
    // 删除元素
    list_1.removeAt(0); // 通过索引删除
    list_1.remove("1"); // 通过对象删除 如果有两个 "1" 那么会删除第一个
    list_1.removeLast(); // 删除最后一个元素
    list_1.removeRange(start,end); // 删除范围内的数据
    list_1.removeWhere((item) => item.length > 6);// 删除元素长度大于6的元素 这种写法可见下面箭头函数的详解
    list_1.clear(); // 清除所有元素
    
    /*注意*/
    List list_2 = new List(2); // 固定长度
    list_2.add('1'); // 报错 报错信息 Cannot add to a fixed-length list
    
    // 另外一种定义方式
    List list_3 = ['1','2','3'];
    print(list_3); // 输出 [1,2,3]
  5. Set 集合 Set是没有顺序且不能重复的集合,所以不能通过索引去获取值

    Set set_1 = new Set();
    Set set_2 = new Set(2); // 报错 set没有固定长度的定义
    set_1.add('1');
    set_1.add('2');
    set_1.add('1');
    print(set_1); // 输出 {1, 2}
    // 可以和list一样通过contains来判断是否包含某个元素
    print(set_1.contains("1")); // 输出 true 
    set_1.addAll(['b','c']);
    print(set_1); // 输出 {1, 2, b, c}
  6. Map 映射是无序的键值对。键和值都可以是任何类型的对象。

    // 常用的两种定义方式
    Map map_1 = Map();
    Map map_2 = {"key_1":"value_1","key_2":"value_2"};
    print(map_1); // 输出 {}
    print(map_2); // 输出 {key_1: value_1, key_2: value_2}
    
    // 赋值
    map_1["1"] = "one";
    map_1["2"] = "two";
    print(map_1); // 输出 {1: one, 2: two}
    map_1["1"] = "first";
    print(map_1); // 输出 {1: first, 2: two}
    map_1[1] = "one"; // 在map中key必须要保持唯一 value可以相同 key类型可以为任意其他类型
    print(map_1); // 输出 {1: one, 2: two, 1: one}
    
    // 常用api
    map_1.remove(1); // 删除key为1的元素
    map_1.containsKey(1); // 判断是否存在key为1的元素
三、函数/方法
  1. 内置函数/方法

    print("hello Dart");

    这个是常用的将数据打印到控制台的方法,这是系统提供给我们的内置函数,可以直接调用。

  2. 自定义方法

    自定义方法的基本格式:

    返回类型 方法名称 (参数1,参数2,...){

    ​ 方法体

    ​ return 返回值;

    }

    int getMaxCount(int a, int b){
        if (a > b){
            return a;
        }
      return b;
    }
    print(getMaxCount(2, 1)); // 输出 2
    
    // 在dart中 返回值和参数类型是可以省略的
    getMinCount(a,b){
      if (a < b){
        return a;
      }
      return b;
    }
    print(getMinCount(2, 1)); // 输出 1
  3. 方法的传参

    // 可选参数
    String printUserInfo(String username,[int age]){ // username和age表示是形参 其中age表示可选参数
      return "姓名:$username  年龄:$age";
    }
    print(printUserInfo("张三",30)); // 张三 和 30 表示实参
    print(printUserInfo("张三")); // 这里可以不用传入age的参数,如果age不是可选参数的话,那么age是必须要传的
    
    // 默认参数
    String printUserInfoDefalut(String username,[int age,String sex = "男"]){
      return "姓名:$username  年龄:$age  性别:$sex";
    }
    print(printUserInfoDefalut("李四",30)); // 输出 姓名:李四  年龄:30  性别:男
    /*注意
    对于可选参数的传递是按照参数的顺序进行的 print(printUserInfoDefalut("李四","女")); 这里会报错。虽然sex和age都是可选参数,但是当对一个形参赋值时,会默认选择第一个。这里可选参数的第一个形参是age int类型的,这里传递一个"女"是string类型的,所以会报错。
    */
    // 命名参数
    String printUserInfoName(String username,{int age,String sex = "男"}){
      return "姓名:$username  年龄:$age  性别:$sex";
    }
    print(printUserInfoDefalut("李四",age:30,sex:"男")); // 输出 姓名:李四  年龄:30  性别:男
    // 这样我们通过指定参数名称实现我们想要传递的参数。这个比较常用。
    
    // 将方法作为参数
    fun_1(){
      print("方法1");
    }
    
    fun_2 (Function fun){ // fun方法其实就是一个Function对象
      fun();
    }
    
    fun_2(fun_1); // 输出 方法1
  4. 箭头函数

    // 遍历数组
    List list = ["h","e","l","l","0"];
    // 正常写法
    list.forEach((element){
      print(element);
    });
    // 箭头函数的方式 箭头后跟的方法体只能是一行 这只是一种简写的方式
    list.forEach((element)=>print(element));
    list.forEach((element) => {
      print(element) // 这里不能写分号,也是只能执行一行代码
    });
    
    List list_1 = [1,3,5,1,3,7];
    // 这也是数组的一种遍历并处理的方式
    List list_2 = list_1.map((element){
      if(element >3){
        return element*3;
      }
      return element;
    }).toList();
    print(list_2);
    List list_3 = list_1.map((element)=>element>3?element*3:element).toList();
    print(list_3);
  5. 自执行与自调用

    //实际上自执行方法我们可以理解为一个匿名函数的自调用
    ((){})();// 格式 将一个匿名函数用()包起来 然后执行
    
    ((int n){
      print(n);
    })(12); //输出12
    
    // 方法可以自己调用自己
    int sum = 1;
    fn(n){
      sum*=n;
      if (n == 1){
        return;
      }
      fn(n-1);
    }
    fn(5); // 这样通过方法的自调用可以实现5的阶乘
  6. 闭包

    • 全局变量特点:全局变量常驻内存,会污染全局。
    • 局部变量特点:不常驻内存会被垃圾回收机制回收,不会污染全局。
    • 闭包:常驻内存,不会污染全局。写法:函数嵌套函数,并return内层函数。

      //闭包
      fun(){
        // 虽然这里定义的是一个局部变量,但是通过闭包的写法就不会被垃圾回收机制回收,且不会污染全局
        int a = 1;
        return (){
          a++;
          print(a);
        };
      }
      Function b = fun(); // 因为fun方法返回的就是一个方法,所以下面直接调用b方法。
      b();
      b();
      b();
      // 输出 2 3 4
四、语法规则
  1. Dart中一切皆对象,都是继承自Object类。所以在Dart中不存在基本数据类型,我们定义的任何一个对象,如果不将其赋值的话,那么这个对象就是一个null对象。

    int a;
    int b = 0;
    print(a); // 输出 null
    print(b); // 输出 0
  2. Dart是动态型语言,如果没有指定其类型时,则默认时dynamic类型。在运行时会自动推导出具体的类型。

    var str = 'shsh';
    str = 1; // 报错
  3. 运算符及修饰符

    • static:用于修饰类成员变量,这个变量是属于类的,通过类名直接调用,而不是通过对象调用。这个与java类似。非静态方法可以访问静态成员以及非静态成员。静态方法无法访问非静态成员,也无法调用非静态方法。
    • final:用于修饰变量,表示单赋值(single-assignment),使用final修饰的变量必须进行初始化,一旦被赋值之后,不能够再次被赋值,否则编译会报错。
    • const:与final有一点类似,即只能被赋值一次。但是其修饰的对象有一定的限制。const修饰的对象的状态是完全可以在编译期间就能确定的,并且是不可变的。

      const n = 1+2;// 可行 因为这个是在编译期间我们就知道n = 3的
      const list = new List(); // 不可行
      const list_1 = [1,2,3]; // 可行
      list_1 = [3,4]; // 不可行
    • 常用的运算符和表达式

      //加减乘除
      int a = 10;
      int b = 3;
      print(a/b); // 3.3333333333333335 不用判断除数和被除数的类型,默认返回double类型
      print(a~/b); // 3  整除取整
      print(a*b); // 30
      print(a+b); // 13
      print(a-b); // 7
      print(a%b); // 1 取余
      
      //as 定型
      Person per = new Person(); // 我们定义一个Person类,关于类的定义下文会介绍
      // 如果per是Person类型的话 那么就给Person的name属性赋值,如果不是Person类型则不赋值 并
      (per as Person).name = "张三";
      
      // is 如果对象是指定的类型 那么就返回true 否则就返回false
      if (per is Person){
        per.name = "张三";
      }
      /// 注意:以上代码不相等。如果per为空或不为Person,第二个示例(带is)什么也不做;第一个(带有as)抛出异常。
      
      // ??与三目运算符
      String str_1;
      String str_2 = "张三";
      print(str_1??str_2); // 输出 张三
      print((str_1 is! Null)?str_1:str_2); // 输出张三 在dart中 关于条件的判断必须是bool类型
      
      // 级联符号 可以避免创建临时变量的繁琐步骤
      Person() ..name ="张三";
      // 等价于
      Person per = new Person();
      per.name = "张三";
      
      // ?. 有条件的成员属性访问
      Person per; // 这里per就是null
      // 如果直接调用成员会报错
      per.name = "张三"; // 报错
      per?.name = "张三"; // 不会报错
五、类与对象
  1. 类的创建

    // 与上面的例子不同,类的定义需要与mian方法同级
    class Person{
      String name; // 属性
      void study (){
        print("学习");
        } // 方法
    }
    
    void main (){
      // 实例化,也就是创建对象
      Person per = new Person(); // per就是一个对象
      per.name = "张三";
      print("${per.name}"); // 输出 张三 调用对象的属性
      per.study(); // 输出 学习 调用方法
    }
  2. 类的构造函数

    • 默认构造函数。每个类都有一个默认的构造函数,默认是不用写的,如果需要在构造函数做操作的话,则重写一下默认构造函数。

      class Person{
        String name;
        //构造参数名与类名要相同
        Person(){ // 无参
          print("我需要在这里做一些操作");
        }
        // 初始化列表
        person():name = "张三"{
          // 在执行构造函数运行之前 初始化一些实例变量
        }
      }
      
      class Student{
        String name;
        Student(String name){ // 有参数 参数必传
          this.name = name;
        }
        void printStudentInfo (){
          print("学生的姓名:${this.name}");
        }
      }
      
      class Coder{
        String name;
        Coder(this.name); // 简写的有参构造函数 参数必传
        void printCoderInfo (){
          print("程序员的姓名:${this.name}");
        }
      }
      
      class Engineer{
        String name;
        Engineer({this.name}); // 简写的有参构造函数 参数非必传
        void printEngineerInfo (){
          print("工程师的姓名:${this.name}");
        }
      }
      
      void main (){
        // 实例化,也就是创建对象
        Person per = new Person(); // 输出  我需要在这里做一些操作。
        
        // 报错 因为我们修改了默认的构造函数,所以之前的无参构造函数就不能使用了。
        // Student stu = new Student(); 
        
        Student stu_1 = new Student('张三');
        stu_1.printStudentInfo();
        
        Coder coder = Coder("张三");
        coder.printCoderInfo();
        
        Engineer eng = new Engineer();
        eng.printEngineerInfo(); // 输出 工程师的姓名:null
        
          Engineer eng_1 = new Engineer(name: "张三");
        eng_1.printEngineerInfo(); // 输出 工程师的姓名:张三
      }
    • 命名构造函数。类是可以有多个构造函数的,通过不同的命名来实现不同功能的构造函数

      class Engineer{
        String name;
        String hairStyle; //发型 
        Engineer({this.name,this.hairStyle = "茂密的头发"}); // 简写的有参构造函数 参数非必传
        Engineer.seniorEngineer(String name){ //命名构造函数
          this.name = name;
          this.hairStyle = "秃了";
        }
        void printEngineerInfo (){
          print("工程师的姓名:${this.name} 发型:${this.hairStyle}");
        }
      }
      
      void main (){
        Engineer eng_1 = Engineer(name:"张三");
        eng_1.printEngineerInfo();
        
        Engineer eng_2 = Engineer.seniorEngineer("李四");
        eng_2.printEngineerInfo();
      }
    • 类中的setter和getter方法

      class Rect{
        num height;
        num width;
        Rect(this.height,this.width);
        // getter方法
        get area{
          return this.height*this.width;
        }
        // setter方法
        set areaHeight(num height){
          this.height = height;
        }
      }
      
      void main(){
        Rect re = Rect(10,10);
        print("面积:${re.area}"); // 输出 面积:100
        re.areaHeight = 5;
        print("面积:${re.area}"); // 输出 面积:50
      }
  3. 类的继承

    • 子类使用 extends 关键字来继承父类
    • 子类会继承父类中可见你的属性和方法,但是不会继承构造函数
    • 子类能复写父类的方法 getter和setter

      class Person{
        String name;
        num age;
        Person(this.name,this.age);
        void printPersonInfo(){
          print("姓名:${this.name} 年龄:${this.age}");
          }
        
        void work(){
          print("我正在工作");
        }
      }
      
      class Coder extends Person{
        // 因为Person是重写了构造函数,所以这里也需要写一下与父类关联的构造函数
        // 如果父类有多个构造函数 则需要选一个自己需要的
        Coder(String name,num age,String grade):super(name,age){
          // 通过代码可以看出,这么写的目的是当我们在实例化Coder时也会将值传递给父类
          // grade 是子类的属性,所以不用将参数传递给父类
          this.grade = grade;
          }
        String grade;
        void printCoderInfo(){
          super.work(); // 调用父类的方法
          print("姓名:${this.name} 年龄:${this.age} 等级:${this.grade}");
          }
        
        @override // 重写父类方法
        void work() {
          print("我正在写代码");
        }
      }
      
      void main(){
        Coder code = Coder("zhangsan",20,"初级码农");
        code.printCoderInfo(); // 输出 姓名:zhangsan 年龄:20 等级:初级码农
        code.work(); // 输出 我正在写代码
      }
  4. 抽象类。Dart中的抽象类主要是用于定义标准,子类可以继承抽象类,然后实现抽象类的接口。

    • 抽象类通过abstract关键字来定义。
    • 抽象方法不能用abstract声明,我们将没有方法体的方法称为抽象方法。
    • 如果子类继承抽象类必须实现里面的抽象方法。
    • 如果把抽象类当作接口实现的话必须得实现抽象类里定义的所有属性和方法。
    • 抽象类不能被实例化,子类可以。
    • 如果要实现抽象方法约束子类的话我们需要用extends继承抽象类。
    • 如果只是把抽象类作为一个标准的话我们需要用implements实现抽象类。

      abstract class Person{
        String name;
        int age;
        Person(this.name,this.age);
        work(); // 没有实现方法体 这是一个抽象方法 抽象方法是必须要在子类中实现的
        eat(){
          print('吃饭');
        }
      }
      
      class Coder extends Person{
        Coder(String name, int age) : super(name, age);
        @override
        work(){
          print("${this.name}在搬运代码,他今年已经${this.age}了");
        }
      }
      /*
      在dart中普通类和抽象类都可以作为接口被实现 使用关键字 implements 
      dart的接口会将普通类或者是抽象类中的属性或者方法都重写一遍,所以我们一般采用抽象类实现接口。
      因为只有抽象类才可以创建抽象方法。
      */
      class Engineer implements Person{
        // 这里就不用像继承一样需要实现父类的构造方法。
        @override
        int age;
      
        @override
        String name;
      
        @override
        eat() {
          print("工程师在吃饭");
        }
      
        @override
        work() {
          print("${this.name}在搞算法,他今年才${this.age}");
        }
      }
      
      void main (){
        Coder code = Coder("张三", 30);
        code.work(); // 输出 张三在搬运代码,他今年已经30了
        
        Engineer eng = Engineer()
          ..name = "李四"
          ..age = 20
          ..work(); // 输出 李四在搞算法,他今年才20
      }
  5. 多接口和mixins

    • 通过implements的方式,可以实现多接口。

      abstract class A{
        String a;
        printA();
      }
      abstract class B{
        String b;
        printB();
      }
      class C implements A,B{
        @override
        String a;
      
        @override
        String b;
      
        @override
        printA() {
        // TODO: implement printA
        return null;
        }
      
        @override
        printB() {
        // TODO: implement printB
        return null;
        }
      }
      // 这样C类的对象就可以同时实现 A B的的抽象方法
    • 通过mixins实现多继承,Dart是不支持多继承,是无法实现类似C++的多继承的功能。

      class A{
      // mixins 不能有显示的构造方法 其实这个也好理解 因为使用mixins继承多个类的话,它也不知道用什么构造方法 只能使用默认的了
      //  A.withName(){
      //
      //  }
        a(){
          print('A.a()');
        }
      }
      
      class B{
        a(){
          print('B.a()');
        }
        b(){
          print('B.b()');
        }
      }
      
      class C{
        a(){
          print('C.a()');
        }
        b(){
          print('C.b()');
        }
        c(){
          print('C.c()');
        }
      }
      
      class D extends A with B,C{
        int count; // 可以定义自己的属性
      }
      void main (){
        var d = new D();
        d.a(); // 输出 C.a() 默认调用最后一个方法
      }
      /*mixins的方式组合的类 是必须要继承object的 A B C 就是不能继承与任何的类 除了默认的object 如果想实现类似继承的效果 我们可以使用接口的方式*/
六、泛型
  1. 泛型方法

    T getData<T>(value){
      return value;
    }
    print(getData("xingming")); // 输出 xingming
    print(getData(20)); // 输出 20
    print(getData<String>(20)); // 报错 因为指定的是string类型 但是传入的是int类型
    
    // 通过泛型方法我们就可以不用区分传入的参数类型和输出的参数类型。
    var names = List<String>();
    names.addAll(['1', '2', '3']);
    names.add(42); // 报错
  2. 泛型类

    通过指定类的泛型类型,来确定输入的数据类型是否正确

    void main (){
      LogList loglist = LogList<int>(); // 确定泛型类型为int类型,后续类中的类型判断就是int类型
      loglist.addData(1);
      loglist.addData("1"); // 报错
      loglist.printList();
      
      LogList loglist_1 = LogList(); // 没有确定泛型类型
      loglist_1.addData(1);
      loglist_1.addData("1"); // 没有报错
      loglist_1.printList();
    }
    
    class LogList<T>{
      List list = List<T>();
      void addData(T value){
        list.add(value);
      }
    
      void printList(){
        for (int i = 0;i<list.length;i++){
          print(list[i]);
        }
      }
    }
  3. 泛型接口

    void main (){
      B b = B<int>();
      b.pirntA(1);
      //b.pirntA("12"); // 报错
    }
    
    abstract class A<T>{
      pirntA(T value);
    }
    
    class B<T> implements A<T>{
      @override
      pirntA(T value) {
        print(value);
      }
    }
七、结尾

​ 上面简单的介绍了一些Dart语言的基础语法知识,关于Dart中更深层次的内容,我会在以后的文章中再慢慢的剖析。关注公众号,阅读更多精彩技术文章。


1029
622 声望5 粉丝