开发者社区> 代码迷途> 正文

[Flutter]足够入门的Dart语言系列之面向对象:类的定义详解、成员和实例使用

简介: 类表示的是分类,一类问题或事物,它是对具体或现实世界的抽象。比如动物类、犬科动物类、猫科动物类、房子类、数学类,类是具体事物的描述,它不是指具体的某个动物、某栋房子、某个数学题,而是对它们的概括...
+关注继续查看
福利推荐:阿里云、腾讯云、华为云等大品牌云产品全线2折优惠活动来袭,4核8G云服务器899元/3年,新老用户共享优惠,点击这里立即抢购>>>

表示的是分类,一类问题或事物,它是对具体或现实世界的抽象。比如动物类犬科动物类猫科动物类房子类数学类等,类是具体事物的描述,它不是指具体的某个动物、某栋房子、某个数学题,而是对它们的概括。

上面的定义虽然比较正式化,但它却能明确的表达编程中的含义,一个可以描述这个事物,另一个就可以描述另一个事物(只是抽象程度可能有所不同),由此可以推出,可以用描述任何事物。可以抽象现实中任何事物(包括非现实中的),的具体化就是一个具体的特指的对象,任何事物可以用描述为一个对象,这就是面向对象的开发思想。

不仅描述数据或状态,通常还包含行为或功能,用来"完整"的表示一类事物,完成指定的功能逻辑。

类class详细介绍

类的定义

class关键字用来定义一个,后面的标识符表示类的名称。名称后面,通过 {} 表示类定义的作用域,在其中可以定义类的 成员变量成员方法

class ClassName{
    // 类中的成员
}

比如我们定义一个MyAdd实现加法功能的类。

class MyAdd{
    double a=0;
    double b=0;

    double sum(){
        return a+b;
    }
}

类的实例化

定义了类就需要使用,使用类就要创建类对应的具体的对象。

创建对象的过程称为类的实例化,对象是类的一个实例。

var myClassName=ClassName();

比如,我们创建加法类MyAdd的实例,并使用其中的成员:

var myadd=MyAdd();

myadd.a=10;
print(myadd.sum()); // 10
dart提供可选的new关键字来创建对象: var myClassName=new ClassName();

成员方法

成员变量表示类有哪些数据或状态;成员方法表示类有哪些功能或行为。

MyAdd类的成员方法sum用于实现计算和的功能。通过.进行方法的调用。

var sum=myadd.sum();

成员方法的作用就是对类的逻辑功能的封装,可以达到直接调用,使用对应功能的目的。

构造函数

声明一个与类名一样的函数即可声明一个构造函数。构造函数的作用是用于创建一个类的实例,实现类的实例化,创建一个具体的对象。

构造函数的特定

构造函数是一个特殊的函数:

  1. 构造函数在声明时无返回值。
  2. 构造函数可以没有方法体。
  3. 在参数列表中可以通过 this.成员 ,为成员变量进行赋值。
  4. 没有显式声明构造函数时,默认自动生成一个无参、无方法体的构造函数。

比如上面的MyAdd类,其对应的完整类定义如下,两者等同:

class MyAdd{
    double a=0;
    double b=0;

    MyAdd();
    // ...
}

声明一个有参的构造函数,函数体内实现对属性的赋值。

class MyAdd{
    double a=0;
    double b=0;

    MyAdd(double a,double b){
        this.a=a;
        this.b=b;
    }
    // ...
}
this关键字引用当前实例,指代的是当前对象。

上面使用this是因为参数和成员变量有命名冲突,为了区分才显式的使用this。如果没有冲突,通常不需要指定this

使用时,构造函数需要传入正确的参数。

var myadd = MyAdd(10,6);

参数的初始化形式

构造函数的参数有简化的初始化形式,而不用在构造函数体内进行赋值。

如下,这是为实例变量赋值简化的语法糖:

class MyAdd {
  double a;
  double b;

  MyAdd(this.a,this.b);
}

初始化列表

构造函数可以在执行函数体之前,初始化实例变量。

MyAdd(double a, double b)
    : this.a=a,
    this.b=b;

通过在构造函数的右括号有加:,后面跟随对变量的赋值,多个变量之间用逗号分隔。

在开发模式下,HIA可以在初始化列表中使用 assert 来验证输入数据

MyAdd(this.a, this.b): assert(a>0&&b>0){
  print('参数需要大于0');
}

构造函数的参数

构造函数的参数和普通函数参数一样,可以是位置参数、可选位置、命名参数等。

如下,使用命名参数对_name成员变量赋值。

class MyAdd {
  double a;
  double b;
  String? _name;

  MyAdd(this.a,this.b,{String? name}):_name=name;
  //...
}

使用方法:

var add1=MyAdd(1,2);
var add2=MyAdd(1,2,name:"add2");

和普通函数的调用一样。

类中的非空类型的成员变量,必须进行初始化,要么在声明时初始化,要么在构造函数中进行初始化。

命名参数是可选的,所以,要么是可空的类型,要么需要提供默认值,否则,就要使用required关键字,保证参数是必须的,实现成员的初始化。

class MyAdd {
  double a;
  double b;
  String _name;

  MyAdd(this.a,this.b,{String name="MyAdd"}):_name=name;
}

Dart中大量使用了命名参数的形式,将MyAdd构造函数改为命名参数。

class MyAdd {
  double a;
  double b;
  String? _name;

  MyAdd({
    this.a=0,
    required this.b,
    String? name
  }):_name=name;
  // ...
}

使用形式如下:

var add1=MyAdd(b:2);
var add2=MyAdd(a:10,b:15,name:"add2");

命名构造函数

在Dart中,上面所示的构造函数形式被称为“生成式构造函数”(generative constructor)。

使用命名式构造函数(Named constructor),可以为一个类声明多个构造函数,用于以不同形式创建对象。通过命名的方式,表达更明确的意图。

以一个表示向量的类为例:

import 'dart:math' as math;

class Vec2 {
  double x;
  double y;

  Vec2(this.x, this.y);

  Vec2.polar(double length, double rad)
      : x = length * math.cos(rad),
        y = length * math.sin(rad);
}

通常,都是使用Vec2(this.x, this.y)坐标系中的坐标来构建向量;

除此之外,还可以使用极坐标来获取,即命名构造函数Vec2.polar(double length, double rad),通过长度和角度来创建向量。

命名构造函数通过类名.构造名的形式定义,可以定义多个不同含义或使用场景命名的构造函数。

重定向构造函数

有时,类中的构造函数仅用于调用类中其它的构造函数,此时该构造函数没有函数体,在函数签名后使用:,通过this指定需要重定向的其他构造函数。

本质上,是构造函数的重新利用,避免写相关的重复代码。

class Vec2 {
  double x;
  double y;

  Vec2(this.x, this.y);

  Vec2.alongXAxis(double x) : this(x, 0);
}

成员变量(对象属性)的 Getter 和 Setter

类中的变量称为成员变量,也称为对象的属性,或,实例变量。通过对象可以访问和修改其属性。

比如MyAddab成员变量、Vec2xy成员变量。

除了像正常的变量一样定义和使用成员变量,还可以使用 GetterSetter 实现对象属性的读写方法。

默认,实例对象的每一个属性都有一个隐式的 Getter 方法,如果为非 final 属性的话还会有一个 Setter 方法。

所有未初始化的实例变量其值均为 null。

可以使用 get 和 set 关键字显式的定义 Getter 和 Setter 方法。get/set属性本质上是特殊的方法。

比如,为Vec2添加一个只读的length属性。

import 'dart:math' as math;

class Vec2 {
  double x;
  double y;

  Vec2(this.x, this.y);

  double get length => math.sqrt(x * x + y * y);
  // 等同
  // double get length{
  //   return math.sqrt(x * x + y * y);
  // }
}

通常 Getter 和 Setter 都写为箭头函数的形式。

下面以一个正方形的类为例,看一下get、set两者的具体使用:

class Rectangle {
  double left, top, width, height;

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

  // 定义两个计算属性: right 和 bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

get 关键字用于获取属性值,此方法不能传参;set 关键字用于向属性设置值,此方法只能传入一个参数(即属性赋值时=右边的值)。

只有get方法的属性称为只读属性;只有set方法的属性称为只写属性。

类变量和方法(静态成员)

使用static关键字可以声明静态成员,静态变量和静态方法是属于类的,而不是类创建的对象,因此,也被称为类变量和类方法。

静态成员由 类 本身直接访问和修改,不依附于 对象。

下面定义一个Person类,其中静态变量dynasty表示朝代。

class Person {
  String name;
  static String dynasty = "";

  Person(this.name);

  void info() {
    print('$name是$dynasty人');
  }
}

通过类名.静态成员名直接访问或修改静态成员。

Person.dynasty = "唐朝";
print(Person.dynasty); // 唐朝

普通成员方法可以直接访问静态成员,但是静态成员中不能访问实例方法或属性。

下面通过Person对象的info方法直接访问静态变量:

Person.dynasty = "唐朝";

var p = Person('李白');
p.info(); // 李白是唐朝人

静态成员是属于类的,实例对象共享同一个静态成员,通过类修改静态成员后,所有的对象都能访问到。

这是静态成员变量的特点,它不依赖于具体对象,是类本身的一种属性,是所有对象的公共属性或特性

下面,首先创建一些唐朝人,然后修改朝代,再创建一些宋朝人:

// 创建一些唐朝Person对象
Person.dynasty = "唐朝";
var p1 = Person('李白');
var p2 = Person('杜甫');
p1.info();
p2.info();


// 创建一些宋朝Person对象
Person.dynasty = "宋朝";
var p3 = Person('苏轼');
var p4 = Person('范仲淹');
p3.info();
p4.info();

// -------------------------
// 输出:
// 李白是唐朝人
// 杜甫是唐朝人
// 苏轼是宋朝人
// 范仲淹是宋朝人

static也可以修饰类的成员方法,和静态变量一样,静态方法也是属于类的,独立于具体的对象而存在。通过类名.静态方法名();进行调用。

比如下面的静态方法printDynasty,调用方式Person.printDynasty();

class Person {
  String name;
  static String dynasty = "";

  Person(this.name);

  void info() {
    print('$name是$dynasty人');
  }

  static void printDynasty() {
    print("现在朝代是: $dynasty");
  }
}

静态方法中不能访问实例变量或实例方法,因为实例成员属于对象,只用对象才能调用。而静态方法中是没有当前实例对象的。

如果在静态方法中访问实例成员将会报错。

类中的不可变成员

final关键字修饰的成员在声明后只能被赋值一次,在类中,通常用于构造函数中对其进行初始化赋值。此后,不允许被修改。

class Vec2 {
 final double x;
 final double y;

  Vec2(this.x, this.y);
}

修改final变量将会报错:

通常对于没有修改需求的成员变量使用final修饰,避免后续对其进行误操作,修改掉固定值。比如上面的Vec2对象,在创建后表示一个唯一的点,点对象创建后不允许修改。如果想要得到另一个点,需要创建一个新的Vec2对象。

const关键字用在类中,必须修饰静态成员,并且要在声明时初始化。

参考

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Flutter如何调试应用【Dart Observatory 】以及调试模式断言
Flutter如何调试应用【Dart Observatory 】以及调试模式断言
113 0
Flutter(三)——一篇文章掌握Dart语言的用法(三)
Flutter(三)——一篇文章掌握Dart语言的用法(三)
82 0
Flutter(三)——一篇文章掌握Dart语言的用法(一)
Flutter(三)——一篇文章掌握Dart语言的用法(一)
102 0
Flutter(三)——一篇文章掌握Dart语言的用法(二)
Flutter(三)——一篇文章掌握Dart语言的用法(二)
88 0
[Flutter]足够入门的Dart语言系列之面向对象:类之间的6大关系
无论是面向对象编程,还是面向过程、函数式编程,代码与代码之间的组织关系都是最重要的。通过代码的编写、代码间的相互调用、不同代码的合理使用、集合不同的数据和方法,才能...
62 0
[Flutter]足够入门的Dart语言系列之面向对象:类成员的可见性、常量和工厂构造函数详解
类和成员的可见性,这在基于库或包的开发中非常重要,Dart中提供了工厂构造函数,可以方便的实现单例、缓存实例对象、返回子类等,以及常量构造函数的使用......
47 0
[Flutter]足够入门的Dart语言系列之函数:匿名函数、作用域闭包、类型别名和内联函数类型
函数在Dart中是对象,因此,可以将函数作为参数传递给另一个函数、作为一个'值'赋值给一个变量。下面主要介绍匿名函数、内联函数类型的使用,以及了解下作用域和闭包...
122 0
Flutter 文件、类、方法添加注释模板
Flutter 文件、类、方法添加注释模板
515 0
【Flutter】JSON 模型转换 ( JSON 序列化工具 | JSON 手动序列化 | 根据 JSON 编写 Dart 模型类 | 在线自动根据 JSON 转换 Dart 类 )(二)
【Flutter】JSON 模型转换 ( JSON 序列化工具 | JSON 手动序列化 | 根据 JSON 编写 Dart 模型类 | 在线自动根据 JSON 转换 Dart 类 )(二)
319 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Flutter企业级应用开发实战手册
立即下载
Flutter技术解析与实战
立即下载
Flutter in action
立即下载


http://www.vxiaotou.com