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

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

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

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

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

类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关键字用在类中,必须修饰静态成员,并且要在声明时初始化。

参考

相关文章
|
2月前
|
Dart
如何在 Flutter 项目中使用 Dart 语言?
如何在 Flutter 项目中使用 Dart 语言?
127 58
|
19天前
|
Dart
flutter dart mixin 姿势
flutter dart mixin 姿势
|
21天前
|
搜索推荐
Flutter 中的 AnimationController 类
【10月更文挑战第18天】深入了解了 Flutter 中的 `AnimationController`类。它是构建精彩动画效果的重要基石,掌握它的使用方法对于开发具有吸引力的 Flutter 应用至关重要。
|
1月前
|
Dart 开发者 Windows
flutter:dart的学习
本文介绍了Dart语言的下载方法及基本使用,包括在Windows系统上和VSCode中的安装步骤,并展示了如何运行Dart代码。此外,还详细说明了Dart的基础语法、构造函数、泛型以及库的使用方法。文中通过示例代码解释了闭包、运算符等概念,并介绍了Dart的新特性如非空断言操作符和延迟初始化变量。最后,提供了添加第三方库依赖的方法。
28 12
|
3月前
|
Dart JavaScript 前端开发
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
128 4
|
3月前
|
Dart 开发工具
消除Flutter doctor的警告Warning: `dart` on your path resolves to xxx/bin/dart
消除Flutter doctor的警告Warning: `dart` on your path resolves to xxx/bin/dart
67 0
|
1月前
|
Android开发 iOS开发 容器
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
|
28天前
|
开发者
鸿蒙Flutter实战:07-混合开发
鸿蒙Flutter混合开发支持两种模式:1) 基于har包,便于主项目开发者无需关心Flutter细节,但不支持热重载;2) 基于源码依赖,利于代码维护与热重载,需配置Flutter环境。项目结构包括AppScope、flutter_module等目录,适用于不同开发需求。
69 3
|
13天前
|
传感器 开发框架 物联网
鸿蒙next选择 Flutter 开发跨平台应用的原因
鸿蒙(HarmonyOS)是华为推出的一款旨在实现多设备无缝连接的操作系统。为了实现这一目标,鸿蒙选择了 Flutter 作为主要的跨平台应用开发框架。Flutter 的跨平台能力、高性能、丰富的生态支持和与鸿蒙系统的良好兼容性,使其成为理想的选择。通过 Flutter,开发者可以高效地构建和部署多平台应用,推动鸿蒙生态的快速发展。
114 0
|
15天前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
55 0