【亚洲必赢官网】操作符重载及中缀调用,转换关键字

operator

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

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

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

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

相比较多少个战表差距

            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++ 碎知识点

亚洲必赢官网 1

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

二三. 不可能被重载的运算符

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

操作符重载其实很风趣!但以此定义却很少有人知晓,使用操作符重载在某种程度上会给代码的读书拉动一定的难为。因而,慎用操作符被认为是三个好习惯。的确,操作符重载是①把双刃剑,既能削铁如泥,也能“引火烧身”,那篇作品将从实用的角度来教学操作符重载的主干用法。

 

2四. 重载限制

  • 重载后的运算符必须至少有八个操作数是用户定义的档次,那将预防用户为标准项目重载运算符。
  • 使用运算符时不能够违反运算符原来的句法规则。同样,无法修改运算符的优先级和结合性。
  • 不可能创设新的运算符。
  • 不可能重载下面难题 2三 中的那个运算符。
  • 大多数运算符都能够透过成员函数或非成员函数进行重载,但下边包车型大巴演算符只可以通过成员函数进行重载:
    • ** = **:赋值运算符
    • ** ( ) **:函数调用运算符
    • ** [ ] **:下标运算符
    • ** -> **:通过指针访问类成员的运算符

援助重载的操作符类型

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代码,领悟底层是如何执行的。

  亚洲必赢官网 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方法。

 

贰五. 怎么重载前置++ 和前置++ 运算符?

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代码如下所示: 

  亚洲必赢官网 3

  我们来看p1.Equals(p2)代码,它是经过调用Object.Equals(Object)虚方法来相比较相等,那是在预料之中的事情;未来我们来看==运算符生成的IL代码,与基元类型1致,使用的也是ceq指令。

 

2陆. 重载 == 运算符达成八个指标的可比

请在 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()

如上多个操作符在平凡行使中频率很高,第二个操作符在基本运算中很少使用,第3个操作符便是广阔的取反操作,第5个操作符是逻辑取反操作。接下来,大家采取扩张的不贰诀窍重载那三个操作符:

/**
 * 一元操作符
 *
 * @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连串的变量。大家建1个字符串,并交付s1变量,在下一行代码大家成立这么些字符串的副本,并交由另三个变量名称s2

  运营方面包车型客车代码,在控制台出口的结果如下:

  亚洲必赢官网 4

  您能够观察ReferenceEquals返回false,那意味着那五个变量是例外的实例,不过==运算符和Equals艺术重临的均是true。在String体系中,==运算符执行的结果与Equals履行的结果同样。

  同样大家采用过ildasm.exe工具反编写翻译查看生成IL代码。

  亚洲必赢官网 5

  在那边大家从不见到ceq指令,对String品种应用==运算符判断相等时,调用的是一个op_equality(string,string)的新点子,该办法须求四个String项目标参数,那么它到底是何许吗?

  答案是String系列提供了==运算符的重载。在C#中,当大家定义2个连串时,我们能够重载该项目标==运算符;例如,对于之前的例证中大家落到实处的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 }

  上边的代码很不难,大家落到实处了==运算符重载,那是3个静态方法,但那里要留意的是,方法的名号是perator ==,与静态方法的相似性;事实上,它们会被由编写翻译器成二个名称叫op_Equality()的出色静态方法。

  为了利用工作越来越透亮,大家查阅微软落到实处的String类型。

  亚洲必赢官网 6

  在上边的截图中,大家能够看来,有多少个运算符的重载,一个用以相等,另贰个是不等式运算符,其运算情势完全相同,但是否定等于运算符输出。要求留意的一点是,假设你想重载3个品种的==运转符的落实,那么您还需求重载!=操作符的实现,不然编写翻译会报错。

 

自增和自减操作符

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

重载这么些操作符相对相比难知晓,官方文书档案有壹段简短的文字表达,翻译成代码能够这样表示:

// 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将唤起如下错误:

亚洲必赢官网 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方法。

  关于值类型,我们还亟需说爱他美(Aptamil)(Nutrilon)个题材,在不重写Equals(object)措施时,该格局达成的法则是通过反射遍历全数字段并检查各种字段的相等性,关于那或多或少,我们不演示;对于值类型,最佳重写该方式。

 

二元操作符

==运算符与泛型

  我们编辑另1段示例代码,证明多个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 }

  输出的结果如下:

  亚洲必赢官网 8

  首先,大家利用ReferenceEquals措施判断几个String变量都引用相同,接下去大家再使用实例方法Equals(string),在第3行,大家选择==运算符,最后,大家选拔静态方法Object.quals(object,object)(该办法最终调用的是String品类重写的Object.Equals(object)措施)。大家赢得结论是:

  • ReferenceEquals格局再次来到false,因为它们不是同1个目的的引用;
  • String类型的Equals(string)措施重返也是true,因为多个String连串是同等的(即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 }

 

  运维的结果如下:

  亚洲必赢官网 9

  第三种格局再次来到的结果与修改从前不等同,==运算符重回的结果是false,那是干吗呢?

  那是因为==运算符实际上是二个静态的方法,对壹非虚方法,在编写翻译时就已经决定用调用的是哪1个办法。在上头的例子中,引用类型应用的是ceq指令,而String亚洲必赢官网,品种调用是静态的op_Equality方法;那多少个实例不是同三个目标的引用,所以ceq命令执行后的结果是false

  再来说一下==运算符与泛型的标题,我们创造二个粗略的主意,通过泛型方法判断五个泛型参数是或不是等于并在控制台上打字与印刷出结果:

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

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

亚洲必赢官网 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 }

 

  输出的结果如下:  

  亚洲必赢官网 11

  结果与你预期的结果不平等吧,大家盼望的结果是true,输出的结果是false。可是仔细想想一下,大概会找到答案,因为泛型的束缚是援引类型,==运算符对于引用类型应用的是引用相等,IL代码可以表明那或多或少:

  亚洲必赢官网 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

争持比较难驾驭的是第5个范围运算符,那一个操作符重要用于生成壹段数据范围。我们以为Number本身就表示三个整型数字,因而,重载Number是1件有含义的作业。直接看例子:

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其壹特殊的秘籍;

  尽量保险==操作符重载和Object.Equals(Object)虚方法的写再次来到的是如出一辙的结果;

  对于值类型,Equals主意暗中同意是因而反射遍历全部字段并检查每种字段的相等性,为了增强品质,大家须求重写该办法;

  值类型暗中同意景况下无法利用==运算符,须求达成==运算符的重载;

  由于==运算符重载达成实际上是二个静态的法子,在泛型类或措施中应用时与事实上的结果恐怕存在差距,使用Equals艺术可防止止那么些难点。

  

  转发请表明来源,原版的书文链接:

“In”运算符

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

以此操作符相对相比较好明白,重载那么些操作符能够用来判断某些数据是还是不是在别的八个指标中。大家用三个非常简单的自定义类来效仿集合操作:

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])
}

接下去,大家用索引运算符来做一些更好玩的事务!新建一个平日的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"])
}

由此,索引运算符不仅仅能够对集合类数据开始展览操作,对1个普通的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)

重载那么些操作符并不难,精晓它的使用场景却有必然的难度。为了驾驭它的应用场景,我们来举3个简短的例子:

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个功用。要是一个Kotlin类仅仅唯有八个办法,直接利用括号调用的确是3个不易的呼声。可是,在运用的时候依旧要某些注意一下,制止出现歧义。

广义赋值操作符

操作符 对应方法
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类为例,举1个简约的例证:

// 广义赋值运算符
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端==用以判断四个对象是或不是是同一对象(指针级别)。而在Kotlin语言中,假诺大家不做别的处理,==壹样使用Java对象的equals主意判断七个指标是还是不是等于。

除此以外,那里还有壹种卓殊情况,假设左值等于null,那一年a?.equals(b)将回到null值。因而,这里还扩展了?:运算符用于进一步判断,在那个情形下,当且仅当b

null的时候,a、b才有十分大希望非常。由此,才有了上边的附和关系,那里以User类为例举2个简易的例证:

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
    }
}

留意:那里有贰个10分的地点,与此外操作符分歧的地点是,若是应用扩充的章程尝试重载该操作符,将会报错。由此,如若要重载该操作符,一定要在类中进行重写。

比较操作符

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

比较操作符是一个在常常行使中频率10分高的操作符,重载这些操作符只须求驾驭以上表格中多少个规则即可。我们以Number类为例举二个简短的例证:

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

属性委托操作符

品质源委员会托操作符是1种尤其非凡的操作符,其主要用在代理属性中。关于Kotlin代理的知识,尽管你还不理解的话,请参考那篇小说
Delegation。这篇文章介绍的争辨简便易行,前边会出1篇更详尽的篇章介绍代理相关的文化。

中缀调用

见状此间,恐怕有局地追求更尖端玩法的同学会问:Kotlin补助自定义操作符吗?

答案自然是:不可能!可是,别失望,infix或是适合你,它实质上可以作为1种自定义操作符的落到实处。那里大家对聚集List增加产量2个增添方法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连同子类中央银行使点语法调用了。但,它看起来照旧不像3个操作符。为了让它更像四个操作符,大家继续做点工作:

  • 添加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,如何?是或不是很像自定义了3个获得交集的操作符n?假诺您愿意自定义操作符,能够品尝那样做。

其实infix的选取场景还不止这个,接下去,大家再用它完毕一件更旧事务。

在骨子里项目支付中,数据库数据到目的的处理是一件繁琐的经过,最麻烦的地点实际上思维的转移。那大家是还是不是足以在代码中央直属机关接采取SQL语句询问对象数据吧?例如那样:

val users = Select * from User where age > 18

纸上学来终觉浅,觉知此事需躬行。有了那一个idea,接下去,大家就朝着那一个目的努力。
一、先声圣元(Aptamil)个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
    }
}

3、为了看起来更相像,再充实如下七个办法:

// 使其看起来像在数据库作用域中执行
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语句的不二秘诀开始展览方式调用了。

总结

本篇文章从操作符重载实用的角度讲解了操作符重载的有所相关文化。如小说开首所说,操作符重载是1把双刃剑。用得好一语双关,用不好斗倍功半。由此,笔者给大家的建议是:使用的时候自然要保管能够自圆其说,简而言之,正是自然。笔者以为相对于古老的语言C++来说,Kotlin语言操作符重载的设计是那几个棒的。假若您明白本身在做怎么着,小编格外推荐您在生养条件中央银行使操作符重载来简化操作。

本篇文章例子代码点那里:kotlin-samples


自个儿是欧阳锋,一个热爱Kotlin语言编制程序的上学的小孩子。就算您欣赏作者的作品,请在篇章下方留下你爱的印记。要是你不爱好作者的稿子,请先喜欢上本身的文章。然后再留下爱的印记!

下次小说再见,拜拜!


网站地图xml地图