威尼斯人线上娱乐

【威尼斯人线上娱乐】碎知识点,操作符重载及中缀调用

3 4月 , 2019  

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类型
  • ==运算符与值类型
  • ==运算符与泛型

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

  • ** sizeof **:sizeof 运算符
  • ** . **:成员运算符
  • ** .* **:成员指针运算符
  • ** :: **:效率域解析运算符
  • ** ? : **:条件运算符
  • ** typeid **:一个 RTT 运算符
  • ** const_cast **:强制类型转换运算符
  • ** dynamic_cast **:强制类型转换运算符
  • **【威尼斯人线上娱乐】碎知识点,操作符重载及中缀调用。 reinterpret_cast **:强制类型转换运算符
  • ** static_cast **:强制类型转换运算符

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

 

二四. 重载限制

  • 重载后的运算符必须至少有一个操作数是用户定义的类别,那将防备用户为专业项目重载运算符。
  • 选取运算符时不可能违反运算符原来的句法规则。同样,不能修改运算符的优先级和结合性。
  • 不能够制造新的运算符。
  • 无法重载上面难点 2三 中的那多少个运算符。
  • 大部分运算符都能够经过分子函数或非成员函数进行重载,但上边包车型客车运算符只可以透过成员函数实行重载:
    • ** = **:赋值运算符
    • ** ( ) **:函数调用运算符
    • ** [ ] **:下标运算符
    • ** -> **:通过指针访问类成员的运算符

支撑重载的操作符类型

Kotlin语言支持重载的操作符类型相比较多。以新颖版本1.2.21为准,近期帮助重载的操作符能够归纳为以下几类:

==运算符与基元类型

  大家独家用二种办法比较五个整数,第一个利用的是Equals(int)艺术,每3个利用的是==运算符:  

 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行代码:

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

1元操作符

==运算符与引用类型

  修改上边的以身作则代码,将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版)

1元前缀操作符

操作符 对应方法
+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品类的变量。大家建二个字符串,并交给s1变量,在下一行代码大家成立那个字符串的副本,并提交另贰个变量名称s2

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

  威尼斯人线上娱乐 4

  您能够看看ReferenceEquals返回false,那象征那多个变量是例外的实例,不过==运算符和Equals艺术重返的均是true。在String体系中,==运算符执行的结果与Equals进行的结果同样。

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

  威尼斯人线上娱乐 5

  在此处我们从没观察ceq指令,对String品种应用==运算符判断相等时,调用的是多个op_equality(string,string)的新格局,该情势必要八个String项目标参数,那么它到底是何等吧?

  答案是String花色提供了==运算符的重载。在C#中,当大家定义3个类型时,我们得以重载该项指标==运算符;例如,对于在此以前的例证中大家兑现的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

  在地点的截图中,大家得以见到,有八个运算符的重载,2个用来相等,另二个是不等式运算符,其运算情势完全相同,但是否定等于运算符输出。要求小心的一些是,即使你想重载一个档次的==运转符的得以实现,那么您还亟需重载!=操作符的实现,不然编写翻译会报错。

 

自增和自减操作符

操作符 对应方法
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方法。

  关于值类型,大家还索要验证二个难点,在不重写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,因为它们不是同三个对象的引用;
  • 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 }

 

  运转的结果如下:

  威尼斯人线上娱乐 9

  第二种办法重临的结果与修改以前不一样,==运算符再次回到的结果是false,这是干什么吗?

  那是因为==运算符实际上是三个静态的法子,对壹非虚方法,在编写翻译时就早已决定用调用的是哪一个措施。在上边的事例中,引用类型应用的是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自律;假诺大家再一次运转代码,控制台打字与印刷的结果与我们预料的1致,那是因为调用是虚方法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)

以此操作符相对相比好理解,重载这些操作符能够用来判断有个别数据是不是在其它四个对象中。大家用3个万分不难的自定义类来模拟集合操作:

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些更好玩的作业!新建3个司空眼惯的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)

重载那么些操作符并简单,精晓它的接纳场景却有自然的难度。为了精晓它的施用场景,大家来举1个简易的事例:

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类仅仅只有贰个方式,直接动用括号调用的确是1个不错的主见。可是,在采纳的时候如故要有个别注意一下,防止出现歧义。

广义赋值操作符

操作符 对应方法
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语言中,借使我们不做任何处理,==相同使用Java对象的equals方法判断四个目的是还是不是等于。

别的,这里还有1种尤其意况,倘诺左值等于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

正如操作符是1个在普通使用中频率相当高的操作符,重载那个操作符只需求精通以上表格中多少个规则即可。大家以Number类为例举1个简约的例证:

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

性情委托操作符

品质源委员会托操作符是壹种十分特其余操作符,其主要用在代理属性中。关于Kotlin代理的学问,假若您还不领悟的话,请参见这篇文章
Delegation。那篇小说介绍的周旋简便易行,前面会出一篇更详细的小说介绍代理相关的文化。

中缀调用

探望此间,只怕有一些追求更高级玩法的同学会问:Kotlin支持自定义操作符吗?

答案自然是:不能够!不过,别失望,infix兴许适合你,它其实能够看成一种自定义操作符的落到实处。那里大家对聚集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会同子类中央银行使点语法调用了。但,它看起来依旧不像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,怎么着?是还是不是很像自定义了1个获得交集的操作符n?假设您愿意自定义操作符,能够品尝那样做。

其实infix的选拔场景还不止这个,接下去,大家再用它完结1件更逸事体。

在其实项目支出中,数据库数据到指标的拍卖是一件繁琐的进程,最麻烦的地点莫过于思维的转移。那大家是否能够在代码中央直机关接行使SQL语句询问对象数据吧?例如那样:

val users = Select * from User where age > 18

纸上学来终觉浅,觉知此事需躬行。有了那个idea,接下去,大家就朝着那个目的努力。
1、先声Bellamy个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语句的主意展开药格局调用了。

总结

本篇文章从操作符重载实用的角度讲解了操作符重载的享有相关知识。如文章发轫所说,操作符重载是一把双刃剑。用得好一石二鸟,用不佳斗倍功半。由此,小编给我们的建议是:使用的时候一定要保管能够自圆其说,简单的话,便是自然。我以为相对于古老的言语C++来说,Kotlin语言操作符重载的宏图是很厉害的。假如您理解本身在做什么样,笔者十二分推荐你在生育环境中运用操作符重载来简化操作。

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


自家是欧阳锋,三个好感Kotlin语言编制程序的学员。若是您喜爱本人的篇章,请在文章下方留下您爱的印记。假若您不希罕本身的作品,请先喜欢上本人的篇章。然后再留下爱的印记!

下次小说再见,拜拜!



相关文章

发表评论

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

网站地图xml地图