Dart 运算符重载

一,什么是运算符重载(operator overloading)

在软件开发过程中,运算符重载(英语:operator overloading)是多态的一种。运算符重载通常只是一种语法糖,这种语法对语言的功能没有影响,但是更方便程序员使用。让程序更加简洁,有更高的可读性。

二,语法糖的现实意义

在日常工作过程中,我们读代码读机会往往超过写代码,软件工程是门协作的艺术

我们写的代码主要是给机器和人看的,给机器看的可以通过代码测试和实际运行来进行检验,给人的目前还没太好的评价方式。不过在设计一门语言的时候,方便阅读也是语言设计重点考虑的一个方面。

运算符重载在功能实现方面并没有任何影响,但是会给未来的自己和其他程序员带来极大的便利。

三,operator关键字

operator 是 Dart 的一个关键字,它和运算符(如=)一起使用,表示一个 运算符重载函数,在理解时可将operator和运算符(如operator=)视为一个函数名。

使用 operator 重载运算符,是 Dar 扩展运算符功能的方法。使用 operator 扩展运算符功能的原因如下:

  • 使重载后的运算符的使用方法与重载前一致
  • 扩展运算符的功能只能通过函数的方式实现

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

比如,我们要设计一个名为“person”的类,现在要判断person类的两个对象p1和p2是否一样大,我们设计的比较规则是按照其年龄来比较,那么,在设计person类的时候,就可以通过对操作符“==”进行重载,来使用操作符“==”对对象p1和p2

进行比较了(根据前面的分析,实际上比较的内容应该是person类中的数据成员“age”)。

我们上面说的对操作符“==”进行重载,说是“重载”,是由于编译器在实现操作符“==”功能的时候,已经为我们提供了这个操作符对于一些基本数据类型的操作支持,只不过由于现在该操作符所操作的内容变成了我们自定义的数据类型(如

class),而默认情况下,该操作符是不能对我们自定义的class类型进行操作的,所以,就需要我们通过重载该操作符,给出该操作符操作我们自定义的class类型的方法,从而达到使用该操作符对我们自定义的class类型进行运算的目的。

四,实现一个操作符重载的方式通常分为两种情况  

  1. 将操作符重载实现为类的成员函数;
  2. 操作符重载实现为非类的成员函数(即全局函数)
  • 将操作符重载实现为类的成员函数;

    在类体中声明(定义)需要重载的操作符,声明方式跟普通的成员函数一样,只不过操作符重载函数的名字是“关键字 operator +以及紧跟其后的一个 Dart 预定义的操作符”,样式如下(person是我们定义的类):

bool operator==(const person& ps)
 {
   if (this->age == ps.age)
  {
      return true;
  }
      return false;
 }
  • 示例代码operator_test2.cpp)如下:
    class Person
    {
       int age;
       public Person(int nAge){
             this->age = nAge;
       }
     
       bool operator==(Person ps){
       if (this.age == ps.age) {
            return true;
        }
            return false;
      }
    };
     
    int main()
    {
        Person p1 = new Person(10);
        Person p2 = new Person(10);
        
        if (p1 == p2) {
        
         } else {
        
         }
        return 0;
    }

    通过上述代码能够知道:因为操作符重载函数“operator==”是person类的一个成员函数,所以对象p1、p2都可以调用该函数。其中的 if (p1 == p2) 语句,相当于对象p1调用函数“operator==”,把对象p2作为一个参数传递给该函数,从而实现了两个对象的比较。

  • 操作符重载实现为非类的成员函数(即全局函数)

    对于全局重载操作符,代表左操作数的参数必须被显式指定。

    示例代码如下:

    class Person
    {
      public int age;
    };
     
    // 左操作数的类型必须被显式指定
    // 此处指定的类型为person类
    bool operator==(Person p1 ,Person p2)
    {
        if (p1.age == p2.age)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
     
    int main()
    {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.age = 18;
        p2.age = 18;
     
        if (p1 == p2){
            
        }else{
        }
        return 0;
    }

    重载后操作符的操作数至少有一个是用户定义类型;
    不能违反原来操作数的语法规则;
    不能创建新的操作符;
    不能重载的操作符包括(以空格分隔):sizeof . .* :: ?: RTTI类型运算符
    =、()、[]、以及 ->操作符只能被类的成员函数重载

五,Dart语言的运算符重载实例

  • 第一步创建个类

class Role{
  final String name;
  final int accessLevel;
  
  const Role(this.name,this.accessLevel);
}

main()
{
  print('hello  operator overloading');
}
  • 第二步 通过函数实现Role比较

    class Role{
      final String name;
      final int accessLevel;
      
      const Role(this.name,this.accessLevel);
    }
    
    main()
    {
      var adminRole =new Role('管理员',3);
      var reporterRole = new Role('报告员',2);
      var userRole= new Role('用户',1);
      if(adminRole.accessLevel > reporterRole.accessLevel){
        print("管理员的权限大于报告员");
      }  
      if(reporterRole.accessLevel > userRole.accessLevel){
        print("报告员的权限大于用户");
      }  
    }

  • 第三步 重载运算符

    class Role {
      final String name;
      final int _accessLevel;
    
      const Role(this.name, this._accessLevel);
      bool operator >(Role Other) {
        return this._accessLevel > Other._accessLevel;
      }
    
      bool operator <(Role Other) {
        return this._accessLevel < Other._accessLevel;
      }
    }
    
    main() {
      var adminRole = new Role('管理员', 3);
      var reporterRole = new Role('报告员', 2);
      var userRole = new Role('用户', 1);
      if (adminRole > reporterRole) {
        print("管理员的权限大于报告员");
      }
      if (reporterRole > userRole) {
        print("报告员的权限大于用户");
      }
    }

    重写运算符

    下标的运算符可以被重写。 例如,想要实现两个向量对象相加,可以重写 + 方法。

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>

提示: 你可能会被提示 != 运算符为非可重载运算符。 因为 e1 != e2 表达式仅仅是 !(e1 == e2) 的语法糖。 

 下面示例演示一个类重写 + 和 - 操作符:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // 运算符 == 和 hashCode 部分没有列出。 有关详情,请参考下面的注释。
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果要重写 == 操作符,需要重写对象的 hashCode getter 方法。 重写 == 和 hashCode 的实例,参考 Implementing map keys.

Implementing map keys

在 Dart 中每个对象会默认提供一个整数的哈希值, 因此在 map 中可以作为 key 来使用, 重写 hashCode 的 getter 方法来生成自定义哈希值。 如果重写 hashCode 的 getter 方法,那么可能还需要重写 == 运算符。 相等的(通过 == )对象必须拥有相同的哈希值。 哈希值并不要求是唯一的, 但是应该具有良好的分布形态。

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // 重写 hashCode,实现策略源于  Effective Java,
  // 第11章。
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + firstName.hashCode;
    result = 37 * result + lastName.hashCode;
    return result;
  }

  // 如果重写了 hashCode,通常应该从新实现 == 操作符。
  @override
  bool operator ==(dynamic other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.lastName == lastName);
  }
}

void main() {
  var p1 = Person('Bob', 'Smith');
  var p2 = Person('Bob', 'Smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

根据上面的这个例子,我们可以实现下Vector类的==运算符的重写

class Vector{
  final int x,y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);


  @override
  bool operator ==(Object other) {
    if(other is! Vector) return false;
    if(x == other.x && y == other.y) return true;
    return false;
  }

  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + x.hashCode;
    result = 37 * result + y.hashCode;
    return result;
  }

}

Dart函数与运算符

函数

Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型Function。 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。 也可以把 Dart 类的实例当做方法来调用。

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

如果函数中只有一句表达式,可以使用简写语法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr 语法是 { return expr; }的简写。=>` 符号 有时也被称为 箭头 语法。

运算符

下表是 Dart 定义的运算符。 多数运算符可以被重载,详情参考 重写运算符

描述 操作员
一元后缀 *expr*++ *expr*-- () [] . ?.
一元前缀 -*expr* !*expr* ~*expr* ++*expr* --*expr*
乘法 * / % ~/
添加剂 + -
转移 << >> >>>
按位与 &
按位异或 ^
按位或 `
关系和类型测试 >= > <= < as is is!
平等 == !=
逻辑与 &&
逻辑或 `
如果为空 ??
有条件的 *expr1* ? *expr2* : *expr3*
级联 ..
任务 = *= /= += -= &= ^= 等等。

算术运算符

Dart 支持常用的运算运算符,如下表所示:

操作员 意义
+
-*expr* 一元减号,也称为否定(反转表达式的符号)
*
/
~/ 除,返回整数结果
% 获取整数除法的余数(模)

print(2 + 3 == 5);
print(2 - 3 == -1);
print(2 * 3 == 6);
print(5 / 2 == 2.5);
print(5 ~/ 2 == 2);
print(5 % 2 == 1);

关系运算符

下表列出了关系运算符及含义:

操作员 意义
== 相等
!= 不相等
> 大于
< 少于
>= 大于或等于
<= 小于或等于

类型判定运算符

as, is, 和 is! 运算符用于在运行时处理类型检查:

操作员 意义
as Typecast (也被用于指定库前缀)
is 如果对象具有指定的类型,则为true
is! 如果对象具有指定的类型,则为 false

例如, obj is Object 总是 true。 但是只有 obj 实现了 T 的接口时, obj is T 才是 true。

使用 as 运算符将对象强制转换为特定类型。 通常,可以认为是 is 类型判定后,被判定对象调用函数的一种缩写形式。

赋值运算符

使用 = 为变量赋值。 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它。

1
2
3
// 将值赋值给变量a
a = value;// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;

复合赋值运算符(如 += )将算术运算符和赋值运算符组合在了一起。

= –= /= %= >>= ^=
+= *= ~/= <<= &= `

以下说明复合赋值运算符的作用:

复合赋值 等价表达
例子: a += b a = a + b

逻辑运算符

逻辑操作符可以反转或组合布尔表达式。

操作员 意义
!*expr* 反转以下表达式(将 false 更改为 true,反之亦然)
`
&& 逻辑与

下面是关于逻辑表达式的示例:

1
if (! done && ( col == 0 || col == 3 )) { // ...do something... }      

按位和移位运算符

在 Dart 中,可以单独操作数字的某一位。 通常情况下整数类型使用按位和移位运算符来操作。

操作员 意义
&
` `
^ 异或
~*expr* 一元按位补码
<< 左移
>> 右移

条件表达式

Dart有两个运算符,有时可以替换 if-else 表达式, 让表达式更简洁:

  • *condition* ? *expr1* : *expr2*

    如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。

  • *expr1* ?? *expr2*

    如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。

如果赋值是根据布尔值, 考虑使用 ?:

1
var visibility = isPublic ? 'public' : 'private';

如果赋值是基于判定是否为 null, 考虑使用 ??

1
String playerName(String name) => name ?? 'Guest';

下面给出了其他两种实现方式, 但并不简洁:

级联运算符 (..)

级联运算符 (..) 可以实现对同一个对像进行一系列的操作。 除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。

考虑一下代码:

1
2
3
4
querySelector('#confirm') // 获取对象。
  ..text = 'Confirm' // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一句调用函数 querySelector() , 返回获取到的对象。 获取的对象依次执行级联运算符后面的代码, 代码执行后的返回值会被忽略。

上面的代码等价于:

1
2
3
4
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

级联运算符可以嵌套,例如:

1
2
3
4
5
6
7
8
final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = '[email protected]'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

在返回对象的函数中谨慎使用级联操作符。 例如,下面的代码是错误的:

1
2
3
var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: 'void' 没定义 'write' 函数。

sb.write() 函数调用返回 void, 不能在 void 对象上创建级联操作。

提示: 严格的来讲, “两个点” 的级联语法不是一个运算符。 它只是一个 Dart 的特殊语法。

其他运算符

大多数剩余的运算符,已在示例中使用过:

操作员 Name 意义
() 功能应用 代表一个函数调用
[] 列表访问 指列表中指定索引处的值
. 会员访问 引用表达式的属性;示例:从表达式中foo.bar选择属性bar``foo
?. 有条件的成员访问 类似.,但最左边的操作数可以为空;示例:从表达式中foo?.bar选择属性barfoo除非foo为空(在这种情况下,值为foo?.bar空)

更多关于 .?. 和 .. 运算符介绍,参考 Classes.

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/128102256