一、基础术语
1、对象
对象是具有数据、行为和标识的三部分编程结构。对象的数据包含在对象的字段、属性、事件中。对象的行为则由对象的方法和接口定义。对象通过classe和struts定义。对象通过标识来区分是否相同。对象都是从类和结构中定义的模板实例化得到。均继承于Object类。
2、类
类定义了数据类型的数据和行为。可以根据这些来创建类的实例化对象。类支持继承但不可以多继承。类属于引用对象。最终类可以有很多个基类(类继承基类而导致)。可以实现多个接口。类和对象本质有不同,类是对象的类型,对象是类的实例。
3、分部类
分部类可以将类、结构、或接口的定义拆分到两个或多个源文件中,每个源文件都包括类定义的一部分,编译时把所有部分组合起来。使用partial定义。
4、结构
结构:定义基本于类相同,结构声明时,如果字段不是const或static,就无法初始化。结构不可以声明默认的构造函数(无参构造函数)或析构函数。可以声明带有参数的构造函数。结构副本由编译器自动创建和销毁,故不需要。结构不可以从类或其他结构继承。属于值类型,结构实例化不可以使用new,不可以继承,直接继承自System.ValueType。可以实现接口。
5、继承
类继承通过:号定义,:号后为要继承的单一的基类。由基类派生的类将会获得基类所有非私数据和行为(方法)以及派生类为自己定义的所有其它数据或行为(方法),所以派生类有两个有效类型:派生类型和基类类型。
6、抽象类
使用abstract定义用来继承的类和成员。抽象类不可以实例化,用来提供多个派生类可以共享的基类的公共定义(有点类似接口的意思),抽象类可以定义抽象方法,用abstract定义,但不可以有方法的实现。由抽象类派生的类必须实现抽象方法。
7、密封类
使用sealed关键字来定义防止继承以前标记的为virtual的类或某些成员。密封类主要用来防止派生。
8、多态
一个类可以用作多种类型,可以作自己的类型,可以做基类,可以实现接口时用作任何接口类型。
9、接口
接口使用interface定义,可以描述属于任何类或结构的一组相关行为,可由方法、属性、事件、索引器或这四种成员类型的任何组合构成。接口不可以包含字段,必须为公共的。可以同时继承多个接口,只能继承到方法名称和签名。
10、类成员
包括字段、属性、方法、事件、运算符、索引器、构造函数、析构函数、嵌套类型。
11、方法
方法包括一些类语句的代码块,方法在类或结构中声明,需要指定访问级别,返回值,方法名称以及任何方法参数。
12、构造函数
类和结构可以有多个接受不同参数的构造函数,可以使得可以设置默认值,限制实例化等功能。
13、析构函数
只能对类定义析构函数,并且一个类的析构函数唯一,无法继承和重载,无法调用,无修饰符和参数。
14、字段
包含在类或结构中的对象和值,字段可以使类和结构封装数据。一般用private,通过方法、属性或索引器访问字段。
15、常量
类和结构可以将常数声明为成员,用const修饰。
16、嵌套类型
在类或结构中定义的类型为嵌套类型,默认为private可以设置为其它修饰符,
17、静态类
静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数,静态类成员可以用于分离独立于任何对象标识的数据和行为。
18、面向对象的思想主要包括:继承 多态 封装
封装:用抽象的数据类型将数据和基于数据的操作封装在一起,数据被保护在抽象数据类型内部。
继承:子类拥有父类的所有数据和操作。
多态:一个程序中同名的不同方法共存的情况。
二、名词解释
1、.NET Framework(.NET 框架)
.NET 框架是Microsoft公司的一种新的便捷开发平台。.NET 框架有两个核心组件: CLR(公共语言运行库)和FCL(.NET Framework 类库)。.NET 框架好比一个航母,他支持的开发语言(C#、VB.NET、J#)就像航母上的战斗机。
2、CLR(Common Language Runtime)公共语言运行时
是 .NET Framework 的基础、相当于Java虚拟机。可以将运行库看作一个在执行时管理代码的代理,它提供内存管理、线程管理和远程处理等核心服务,并且还强制实施严格的类型安全以及可提高安全性和可靠性的其他形式的代码准确性。事实上,代码管理的概念是运行库的基本原则。以运行库为目标的代码称为托管代码,而不以运行库为目标的代码称为非托管代码。
3、FCL(Framework Class Library).NET框架类库
它提供了大量的实用类,如:System.console、System.IO等,它是一个综合性的面向对象的可重用类型集合,可以使用它开发多种应用程序,这些应用程序包括传统的命令行或图形用户界面 (GUI) 应用程序。
4、MSIL(Microsoft Inter Language)微软中间语言
所有面向.NET的语言都要先编译成IL, MSIL是将.Net代码转化为机器语言的一个中间过程。当用户编译一个.Net程序时,编译器将源代码翻译成一组可以有效地转换为本机代码且独立于CPU 的指令。当执行这些指令时,实时(JIT)编译器将它们转化为CPU特定的代码。这是为什么.NET能支持多种开发语言的原因。
5、CTS(Common Type System) 通用类型系统
不同编程语言定义的基本数据类型各不相同。既然要实现语言之间的“沟通”,就需要一个规范来实现“不同”数据类型(也许只是名称不同,而实质相同)间的交互操作,CTS就完成这个工作。如C#使用VB.NET开发的类库。
6、CLS(Common Language Specification) 公共语言规范
.NET平台上所有语言都应遵守的规则,用集合来说是多个语言规则的交集。如:在C# 中命名是区分大小写的,而VB.NET不区分大小写,这样CLS就规定,编译后的中间代码必须除了大小写之外有其它的不同这处。
7、JIT(Just-In-Time)
将MSIL编译成计算机能执行的二进制码。强调实时,实时执行时针对本地计算机的不同,被编译成本机代码。
8、Assembly程序集
是.NET程序的最小组成单位。每个程序集都有自己的名称、版本等信息。程序集通常表现为一个文件(.exe或.dll文件),这样的程序集被称为单文件程序集,这种程序集是最常见的。程序集也可以由多个文件组成,每个文件都是一个模块文件或者是一个资源文件,这样的程序集被称为多文件程序集。
三、比较记忆
1、访问修饰符。
public(公共的) |
访问不受限制 |
如果对类不指定访问修饰符,则类的默认访问修饰符为internal,但是类成员的默认访问修饰符为private。类只能修饰为public或internal类成员可以是所有。 |
protected (保护的) |
访问范围限定于它所属的类或从该类派生的类型 |
|
internal(内部的) |
同一程序集中的任何代码都可以访问该类型或成员 |
|
protected internal(内部保护的) |
访问范围限定于同一程序集或那些由它所属的类派生的类型 |
|
private(私有的) |
访问范围限定于它所属的类型 |
2、类与对象
一切事物皆为对象,类是一组具有相同属性和方法的对象的抽象,对象是类的实体,类是对象的抽象。对象是包含数据和操作的实体,它既定义数据元素,又定义可应用这些数据元素的操作。类是对一组具有相同属性和行为的对象的描述,类的内容称为类的成员。
3、静态类与非静态类
所谓静态类即在类的前面用static关键字来修饰的类。这样的类不能被实例化、是密封类、仅包含静态成员、不包含实例构造函数,静态类不能使用new创建实例,用类名就可能直接访问其成员,非静态类则不行。静态类中不能创建非静态的方法。即静态方法中只能创建静态方法,但在非静态类中可以调用静态方法,也可以创建静态成员,包括方法。
4、程序集和命名空间
可以把程序集简单理解为你的.NET项目在编译后生成的*.exe或*.dll文件,一个程序集可以跨越n个命名空间,一个命名空间也可以包含n个程序集。如果说命名空间是类库的逻辑组织形式,那么程序集就是类库的物理组织形式。
5、Const与Readonly
(1)、const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。(2)、const 字段是编译时常数,而 readonly 字段可用于运行时常数。(3)、const 默认就是静态的,而 readonly 如果设置成静态的就必须显示声明。(4)、const 对于引用类型的常数,可能的值只能是 string 和 null。readonly可以是任何类型
6、类与结构
(1)、结构是值类型,而类是引用类型。在结构中初始化字段是错误的。
(2)、向方法传递结构时,结构是通过传值方式传递的,而不是作为引用传递的。
(3)、与类不同,结构的实例化可以不使用 new 运算符。结构保存在栈上,而类保存在受管制的堆上。
(4)、结构不能声明默认构造函数或析构函数。结构可以声明带有参数的构造函数。
(5)、一个结构不能从另一个结构或类继承,而且不能作为一个类的基。类是重量级的,而结构是轻量级的。
(6)、所有结构都直接继承自 System.ValueType,所有的引用类型继承自System.Object。
(7)、类与结构都可以实现接口,都包括字段与方法。结构的执行效率更高。
7、装箱和拆箱
装箱(boxing)和拆箱(unboxing)机制使得在C#类型系统中,任何值类型、引用类型和object(对象)类型之间进行转换,这种转换称为绑定连接。装箱:将值类型转换成引用类型,拆箱:将引用类型转换成值类型。
8、接口和类
接口可以多继承,类只能单继承。接口描述可属于任何类或结构的一组相关行为。接口可由方法、属性、事件、索引器或这四种成员类型的任何组合构成。接口不能包含字段。接口成员一定是公共的。类 只支持单继承:类只能从一个基类继承实现。一个类可以实现多个接口。
9、值类型和引用类型
值类型和引用类型的区别在于,值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。值类型变量直接把变量的值保存在堆栈中,引用类型的变量把实际数据的地址保存在堆栈中,而实际数据则保存在堆中。类、数组、接口、委托是引用类型,struct与enum是值类型。
10、重载与override
规则 |
重载(overload) |
覆写(override) |
存在位置 |
存在于同一个类中 |
存在于有继承关系的不同类中 |
调用机制 |
编译时确定 |
运行时确定 |
方法名 |
必须相同 |
必须相同 |
参数列表 |
必须不同 |
必须相同 |
返回值类型 |
可以不相同 |
必须相同 |
泛型方法 |
可以重载 |
可以覆写 |
11、抽象类(abstract class)与接口(interface)
相同点:1、都不能被实例化,都可以通过继承实现其抽象方法;2、都是面向抽象编程的技术基础;
不同点:1、接口支持多继承,也就是说一个类可以实现多个接口;抽象类不能实现多继承;2.接口只能定义抽象规则或对象;抽象类即可以定义抽象的,也可以提供一些已经实现的成员或方法;3、接口只包含方法、属性、索引器、事件的签名,但却不能定义字段和包含实现的方法;抽象类可以定义字段、属性和包含有实现的方法;4.接口属于值类型;抽象类属于引用类型;
12、虚方法(virtual)与抽象(abstract)方法
相同:都可以在派生类中重写;不同点:虚方法在基类中有实现的部分,在派生类中可重写也可不重写;而抽象方法在基类中没有实现的部分,在派生类中必须重写;抽象方法只能在抽象类中,抽象类中可以有实休方法。
四、典型例子
namespace T82 //命名空间 { public interface objects //定义接口 { void Objinterface(); //接口中的方法成员,不能有方法体 } abstract class People:objects //抽象类,没有访问修饰符默认为internal,实现objects接口 { const string className = "People"; //常量,默认是静态的 public People() { } //构造方法 public People(string _name) { this.Name = _name; } //方法重载 private string name; //字段 public string Name //属性 { get { return name; } set { name = value; } } public virtual void SayHi() //虚方法 { Console.WriteLine("Hi,"+this.Name); } public abstract void ShowDemo(string DemoString); //定义抽象方法,必须在抽像类中,不能有方法体 public static void ClassName() //静态方法,使用到的成员必须是静态的 { Console.WriteLine(className); //显示常量 } public void Objinterface() //必须实现objcets接口的方法 { Console.WriteLine("Objinterface()"); } } class Student:People //继承, { public Student(string _name):base(_name){} //调用父类构造方法 public override void SayHi() //重写虚方法 {Console.WriteLine("Hello," + this.Name);} private string[] nickName=new string[2]; //字段 public string this[int index] //定义索引器 { get { return nickName[index]; } set { nickName[index] = value;} } public override void ShowDemo(string DemoString) //重写抽象方法 { Console.WriteLine(this.Name + " is a " + DemoString);} } public class Client { public static void Main(string[] args) { Student s1 = new Student("zhangSan"); //实例化一个学生 Student.ClassName(); //静态方法通过类名调用 s1[0] = "SanSan"; //通过实例名访问字段 s1[1] = "zhangzhang"; Console.WriteLine(s1.Name+"叫"+s1[0] + "也叫"+s1[1]); s1.SayHi(); //非静态方法通过实例.调用 s1.ShowDemo("Top Student"); //通过实调用已经实现的抽象方法 s1.Objinterface(); //通过s1通用已实现的方法 Console.ReadKey(); } } }
面向对象的特性
各种面向对象编程语言相互有别,但都能看到它们对面向对象三大机制的支持,即: “封装、继承、多态”
– 封装,隐藏内部实现
– 继承,复用现有代码
– 多态,改写对象行为 使用面向对象编程语言(如C#),可以推动程序员以面向对象的思维来思考软件设计结构,从而强化面向对象的编程范式。
C#是一门支持面向对象编程的优秀语言,包括:各种级别的封装支持;单实现继承+多接口实现;抽象方法与虚方法重写。
封装对象的原理
封装是把类的内部隐藏起来,以防止外部世界看见的一个面向对象的概念。
1、通过只是使用那些对外公有的类成员(public标示的),可以尽可能的减少依赖
2、减少了其他人破坏程序代码的可能性
3、用户只能看到那些对外公有的成员,清晰的展现了此类对外开放的接口,易于其他类的调用。
封装性最有用的方式之一
实现方法——访问限制修饰符
public 无限制,允许任何人来访问
protected internal = protected + internal 允许程序集内部或继承访问
Internal 允许项目或程序内部的类来访问
protected 继承时子类可以对基类有完全访问权
private 只允许同一个类中的成员访问
属性和索引器也用来封装类的细节,并提供公用接口给该类的用户
继承
一个类可以有能力直接从另一个类获得其代码和数据
派生类从基类那里获得其所有的成员
例:TextBox继承自 System.Web.UI.WebControls.WebControl继承自System.Web.UI.Control
如何实现继承?
public class InsuranceAccount : Account
C# 中只支持单继承
防止继承
public sealed class YY
class Point ...{ private int x; public int X ...{ get ...{ return x; } set ...{ x = value; } } private int y; public int Y ...{ get ...{ return y; } set ...{ y = value; } } public Point(int x, int y) ...{ this.x = x; this.y = y; } } lass Point3D:Point ...{ private int z; public int Z ...{ get ...{ return z; } set ...{ z = value; } } public Point3D(int x, int y, int z) : base(x, y) ...{ this.Z = z; } }
程序讲解
类的声明可能通过在类名后加上冒号和基类的名字来指定一个基类译注4。省略基类等同于直接从object类派生。在下面的示例中,Point3D的基类是Point,而Point的基类是object:
Point3D类继承了其基类的成员。继承意味着类将隐式地包含其基类的所有成员(除了基类的构造函数)。派生类能够在继承基类的基础上增加新的成员,但是它不能移除继承成员的定义。在前面的示例中,Point3D类从Point类中继承了x字段和y字段,并且每一个Point3D实例都包含三个字段x,y和z。
如何访问基类成员?
派生类可以调用基类的方法
通过使用base关键字
派生类在访问基类的时候有一定的限制,不能访问 private 的成员;internal的基类成员只能被同一个程序集中的派生类访问
ublic class Account ...{ public double balance; public bool ; public Withdraw(double amt) ...{ balance -= amt; return true; } } public class CheckAccount:Account ...{ public bool Withdraw(double amt) ...{ if(amt<= base.balance) return base.Withdraw(amt); else return false; } }
调用基类构造函数
因为不能继承构造方法,派生类必须实现自己的构造方法
public child(int a,int b,int c):base(a,b) { }
多态性(1)
面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的引用,来调用实现派生类中的方法。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类覆写基类中的虚函数型方法来实现。
在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。
“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。
多态性通过派生类覆写基类中的虚函数型方法来实现。
多态性(2)
编译时的多态性
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
运行时的多态性
运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。
C#中,运行时的多态性通过覆写虚成员实现。
虚拟函数
声明虚方法
使用virtual关键字 public virtual bool Withdraw(…);
调用虚方法,运行时将确定调用对象是什么类的实例,并调用适当的覆写的方法。
虚方法可以有实现体
具体的检查的流程如下:
1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
2、如果不是虚函数,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。
3、在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。
public class A ...{ protected internal int a=0; public virtual void Func() // 注意virtual,表明这是一个虚拟函数 ...{ Console.WriteLine("Func In A"); } } class B : A // 注意B是从A类继承,所以A是父类,B是子类 ...{ public override void Func() // 注意override ,表明重新实现了虚函数 ...{ Console.WriteLine("Func In B"); } } class C : B // 注意C是从A类继承,所以B是父类,C是子类 ...{ } class D : A // 注意B是从A类继承,所以A是父类,D是子类 ...{ public new void Func() // 注意new ,表明覆盖父类里的同名类,而不是重新实现 ...{ Console.WriteLine("Func In D"); } } static void Main(string[] args) ...{ //Point3D p3d = new Point3D(1, 2, 3); //Console.WriteLine("x:{0},y:{1},z:{2}", p3d.X, p3d.Y, p3d.Z); A a; // 定义一个a这个A类的对象.这个A就是a的申明类 A b; // 定义一个b这个A类的对象.这个A就是b的申明类 A c; // 定义一个c这个A类的对象.这个A就是b的申明类 A d; // 定义一个d这个A类的对象.这个A就是b的申明类 a = new A(); // 实例化a对象,A是a的实例类 b = new B(); // 实例化b对象,B是b的实例类 c = new C(); // 实例化b对象,C是b的实例类 d = new D(); // 实例化b对象,D是b的实例类 Console.WriteLine("a.Func() "); a.Func(); /**///// 执行a.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类A,就为本身 4.执行实例类A中的方法 5.输出结果 Func In A Console.WriteLine("b.Func() "); b.Func(); /**///// 执行b.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类B,有重载的 4.执行实例类B中的方法 5.输出结果 Func In B Console.WriteLine("c.Func() "); c.Func(); /**///// 执行c.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类C,无重载的 4.转去检查类C的父类B,有重载的 5.执行父类B中的Func方法 5.输出结果 Func In B Console.WriteLine("d.Func() "); d.Func(); /**///// 执行d.Func:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类D,无重载的(这个地方要注意了,虽然D里有实现Func(),但没有使用override关键字,所以不会被认为是重载) 4.转去检查类D的父类A,就为本身 5.执行父类A中的Func方法 5.输出结果 Func In A D d1 = new D(); d1.Func(); // 执行D类里的Func(),输出结果 Func In D} // Demo.多态.Shoot.ShootBird(new Demo.多态.Sparrow()); }
什么时候用到虚函数
既然虚函数应为多态而生,那么简单的说当我们在C#中要想实现多态的方法之一就是使用到虚函数。复杂点说,那就是因为OOP的核心思想就是用程序语言描述客观世界的对象,从而抽象出一个高内聚、低偶合,易于维护和扩展的模型。
但是在抽象过程中我们会发现很多事物的特征不清楚,或者很容易发生变动。
比如飞禽都有飞这个动作,但是对于不同的鸟类它的飞的动作方式是不同的,有的是滑行,有的要颤抖翅膀,虽然都是飞的行为,但具体实现却是千差万别,在我们抽象的模型中不可能把一个个飞的动作都考虑到,那么怎样为以后留下好的扩展,怎样来处理各个具体飞禽类千差万别的飞行动作呢?比如我现在又要实现一个类“鹤”,它也有飞禽的特征(比如飞这个行为),如何使我可以只用简单地继承“飞禽”,而不去修改“飞禽”这个抽象模型现有的代码,从而达到方便地扩展系统呢?
因此面向对象的概念中引入了虚函数来解决这类问题。
使用虚函数就是在父类中把子类中共有的但却易于变化或者不清楚的特征抽取出来,作为子类需要去重新实现的操作(override)。而虚函数也是OOP中实现多态的关键之一。