C#语言 基础

来源:互联网 发布:小刀资源网源码 编辑:程序博客网 时间:2024/04/29 18:28

C#语言 基础

http://blog.csdn.net/mousebaby808/category/665050.aspx?PageNumber=2
C#语言 第一部分 面向对象 (一) 类和Main方法

通过“面向对象基本原理”的学习,我们应该了解如何从“面向过程”过渡到“面向对象”,也能基本了解面向 对象编程的一些优点,这一章,我们进入到一门纯粹的面向对象语言C#语言的学习。

C# 语言是.net平台的一部分,.net平台提供了一种叫做“公共语言”的中间语言,几乎各种语言都可以映射倒公共语言上,例如 C++,Visual Basic,JScript等,C#是微软专门为.net平台开发的,该语言语法简洁,功能强大,已经成为一门重要的主流开发语言。

关于 C#语言的特色,可以参考各类教科书或查阅网络资料。这里我们只针对C#语言本身进行介绍,关于语言的其它方面,请大伙自学。

我们从最基本的HelloWorld程序开始,了解一下C#编程的基本结构。


可以看到,C#依旧秉承了C和C++的很多语法,另外还带有些许Java的影子,C#是一门简单易学的编程语言,只要努力学习,都可以掌握这门优秀 的编程语言。
C#语言 第一部分 面向对象 (二) 类

类是面向对象的基础,类表现出的最基本特性是其“封装性”。

类是某一些具有相同属性和行为对象的抽象。例如:波斯猫、野猫、家猫、花猫都具有相同的属性和行为,所以被抽象成为猫类。

类具有属性和方法,表示这个类所代表的对象具有的特性和行为。从类实例化对象后,对象就可以给类中定义的属性“赋值”或运行类中定义的方法,属性和 方法和每个对象相关,即相同类的不同的对象具有相同的属性,但属性的属性值可以不同;具有相同的方法,但方法运行的结果可以不同。

类需要将和其相关的属性和方法“封装”起来,并和对象进行绑定。

看下面的例子,自行理解:

C#语言 第一部分 面向对象 (三) 对象的构造

上一章我们介绍了一个Person 类,它可以正常工作,但从逻辑上存在很多问题。

首先我们注意到,Person类对象实例化完毕后,我们才去确定其属性,如 Name,Sex,Age属性。但客观情况下,这些属性应该是同对象生成 一起赋值的。

一句话,当对象被实例化的同时,对象的属性就应当有初始值,反映到代码中,就是“用来保存属性值的字段应该有初始值”

构造器,也成为构造方法,构造函数,就是专门用来做这件事情的。

一个类可以有0或多个构造器,在有多个构造器的情况里,各个构造器的参数必须不同。

在 C#类中,有这样一类特殊的方法,没有返回值类型,方法名必须和类名相同,可以有参数列表。这类方法就叫做构造器,构造器通过new操作符指定, 在实例化对象的同时运行。

我们用构造器来改造上一章讲到的Person类。


我们给Person类增加了若干个“构造器”,并修改Sex属性为只读,从而让Person类更更符合客观实际。

对于任何一个类,可以不人为提供构造器,此时编译器会赋予类一个“默认构造器”,即没有参数的构造器。这个构造器什么也不做,仅仅是为了new操作 符能够得以指定一个构造器。

而我们一旦定义了有参数的构造器,编译器便会这样认为“这个类的对象必须提供参数才能够实例化”,便不再会提供无参数的默认构造器了。所以一个类如 果同时需要有默认构造器和有参数的构造器,则默认构造器必须手动提供,不要指望编译器会自动生成。

现在我们可以完整定义new操作符的语法规范:类名 变量名 = new 类构造器名(构造器实参数列表);


C#语言 第一部分 面向对象 (四) 字段默认值

C# 中,可以给类字段以一个默认值,这样一方面可以简化构造器的代码,另一方面较为直观,是一种推荐的方式。

这一章比较简单,可结合上一章深入理解构造器的作用。

看代码:


在声明字段的同时,可以用赋值运算符(=)在字段后面直接加上一个值(例如第17行),但这和给变量赋值是两个概念。

以赋值运算符跟随在字段之后的值称为字段的“默认值”。这只是一种形式,并不是写在这里就在这里发生赋值,真正 发生赋值的时机还是在构造器中,只不过……

无论我们通过哪个构造器创建类的对象实例,这个构造器中,没有显式赋值的字段,都会由编译器增加一句赋值代码,将该字段赋值为设定 的默认值。

其实,无论我们是否给字段增加了默认值,字段都具备默认值的,只不过如果我们没有人为增加默认值,字段的默认值将是0或null。

C#语言 第一部分 面向对象 (五) 继承

在现实生活中,类和类之间可能会有一种这样的关系:一个类是另一个类的扩展,反过来讲,后一个类是前一个类的基础。

例如:动物类是猫类的基础,猫类是动物类的扩展。这种关系称为继承,即猫类继承了动物类。

被继承的类称为父类(C++说法),基类(C#说法)或超类(Java说法),另一个类叫做子类。怎么叫无所谓,关键看效果。

所谓扩展,就是子类拥有超类的一切特征(包括属性和方法),而子类还可以在超类的基础上添加自身的属性和方法。例如:猫类拥有动物类的一切属性和行 为,但猫类还具有自身的属性(例如长胡子,喵喵叫)和行为(例如磨爪子,抓耗子)。

子类虽然拥有超类的一切特征,但并不是说子类可以任意的去访问继承下来的这些超类特征,子类只能访问到超类中访问修饰符为 public或proctected的那部分属性和方法,其余的属性和方法无法直接访问。

C#的这种继承成为无条件继承,即子类必须继承超类的所有特性,无法选择。

看代码:


继承最直接的好处是:再设计类的时候,如果存在继承关系,则可以通过继承省略大量代码的书写。

继承可以将类组成宗系,更符合软件开发的思想:组织功能模块(超类),细化功能(子类),功能异化(子类)。这句话可能暂时不好理解,随着对面向对 象学习的深入,慢慢去理解这句话的含义。看看在软件开发中,如何定义一个基本功能类,然后不断的继承它,完成功能的细化和扩展。

对于继承,语法上没有任何难点,关键要理解子类的构造器。

子类必须要调用超类的某个构造器,缺省情况下,子类调用超类的无参默认构造器,但如果超类中没有默认构造器,则必须显式说明,子类 要调用超类的哪个构造器。参考89、90行代码。所谓缺省情况,指的是无需使用base关键字显式调用超类构造器,此时子类自动调用超 类中没有参数的那个构造器。
C#语言 第一部分 面向对象 (六) 变量对对象的引用

很容易理解的概念,直接看代码,不多说了。


只要能够理解变量保存的是一个“对象的引用”,就可以理解上面代码的含义了。
C#语言 第一部分 面向对象 (七) 方法重载

一只狼狗无忧无虑的走在大街上,狼狗天性好斗,所以应该有一个Fight方法。

这时候他碰到了一只小狗,即Puppy类的一个对象,此时狼狗调用Fight方法,将这只puppy作为参数传入,将其狠狠修理了一顿。

不一会儿,它又碰到一只大狗,很凶恶的样子,此时狼狗的心里没有必胜的把握,但它依旧调用了Fight方法,将大狗作为参数传入。不过这 次,Fight方法只是示威了一下而已,叫了几声,然后就结束了。

又过了一会儿,它碰到了一大群狗,即一个狗数组,此时狼狗吓得屁滚尿流,但还是调用了Fight方法,将狗数组作为参数传入。这次,Fight方法 执行的结果,狼狗逃之夭夭了。

可见,在我们现实生活中,总是存在这样一种情况:某类对象的某种行为,因为外界条件不同(参数不同),执行的具体流程也不同。体现在编程代码中,就 是方法的重载。

在同一个类(或某个类和它的子类)中,一系列同名的方法就构成了“重载”,重载的条件是:方法名相同,参数列表不同。 对于子类要重载超类方法的情况,超类该方法必须为protected或public类型。

看代码:


熟练应用重载,虽然它并不能直接简化类的编程,但对于调用这个类的程序开发人员来说,重载则更好理解,也更直观。

前面我们讲过,只要参数列表不同,一个类可以拥有多个构造器。构造器的本质还是方法,所以多个构造器实际上还是构成了方法的重载。
C#语言 第一部分 面向对象 (九) 方法的抽象和类的抽象 练习

上一章我们讲了方法和类的抽象,这一章我们从一些例子,继续加深对抽象的理解。

第一个例子,我们定义了形状类(Shap类),从类名字就可以看出该类必然是一个抽象类。形状……本来就很抽象嘛。

好了,既然是形状,就必然有面积,Shap类的Area方法必然是一个抽象方法。

接下来,我们创建一个子类:正多边形类(Polygon类),继承自Shap类。这个类比形状类具体多了,我们知道了一个新的属性:边长。但只知道 边长依旧无法求出形状的面积,所以其继承下来的Area方法依旧只能作为抽象方法,所以Polygon类仍是一个抽象类。

最后,定义正四边形类和正五边形类,这两个类显然非常具体了,可以定义Area方法了。就这样,看代码:

我们继续,下面的例子展示了抽象的属性,我们说过,一个对象的属性由一组get/set方法来表现,所以属性也可以作为抽象存在。

下面的例子我们定义了Person类,其EyeColor属性为一个抽象属性,由其子类来具体定义。看代码:

Technorati 标签: C#,教学

另外,我们也可以单独定义get或set访问器中的一个为抽象。这里就不做展示了。
C#语言 第一部分 面向对象 (十) 方法的隐藏

我们前面讲过了类和类之间的继承关系,我们了解到,超类方法和子类方法(或属性)可以具备三种联系方式。

    * 继承。超类修饰为public或protected的方法或属性可以被子类继承并访问;
    * 虚拟。虚拟的前提是可继承。超类中修饰为virtual关键字的方法或属性可以被子类继承或覆盖;
    * 抽象。抽象的前提是可继承。超类中修饰为abstract关键字的方法或属性可以被子类继承并实现,超类方法 没有方法体,由子类提供;

现在我们来学习超类方法(或属性)和子类方法(或属性)的第四种联系——隐藏。

子类继承超类方法,并一个和该方法同名、同参数的新方法,子类方法前不使用override关键字修饰。这种情况子类隐藏了超类继承的一个方法。

隐藏和覆盖最大的不同:隐藏的方法只能用该方法所属的类的变量访问,使用超类变量则无法访问,只能访问被隐藏的方法。看例子:


从代码中可以清楚的看到覆盖和隐藏具体的区别,当使用超类变量引用到子类实例后,依旧可以访问子类覆盖后的方法,但无法访问到子类隐藏后的方法,只 能访问被子类隐藏的方法。

注意,本例是为了让大家看明白覆盖和隐藏的区别,对于隐藏来说,被隐藏的超类方法无须修饰为virtual。

一半,子类方法要隐藏父类方法,在方法声明前加上new关键字,表示此方法是被隐藏的方法。new关键字并不是必需的。

隐藏表示了这样一种含义:子类定义了和超类名称相同但流程不同的方法,但只能以子类类型可以访问,使用超类类型则无法访问。

隐藏的情况较之覆盖要少见一些,以至于许多面向对象语言(例如Java)并不提供方法的隐藏。C#提供此特性是为了保证语言的完整性,在一些特殊情 况下可以解决一些问题。
C#语言 第一部分 面向对象 (十一) 接口

学习过C++的童鞋都应该知道,在C++中允许将一个类的成员函数定义为virtual(虚拟函数),定义为虚拟的函数,例如:


形如第4行的函数声明称为“虚拟函数”,可以被子类的同名函数(返回值类型,参数也必须完全相同)覆盖;形如第 5行的函数声明称为“纯虚拟函数”,这个函数没有函数体,必须被子类的同名函数(返回值类型,参数也必须完全相同)覆盖。

前面的课程,我们学习了C#中的虚拟方法(使用virtual修饰的方法)和抽象方法(使用abstract修饰的方法),相当于C++中的虚拟函数和纯虚拟函数。

我们可以在C++中设计这样的一种类,这个类中的所有成员函数都为纯虚拟函数,即都没有方法体。这种类型的类非常有用。在C#中,语言设计者为它起了一个新的名字——接口。

接口是那样的有用,以至于成为了一个程序员是否完全理解面向对象的重要标志。

接口在现实生活中非常常见,例如我们连接DVD影碟机和电视机,只要我们有这样的两个设备,使用电缆将它们连接在一起就可以了,无须考虑这两个设备是否可以相互兼容。这其中的道理非常简单,影碟机可以输出AV信号,电视机可以接受AV信号,所以可以连接。

电脑主机箱和显示器的道理也是如此;U盘和USB接口的道理也相同。

所以接口并不是一个实际存在的插口,而是双方都共同遵守的一种行为准则。因为双方都遵守这样的规范,所以双方可以毫无障碍的联系在一起。

什么叫做行为准则?例如我定义一个行为准则叫做Eatable(可食用的),凡是遵守这个准则的对象,就都可以塞进嘴巴里并咽下去,就像超市卖的食品上面蓝色的S标志,当得知对象遵循这个准则,则无需关心这个对象的成分、形状或任何其它属性,就可以把它和我们的嘴巴联系起来(当然营养与否,好吃不好吃那是另一码事儿了)。

聪明的童鞋可能已经反应过来了,所谓行为规范,就是不同对象所属的不同类,具有相同的超类,并且都实现了超类中定义的抽象方法。这样所有的类就具备相同的行为规范了。这种想法,在C++中是可行的。但在C#中,对于接口的定义则更为严格,原因有二:

   1. C#并不允许多继承,而C++可以,C#不想出现C++中难以理解的父子悖论而禁止多继承,所以在C#中,一旦使用超类来约束行为,则会对编程带来不小的麻烦——我们没法再继承其它类或引入其它行为准则了。
   2. 符合同一行为准则的,可以是完全不相干的不同类,例如同样符合 Eatable可食用准则的类,有可能是化学品类的子类,也有可能是生物类的子类或者植物种子类的子类。使用超类引入行为准则从继承的角度是非常不合理的。

我们来看一个例子:


代码中,我试图确立一种行为规范称之为“会跑的”(Runnable),有两个类需要遵循此规范,Car类和Cat类,显然汽车和猫都是会跑的东西,都可以实现抽象方法Run。

但问题出现了,Car类显然需要从机械类(Mechanical)继承,因为汽车是机械的一种,但显然并不是所有的机械都会跑(例如电动刮胡刀);而Cat类显然是从动物类(Animal)继承,但也不是所有的动物都会跑(例如鱼)。Car和Cat完全找不到相同的超类,但它们又可以遵循同样的行为规范,一个类不可能既继承自Animal类又继承自Runnable类!怎么办?

再看代码:


我们使用了一个新的关键字interface,很容易就解决了上面的问题,用interface声明的我们称为接口。我们发现,接口中可以包含方法的声明,而所有的类除了可以继承一个超类外,还可以继承多个接口。当然,习惯上把对接口的继承不叫做继承,而叫做“实现”,即一个类实现了某些接口。

接口定义的特点:接口中定义的方法上,自动为public abstract,即公共抽象方法。接口本身也为抽象。接口中不允许以任何方式包含具有方法体的方法。

接口完美的解决了所有的问题:

    * 接口中只允许包含方法声明,所以即便接口被多继承,也不会产生任何父子悖论问题;
    * 接口不是实体类,不带有类别的含义,只是一种行为的规范,所以可以被不同含义的多个类同时继承;
    * 接口可以赋予不同的类相同的行为准则(接口中定义的方法声明),所以接口类型变量可以引用到所有实现此接口类的实例上。

属性是一对方法,所以接口中也可以包含属性的定义。接口中不允许出现字段的定义。


好了,我们看一段完整的代码,从中体会接口的特点。

 

除了上面讲的接口概念,C#还有一个非常有趣的特性,即接口的显式实现,接口显式实现体现了这样一种效果:一个类同时实现多个接口中的多个同名方法。例如接口IA中有个方法void Test();,接口IB也有一个方法void Test();,类C实现IA和IB接口。如下:

所以,接口的显式实现就是一个类可以显式的指定一个方法实现的是哪个接口的方法。显式实现接口的方法前无须添加访问修饰符,使用类的对象也无法直接访问(因为它不是public的),但可以通过将类对象引用类型提升为其接口类型来间接访问该方法。

注意:不到万不得已,无须使用接口的显示实现以尽量减少一个类对接口实现的复杂程度。