operator

使用 operator
关键字重载内置运算符,或在类或结构评释中提供用户定义的更换。

假如场景,多少个Student类,有语文和数学两科成绩,Chinese
Math,加减两科成绩,不重载运算,代码如下。

    class Student
    {
        /// <summary>
        /// 语文成绩
        /// </summary>
        public double Chinese { get; set; }

        /// <summary>
        /// 数学成绩
        /// </summary>
        public double Math { get; set; }
    }

【金沙注册送58】碎知识点,操作符重载及中缀调用。比较五个战绩差距

            var a = new Student
            {
                Chinese = 90.5d,
                Math = 88.5d
            };

            var b = new Student
            {
                Chinese = 70.5d,
                Math = 68.5d
            };

            //a的语文比b的语文高多少分
            Console.WriteLine(a.Chinese - b.Chinese);
            //a的数学比b的数学高多少分
            Console.WriteLine(a.Math - b.Math);

使用operator 重载 -

    class Student
    {
        /// <summary>
        /// 语文成绩
        /// </summary>
        public double Chinese { get; set; }

        /// <summary>
        /// 数学成绩
        /// </summary>
        public double Math { get; set; }

        public static Student operator -(Student a, Student b)
        {
            return new Student
            {
                Chinese = a.Chinese - b.Chinese,
                Math = a.Math - b.Math
            };
        }
    }

相比成绩差距的代码能够改为

    class Program
    {
        static void Main(string[] args)
        {
            var a = new Student
            {
                Chinese = 90.5d,
                Math = 88.5d
            };

            var b = new Student
            {
                Chinese = 70.5d,
                Math = 68.5d
            };

            var c = a - b;
            //a的语文比b的语文高多少分
            Console.WriteLine(c.Chinese);
            //a的数学比b的数学高多少分
            Console.WriteLine(c.Math);
        }
    }

参考:运算符(C#
参考)

  在那篇博客中,大家将介绍如下内容:

C++ 碎知识点

金沙注册送58 1

  • ==运算符与基元类型
  • ==运算符与引用类型
  • ==运算符与String类型
  • ==运算符与值类型
  • ==运算符与泛型

2三. 不能够被重载的运算符

  • ** sizeof **:sizeof 运算符
  • ** . **:成员运算符
  • ** .* **:成员指针运算符
  • ** :: **:成效域解析运算符
  • ** ? : **:条件运算符
  • ** typeid **:一个 RTT 运算符
  • ** const_cast **:强制类型转换运算符
  • ** dynamic_cast **:强制类型转换运算符
  • ** reinterpret_cast **:强制类型转换运算符
  • ** static_cast **:强制类型转换运算符

操作符重载其实很有趣!但这么些定义却很少有人知道,使用操作符重载在某种程度上会给代码的阅读拉动一定的麻烦。由此,慎用操作符被认为是贰个好习惯。的确,操作符重载是1把双刃剑,既能削铁如泥,也能“引火烧身”,那篇小说将从实用的角度来上课操作符重载的大旨用法。

 

二四. 重载限制

  • 重载后的运算符必须至少有三个操作数是用户定义的种类,这将防备用户为正式项目重载运算符。
  • 利用运算符时无法违反运算符原来的句法规则。同样,不可能改改运算符的优先级和结合性。
  • 无法创建新的运算符。
  • 无法重载上边难点 23 中的那些运算符。
  • 大部运算符都能够因而分子函数或非成员函数进行重载,但下边包车型地铁运算符只可以经过分子函数实行重载:
    • ** = **:赋值运算符
    • ** ( ) **:函数调用运算符
    • ** [ ] **:下标运算符
    • ** -> **:通过指针访问类成员的运算符

支撑重载的操作符类型

Kotlin语言帮忙重载的操作符类型相比较多。以最新版本1.2.21为准,近日支撑重载的操作符可以综合为以下几类:

==运算符与基元类型

  大家独家用二种方法相比较七个整数,第3个利用的是Equals(int)办法,每二个应用的是==运算符:  

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         int num1 = 5;
 6         int num2 = 5;
 7 
 8         Console.WriteLine(num1.Equals(num2));
 9         Console.WriteLine(num1 == num2);
10     }
11 }

  运营方面包车型客车言传身教,八个语句出的结果均为true。大家经过ildasm.exe工具进行反编译,查看IL代码,理解底层是什么样执行的。

  金沙注册送58 2

  假如您从前平素未有接触过IL指令,不过没什么,在那里您不要求精通有所的命令,我们只是想通晓那多个比较艺术的差异。

  您能够看看这样壹行代码:

1   IL_0008:  call       instance bool [mscorlib]System.Int32::Equals(int32)

  在此地调用的是int类型Equals(Int32)措施(该方法是IEquatable<Int>接口的兑现)。

  今后再来看看使用==运算符相比较生成的IL指令:

1   IL_0015:  ceq

  您能够见到,==运转符使用的是ceq.aspx)指令,它是运用CPU寄存器来相比较多个值。C#==运算符底层机制是使用ceq一声令下对基元类型举办比较,而不是调用Equals方法。

 

二5. 怎么着重载前置++ 和后置++ 运算符?

C++中鲜明重载前置运算时须要丰富三个整型参数举办标识。例如,上面包车型大巴代码重载了++运算符,达成了内置运算和前置运算。

class CEnty
{
public:
    int count;
    CEnty operator++(int) //重载后置++运算符
    {
        CEnty enty = *this;
        this->count++;
        return enty;
    }
    CEnty operator++() //重载前置++运算符
    {
        this->count++;
        CEnty enty = *this;
        return enty;
    }
    CEnty() //默认构造函数
    {
        count = 1;
    }
};
int main(int argc, char* argv[])
{
    CEnty a;
    CEnty b = a++; //调用后置++运算符重载函数
    CEnty c = ++a; //调用前置++运算符重载函数
    return 0;
}

一元操作符

==运算符与引用类型

  修改上边的示范代码,将int品种改为引用类型,编译后经过ildasm.exe工具反编写翻译查看IL代码。

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         Person p1 = new Person();
 6         p1.Name = "Person1";
 7 
 8         Person p2 = new Person();
 9         p2.Name = "Person1";
10 
11         Console.WriteLine(p1.Equals(p2));
12         Console.WriteLine(p1 == p2);
13     }
14 }

  上述C#代码的IL代码如下所示: 

  金沙注册送58 3

  我们看出p1.Equals(p2)代码,它是通过调用Object.Equals(Object)虚方法来比较相等,那是在预期之中的工作;今后我们来看==运算符生成的IL代码,与基元类型壹致,使用的也是ceq指令。

 

二六. 重载 == 运算符达成七个对象的相比较

请在 CArea 类中添加重载 == 运算符的代码,当八个对象的 Length 和 Height
数据成员完全相等时,则觉得五个对象相等,否则认为不对等。

class CArea
{
public:
    int Length;
    int Height;
    CArea()
    {
        Length = 0;
        Height = 0;
    }
    CArea(int len, int height)
    {
        Length = len;
        Height = height;
    }
};

我们得以定义三个布尔类型的 == 运算符重载函数。
譬如说参考代码:

class CArea
{
public:
    int Length;
    int Height;
    CArea() //默认构造函数
    {
        Length = 0;
        Height = 0;
    }
    CArea(int len, int height) //自定义构造函数
    {
        Length = len;
        Height = height;
    }
    bool operator==(CArea &area) //运算符重载
    {
        if (area.Length = Length && area.Height==Height)
        {
            cout <<"两个对象相等!" << endl;
            return true;
        }
        else
        {
            cout <<"两个对象不相等!" << endl;
            return false;
        }
    }
};
int main(int argc, char* argv[])
{
    CArea area1(30, 25);
    CArea area2(20, 30);
    if (area1 == area2) //调用重载的==运算符
    {
        ;
    }
    return 0;
}

参考资料:
C++ Primer Plus (第6版)

壹元前缀操作符

操作符 对应方法
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

上述三个操作符在普通使用中频率很高,第四个操作符在宗旨运算中很少使用,第二个操作符就是广泛的取反操作,第多少个操作符是逻辑取反操作。接下来,大家选拔增加的方法重载那三个操作符:

/**
 * 一元操作符
 *
 * @author Scott Smith 2018-02-03 14:11
 */
data class Number(var value: Int)

/**
 * 重载一元操作符+,使其对Number中实际数据取绝对值
 */
operator fun Number.unaryPlus(): Number {
    this.value = Math.abs(value)
    return this
}

/**
 * 重载一元操作符-,使其对Number中实际数据取反
 */
operator fun Number.unaryMinus(): Number {
    this.value = -value
    return this
}

/**
 * 这个操作符通常是用于逻辑取反,这里用一个没有意义的操作,来模拟重载这个操作符
 * 结果:始终返回Number中实际数据的负值
 */
operator fun Number.not(): Number {
    this.value = -Math.abs(value)
    return this
}

fun main(args: Array<String>) {
    val number = Number(-3)
    println("Number value = ${number.value}")
    println("After unaryPlus: Number value = ${(+number).value}")
    println("After unaryMinus: Number value = ${(-number).value}")

    number.value = Math.abs(number.value)
    println("After unaryNot: Number value = ${(!number).value}")
}

运营上述代码,将得到如下结果:

Number value = -3
After unaryPlus: Number value = 3
After unaryMinus: Number value = -3
After unaryNot: Number value = -3

==运算符与String类型

   接来下来看String连串的事例:  

 1 class Program
 2 {
 3     static void Main(String[] args)
 4     {
 5         string s1 = "Sweet";
 6         string s2 = String.Copy(s1);
 7 
 8         Console.WriteLine(ReferenceEquals(s1, s2));
 9         Console.WriteLine(s1 == s2);
10         Console.WriteLine(s1.Equals(s2));
11     }
12 }

  上边的代码与大家原先看过的那些相像,但是这一次大家运用String金沙注册送58,种类的变量。大家建一个字符串,并交付s1变量,在下一行代码大家创设这么些字符串的副本,并付出另3个变量名称s2

  运营方面的代码,在决定台出口的结果如下:

  金沙注册送58 4

  您能够见到ReferenceEquals返回false,这表示这四个变量是差别的实例,可是==运算符和Equals主意再次回到的均是true。在String花色中,==运算符执行的结果与Equals执行的结果一律。

  同样我们采纳过ildasm.exe工具反编译查看生成IL代码。

  金沙注册送58 5

  在此间大家从不看出ceq指令,对String类型应用==运算符判断相等时,调用的是1个op_equality(string,string)的新措施,该办法须要七个String品类的参数,那么它终归是什么样吗?

  答案是String品种提供了==运算符的重载。在C#中,当大家定义一个项目时,大家能够重载该品种的==运算符;例如,对于过去的事情例中大家落实的Person类,假若大家为它重载==运算符,大约的代码如下:

 1 public class Person
 2 {
 3 
 4     public string Name { get; set; }
 5 
 6     public static bool operator ==(Person p1, Person p2)
 7     {
 8         // 注意这里不能使用==,否则会导致StackOverflowException
 9         if (ReferenceEquals(p1, p2))
10             return true;
11 
12         if (ReferenceEquals(p1, null) || ReferenceEquals(p2, null)) 
13             return false; 
14 
15           return p1.Name == p2.Name;
16     }
17 
18     public static bool operator !=(Person p1, Person p2)
19     {
20         return !(p1 == p2);
21     }
22 }

  下边包车型客车代码很简短,大家贯彻了==运算符重载,那是一个静态方法,但此处要留意的是,方法的称谓是perator ==,与静态方法的相似性;事实上,它们会被由编写翻译器成贰个名字为op_Equality()的分裂平常静态方法。

  为了接纳工作更是清楚,大家查阅微软落到实处的String类型。

  金沙注册送58 6

  在上边的截图中,大家得以看到,有五个运算符的重载,三个用以相等,另一个是不等式运算符,其运算格局完全相同,然则否定等于运算符输出。要求专注的少数是,假使你想重载3个类别的==运营符的兑现,那么您还须求重载!=操作符的贯彻,不然编写翻译会报错。

 

自增和自减操作符

操作符 对应方法
a++/++a a.inc()
a–/–a a.dec()

重载那么些操作符相对相比难了然,官方文书档案有1段简短的文字表明,翻译成代码能够这么表示:

// a++
fun increment(a: Int): Int {
  val a0 = a
  a = a + 1
  return a0
}

// ++a
fun increment(a: Int): Int {
  a = a + 1
  return a
}

看懂下面的代码后,咱们换来须要重载的Number类,Kotlin最后会这么处理:

// Number++
fun increment(number: Number): Number {
  val temp = number
  val result = number.inc()
  return result
}

// Number++
fun increment(number: Number): Number {
  return number.inc()
}

因此,重载Number类自加操作符,我们得以那样做:

operator fun Number.inc(): Number {
    return Number(this.value + 1)
}

重载自减操作符同理,完整代码请参见作者的Git版本库:kotlin-samples

==运算符与值类型

  
在示范值类型的示范前,我们先将Person类型从引用类型改为值类型,Person定义如下:

 1 public struct Person
 2 {
 3     public string Name { get; set; }
 4 
 5     public Person(string name)
 6     {
 7         Name = name;
 8     }
 9 
10     public override string ToString()
11     {
12 
13         return Name;
14     }
15 }

  大家将示例代码改为如下:

 1  class Program
 2  {
 3      static void Main(String[] args)
 4      {
 5          Person p1 = new Person("Person1");
 6          Person p2 = new Person("Person2");
 7 
 8          Console.WriteLine(p1.Equals(p2));
 9          Console.WriteLine(p1 == p2);
10      }
11  }

   当大家在品味编写翻译上述代码时,VS将唤起如下错误:

金沙注册送58 7

  依据错误提醒,大家须要贯彻Person结构体的==运算符重载,重载的口舌如下(忽略具体的逻辑):

1  public static bool operator ==(Person p1, Person p2)
2  {
3  }
4  public static bool operator !=(Person p1, Person p2)
5  {
6  }

   当添加上边代码后,重新编译程序,通过ildasm.exe工具反编写翻译查看IL代码,发现实价值类型==运算符调用也是op_Equality方法。

  关于值类型,大家还亟需表明一(Karicare)个题材,在不重写Equals(object)措施时,该形式达成的法则是通过反射遍历全部字段并检讨各个字段的相等性,关于那或多或少,大家不演示;对于值类型,最棒重写该措施。

 

二元操作符

==运算符与泛型

  大家编辑另一段示例代码,评释五个String类型变量,通过四种分裂的秘诀比较运算:

 1 public class Program
 2 {
 3     public static void Main(string[] args)
 4     {
 5         string str = "Sweet";
 6         string str1 = string.Copy(str);
 7 
 8         Console.WriteLine(ReferenceEquals(str, str1));
 9         Console.WriteLine(str.Equals(str1));
10         Console.WriteLine(str == str1);
11         Console.WriteLine(object.Equals(str, str1));
12     }
13 }

  输出的结果如下:

  金沙注册送58 8

  首先,我们应用ReferenceEquals措施判断八个String变量都引用相同,接下去大家再使用实例方法Equals(string),在第二行,我们利用==运算符,最终,我们接纳静态方法Object.quals(object,object)(该办法最终调用的是String体系重写的Object.Equals(object)主意)。大家获得结论是:

  • ReferenceEquals艺术重回false,因为它们不是同叁个对象的引用;
  • String类型的Equals(string)主意重返也是true,因为多个String花色是1致的(即1律的行列或字符);
  • ==运算符也将再次来到true,因为那七个String花色的值相同的;
  • 虚方法Object.Equals也将回来true,那是因为在String品种重写了章程,判断的是String是还是不是值相同。

  今后我们来修改一下以此代码,将String品种改为Object类型:

 1 public class Program
 2 {
 3     public static void Main(string[] args)
 4     {
 5         object str = "Sweet";
 6         object str1 = string.Copy((string)str);
 7 
 8         Console.WriteLine(ReferenceEquals(str, str1));
 9         Console.WriteLine(str.Equals(str1));
10         Console.WriteLine(str == str1);
11         Console.WriteLine(object.Equals(str, str1));
12     }
13 }

 

  运转的结果如下:

  金沙注册送58 9

  第三种艺术再次来到的结果与修改在此之前不一致,==运算符重返的结果是false,那是为啥吗?

  那是因为==运算符实际上是一个静态的秘诀,对1非虚方法,在编写翻译时就早已控制用调用的是哪多个方法。在上面的事例中,引用类型应用的是ceq指令,而String项目调用是静态的op_Equality情势;那四个实例不是同贰个对象的引用,所以ceq一声令下执行后的结果是false

  再来说一下==运算符与泛型的题材,大家创设1个总结的不2秘诀,通过泛型方法判断八个泛型参数是或不是等于并在控制台上打字与印刷出结果:

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(a == b);
4 }

  然则当大家编写翻译那段代码时,VS提示如下错误:

金沙注册送58 10

  下面呈现的荒唐很简短,无法运用==运算符比较多个泛型T。因为T能够是其余项目,它能够是引用类型、值类型,不能够提供==运算符的实际贯彻。

  假诺像下边那样修改一下代码:

1 static void Equals<T>(T a, T b) where T : class
2 {
3     Console.WriteLine(a == b);
4 }

  当我们将泛型类型T改为引用类型,能学有所成编写翻译;修改Main主意中的代码,创造八个相同的String花色,和原先的例证一样:  

 1 public class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         string str = "Sweet";
 6         string str1 = string.Copy(str);
 7 
 8         Equals(str, str1);
 9     }
10 
11     static void Equals<T>(T a, T b) where T : class
12     {
13         Console.WriteLine(a == b);
14     }
15 }

 

  输出的结果如下:  

  金沙注册送58 11

  结果与你预期的结果不等同呢,我们目的在于的结果是true,输出的结果是false。不过仔细思量一下,只怕会找到答案,因为泛型的自律是援引类型,==运算符对于引用类型应用的是引用相等,IL代码能够表明那或多或少:

  金沙注册送58 12

  假如我们泛型方法中的==运算符改为利用Equals方法,代码如下:  

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(object.Equals(a, b));
4 }

   大家改用Equals,也能够去掉class自律;假使大家再一次运转代码,控制台打字与印刷的结果与大家预料的同样,那是因为调用是虚方法object.Equals(object)重写之后的达成。

  然而任何的标题来了,如若对于值类型,那里就会发出装箱,有未有消除的情势呢?关于那一点,我们直接交给答案,有时间专程来谈谈那个题材。

  将相比较的值类型完毕IEquatable<T>.aspx)接口,并将比较的代码改为如下,那样能够制止装箱(关于这或多或少,能够参照老赵的博客:):

1 static void Equals<T>(T a, T b)
2 {
3     Console.WriteLine(EqualityComparer<T>.Default.Equals(a, b));
4 }

   

算术运算符

操作符 对应方法
a + b a.plus(b)
a – b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a..b a.rangeTo(b)

前两个操作符相对相比较好通晓,我们以a + b为例,举个五个简单易行的例证:

// 重载Number类的加法运算符
operator fun Number.plus(value: Int): Number {
    return Number(this.value + value)
}

fun main(args: Array<String>) {
       println((Number(1) + 2))
}
// 输出结果:
Number value = 3

相对相比难驾驭的是第四个范围运算符,这一个操作符紧要用于生成一段数据范围。咱们认为Number自笔者就象征三个整型数字,因而,重载Number是一件有含义的作业。直接看例子:

operator fun Number.rangeTo(to: Number): IntRange {
    return this.value..to.value
}

fun main(args: Array<String>) {
    val startNumber = Number(3)
    val endNumber = Number(9)

    (startNumber..endNumber).forEach {
        println("value = $it")
    }
}

// 运行结果:
value = 3
value = 4
value = 5
value = 6
value = 7
value = 8
value = 9

总结

  对于基元类型==运算符的最底层机制使用的是ceq指令,通过CPU寄存器实行比较;

  对于引用类型==运算符,它也应用的ceq命令来相比较内部存款和储蓄器地址;

  对于重载==运算符的类型,实际上调用的是op_equality其1尤其的措施;

  尽量保障==操作符重载和Object.Equals(Object)虚方法的写重回的是同等的结果;

  对于值类型,Equals艺术暗中同意是透过反射遍历全体字段并检查每一种字段的相等性,为了增强质量,大家需求重写该办法;

  值类型默许景况下无法使用==运算符,须要完毕==运算符的重载;

  由于==运算符重载完成实际上是1个静态的点子,在泛型类或方法中央银行使时与事实上的结果恐怕存在差别,使用Equals方法可以制止那么些题材。

  

  转发请注解来源,原版的书文链接:

“In”运算符

操作符 对应方法
a in b b.contains(a)
a !in b !b.contains(a)

其一操作符相对相比较好精通,重载那些操作符能够用来判断某些数据是或不是在其余贰个对象中。大家用2个万分简单的自定义类来模拟集合操作:

class IntCollection { 
    val intList = ArrayList<Int>()
}

// 重载"in"操作符
operator fun IntCollection.contains(value: Int): Boolean {
    return this.intList.contains(value)
}

fun main(args: Array<String>) {
    val intCollection = IntCollection()
    intCollection.add(1, 2, 3)
    println(3 in intCollection)
}

// 输出结果:
true

目录访问运算符

操作符 对应方法
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, …, i_n] a.get(i_1, …, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, …, i_n] = b a.set(i_1, …, i_n, b)

其壹操作符很有意思,例如,如若你要访问Map中有个别数据,平常是那般的map.get("key"),使用索引运算符你还能那样操作:

val value = map["key"]

大家继续以IntCollection类为例,尝试重写a[i]a[i] = b三个运算符,其余运算符同理。

// 重载a[i]操作符
operator fun IntCollection.get(index: Int): Int {
    return intList[index]
}

// 重载a[i] = b操作符
operator fun IntCollection.set(index: Int, value: Int) {
    intList[index] = value
}

fun main(args: Array<String>) {
    val intCollection = IntCollection()
    intCollection.add(1, 2, 3)
    println(intCollection[0])

    intCollection[2] = 4
    print(intCollection[2])
}

接下去,大家用索引运算符来做壹些更好玩的业务!新建1个平常的KotlinUser

class User(var name: String,
           var age: Int) {

}

动用上面包车型客车主意重载索引运算符:

operator fun User.get(key: String): Any? {
    when(key) {
        "name" -> {
            return this.name
        }
        "age" -> {
            return this.age
        }
    }

    return null
}

operator fun User.set(key: String, value:Any?) {
    when(key) {
        "name" -> {
            name = value as? String
        }
        "age" -> {
            age = value as? Int
        }
    }
}

接下去,你会神奇地发现,二个家常的Kotlin类照旧也能够使用索引运算符对成员变量进行操作了,是否很神奇?

fun main(args: Array<String>) {
    val user = User("Scott Smith", 18)
    println(user["name"])
    user["age"] = 22
    println(user["age"])
}

因而,索引运算符不仅仅能够对集合类数据实行操作,对三个见惯司空的Kotlin类也得以发挥同样的职能。就算你脑洞丰硕大,你还足以窥见越多更神奇的玩法。

调用操作符

操作符 对应方法
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

重载这些操作符并简单,精通它的选择场景却有肯定的难度。为了知道它的施用场景,我们来举几个简单易行的例证:

class JsonParser {

}

operator fun JsonParser.invoke(json: String): Map<String, Any> {
    val map = Json.parse(json)
    ...
    return map
}

// 可以这样调用
val parser = JsonParser()
val map = parser("{name: \"Scott Smith\"}")

此间的调用有点像省略了一个解析Json数据的格局,难道它仅仅便是以此效应吧?是的,调用操作符其实就那1个作用。即便3个Kotlin类仅仅唯有二个办法,直接利用括号调用的确是一个不错的主见。可是,在动用的时候依然要多少注意一下,幸免出现歧义。

广义赋值操作符

操作符 对应方法
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

那个操作符绝相比较好领悟,大家以Number类为例,举叁个大致的例证:

// 广义赋值运算符
operator fun Number.plusAssign(value: Int) {
    this.value += value
}

fun main(args: Array<String>) {
    val number = Number(1)
    number += 2
    println(number)
}

// 输出结果:
Number value = 3

拾分与分歧操作符

操作符 对应方法
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

重载那几个操作符与Java重写equals方法是一律的。然则,那里要小心与Java的差别,在Java端==用以判断八个对象是还是不是是同1对象(指针级别)。而在Kotlin语言中,若是我们不做其它处理,==一样使用Java对象的equals主意判断五个对象是否等于。

除此以外,那里还有一种13分处境,假如左值等于null,这一年a?.equals(b)将重回null值。由此,那里还扩大了?:运算符用于更为认清,在这几个场地下,当且仅当b

null的时候,a、b才有不小恐怕也便是。由此,才有了地点的对应关系,那里以User类为例举一个简练的事例:

class User(var name: String?,
           var age: Int?) {

    operator override fun equals(other: Any?): Boolean {
        if(other is User) {
            return (this.name == other.name) && (this.age == other.age)
        }
        return false
    }
}

在意:那里有一个异样的地方,与其余操作符不1致的地点是,假设选拔扩展的秘籍尝试重载该操作符,将会报错。由此,假若要重载该操作符,一定要在类中展开重写。

相比较操作符

操作符 对应方法
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

正如操作符是3个在1般行使中频率分外高的操作符,重载这几个操作符只要求通晓以上表格中多少个规则即可。我们以Number类为例举二个回顾的事例:

operator fun Number.compareTo(number: Number): Int {
    return this.value - number.value
}

质量源委员会托操作符

天性委托操作符是一种特别独特的操作符,其首要用在代理属性中。关于Kotlin代理的知识,若是你还不打听的话,请参考那篇文章
Delegation。那篇小说介绍的相对不难,后边会出壹篇更详尽的篇章介绍代理相关的学识。

中缀调用

观望这里,也许有壹些追求更尖端玩法的同学会问:Kotlin帮忙自定义操作符吗?

答案当然是:不可能!然则,别失望,infix或然适合您,它实在能够当作1种自定义操作符的贯彻。那里大家对聚集List新增三个扩充方法intersection用来获取八个聚众的犬牙相错:

// 获取两个集合的交集
fun <E> List<E>.interSection(other: List<E>): List<E> {
    val result = ArrayList<E>()
    forEach {
        if(other.contains(it)) {
            result.add(it)
        }
    }

    return result
}

接下去,大家就足以在List连同子类中运用点语法调用了。但,它看起来依旧不像二个操作符。为了让它更像贰个操作符,我们继承做点事情:

  • 添加infix关键词
  • 将函数名修改为∩(那是数学上取得交集的标记符号)
    而是,万万没悟出,修改完毕后甚至报错了。Kotlin并不容许直接动用特殊符号作为函数名开首。因而,我们取形近的字母n用以表示函数名:

// 获取两个集合的交集
infix fun <E> List<E>.n(other: List<E>): List<E> {
    val result = ArrayList<E>()
    forEach {
        if(other.contains(it)) {
            result.add(it)
        }
    }

    return result
}

接下去,咱们就能够这么调用了val interSection = list1 n list2,怎么着?是或不是很像自定义了二个拿走交集的操作符n?如果你指望自定义操作符,能够尝尝那样做。

其实infix的运用场景还不止这几个,接下去,大家再用它做到1件更好玩的事情。

在实际上项目支付中,数据库数据到对象的处理是1件繁琐的进程,最麻烦的地点实际上思维的转换。那我们是不是足以在代码中一直动用SQL语句询问对象数据吧?例如那样:

val users = Select * from User where age > 18

纸上学来终觉浅,觉知此事需躬行。有了这些idea,接下去,我们就朝着那几个目的努力。
1、先声美素佳儿个Sql类,准备如下方法:

   infix fun select(columnBuilder: ColumnBuilder): Sql {

   infix fun from(entityClass: Class<*>): Sql 

   infix fun where(condition: String): Sql 

   fun <T> query(): T 

2、我们的指标是:最后转换来SQL语句情势。因而,扩大如下完结:

class ColumnBuilder(var columns: Array<out String>) {

}

class Sql private constructor() {
    var columns = emptyList<String>()
    var entityClass: Class<*>? = null
    var condition: String? = null

    companion object {
        fun get(): Sql {
            return Sql()
        }
    }

    infix fun select(columnBuilder: ColumnBuilder): Sql {
        this.columns = columnBuilder.columns.asList()
        return this
    }

    infix fun from(entityClass: Class<*>): Sql {
        this.entityClass = entityClass
        return this
    }

    infix fun where(condition: String): Sql {
        this.condition = condition
        return this
    }

    fun <T> query(): T {
        // 此处省略所有条件判断
        val sqlBuilder = StringBuilder("select ")

        val columnBuilder = StringBuilder("")
        if(columns.size == 1 && columns[0] == "*") {
            columnBuilder.append("*")
        } else {
            columns.forEach {
                columnBuilder.append(it).append(",")
            }
            columnBuilder.delete(columns.size - 1, columns.size)
        }

        val sql = sqlBuilder.append(columnBuilder.toString())
                            .append(" from ${entityClass?.simpleName} where ")
                            .append(condition)
                            .toString()
        println("执行SQL查询:$sql")

        return execute(sql)
    }

    private fun <T> execute(sql: String): T {
        // 仅仅用于测试
        return Any() as T
    }
}

三、为了看起来更相像,再追加如下八个办法:

// 使其看起来像在数据库作用域中执行
fun database(init: Sql.()->Unit) {
    init.invoke(Sql.get())
}

// 因为infix限制,参数不能直接使用可变参数。因此,我们增加这个方法使参数组装看起来更自然
fun columns(vararg columns: String): ColumnBuilder {
    return ColumnBuilder(columns)
}

接下去,正是见证神跡的每一天!

fun main(args: Array<String>) {
    database {
        (select (columns("*")) from User::class.java where "age > 18").query()
    }
}

// 输出结果:
执行SQL查询:select * from User where age > 18

为了便于大家查看,大家领到完整执行代码段与SQL语句比较:

select          *       from User             where  age > 18
select  (columns("*"))  from User::class.java where "age > 18"

神奇吗?
由来,大家就能够直接在代码中春风得意地选拔类似SQL语句的措施开始展览艺术调用了。

总结

本篇作品从操作符重载实用的角度讲解了操作符重载的兼具有关知识。如小说初步所说,操作符重载是壹把双刃剑。用得好一箭双雕,用不佳斗倍功半。因而,作者给我们的提议是:使用的时候一定要力保能够自圆其说,不难的话,正是当然。作者认为相对于古老的言语C++来说,Kotlin语言操作符重载的设计是非常屌的。若是你明白本人在做什么样,我12分推荐您在生养环境中运用操作符重载来简化操作。

本篇小说例子代码点那里:kotlin-samples


自身是欧阳锋,三个热衷Kotlin语言编制程序的学生。假若您喜爱我的稿子,请在篇章下方留下你爱的印记。要是你不欣赏笔者的篇章,请先喜欢上自家的稿子。然后再留下爱的印记!

下次小说再见,拜拜!


相关文章

网站地图xml地图