第4章 面向对象

      到目前止,主要学习了C# 2008的基本语法:如何声明变量,如何控制流程等。C# 2008是一个完全面向对象的语言,要写出语法正确、设计合理的好代码,必须掌握面向对象的特性。从本章开始,我们将迈进面向对象的大门,即面向对象的思想将取代面向过程的思想。
4.1 类
      C# 2008秉承了C++面向对象的所有关键概念:封装、继承和多态性。其类型模型是建立在.NET虚拟对象系统之上的。类是面向对象的程序设计的基本构成模块。从定义上讲,类是一种数据结构,这种数据结构可以包含数据成员、函数成员等类型。其中数据成员类型有常量和事件;函数成员类型有方法、属性和索引器等。
4.1.1 面向对象的概念
       随着计算机的应用越来越广泛,社会对软件开发提出了更高的要求。然而软件技术的进步却远远落后于硬件技术的进步,人们常常无法控制软件开发的周期和成本,软件质量总是无法让人满意,即所谓的软件危机。
       为了摆脱软件危机,必须按照工程化的原则和方法来组织软件开发工作。在涉及大量计算的问题上,面向过程的设计方法暴露了越来越多的不足。例如:
       功能与数据分离,不符合对现实世界的认识。
       基于模块的设计方式,导致软件修改困难。
       自顶向下的设计方法,限制了软件的可重用性,降低了开发效率,也导致最后开发出来的系统难以维护。
4.1.2 定义一个类
        类是C#中的两种基本封装结构之一(另一个是结构)。每个可执行语句必须放在类或结构中。简单的说,类是一种抽象的数据类型,但是抽象的程度可能不同。而对象就是一个类的实例。即类是对象的蓝图。
        1.定义类
        创建一个用户定义的类非常简单。的声明格式如下:
        attributes class-modifiers class identifier class-base
        {
           data members                                           //数据成员
           function                                                 //函数
           nested types                                             //嵌套类型
        }
 
        2.类的修饰符
        类的修饰符可以是以下几种之一或者是它们的组合在类的声明中同一修饰符不允许出现多次。类的修饰符有abstractinternalnewprivateprotectedpublicsealed关键字。其中internalprivateprotectedpublic关键字定义了对类的访问。在类中声明的类只能有publicinternal访问属性。嵌套类可以具有以上4种访问类型中的一种,还可以有protected internal访问类型,它等效于受保护的或内部的访问。其他关键字涉及成员隐藏和类是否能够被实例化或继承。
4.1.3 类成员的修饰符
      类的成员可以分为两大类:类本身所声明的,以及从基类中继承而来的。在编写程序时,可以对类的成员使用不同的访问修饰符从而定义它们的访问级别。类的成员有以下类型,如表所示。
4.1.4 类的构造函数
      【本节示例参考:\示例代码\Chap04\UseRectangular】
      构造函数用于执行类的实例的初始化。每个类都有构造函数,即使我们没有声明它,编译器也会自动地为我们提供一个默认的构造函数。在访问一个类的时候,系统将最先执行构造函数中的语句。实际上,任何构造函数的执行都隐式地调用了系统提供默认的构造函数base()。
      1.构造函数的概念
      2.构造函数的访问类型
4.1.5 类的析构函数
       本节示例参考:\示例代码\Chap04\Destructor
       在类的实例超出范围时,要确保其所占的存储能被回收,于是就出现了析构函数。在C++中,其是用来释放内存并执行清除操作的。C#使用一个垃圾收集器来自动完成大部分的类似工作。析构函数定义方式如下:
       class Myclass
       {
       ~ Myclass()                              //Myclass的析构函数
       {
           Perform cleanup operations
       }                    
       }
4.1.6 结构与类的区别
      在上一章中已经介绍过结构。下面让我们看看结构和类的区别。
      1.实例在内存中的位置以及内存回收
      2.结构不支持继承
      3.结构的默认值
      4.关键字this的含义
4.1.7 类的继承 
       本节示例参考:\示例代码\Chap04\TestInheritance
       如果所有的类都处在同一级别上,这中没有相互关系的平坦结构就会限制了系统面向对象的特性。继承的引入,就是在类之间建立一种相交关系,使新定义的派生类的实例可以继承已有的基类的特征和能力而且可以加入新的特性或者是修改已有的特性,建立起类的层次。在C#中,定义类的继承的语法格式如下所示:
       class subclass :baseclass
       {
           //类的成员和方法
       }
4.2 接口
      接口是面向对象编程技术的一个重要内容。在代码中,接口负责功能的定义;在项目中,接口负责类和结构的规范。同时其能声明属性、索引器、事件和方法的编程构造,不为这些成员提供实现,只提供定义。虽然,一个类只能继承一个基类,但它却可以实现任意数量的接口。结构也能实现接口。接口本身可以从多个基接口派生。
4.2.1 接口的基本概念
       本节示例参考:\示例代码\Chap04\TestInterface
       接口是类或结构的蓝图,即主要是对类或结构的构成进行限制,而具体的实现还是由类或结构来具体实现的。
       1.接口声明
       接口的基本格式为:
       Interface-modifiers interface interface-name
       {
       Interface members //接口成员
       }
 
       2.接口成员的定义
 
4.2.2 接口的继承
       类似于类的继承性,接口也可以继承。接口继承的语法和类继承的语法相同。支持多继承表现在一个接口可以有多个基接口。基本格式:
       public Interface son-interface:father-interface,mother-inferface
       {
       Son-interface member //son-interface 接口的成员
       }
       在上述定义中,接口要继承接口要用到冒号和接口名,如果继承多个接口,接口名前后列出,中间用逗号分割。接口son-interface继承了father-interfacemother-inferface接口。实现son-interface接口的类型必须实现在son-interfacefather-interfacemother-interface接口中声明的成员。
4.2.3 接口的特点
      接口具有自己独特的特点:
      接口可以用任何可访问性来声明,但是接口成员必须全都是公共可访问性。即接口成员不能有任何访问修饰符。
      不能使用static、virtual、abstract和sealed来定义。
      接口没有构造函数。
      接口中不允许定义字段。
4.3 属性和域
      为了保存类的实例的各种数据信息,C#为我们提供了两种方法——属性和域。属性实现了良好的数据封装和数据隐蔽。
4.3.1 域(field)
      域表示与对象或类相关联的变量,它使类和结构可以封装数据。定义需要满足类的需要,并选用合适的数据类型。在有些资料和文档中也将其翻译为域。声明格式如下:
      attributes   field-modifiers   type   field-name = value;          //声明和初始化值类型
      attributes   field-modifiers   type   field-name = new constructor_call //声明和初始化值引用类型或者结构
4.3.2 静态域和非静态域
      静态域的声明是使用static修饰符,其他的域都是非静态域。静态域和非静态域分
      别属于C#中静态变量和非静态变量。
      若将一个域说明为静态的,无论建立多少个该类的实例,内存中只存在一个静态数据的复制。当这个类的第一个实例建立时,域被初始化以后再进行类的实例化时,不再进行初始化,所有属于这个类的实例共享一个副本。
      与之相反,非静态域在类的每次实例化时,每个实例都拥有一份单独的复制。代码演示了静态域和非静态域的区别。
      【本示例参考:\示例代码\Chap04\TestField2】
4.3.3 只读域
       域的声明中如果加上了readonly   修饰符,表明该域为只读域。对于只读域我们只能在域的定义中和它所属类的构造函数中进行修改,在其他情况下,域是只读的。
       熟悉CC++程序员可能习惯了使用const#define定义一些容易记住的名字来表示某个数值staticreadonly修饰符可以起到同样的效果:
       public class A
       {
       public static readonly double PI = 3.14159;
       public static readonly Color White = new Color(255, 255, 255);
       public static readonly int kByte = 1024;
       //other members
       }
4.3.4 域的初始化
       在C和C++中,未经初始化的变量是不能使用的。在C#中,系统将为每个未经初始化的变量提供一个默认值。这虽然在某种程度上保证了程序的安全性,但对本应初始化为某个特殊值的变量忘记了初始化,也常常会导致程序的执行误入歧途。
       对于静态变量、非静态的对象变量和数组元素,这些变量自动初始化为本身的默认值。对于所有引用类型的变量默认值为null,所有值类型的变量的默认值如表所示。
4.3.5 属性(property)
      属性是对现实世界中实体特征的抽象,它提供了对类或对象性质的访问。例如一个用户的姓名、一个文件的大小或一个窗口的标题,都可以作为属性。类的属性所描述的是状态信息,在类的某个实例中属性的值表示该对象的状态值。
      在属性中提供了get和set访问器来对属性值进行读写,这就避免了直接操作属性值,充分体现了面向对象中的封装性。
      属性可以是只读的或者可读写。我们可以像访问或改变公有域那样,访问或改变属性的值。可以利用属性来允许从外部访问私有域的值。
4.3.6 访问属性的值
      在属性的访问声明中,对属性的值的读操作用get关键字标出,对属性的值的写操作用set关键字标出。除了使用abstract修饰符的抽象属性,其他属性中get访问器都通过return来读取属性的值,set访问器都通过value来设置属性的值。
      【本示例参考:\示例代码\Chap04\TestAttribute2】
4.4 索引器
      索引器跟属性一样,都是为了以更直观的方式使用类。索引器在语法上比较简单,允许像访问数组那样来访问对象,即通过索引方式方便的访问类的数据信息的方法。
4.4.1 索引器的基本概念
       本节示例参考:\示例代码\Chap04\TestIndex1
       索引器声明的方式跟属性声明的方式很像。基本格式如下:
       Indexer-modifiers this parameter-list
       {
       Get
       {
       body of get accessor      //对索引部分的读
       }
       Set
       {
       body of set accessor       //对索引部分的写
       }
       }
4.4.2 索引器的使用
      【本节示例参考:\示例代码\Chap04\TestIndex2】
      在索引器中,如果只定义了一个get存取器,则索引器是只读的;如果只定义了一个set存取器,索引器可以是只写的;如果同时都定义了,则索引器就是可读写的。[]运算符用来调用索引器的get或set存取器,放在“[]”中参数必须对应索引器定义的参数列表,同时“[]”运算符的前面要加上类或结构实例的名称。下面来看一个例子如代码4-21所示,了解一下索引器是如何使用的。
4.4.3 索引器与属性的区别
4.5 迭代器(Iterator)
      迭代器是开发人员能够在类或结构中遍历各自的数据结构的一种功能,可以用两种技术来实现,即foreach语句和迭代器。
4.5.1 foreach语句
       本节示例参考:\示例代码\Chap04\TestIterator1
       foreach语句在目的上和for语句相似,都是用来迭代数组或另外集合的元素。如果要对一个类使用foreach,那么必须确保这个类提供了foreache所必须的GetEnumeratorMoveNextResetCurrent成员。这些成员分别在IEnumerableIEnumerator接口里定义。所以,只要实现了这两个接口就可以对其用foreach语句。IEnumerable接口声明一个方法支持对集合进行简单的遍历,的定义格式为:
       public interface IEnumerable
       公有实例方法的定义如下:
       IEnumerable GetEnumerator()
4.5.2 迭代器
      如果类要实现GetEnumerator、MoveNext、Reset和Current成员,才能与foreach兼容实现迭代,那就太麻烦了,所以C# 2008提供了关键字yield来实现迭代器。只要类中提供迭代器,即可遍历类中的数据结构。代码演示了如何使用迭代器。
      【本示例参考:\示例代码\Chap04\TestIterator2】
4.6 小结
      要理解面向对象,最重要的是要理解其中的一些基本概念,例如类和接口等。本节详细的介绍了面向对象的一些概念类、接口、属性和域和索引器。
      虽然这些概念很难懂,但是它们却是进入面向对象的第一步。要想学好面向对象编程,关键就要学好这一节。