操作符重载


有的编程语言允许一个类型定义操作符应该如何操作类型的实例,比如string类型和int类型都重载了(==)和(+)等操作符,当编译器发现两个int类型的实例使用+操作符的时候,编译器会生成把两个整数加到一起的代码。
当编译器发现两个string类型的实例使用+操作符的时候,编译器会生成把两个字符串连接到一起的代码。那么编译器怎么就会知道这样做呢?如何进行操作符重载呢?
下面C#代码展示了一个类中如何进行操作符重载:

namespace DoNet.Seven.ConsoleApplicationTest
{
class Program
{
static void Main(string[] args)
{
rational r1 = new rational();
rational r2 = new rational();
rational r3= r1 + r2;
Console.WriteLine(r3.Value);
Console.ReadKey(); }
}
//有理数
public sealed class rational
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rational(int value)
{
this._value = value;
} public rational()
{ }
public static rational operator+(rational num1,rational num2)
{
rational result = new rational(num1.Value+num2.Value);
return result;
} }
}

运行代码输入结果是15
用IL工具看下编译器生成的代码如下:

  1. 首先CLR规范要求操作符重载方法必须是public和static方法。另外,C#编译器要求操作符重载方法至少有一个参数的类型与当前定义的这个方法的类型相同。之所以这样做,是为了是编译器能在合理的时间内找到要绑定的操作符方法。
  2. 编程语言的编译器看到源码中出现一个+操作符时,会检查是否有一个操作数的类型定义了一个名为op_Addtion的specialname方法,而且该方法的参数兼容于操作数的类型,如果存在这样的一个方法,编译器就生成调用它的代码。如果不存在这样的一个方法,就生成一个编译错误。
  3. 对于其它操作符编译之后对应的方法如下表所示(左边是一元操作符,右边是二元操作符)

转换操作符


当设计一个类型时应该考虑到和其它类型之间的转换,这个其实很重要,将对我们的编码有很大的好处,就像每个类型都会有的一个方法Tostring()一样,我们定义一个int类型,可以很方便的用tostring()方法把
int转换为string,当然也可以转换为其它类型。就像上面的rational一样,如果能将一个int或者double转换为一个rational,就会很方便,反之亦然。

//有理数
public sealed class rational
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rational(int value)
{
this._value = value;
} public rational(double value)
{
this._value =(int)value;
}
public rational()
{ } public int ToInt()
{
return _value;
}
public double ToDouble()
{
return (double)_value;
}
public static rational operator+(rational num1,rational num2)
{
rational result = new rational(num1.Value+num2.Value);
return result;
} }

1、调用这些构造器和方法,开发人员可以很方便的将int和double对象转换成rational对象,这将给编程工作带来很多方便。设计类型时,应该认真考虑类型需要支持的转换构造器和方法。
2、int i=10;long j=i;这样的代码我们经常会看到,那么从int类型到long类型的转换为什么就可以隐士的进行呢?这就涉及到了我们的转换操作符,下面我们也为rational定义几个转换操作符。

namespace DoNet.Seven.ConsoleApplicationTest
{
class Program
{
static void Main(string[] args)
{
int n = ;
rational r1 = n;
double d=(double)r1; Console.WriteLine(r1.Value);
Console.WriteLine(d.ToString());
Console.ReadKey(); }
}
//有理数
public sealed class rational
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rational(int value)
{
this._value = value;
} public rational(double value)
{
this._value =(int)value;
}
public rational()
{ } public int ToInt()
{
return _value;
}
public double ToDouble()
{
return (double)_value;
}
public static rational operator+(rational num1,rational num2)
{
rational result = new rational(num1.Value+num2.Value);
return result;
} public static implicit operator rational(int value)
{
return new rational(value);
}
public static implicit operator rational(double value)
{
return new rational(value);
}
public static explicit operator int(rational value)
{
return value.ToInt();
}
public static explicit operator double(rational value)
{
return value.ToDouble();
} }
}

输出的结果是10、10。 我们可以在rational、int、double之间来回转换,是不是觉得挺方便的,在这个过程中,编译器又帮我们做了什么呢?
在C#中,implicit关键字告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转换(即隐式转换),相反,explicit关键字告诉编译器只有在发现了显示转型时,才调用方法。
在implicit或explicit关键字之后,要指定operator关键字告诉编译器该方法是一个转换操作符。在operator之后,指定对象要转换成什么类型。在圆括号内,则指定要从什么类型转换。
C#编译器检测到代码中的转型,并内部生成IL代码来调用rational类型定义的转换操作符方法,如果用反编译器看的话可以发现,转换操作符方法会生成下面这样的代码:

结论


不论是操作符重载还是转换操作符,都是在设计类型是考虑到我们编码方便而设计的,下面我们看下C#中decimal类型中的定义。

隐式转换重载可自动作用于其它运算符重载


C#的隐式转换重载很多时候会自动作用于其它运算符重载,下面例子中我们在Program类的Main方法中,使用rational类型的对象rational,来加上int类型的变量number:

//有理数
public class rational
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rational(int value)
{
this._value = value;
} public rational()
{ } public static rational operator +(rational num1, rational num2)
{
Console.WriteLine("rational operator + invoked!"); rational result = new rational(num1.Value + num2.Value);
return result;
} public static implicit operator rational(int value)
{
Console.WriteLine("rational implicit operator invoked!"); return new rational(value);
}
} class Program
{
static void Main(string[] args)
{
int number = ;
rational rational = new rational();
rational rationalAdd = rational + number;//rational类型加上int类型 Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}

结果如下:

从输出结果,我们可以看到由于rational类型的对象rational加上int类型的变量number,C#首先调用了rational类的implicit operator隐式转换重载方法,将int类型隐式转换为了rational类型,然后才调用rational类的operator +操作符重载方法,来做的加法。

C#会自动选择最优的操作符重载方法


此外,C#会自动调用最优的操作符重载方法,例如下例中我们有两个类rationalA和rationalB,我们使用rationalA类的变量rationalX,加上rationalB类的变量rationalY,代码如下:

public class rationalA
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalA(int value)
{
this._value = value;
} public rationalA()
{ } public static rationalA operator +(rationalA num1, rationalA num2)
{
Console.WriteLine("rationalA operator rationalA + rationalA invoked!"); rationalA result = new rationalA(num1.Value + num2.Value);
return result;
} public static rationalA operator +(rationalA num1, rationalB num2)
{
Console.WriteLine("rationalA operator rationalA + rationalB invoked!"); rationalA result = new rationalA(num1.Value + num2.Value);
return result;
} public static implicit operator rationalA(rationalB value)
{
Console.WriteLine("rationalA implicit operator invoked!"); return new rationalA(value.Value);
}
} public class rationalB
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalB(int value)
{
this._value = value;
} public rationalB()
{ }
} class Program
{
static void Main(string[] args)
{
rationalA rationalX = new rationalA();
rationalB rationalY = new rationalB(); rationalA rationalZ = rationalX + rationalY;//rationalA类型加上rationalB类型 Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}

结果如下:

可以看到当我们在调用rationalA类型加上rationalB类型时:

C#并没有先调用rationalA类的implicit operator重载方法将rationalB转换为rationalA,再调用rationalA类的operator +(rationalA num1, rationalA num2)重载方法做加法。

而是直接调用了rationalA类的operator +(rationalA num1, rationalB num2)重载方法,说明C#觉得这才是最优的运算符重载方法。

如果我们注释掉rationalA类的operator +(rationalA num1, rationalB num2)重载方法,运行结果就会如下:

操作符重载是有顺序的


需要注意C#在重载操作符时,是有先后顺序的,例如下面的rationalA类中我们定义了操作符重载方法operator +(rationalA num1, rationalB num2),但是在Program类的Main方法中,我们反过来用rationalB类型加上rationalA类型:

public class rationalA
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalA(int value)
{
this._value = value;
} public rationalA()
{ } public static rationalA operator +(rationalA num1, rationalB num2)
{
Console.WriteLine("rationalA operator rationalA + rationalB invoked!"); rationalA result = new rationalA(num1.Value + num2.Value);
return result;
} public static implicit operator rationalA(rationalB value)
{
Console.WriteLine("rationalA implicit operator invoked!"); return new rationalA(value.Value);
}
} public class rationalB
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalB(int value)
{
this._value = value;
} public rationalB()
{ }
} class Program
{
static void Main(string[] args)
{
rationalA rationalX = new rationalA();
rationalB rationalY = new rationalB(); rationalA rationalZ = rationalY + rationalX;//rationalB类型加上rationalA类型会报错,因为rationalA类没有定义操作符重载方法operator +(rationalB num1, rationalA num2) Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}

这样C#会报编译错误,因为rationalB类型加上rationalA类型,需要定义操作符重载方法operator +(rationalB num1, rationalA num2),但是我们只在rationalA类中定义了操作符重载方法operator +(rationalA num1, rationalB num2):

不要定义有二义性的操作符重载方法


C#遇到具有二义性的操作符重载方法时会报错,如下所示:

public class rationalA
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalA(int value)
{
this._value = value;
} public rationalA()
{ } //rationalA类定义了operator +(rationalA num1, rationalB num2)操作符重载方法
public static rationalA operator +(rationalA num1, rationalB num2)
{
Console.WriteLine("rationalA operator rationalA + rationalB invoked!"); rationalA result = new rationalA(num1.Value + num2.Value);
return result;
} public static implicit operator rationalA(rationalB value)
{
Console.WriteLine("rationalA implicit operator invoked!"); return new rationalA(value.Value);
}
} public class rationalB
{
private int _value = ; public int Value
{
get { return _value; }
set { _value = value; }
}
public rationalB(int value)
{
this._value = value;
} public rationalB()
{ } //rationalB类也定义了operator +(rationalA num1, rationalB num2)操作符重载方法
public static rationalB operator +(rationalA num1, rationalB num2)
{
Console.WriteLine("rationalA operator rationalA + rationalB invoked!"); rationalB result = new rationalB(num1.Value + num2.Value);
return result;
}
} class Program
{
static void Main(string[] args)
{
rationalA rationalX = new rationalA();
rationalB rationalY = new rationalB(); var rationalZ = rationalX + rationalY;//rationalA类型加上rationalB类型会报错,因为rationalA类定义了操作符重载方法operator +(rationalA num1, rationalB num2),而rationalB类也定义了同样的操作符重载方法operator +(rationalA num1, rationalB num2) Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}

上面的代码编译时,C#会报错,错误如下:

可以看到由于我们既在rationalA类中定义了操作符重载方法operator +(rationalA num1, rationalB num2),又在rationalB类中也定义了同样的操作符重载方法operator +(rationalA num1, rationalB num2),当使用rationalA类型加上rationalB类型时,C#就不知道该调用哪一个重载方法了,产生了二义性,所以切记操作符重载不能滥用,否则会带来不必要的麻烦。

原文链接

C#中如何利用操作符重载和转换操作符 (转载)的更多相关文章

  1. C#中如何利用操作符重载和转换操作符

    操作符重载 有的编程语言允许一个类型定义操作符应该如何操作类型的实例,比如string类型和int类型都重载了(==)和(+)等操作符,当编译器发现两个int类型的实例使用+操作符的时候,编译器会生成 ...

  2. C#关于操作符重载与转换

    随便写写 首先,假设我们有一个Person类型 其类型定义如下 class Person { public string Name { get; set; } = "Person" ...

  3. (二) operator、explicit与implicit 操作符重载

      有的编程语言允许一个类型定义操作符应该如何操作类型的实例,比如string类型和int类型都重载了(==)和(+)等操作符,当编译器发现两个int类型的实例使用+操作符的时候,编译器会生成把两个整 ...

  4. [置顶] operator overloading(操作符重载,运算符重载)运算符重载,浅拷贝(logical copy) ,vs, 深拷贝(physical copy)

    operator overloading(操作符重载,运算符重载) 所谓重载就是重新赋予新的意义,之前我们已经学过函数重载,函数重载的要求是函数名相同,函数的参数列表不同(个数或者参数类型).操作符重 ...

  5. C++解析(17):操作符重载

    0.目录 1.操作符重载 2.完善的复数类 3.小结 1.操作符重载 下面的复数解决方案是否可行? 示例1--原有的解决方案: #include <stdio.h> class Compl ...

  6. c++ 操作符重载和友元

    操作符重载(operator overloading)是C++中的一种多态,C++允许用户自定义函数名称相同但参数列表不同的函数,这被称为函数重载或函数多态.操作符重载函数的格式一般为: operat ...

  7. C++一些注意点之操作符重载

    重载操作符需要注意 (1)重载操作符必须具有一个类类型操作数.不能重载内建类型的操作符. operator +(int,int);//这个是错误的,都为内建类型 operator +(int,clas ...

  8. 15.C++-操作符重载

    首先回忆下以前学的函数重载 函数重载 函数重载的本质为相互独立的不同函数 通过函数名和函数参数来确定函数调用 无法直接通过函数名得到重载函数的入口地址 函数重载必然发生在同一个作用域中 类中的函数重载 ...

  9. 15.C++-操作符重载、并实现复数类

    首先回忆下以前学的函数重载 函数重载 函数重载的本质为相互独立的不同函数 通过函数名和函数参数来确定函数调用 无法直接通过函数名得到重载函数的入口地址 函数重载必然发生在同一个作用域中 类中的函数重载 ...

随机推荐

  1. 从JSON中读取数据追加到HTML中

    本文来自https://www.jianshu.com/p/04127d74d88c,并非本人原创,只是作为自己学习使用的资料,如有浏览者请点击地址自行到原作者页面浏览 有时候我们需要将json数据直 ...

  2. arguments 参数

    下面要写的是知识梳理的一个案例: 写一个求和的方法sumFn,不管传递的参数有什么,都能将最终的和算出来,并且返回给函数外部使用.(要求:一个参数都不传默认结果为0,对于传递的非正常数字的参数不与累加 ...

  3. Tomcat的学习和使用(一)

    一.Tomcat服务器端口的配置 Tomcat的所有配置都放在conf文件夹之中,里面的server.xml文件是配置的核心文件. 如果想修改Tomcat服务器的启动端口,则可以在server.xml ...

  4. 面向对象第四章(封装、static)

    1.package: 1)作用:避免类名的冲突 2)包名可以有层次结构 3)类的全称: 包名.类名,同包中的类不能同名 4)建议:包名所有字母都小写 import: 1)同包中的类可以直接访问, 不同 ...

  5. laravel开发之-composer安装(windows)

    1 在https://getcomposer.org/download/中下载composer.exe 2 选择php.exe安装composer 3 cmd命令框中输入composer.查看是否安装 ...

  6. <Android 基础(二十四)> EditText

    介绍 A text field allows the user to type text into your app. It can be either single line or multi-li ...

  7. Google Protocol Buffer 的使用(未完待续)

    简介 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 ...

  8. Spring3实战第一章 Aop 切面 XML配置

    刚看spring3实战书籍第一章  切面以前没有关注过 现在看到了  随手试验一下 AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Objec ...

  9. MySQL Database on Azure服务在中国正式商用

    基于由世纪互联运营的Windows Azure平台,MySQL Database on Azure服务助力中国用户实现数据库在云端的快速部署.推进用户的创新开发 2015年9月10日,北京——微软中国 ...

  10. HBuilder:一个不错的web前端IDE(代码编辑器)

    Web前端开发,2000之后基本就是三剑客的天下.到现在DW也是不错的HTMLcoder,如今的前端开发早已是JS的天下.但是DW对于JS方面就弱爆了.DW虽然支持JS语法高亮也支持JQuery Jq ...