一、运算符重载
- 重载运算符是具有特殊名称的函数,是通过关键字 operator 后跟运算符的符号来定义的。 与其他函数一样,重载运算符有返回类型和参数列表。
- 语法:
public
?
~
?static
?
~
?返回值类型
?
~
?operator
?
~
?一元运算符
?
~
?(参数类型
?
~
? 参数名) public
?
~
?static
?
~
?返回值类型
?
~
?operator
?
~
?二元运算符
?
~
?(参数1类型
?
~
? 参数1名,参数2类型
?
~
? 参数2名)
1、可重载和不可重载运算符
using System;
namespace Day10
{
public class Fraction
{
private int num1;
private int num2;
public Fraction (int num1,int num2)
{
this.num1 = num1;
this.num2 = num2;
}
public static Fraction operator +(Fraction f1,Fraction f2)
{
return new Fraction(f1.num1 + f2.num1, f1.num2 + f2.num2);
}
public static bool operator ==(Fraction f1,Fraction f2)
{
if (f1.num1 == f2.num1 && f1.num2 == f2.num2)
return true;
return false;
}
public static bool operator !=(Fraction f1,Fraction f2)
{
return !(f1 == f2);
}
public override bool Equals(object obj)
{
if (!(obj is Fraction))
return false;
return this == (Fraction)obj;
}
public override string ToString()
{
return String.Format("({0},{1})", num1, num2);
}
}
public class Tester
{
public void Run()
{
Fraction f1 = new Fraction(3, 4);
Console.WriteLine("f1:{0}",f1.ToString());
Fraction f2 = new Fraction(5, 6);
Console.WriteLine("f2:{0}",f2.ToString());
Fraction f3 = f1 + f2;
Console.WriteLine("f3:{0}",f3.ToString());
Fraction f4 = new Fraction(8, 10);
if(f1==f2)
Console.WriteLine("f1:{0}==f2:{1}",f1.ToString(),f2.ToString());
else
Console.WriteLine("f1:{0}!=f2:{1}", f1.ToString(), f2.ToString());
if(f4.Equals(f3))
Console.WriteLine("f4:{0}Equalsf3:{1}",f4.ToString(),f3.ToString());
}
}
class Program
{
static void Main(string[] args)
{
Tester t = new Tester();
t.Run();
}
}
}
注意: 1、如果重载了等于(==)运算符,那么也必须重载不等(!=)运算符,类似地有小于(<)和大于(>)必须成对,小于等于(<=)和大于等于(>=)必须成对。 2、如果重载了等于运算符,那么建议也要重载Equals()方法。 3、比较运算符必须返回bool类型的值,这是与其他算术运算符的根本区别。 4、运算符只能采用值参数,不能采用ref 或out 参数。
2、转换运算符
- implicit 和 explicit通常叫作转换运算符,当转换能够成功并且不会发生信息丢失时,使用关键字implicit;如果存在丢失信息的风险,则需要使用关键字explicit。
- 语法:
public
?
~
?static
?
~
?implicit
?
~
?operator
?
~
?转换运算符的目标类型
?
~
?(参数输入类型
?
~
? 参数名) public
?
~
?static
?
~
?explicit
?
~
?operator
?
~
?转换运算符的目标类型
?
~
?(参数输入类型
?
~
? 参数名)
using System;
namespace Day10
{
public class Fraction
{
private int num1;
private int num2;
public Fraction (int num1,int num2)
{
this.num1 = num1;
this.num2 = num2;
}
public Fraction (int num)
{
this.num1 = num;
this.num2 = 1;
}
public static implicit operator Fraction(int num3)
{
return new Fraction(num3);
}
public static explicit operator int(Fraction f1)
{
return f1.num1 / f1.num2;
}
public static Fraction operator +(Fraction f1,Fraction f2)
{
return new Fraction(f1.num1 + f2.num1, f1.num2 + f2.num2);
}
public static bool operator ==(Fraction f1,Fraction f2)
{
if (f1.num1 == f2.num1 && f1.num2 == f2.num2)
return true;
return false;
}
public static bool operator !=(Fraction f1,Fraction f2)
{
return !(f1 == f2);
}
public override bool Equals(object obj)
{
if (!(obj is Fraction))
return false;
return this == (Fraction)obj;
}
public override string ToString()
{
return String.Format("({0},{1})", num1, num2);
}
}
public class Tester
{
public void Run()
{
Fraction f1 = new Fraction(3, 4);
Console.WriteLine("f1:{0}",f1.ToString());
Fraction f2 = new Fraction(5, 6);
Console.WriteLine("f2:{0}",f2.ToString());
Fraction f3 = f1 + f2;
Console.WriteLine("f3:{0}",f3.ToString());
Fraction f4 = new Fraction(8, 10);
Fraction f5 = f4 + 4;
Console.WriteLine("f5:{0}",f5.ToString());
int num = (int)f5;
Console.WriteLine("num:{0}",num);
if(f1==f2)
Console.WriteLine("f1:{0}==f2:{1}",f1.ToString(),f2.ToString());
else
Console.WriteLine("f1:{0}!=f2:{1}", f1.ToString(), f2.ToString());
if(f4.Equals(f3))
Console.WriteLine("f4:{0}Equalsf3:{1}",f4.ToString(),f3.ToString());
}
}
class Program
{
static void Main(string[] args)
{
Tester t = new Tester();
t.Run();
}
}
}
二、接口
-
接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明,接口没有构造函数,所以不能直接使用 new 对接口进行实例化,成员的定义是派生类的责任。 -
接口和抽象类的区别: 1、C#类只能够从一个单独的父类中继承,但能够实现多个接口,即类只能单继承,而接口可以实现跨继承(多继承); 2、当派生自一个抽象类时,必须重写抽象类中的所有抽象方法;实现接口时,接口中定义的每一个方法都必须实现,不能部分地实现一个接口; 3、类的继承体现的是is-a(是一个,属于)关系,接口的实现体现的是able(能够)关系; -
接口使用 interface 关键字声明,它与类的声明类似。接口声明默认是 public 的,通常接口命令以字母 I 开头, ----语法: interface 接口名 { //接口成员 } -
接口的成员不能有 protected、internal、private 、new、static、abstract、override、virtual等修饰符。
using System;
using System.Collections.Generic;
using System.Text;
namespace Day10
{
interface IFlyable
{
public void Fly();
}
abstract class Animal
{
public string Name { get; set; }
public Animal(string name) { this.Name = Name; }
public abstract void Shout();
}
class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void Shout()
{
Console.WriteLine("{0}:汪汪",this.Name);
}
}
abstract class Vehicle
{
}
class Car:Vehicle
{
}
class Plane:Vehicle,IFlyable
{
public void Fly()
{
Console.WriteLine("飞机正在飞!");
}
}
class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void Shout()
{
Console.WriteLine("{0}:喵喵", this.Name);
}
}
class Duck : Animal,IFlyable
{
public Duck(string name) : base(name) { }
public override void Shout()
{
Console.WriteLine("{0}:嘎嘎", this.Name);
}
public void Fly()
{
Console.WriteLine("鸭子正在飞!");
}
}
class Bird : Animal,IFlyable
{
public Bird(string name) : base(name) { }
public override void Shout()
{
Console.WriteLine("{0}:叽叽喳喳", this.Name);
}
public void Fly()
{
Console.WriteLine("鸟儿飞来飞去!");
}
}
class program
{
static void IWant2Fly(IFlyable fly)
{
fly.Fly();
}
static void Main()
{
IWant2Fly(new Duck("小黑"));
}
}
}
- 实现多接口的语法:
访问修饰符
?
~
?class
?
~
?类名:(父类,)接口1,接口2,接口3… 注意: 使用多接口时,每个接口中的方法也都必须实现。
1、is 和 as 运算符
- is运算符可以查询一个对象是否实现了某个接口(或派生自某个基类),运算符形式为:
if(expression is type) 如果表达式(必须为引用类型,如一个类的实例)能够被安全转换为要转换的类型,而不抛出异常,运算符结果为true。 - as 运算符比is运算符多执行一步,它尝试将对象转换为想要转换的类型,并且转换过程如果抛出异常,as运算符返回null。
- 注意:is运算符比as运算符的功能略微弱一点,因此处理只希望做检测而实际不做转换的情况,其他情况都首选as运算符而不是is运算符。
using System;
using System.Collections.Generic;
using System.Text;
namespace Day10
{
interface IStorable
{
void Read();
void Write(object obj);
int Status { get; set; }
}
interface ICompressible
{
void Compress();
void Decompress();
}
public class Note:IStorable
{
private string str;
public int Status { get ; set; }
public Note (string str)
{
this.str = str;
}
public override string ToString()
{
return str;
}
public void Read()
{
Console.WriteLine("为IStorable接口执行Note类中的Read方法");
}
public void Write(object obj)
{
Console.WriteLine("为IStorable接口执行Note类中的Write方法");
}
}
public class Document:Note,ICompressible
{
private int documentID;
public int ID { get { return this.documentID; } }
public Document(string str,int documentID):base(str)
{
this.documentID = documentID;
}
public void Compress()
{
Console.WriteLine("为ICompressible接口执行Document类中的Compress方法");
}
public void Decompress()
{
Console.WriteLine("为ICompressible接口执行Document类中的DeCompress方法");
}
}
public class Tester
{
public void Run()
{
string str = "string";
Note[] myNoteArray = new Note[3];
for (int i = 0; i < 3; i++)
{
string docText = str + i.ToString();
if(i%2==0)
{
Document myDocument = new Document(docText, (i+1)*10);
myNoteArray[i] = myDocument;
}
else
{
Note myNote = new Note(docText);
myNoteArray[i] = myNote;
}
}
foreach (var theID in myNoteArray)
{
Console.WriteLine("\nTesting {0} with IS",theID);
theID.Read();
if(theID is ICompressible)
{
ICompressible myCompressible = theID as ICompressible;
myCompressible.Compress();
}
else
{
Console.WriteLine("This is not compressible!");
}
if(theID is Document)
{
Document myDoc = theID as Document;
myDoc = theID as Document;
Console.WriteLine("my document ID is{0}",myDoc.ID);
}
}
foreach (var theID in myNoteArray)
{
Console.WriteLine("\nTesting {0} with AS",theID);
ICompressible myCompressible = theID as ICompressible;
if(myCompressible!=null)
{
myCompressible.Compress();
}
else
{
Console.WriteLine("This is not compressible!");
}
Document myDoc = theID as Document;
if(myDoc!=null)
{
Console.WriteLine("my document ID is{0}",((Document)theID).ID);
}
else
{
Console.WriteLine("this is not a document!");
}
}
}
static void Main()
{
Tester test = new Tester();
test.Run();
}
}
}
2、重载接口方法
- 扩展接口以及合并接口都可以视为是接口的继承;
- 父类引用,子类对象:运行和编译时,成员变量和方法调用情况:
以下代码为上图的解释:
using System;
using System.Collections.Generic;
using System.Text;
namespace Day10
{
class Father
{
public int a = 10;
public string str = "父类";
public virtual void Run()
{
Console.WriteLine("这是父类");
}
}
class Son:Father
{
public int a = 5;
public string str = "子类";
public override void Run()
{
Console.WriteLine("这是子类");
}
public void Stop()
{
Console.WriteLine("子类停止");
}
}
class Test
{
static void Main()
{
Father X = new Son();
Console.WriteLine("X.a={0}",X.a);
Console.WriteLine("X.str={0}",X.str);
X.Run();
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Day11
{
interface IStorable
{
void Read();
void Write();
}
public class Note : IStorable
{
public Note(string str)
{
Console.WriteLine("creating note with:{0}",str);
}
public virtual void Read()
{
Console.WriteLine("为IStorable接口执行父类中的Read方法");
}
public void Write()
{
Console.WriteLine("为IStorable接口执行父类中的Write方法");
}
}
public class Document : Note
{
public Document(string str) : base(str)
{
Console.WriteLine("creating document with:{0}",str);
}
public override void Read()
{
Console.WriteLine("Override执行子类中的Read方法");
}
public new void Write()
{
Console.WriteLine("执行子类中的新的Write方法");
}
}
class Chongzai
{
public void Run()
{
Note theNote = new Document("父类引用,子类对象");
theNote.Read();
theNote.Write();
Console.WriteLine("\n");
IStorable isStorable1 = theNote as IStorable;
if(isStorable1!=null)
{
isStorable1.Read();
isStorable1.Write();
}
Console.WriteLine("\n");
Document theDoc = new Document("子类引用,子类对象");
theDoc.Read();
theDoc.Write();
Console.WriteLine("\n");
IStorable isStorable2 = theDoc as IStorable;
if(isStorable2!=null)
{
isStorable2.Read();
isStorable2.Write();
}
}
static void Main()
{
Chongzai test = new Chongzai();
test.Run();
}
}
}
以上代码需要着重理解,这里面涉及了在接口中实现多态,实际中可能运行不多。
3、显示接口实现
- 当多个接口具有相同的名称时,显示实现能够避免冲突。
using System;
using System.Collections.Generic;
using System.Text;
namespace Day12
{
interface IStorable
{
void Read();
void Write();
}
interface ITalk
{
void Read();
void Talk();
}
class Document:IStorable,ITalk
{
public Document (string s)
{
Console.WriteLine("creating document with :{0}",s);
}
public virtual void Read()
{
Console.WriteLine("document read for istorable");
}
public void Write()
{
Console.WriteLine("document write for istorable");
}
void ITalk.Read()
{
Console.WriteLine("implementing Italk.read");
}
public void Talk()
{
Console.WriteLine("implementing Italk.talk");
}
}
class Tester
{
public void Run()
{
Document doc = new Document("test document");
IStorable istorable = doc as IStorable;
if(istorable!=null)
{
istorable.Read();
}
ITalk italk = doc as ITalk;
if(italk!=null)
{
italk.Read();
}
doc.Read();
doc.Talk();
}
static void Main()
{
Tester test = new Tester();
test.Run();
}
}
}
三、小结
- 接口是一个契约,类通过这个契约可以保证实现确定的方法,提供确定的属性和索引并支持确定的事件,所有这些内容都在接口定义中描述;
- 不能定义一个接口的实例,要访问接口方法,需要创建一个实现了该接口的类;
- 声明一个接口与声明类相似,但使用关键字interface;
- 为了在一个类中实现接口,需要使用冒号运算符,冒号运算符之后是接口的名字,这个语法与继承的语法类似;
- 类只能够从一个类派生,但能够实现多个接口。如果一个类有一个基类和一个或多个接口,那么积累必须放在第一个位置(冒号之后),基类以及各接口名字之间用逗号分隔;
- 当定义一个实现了接口的类时,类必须实现接口要求的所有成员;
- is运算符能够判断一个对象是派生自某个类或实现了某个接口。is运算符返回一个类型转换能否成功的bool值,但不执行这个类型转换;
- as运算符尝试将一个引用转换为某一个基类型或接口,如果类型转换不成功则返回空;
- 通过添加新方法或成员能够扩展一个接口。在新接口定义中,使用冒号运算符,之后是原始接口的名称,这与类之间的派生非常相似;
- 扩展接口包含原始接口在,因此实现扩展接口的任何类必须也同样实现原始接口;
- 实现接口的类可以将接口中的方法标识为virtual,然后这些方法可以在派生类中被重载,实现多态;
- 当一个类实现两个或多个接口,并且在不同接口中有相同名称的方法时,为了解决冲突,可以在方法名称前加接口名称和点运算符。如果这样做了就不能使用访问修饰符,这些方法隐含为公有方法。
|