高效掌握C#笔记[第四章]C#的面向对象功能

这一章从 枚举开始,逐步介绍诸如 结构、类、接口 和继承等面向对象的功能。
 
枚举
从System.Enum类继承而来,是一种数据 类型,由一组已命名的 整型常量组成。
enum Weekday{Mon,Tue,Wed,Thu,Fri,Sat,Sun}; //缺省情况下,第一个常量的值为0,随后每个成员的值递增1;
enum Error{FileNotFound=100,AccessDenied=FileNotFound+100,UnknowError=FileNotFound+400};
//使用表达式来初始化枚举值
枚举的默认类型是int,也可以使用其他的整型类型来声明枚举。如
enum Weekday:short{Mon,Tue,Wed,Thu,Fri,Sat,Sun};
用户可以在自己的代码中声明枚举型变量。但需要限定枚举成员的名字。如
enum Weekday{Mon,Tue,Wed,Thu,Fri,Sat,Sun};
Weekday w=Weekday.Mon;
if(w==Weekday.Mon)
{
  Console.WriteLine("It's Monday");
}
System.Enum类的一些成员……
 
结构
结构和枚举一样也是一种用户自定义的数据类型,C#中的结构实质上是一个类,主要用于只是想将一些数据组织到一起的情况。用关键字struct来定义。
C#中的结构具有类的其他特性,如实现构造函数和方法、支持嵌套等,但是不支持继承(除了它们派生于System.Object类之外)和派生。不同的是,结构是值型的,存储在堆栈里,而且没有析构函数,不会被无用单元收集器清除掉。
using System;
using System.Collections.Generic;
using System.Text;
namespace ConApp
{
    //A struct called ComplexNumber
    struct ComplexNumber
    {
        public double Re;
        public double Im;
    }
    //A struct called ComplexCircle
    struct ComplexCircle
    {
        public ComplexNumber Origin;
        public double Radius;
        //我们在ComplexCircle结构中添加OnBoundary方法,来判断射线是否与圆周相切
        public bool OnBoundary(ComplexNumber z)
        {
            double t1 = z.Re - Origin.Re;
            double t2 = z.Im - Origin.Im;
            return ((t1 * t1 + t2 * t2) == Radius * Radius);
        }
    }
    //A class called Test
    public class Test
    {
        static void Main(string[] args)
        {
             //Declare a ComplexCircle
            ComplexCircle c;
            //Set the coordinates
            c.Origin.Re = 2;
            c.Origin.Im = 3;
            c.Radius = 4;
            Console.WriteLine("Complex Circle:Centre{0}+{1}i Radius:{2}",c.Origin.Re,c.Origin.Im,c.Radius);
            ComplexNumber z1, z2;
            z1.Re = 6; z1.Im = 3;
            z2.Re = 4; z2.Im = 4;
            Console.WriteLine("6+3i is on  the boundary:{0}",c.OnBoundary(z1));
            Console.WriteLine("4+4i is on  the boundary:{0}",c.OnBoundary(z2));
        }
    }
}
*定义结构的方法
和定义类的方法一样。
 
*结构构造函数
需特别注意的是,结构不能包含没有参数的构造函数。
 
类的概念是面向对象编程的核心哦。
* 构造函数和析构函数
构造函数其实是一个方法,当构造一个类类型的对象时会调用,通过使用构造函数来初始化对象。其特点有:
  构造方法的名称与类的名称一样
  构造方法没有返回类型
  构造方法不返回任何值
  可以被重载,调用哪一个构造函数取决于给new的变元
析构函数也是个方法,当无用单元收集器回收对象时调用该方法。(此过程称为析构)
结构不能有析构函数
类的析构函数名称是类的名称前加上符号(~)
只有一个析构函数,不使用变元,没有返回类型
 
* 常量和只读成员
const修饰符用来声明类的常量成员的,其值是在编译的时候被计算出来的;
C#中提供了readonly修饰符,指定成员只有在构造函数中可以设置数值一次,然后成员称为只读的。本例中,我们对构造函数中传来的字符串值设置了排序代码 ??
public calss Account
{
   //private const string sortCode="12-00-23";
  private readonly string sortCode;
 
  public Account(string sortCode)
  {
    this.sortCode=sortCode;
  }
}
*  this引用
this关键字用在方法中指示调用方法的对象,常用于消除本地和具有同名的类的变量的歧义。如上例中。
 
* 属性
不好的编程习惯:赋予用户对类的成员公有访问的权限。
为了保证类的完整性,常用方法是通过“get”和“set(或accessor)”方法来提供对类数据成员的访问 。如
public class Account
{
  private string name;
 
  public void setName(string nm)
  {
    //do any checking you need,then
    name=nm;
  }
  
  public int getName()
  {
    //do any checking you need,then
    return name;
  } 
}
 这种对类的数据成员的访问方式比较合理,不过有两个缺点:1.用户必须手工编写存取方法;2.用户必须记得或了解他们必须使用这些存取方法来处理数据成员。
.NET语言提出了通过将get和set代码构建到语言中来访问数据成员的思想,即利用属性properties来实现。与上述get/set方法的区别是,使用属性看起来是直接对数据成员进行访问的,而事实上是编译器将调用映射在了get/set方法上。将上述代码修改,向Account类添加Name属性:
public class Account
{
  private string name;
 
   //A property called Name,公有属性的命名应遵循Pascal大小写输入规则,首字母应大写
  public string Name()
  {
    get
    {
      return name;
    }
    set
    {
      name=value;
    }
  } 
}
用户可以象使用一个公有数据成员那样来使用属性,如
Account a = new Account();
 
a.Name="Mary";
string myName=a.Name;
如果想模仿一个只读属性的话,可以省略set字句子句。
既然属性可以被继承,那么就可以对属性使用abstract(抽象)和virtual(虚方法)修饰符,可能需要派生类来实现属性方法本身的版本。
 
set存取程序也能够用于执行数据验证--这里我们添加一个OverDraftLimit属性,以便通过该属性将透支限额限定为只能够为正值,否则将抛出异常。
public class Account
{
  private double overDraftLimit;
   //A property called OverDraftLimit,命名应遵循Pascal大小写输入规则,首字母应大写
  public double OverDraftLimit()
  {
    get
    {
      return overDraftLimit;
    }
    set
    {
      if(value < 0)
        throw new ArgumentOutOfRangeException("Invalid overdraf limit");
      else
        overDraftLimit=value;
    }
  }
}
 * 静态成员
包括静态数据成员和静态方法,使用关键字static。
静态成员的概念:成员只属于类而不属于任何一个实例。
public class Account
{
  private static double penalty;
 
  public static double Penalty()
  {
    set
    {
      penalty=value;
    }
    get
    {
      return penalty;
    }
  } 
}
 上述代码为Account类设置了一个static Penalty属性,该属性被应用于那些使用透支功能的所有银行账户。
  注意:访问静态成员使用的是类的名称而不是对象引用。
 
Account.Penalty=25; //设置透支限额为25
double thePenalty=Account.Penalty;
 
同样,可以
注意:静态构造函数没有访问修饰符,没有静态析构函数
 
继承
继承原理是面向对象的基本原理之一,使得我们能够从一个已经编写好的类中派生出新类。
public class SavingsAccount:Account //Account is a class
{
  ……
}
Account类是SavingsAccount的基类或超类,后者是前者的子类或派生类。
注意:只有引用类型才能用于构建继承的层次结构,而值型不能。
C#只支持公有继承,因此在基类名称前不需要使用public关键字
*通过基类引用可以访问派生类,如
SavingsAccount sa=new SavingsAccount ();
 
//Refer to it through a base class reference
Account a1=s1;
由于Account是引用型的,所以对同一对象目前有两个引用
 
*受保护的访问
Account类中的方法可以使用第三级访问修饰符protected,使得Account的派生类(如SavingsAccount)能够调用该方法,但是与Account类不相关的类就不能调用该方法了。
 
*调用基类构造函数
如果基类的构造函数没有使用变元,则创建的子类对象会自动调用这个默认的构造函数。
如果基类的构造函数使用了变元,那么用户必须在子类的构造函数中调用它。如
public class Account
{
   public Account(double amt)
  {
    ……
  }
  ……
}
 
public class SavingsAccount:Account //派生
{
   public SavingsAccount(double amt):base(amt)
  {
  }
  ……
}
 
虚方法
使用virtual修饰符在基类中将一个方法声明为虚方法。virtual关键字只有定义在虚拟树顶部的类中才有效,防止造成意外的重写。
public class Account
{
   public virtual bool Withdraw(double amt)
  {
    ……
  }
  ……
}
*当在一个派生类中覆盖一个虚方法时,必须使用override关键字,这样就可以发出通知说明自己在重写方法。
public class SavingsAccount:Account //派生
{
   public override bool Withdraw(double amt)
  {
    ……
  }
  ……
}
 
隐藏基类方法
如果没有将一个基类的方法声明为virtual,但在派生类中使用new关键字提供了该方法的一个新版本,这个新方法就称为隐藏基类版本。
public class Account
{
  public bool Withdraw(double amt)
  {
    ……
  }
  ……
}
 
public class SavingsAccount:Account //派生
{
  public new bool Withdraw(double amt) //如不使用new关键字,则编译器会发出错误
  {
     //修改后的代码
  }
  ……
}
 new方法并没有参加虚拟函数机制( 怎么解释?)。所以,如果使用Account类的引用来访问一个SavingsAccount对象的话,就会调用Account方法。如
Account s1=new SavingsAccount(); //这里引用了一个SavingsAccount对象
 
s1.Withdraw(2000.0); //这里调用的是Account类的Withdraw方法。
*访问基类成员
因为C#只支持单继承,所以可以用base关键字调用基类中的一个方法。而且不必指定基类的实际名称,编译器能够算出是哪一个。
如,Account类需要的是账户中有足够资金进行取款,可是,SavingsAccount类有一个最小的取款额度。派生类SavingsAccount将判断取款是否超过了取款的最小限制,如果超过了最小取款额度的话,将调用基类的方法实际地执行取款。
public class SavingsAccount:Account
{
  public new bool Withdraw(double amt) //如不使用new关键字,则编译器会发出错误
  {
    if (amt>=50)
    else
      throw new ArgumentOutOfRangeException("SavingsAccountWithdraw:Insufficient Withdraw!");
  }
  ……
}
注意:不必对多有的基类方法都这样做,只有在用户想在某方面修改功能的情况下才这样做。
 
抽象类和方法
public abstract class Account
{
  ……
}
抽象类不能创建实例,但可以使用抽象类(Account类)的引用来引用派生类(SavingsAccount类),如
//Create a savings Account
Account s1 = new SavingsAccount();  //OK
 
Account s1=new Account();   //Error!!
使用abstract修饰符可以创建抽象方法,而且抽象方法是隐式虚拟的,这确保了在非抽象的派生类中必须实现它。
这些抽象方法不包括任何实现,也就是没有任何方法主体。
注意:可以将一个不包含抽象方法的类声明位抽象类,但包含抽象方法的类必须被声明为抽象类。
 
防止派生
处于设计或安全的因素,用户可以让任何一个类不被当作基类使用,即不能从这个类派生新类。
public sealed class MySecureClass
{
  ……
}
显然,sealed和abstract是互斥且独占的修饰符,不能同时使用它们。
 
类型强制转换
由于运行时类型检查的原因,用户可以方便又安全地进行类型转换。(第五章将介绍用户自定义转换的内容)
 
对象类
C#中的object类型被映射到System.Object类上,因为所有的.NET数据类型都是从这个类继承而来。这意味着所有的类型都继承了Object类的八个方法。
  方 法            访问修饰符            作用
string ToString()          public virtual        返回对象的字符串表示
 
int GetHashTable()         public virtual       在实现字典(散列表)时使用
 
bool Equals(object obj)       public virtual        对对象的实例进行相等比较
 
bool Equals(object objA,object objB) public static       对对象的实例进行相等比较

bool ReferenceEquals(object objA,object objB) public static 比较两个引用是否指向同一个对象
 
Type GetType()           public            返回对象类型的详细信息
 
object MemberwiseClone()      protected          进行对象的浅表复制
 
void Finalize()          protected virtual      该方法是析构函数的.NET版本
运算符重载
C#支持运算符重载,意味着运算符可以在需要的地方被定义用来和类一起运算,非常有用且直观。
哪些运算符可以重载?
一元运算符  +  -  !  ~  ++  --  true  flase
二元运算符  +  -  *  /  %   &   |  <<  >>
比较运算符  ==  !=  <  >   <=  >==  //比较运算符必须被成对重载,如== 和 != 必须在一起重载
public class Currency
{
  double val;
  
  public Currency(double val)
  {
    this.val=val;
  }
  
  //重载一元运算符,使得我们能简单地将两个Currency对象相加,而不用自定义的Add()方法。
  public static Currency operator+(Currency lhs , Currency lhs)   {
    return ( new Currency (lhs.val + rhs.val));
  }
 
  public static bool operator== (Currency lhs , Curreny rhs) //重载比较运算符
  {
    return (lhs.val == rhs.val);
  }
 
  public static bool operator!= (Currency lhs , Curreny rhs) //与==运算符成对重载
  {
    return (lhs.val != rhs.val);
  }
}
注意:所有的运算符重载都是类的静态方法。
 
接口
C#中的接口是引用类型的,与类相似,但 只包含抽象成员
C#中没有任何实现,不是COM接口!(COM是什么呢 ……)
接口可以从另一个接口继承,但必须实现所有接口的方法
接口的成员都必须定义为公有public的,否则产生编译错误
这部分部分内容待续……
 
接口和继承的比较
C#只支持从一个单独的基类中进行继承,而接口提供了一种实现类型“多重继承”的方式。
举例:
鹰能够飞,但不能游水
鸵鸟不能飞也不能游水
企鹅不能飞但能游水
鸬鹚既能飞也能游水
如果使用接口进行设计的话,如下
interface IFlying
{
  void Fly();
}
 
interface ISwimming
{
  void Swim();
}
 
//Hawk is a bird that can fly
class Hawk:Bird,IFlying
{
  public void Fly(){……};
}
 
//Cormorant is a bird that can fly and swim
class Cormorant:Bird,IFlying,ISwimming
{
  public void Fly(){……};
  public void Swim(){……};
}
……
Bird基类提供了对所有这些鸟都通用的功能,而可选的功能则是作为一连串的接口提供的。
注意:接口可以从另一个接口继承,这意味着任何实现接口的类必须实现所有接口的方法。如
interface ISwimming
{
  void Swim();
}
 
//IDiving实现Dive()的同时也必须实现Swim()
interface IDiving :ISwimming
{
  public Dive(){……};
}
索引器

你可能感兴趣的:(C#,职场,休闲)