操作符重载及中缀调用,碎知识点

operator

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

假诺场景,四个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类型
  • ==运算符与值类型
  • ==运算符与泛型

二3. 不能够被重载的运算符

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

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

 

二四. 重载限制

  • 重载后的运算符必须至少有三个操作数是用户定义的品类,那将防患用户为规范项目重载运算符。
  • 行使运算符时无法违反运算符原来的句法规则。同样,不能够改改运算符的优先级和结合性。
  • 无法创制新的运算符。
  • 无法重载下面难题 二叁 中的那个运算符。
  • 大部运算符都能够由此分子函数或非成员函数实行重载,但下边包车型大巴运算符只好经过分子函数进行重载:
    • ** = **:赋值运算符
    • ** ( ) **:函数调用运算符
    • ** [ ]操作符重载及中缀调用,碎知识点。 **:下标运算符
    • ** -> **:通过指针访问类成员的运算符

支撑重载的操作符类型

Kotlin语言帮助重载的操作符类型比较多。以新型版本1.2.21为准,方今协助重载的操作符能够综合为以下几类:

==运算符与基元类型

  大家独家用三种方法相比八个整数,第多个应用的是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方法。

 

25. 如何重载前置++ 和前置++ 运算符?

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代码,与基元类型一致,使用的也是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()

如上多个操作符在通常使用中频率很高,第3个操作符在主旨运算中很少使用,第二个操作符正是周围的取反操作,第十三个操作符是逻辑取反操作。接下来,大家选拔扩展的秘诀重载那多个操作符:

/**
 * 一元操作符
 *
 * @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品类的变量。大家建二个字符串,并交给s1变量,在下一行代码大家创造那几个字符串的副本,并提交另2个变量名称s2

  运转方面包车型大巴代码,在支配台出口的结果如下:

  美高梅开户网址 4

  您能够观望ReferenceEquals返回false,那意味那三个变量是例外的实例,然则==运算符和Equals艺术再次回到的均是true。在String体系中,==运算符执行的结果与Equals履行的结果同样。

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

  美高梅开户网址 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类型。

  美高梅开户网址 6

  在上头的截图中,大家能够看出,有三个运算符的重载,三个用于相等,另贰个是不等式运算符,其运算格局完全相同,不过否定等于运算符输出。供给专注的有个别是,如若你想重载二个门类的==运维符的兑现,那么您还索要重载!=操作符的贯彻,否则编译会报错。

 

自增和自减操作符

操作符 对应方法
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将唤起如下错误:

美高梅开户网址 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)个标题,在不重写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 }

  输出的结果如下:

  美高梅开户网址 8

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

  • ReferenceEquals格局再次来到false,因为它们不是同三个指标的引用;
  • 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,这是干吗呢?

  那是因为==运算符实际上是2个静态的格局,对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样:  

 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

周旋相比较难明白的是第多少个范围运算符,那几个操作符首要用以生成一段数据范围。大家认为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本条奇特的方式;

  尽量确定保证==操作符重载和Object.Equals(Object)虚方法的写重临的是均等的结果;

  对于值类型,Equals措施暗中同意是因而反射遍历全数字段并检讨各类字段的相等性,为了狠抓质量,大家要求重写该办法;

  值类型暗中同意处境下不可能运用==运算符,需求贯彻==运算符的重载;

  由于==运算符重载实现实际上是一个静态的章程,在泛型类或措施中央银行使时与实际的结果或然存在差别,使用Equals艺术可避防止这些难题。

  

  转发请评释来源,原著链接:

“In”运算符

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

以此操作符绝相比较好领会,重载那几个操作符能够用来判断有个别数据是或不是在此外1个对象中。我们用贰个相当简单的自定义类来模拟集合操作:

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

接下去,咱们用索引运算符来做一点更有意思的事情!新建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
        }
    }
}

接下去,你会神奇地觉察,2个常备的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功效吧?是的,调用操作符其实就那四个职能。假使一个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端==用来判断四个对象是还是不是是同一对象(指针级别)。而在Kotlin语言中,即便大家不做别的处理,==1样使用Java对象的equals艺术判断七个对象是否等于。

其余,那里还有一种特有情况,假如左值等于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
    }
}

只顾:那里有二个独特的地点,与其余操作符不雷同的地方是,假诺应用扩张的主意尝试重载该操作符,将会报错。因而,假若要重载该操作符,一定要在类中实行重写。

比较操作符

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

比较操作符是几个在常常行使中频率12分高的操作符,重载那些操作符只须要控制以上表格中多少个规则即可。大家以Number类为例举贰个容易易行的例证:

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

性子委托操作符

质量源委员会托操作符是1种相对特殊的操作符,其根本用在代理属性中。关于Kotlin代理的学问,如若你还不打听的话,请参见那篇小说
Delegation。那篇作品介绍的相对不难,后边会出1篇更详实的小说介绍代理相关的文化。

中缀调用

阅览此间,恐怕有壹对追求更高级玩法的同学会问:Kotlin帮助自定义操作符吗?

答案自然是:不能够!然则,别失望,infix只怕适合你,它实际上能够视作壹种自定义操作符的落到实处。那里大家对聚集List新增1个恢弘方法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,怎么样?是还是不是很像自定义了三个获取交集的操作符n?假诺您愿意自定义操作符,可以尝尝那样做。

其实infix的利用场景还不止那么些,接下去,大家再用它做到1件更逸事务。

在实际项目支付中,数据库数据到对象的拍卖是壹件繁琐的经过,最艰难的地方实际上思维的转换。那大家是否可以在代码中一向动用SQL语句询问对象数据吧?例如那样:

val users = Select * from User where age > 18

纸上学来终觉浅,觉知此事需躬行。有了这些idea,接下去,大家就朝着那么些指标全力。
一、先声多美滋个Sql类,准备如下方法:

   infix fun select(columnBuilder: ColumnBuilder): Sql {

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

   infix fun where(condition: String): Sql 

   fun <T> query(): T 

二、大家的目的是:最后转换来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语句的点子进行格局调用了。

总结

本篇文章从操作符重载实用的角度讲解了操作符重载的装有相关文化。如小说开始所说,操作符重载是1把双刃剑。用得好一石二鸟,用倒霉斗倍功半。因而,作者给大家的建议是:使用的时候势须求确定保障能够自圆其说,简单的话,便是理所当然。小编觉着相对于古老的言语C++来说,Kotlin语言操作符重载的规划是非常厉害的。假若您领会自身在做什么,作者那几个推荐你在生育环境中选用操作符重载来简化操作。

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


自个儿是欧阳锋,3个热爱Kotlin语言编制程序的学习者。假如您欣赏笔者的文章,请在小说下方留下你爱的印记。如若您不爱好小编的稿子,请先喜欢上本身的小说。然后再留下爱的印记!

下次小说再见,拜拜!


发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图