欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Flutter学习笔记(8)--Dart面向对象

程序员文章站 2022-07-05 09:02:02
Dart作为高级语言,支持面向对象的很多特性,并且支持基于mixin的继承方式,基于mixin的继承方式是指:一个类可以继承自多个父类,相当于其他语言里的多继承。所有的类都有同一个基类Object,这和特性类似于Java语言,Java所有的类也都是继承自Object,也就是说一切皆对象。 ......

如需转载,请注明出处:flutter学习笔记(8)--dart面向对象

 

dart作为高级语言,支持面向对象的很多特性,并且支持基于mixin的继承方式,基于mixin的继承方式是指:一个类可以继承自多个父类,相当于其他语言里的多继承。所有的类都有同一个基类object,这和特性类似于java语言,java所有的类也都是继承自object,也就是说一切皆对象。

//实例化了一个user类的对象user
var user = new user('张三',20);
  • 实例化成员变量

class user{
    string name;//name 成员变量
    int age;//age 成员变量
}

类定义中所有的变量都会隐式的定义setter方法,针对非空的变量会额外增加getter方法。实例化成员变量请参考如下代码:

main(){
    var user = new user();
    user.name = '张三';//相当于使用了name的setter方法
    user.age = 25;
}
  • 构造函数

  1.常规的构造函数

构造函数是用来构造当前类的函数,是一种特殊的函数,函数名称必须要和类名相同才行,如下代码为user类添加了一个构造函数,函数里给user类的两个成员变量初始化了值:

class user{
    string name;
    int age;
    user(string mname,int mage){
        this.name = mage;
        this.age = mage;
    }
}

this关键字指向了当前类的实例,上面的代码可以简化为:

class user{
    string name;
    int age;

    user(this.name,this.age);
}

第一种没有简化的构造方法初始化成员变量是在方法体内进行初始化的,第二种简化的构造方法初始化成员变量,是在实例化类的时候直接进行赋值初始化的。

  2.命名的构造函数

使用命名构造函数从另一类或现有的数据中快速实现构造函数,代码如下所示:

class user{
    string name;
    int age;
    //普通构造函数
    user(this.name,this.age);

    //命名构造函数
    user.fromjson(map json){
        name = json['name'];
        age = json['age'];
    }
}

//在实例化类的时候,如果没有传参,会默认调用无参数的构造方法
//普通构造函数
var user = new user('张三',25);

//命名构造函数
var user = new user.fromjson(mmapjson);

我对命名构造函数的理解就是起了个名字,在java里面,相同参数个数的构造方法只能有一个,在dart里面也不例外,那么如果我们想有多个相同参数个数的构造方法要怎么做呢,这时候命名构造方法就显现出他价值了,我们可以给相同参数个数的构造方法起不同的名字,这种就不会有错误了。

  3.子类的创建

注1:子类在继承父类的时候,如果在父类中有显示的提供一个无名、无参的构造函数,不会继承父类无名有参构造函数和命名构造函数,即:子类只会继承父类无名无参的构造函数。(程序会给类隐式的生成一个无名、无参的构造函数)

注2:子类在继承父类的时候,如果在父类中没有有显示的提供一个无名、无参的构造函数,子类必须手动调用父类的一个构造函数,在这种情况下,调用的父类的构造函数要放在子类构造函数之后,在子类构造函数体之前,用“:”分隔。

注3:父类的构造函数会在子类的构造函数前调用。

注4:默认情况下,子类只能调用父类无名、无参数的构造函数

下面我会用代码给大家解释上面的“注”(开始我也不会,也是上网查资料理解的!!!)

注1和注3:父类中有一个无名、无参的构造函数,子类继承父类,会默认继承父类无名、无参的构造函数(即使有其他无名、有参的构造函数或者命名构造函数,子类都不会调用),并且,父类的无名、无参的构造函数会在子类的构造函数之前被调用。

class futher {

    //无名、无参的构造函数
    futher(){
        print('我是父类无名、无参的构造函数');
    }
}

class son extends futher {
    //因为父类有显式的声明一个无名、无参的构造函数,所以不用手动调用父类的构造函数。
    son.fromjson(map mmapjson){
        print('我是子类的命名构造函数');
    }
}

var son = new son.fromjson(mmapjson);
//打印结果
//我是父类无名、无参的构造函数
//我是子类的命名构造函

注2:下面代码里,子类的命名构造方法写了两种方式,第一种是正确的,第二种是错误的,有详细的注释, 如果有疑问请留言。

class futher {

    //无名、无参的构造函数
    futher.printsth(){
        print('我是父类无名、无参的构造函数');
    }
}

class son extends futher {
    //因为父类没有有显式的声明一个无名、无参的构造函数,所以需要手动的调用父类的构造函数。
    son.fromjson(map mmapjson) : super futher.printsth{
        print('我是子类的命名构造函数');
    }

    //这种写法会报错,因为父类中没有显示的提供一个无名、无参的构造函数。所以需要像上面那样,手动调用父类的一个构造函数
    son.fromjson(map mmapjson){
        print('我是子类的命名构造函数');
    }
}

  4.构造函数初始化列表

上面在讲解常规的构造函数和命名构造函数的时候,示例代码都有对类中的成员变量进行了初始化,特点是在构造函数的方法体内进行初始化,初始化成员变量还有另一种方式,就是在构造函数运行前来初始化成员变量。

class user {
    string name;
    int age;

    user(mname,mage)
        :name = mname,
        age = mage{
            // do some thing
        }
}

特点是在构造函数的方法体前(大括号前面)来初始化成员变量,变量间用“,”分隔。

  • 读取和写入对象

get()和set()方法是专门用于读取和写入对象的属性的方法,每一个类的实例,系统都会隐式的包含有get()和set()方法。

例如,定义一个矩形的类,有上、下、左、右:top、bottom、left、right四个成员变量,使用get及set关键字分别对right、bottom进行获取和设置值。代码如下所示:

class rectangle {
    num left;
    num top;
    num width;
    num height;

    rectangle(this.left,this.top,this.width,this.height);

    num get right => left + width;//获取righht的值(第一行)

    set right(num value) => left = value - width;//设置right的值,同时left也发生了变化(第二行)

    num get bottom => top + height;//获取bottom的值(第三行)

    set bottom(num value) => top = value - height;//设置bottom值,同时top也发生了变化(第四行)
}

main(){
    var rect = new rectangle(3,4,20,15);//实例化rectangle,并给类中的4个变量进行初始化赋值

    print('left:'+rect.left.tostring());//获取left的值,并打印 left = 3
    print('right:'+rect.right.tostring());//获取right的值,并打印,这里执行了rectangle类中第一行代码,right = left + width,right = 3+20 = 23
    rect.right = 30;//重新给right进行赋值 right = 30,这里执行了rectabgke类中的第二行代码,将right的值设置为30,并且,将left的值改为30 - 20,left = 30-20 = 10
    print('right的值改为30');
    print('left:'+rect.left.tostring());//获取left的值,并打印,因为上面给right重新赋值的时候,也改变了left的值,所以,此时left = 10
    print('right:'+rect.right.tostring());//rect.right = 30将right的值改为了30,所以,right = 30


    print('top:'+rect.top.tostring());
    print('bottom:'+rect.bottom.tostring());
    rect.bottom = 50;
    print('bottom的值改为50');
    print('top:'+rect.top.tostring());
    print('bottom:'+rect.bottom.tostring());
}

//打印结果
left:3
right:23
right的值改为30
left:10
right:30
top:4
bottom:19
bottom的值改为50
top:35
bottom:50

上面的示例注释已经解释的很清楚了,如果有任何疑问,请留言!!!

这里我就解释一下“=>”的作用,在dart里面,大家可以简单的理解为接下来要继续执行后面的操作。

  • 重运算符载操作

在讲解重载运算符前需要先说明dart里面的一个关键字operator,operator和运算符一起使用,表示一个运算符重载函数,在理解时可以将operator和运算符(如operator+或operator-)视为一个函数名。编写一个例子方便理解。

class vector {
    final int x;
    final int y;
    const vector(this.x,this.y);

    //重载加号 + (a+b)
    vector operator + (vector v){
        return new vector(x + v.x,y + v.y);
    }
}

main() {
    //实例化两个变量
    final result1 = new vector(10,20);
    final result2 = new vector(30,40);

    final result = result1 + result2;

    print('result.x = '+result.x.tostring()+'',+'result.y = '+result.y.tostring());

    //打印结果
    result.x = 40,result.y = 60
}

 

讲解一下上面的例子,如果有不对的地方,麻烦留言指出。

首先创建了一个vector类,声明两个成员变量x和y还有一个构造方法,在vector类里面重载一个加法运算符,重载操作返回vector对象,接下来在main函数里面,实例化了两个vector变量,两次操作分别给

x和y进行了赋值,x = 10;y = 20;x = 30;y = 40。然后让result1和result2这两个变量相加,看到这里大家可能会有疑问,两个对象变量怎么相加呢?这里我们的运算符重载就发挥出作用了,实际上,在执行final result = result1 + result2;这行代码的时候,其实是对象result1调用了"operator +"这个函数,并将result2这个对象当作一个参数传递给了这个函数,从而实现了对象result1中的x和对象result2中的x相加,对象result1中的y和对象result2中的y相加的操作,所以最终打印的结果result.x = 40,result.y = 60。

看到这里,大家是否对运算符重载有了一定的理解了呢?如果有任何疑问,欢迎留言提问!!!

注:对于 dart 提供的所有操作符,通常只支持对于基本数据类型和标准库中提供的类的操作,而对于用户自己定义的类,如果想要通过该操作符实现一些基本操作(比如比较大小,判断是否相等),就需要用户自己来定义关于这个操作符的具体实现了。

  •  继承类

继承是面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法;或子类从父类继承方法,使得子类具有父类相同的行为。dart里面使用extends关键字来实现继承,super关键字来指定父类。

class animal {
    void eat(){
        print('动物会吃');
    }

    void run(){
        print('动物会跑');
    }
}

class human extends animal {
    void say(){
        print('人会说');
    }

    void study(){
        print('人会学习');
    }
}

main(){
    var animal = new animal();
    animal.eat();
    animal.run();

    value human = new human();
    human.eat();
    human.run();
    human.say();
    human.study();

    //打印结果
    动物会吃
    动物会跑

    动物会吃
    动物会跑
    人会说
    人会学习
}

 

  • 抽象类

抽象类类似于java语言中的接口。抽象类里不具体实现方法,只是写好定义接口,具体实现留着调用的人去实现。抽象类可以使用abstract关键字定义类。

  1. 抽象类通过abstract关键字来定义。
  2. dart中的抽象方法不能用abstract声明,dart中没有方法体的方法我们成为抽象方法。
  3. 如果子类继承了抽象类,就必须实现里面的抽象方法。
  4. 如果把抽象类当作接口实现的话,就必须得实现抽象类里面的所有属性和方法。
  5. 抽象类不能实例化,只有继承它的子类可以实例化。
abstract class animal{
    eat();   //抽象方法
    run();  //抽象方法  
    printinfo(){
    print('我是一个抽象类里面的普通方法');
  }
}

class dog extends animal{
    @override
    eat() {
        print('小狗在吃骨头');
    }

    @override
    run() {
        // todo: implement run
        print('小狗在跑');
    }  
}
class cat extends animal{
    @override
    eat() {
        // todo: implement eat
        print('小猫在吃老鼠');
    }

    @override
    run() {
        // todo: implement run
        print('小猫在跑');
    }

}

main(){
    dog d=new dog();
    d.eat();
    d.printinfo();

    cat c=new cat();
    c.eat();
    c.printinfo();


    // animal a=new animal();   //抽象类没法直接被实例化
}
  • 枚举类型

枚举类型是一种特殊的类,通常用来表示相同类型的一组常量值,用enum来定义,每个枚举类型都有一个index和getter,index用来标记元素的元素位置。第一个枚举元素的索引是0,枚举不能被继承,不能创建实例。

//定义一个枚举类
enum color {
    red,
    green,
    blue
}

//打印枚举类中green的索引
print(color.green.index); // 1

//获取枚举类中所有的值,使用value常数
list<color> colorlist = color.values;

 

因为枚举类中的每个元素都是相同类型,所以可以使用switch语句来针对不同的值做不同的处理,示例代码如下:

//定义一个枚举类
enum color {
    red,
    green,
    blue
}

main(){
    color mcolor = color.blue;

    switch (mcolor){
        case color.red:
        print('红色');
        break;
        
        case color.green:
        print('绿色');
        break;
        
        case color.blue:
        print('蓝色');
        break;

        default:
        break;
    }

    //打印结果
    蓝色
}

 

  • mixins

mixins(混入功能)相当于多继承,也就是说可以继承多个类,使用with关键字来实现mixins的功能,示例代码如下:

class first {
    void printsth(){
        print('im first printsth');
    };
}

class second {
    void printsth(){
        print('im second printsth');
    };

    void secondprint(){
        print('test');
    }
}

class a = second with first;

main (){
    a a = new a();
    a.printsth();
    a.secondprint();

    //打印结果
    im first printsth
    test
}

 

这段示例代码,是将类first混入到了second类中并给了类a,这块我在学习的时候有个疑问,同样的printsth方法,将first混入到second后,打印的是first中的im first printsth,这是因为first中的printsth方法将second中的printsth方法覆盖了嘛?

  • 泛型

泛型通常是为了类型安全而设计的,适当的指定泛型类型会生成更好的代码,可以使用泛型来减少代码重复,dart中使用<t>的方式来定义泛型。例如,如果想要list只包含字符串,可以将其声明为list<string>。如下所示:

var names = new list<string>();
names.addall(['张三','李四','王五']);

泛型用于list和map类型参数化:

list:<type>
map:<keytype,valuetype>

var names = new list<string>['张三','李四','王五'];
var weeks = new map<string,string>{
    'monday’' : '星期一',
    'tuesday' : '星期二',
    'wednesday' : '星期三',
    'thursday' : '星期四',
    'friday' : '星期五',
    'saturday' : '星期六',  
};

 

  • 库的使用

  1.引用库

通过import语句在一个库中引用另一个库的文件,需要注意一下事项:

1.1在import语句后面需要接上库文件的路径。
1.2对dart语言提供的库文件使用dart:xx格式
1.3第三方的库文件使用package:xx格式

import的例子如下:

import 'dart:io';
import 'package:mylib/mylib.dart';

 

  2.指定一个库的前缀

当引用的库拥有相互冲突的名字,可以为其中一个或几个指定不一样的前缀,这与命名空间的概念比较接近,示例代码如下:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

element emelent1 = new element();//默认使用lib1里面的element
lib2.element emelent2 = new lib2.element();//使用lib2里面的element

 

lib1/lib1.dartlib2/lib2.dart里面都有element类,如果直接引用就不知道具体引用哪个element类,所以代码中把lib2/lib2.dart指定成lib2,这样使用lib2.element就不会发生冲突。  3.引用库的一部分如果只需要使用库的一部分内容,可以有选择的进行引用,有如下关键字:

3.1show关键字:只引用一点
3.2hide:除此之外都引用

示例代码如下:

//导入foo
import 'package:lib1/lib1.dart' show foo;

//除了foo导入其他所有内容
import 'package:lib1/lib1.dart' hide foo;

 

  • 异步支持

  • 元数据

  • 注释

下一章节:flutter学习笔记(9)--常用组件