在现实世界中,事物都是分类的,都是由各种类型的事物共同构成的。同一类型的事物会有一些共同的属性和行为,我们把这些共同的属性和行为抽象出来加以封装,就构成了类。从一个类继承下去,把原来的类中的某些方法重载或者覆盖,或者添加一些新的属性和方法,那么就会产生很多新的类。可以用父类的引用指向子类的对象,这个就是多态。
类是对同一类型的事物共同的属性和行为抽象并加以封装的结果。
Class className
{
//类的属性和方法
}
在C#.NET 2005中,在不同的命名空间下面,可以存在同名的类,但是这种在现实中不会出现的,所以不做详细讨论了。在同一个命名空间下面,允许将一个类分成两个同名的类来写,前提是在这个类名前要加上partial关键字,其实这两个类是一个类的两个部分,只是分开写了,所以不能有重复的代码。而且类的修饰符要一致。
在C#.NET中,类是可以嵌套的,即在类中可以定义一个新的类。如下:
Namespace na
{
Class A
{
Public Class A1;
Class A2;
A1 a //OK
A2 b //OK
}
Class B
{
Static void main()
{
A.A1 //OK
A.A2 //NG
}
}
}
在A类中定义了两个新的类,在A类的内部,可以相互访问,没有任何的约束。但是在B类中,只可以访问A类中的public、internal部分,其他的无法访问。在其他的命名空间下面也只能访问A类中的public部分,而且A类本身必须是public的。注意,类一般都不会嵌套。
用abstract修饰的类就是抽象类,抽象类只能被继承,而不能被实例化。抽象类有三种类型:
(1)、所有的成员都是抽象的。
(2)、所有的成员全部是实体的。
(3)、以上两种的混合。
抽象类被继承之后,继承类仍然可以是抽象类,但是如果子类要求是能够定义对象的实体类,那么一定要重写抽象类的所有的抽象成员,变成实体成员。
public abstract class Employee
{
protected string id; //如果定义成了private,那么就无法在子类中访问了
public abstract string Id { get;set;}
public abstract int Add(int a,int b);
}
public class Employee1 : Employee
{
public override string Id
{
get
{
return id;
}
set
{
id = value;
}
}
public override int Add(int a,int b)
{
return a + b;
}
}
(1)、接口里面是无法含有实体的成员的,成员只能是抽象的属性和抽象的方法。
(2)、抽象的属性和抽象的方法不需要abstract来修饰,否则就出错,因为默认就是这个。子类中也不需要override,默认就直接覆盖了。
(3)、抽象的属性和抽象的方法中不能有public,默认就是这个。但是在实现类中各个成员都默认为private,所以在实现类中要用public修饰。
(4)、一个类只能继承一个于父类,但是可以继承于多个接口。
例子1:
public classEmp……
public interfaceEmp1……
public interfaceEmp2……
public interfaceEmp3: Emp1, Emp2 //一个接口克继承于多个接口。
public classEmp4:Emp, Emp1, Emp2//一个类只能继承于一个父类,但是可以实现多个接口。
例子2:
public interface Emp
{
string Id { get;set;}
int Add(int a,int b);
}
public class Emp1 : Emp
{
private string id;
public string Id
{
get
{
return id;
}
set
{
id = value;
}
}
public int Add(int a, int b)
{
return a + b;
}
}
(1)、internal
类是在命名空间(项目)下面定义的,定义一个类的时候,它默认有一个internal修饰符。这个表示在本命名空间(项目)下面,其他的类都可以访问该类,但是在别的命名空间(项目)下面,是无法访问该类的。
(2)、public
被public修饰的类,不仅在本命名空间下可以被别的类访问,也可在别的命名空间下被访问。
(3)、abstract
被abstract修饰的类是抽象类,只能被继承而不能被实例化。
(4)、sealed
被sealed修饰的类是密封类,只能被实例化而不能被继承,密封类里面不能有任何的抽象方法。
类的成员主要分为三类:
即类中定义的变量,一般不对外开放,只能在类中访问。
是对域的封装,是外界访问域的接口。
private int valueTest;
public int ValueTest
{
get { return valueTest; }
set { valueTest = value; }
}
对象是不能直接访问private变量的,但是可以访问public的属性。当读取属性的时候,其实是通过get来完成的,就是通过return来取得域变量的值;当写入的时候,是通过set来完成的,就是把value(赋值的值)赋值给域变量。对属性的读取实质是对域变量的读取。
private的域变量会变成public的属性,但是数据类型不会变,属性名默认域变量的首字母大写。属性名也不一定要按域变量首字母大写来命名,可以任意。
get和set是控制对域变量的读写的,可以只选择某一个来控制只读,只写。
属性也不一定要与域变量配对,只要了解了原理,就可以灵活的应用。
Get和set只是语句块,可以在块中写更多的语句:
public int ValueTest
{
get { return valueTest; }
set { if(value > 100) valueTest =value;else valueTest = 100; }
}
即类中的函数。
类的对象在创建的时候,一定会调用构造函数。构造函数就是完成类对象的创建的,在构造函数中可以完成对对象属性的初始化。构造函数是公有的没有返回类型的,函数名与类名同名的函数。在默认情况下,系统是自带了一个默认的构造函数,这个构造函数没有参数,没有函数体,什么也不做。一旦自己定义了一个构造函数之后,不论是否有参数,系统默认的构造函数就不存在了,再调用肯定错误,除非自己去显示的定义一个默认的不带参数的构造函数。
定义一个类的对象并创建的时候,初始化的顺序是:属性在定义的时候如果没有被赋值,那么就以它的默认值为准,赋值了的就以赋值的结果为准,然后再看构造函数里面的操作,最终对象被创建好之后,各个属性的值就是三个顺序执行完成之后的结果。
对于普通的参数,实参在向形参传递的时候,只是把实参的值传递给了形参;但是对于形参类型为ref的,接受实参的时候,实参必须是变量而不能是常量,而且是有值的,形参会接受实参的值,形参的变化会直接影响实参的变化,其实实参把地址传给了形参;对于out参数也一样,只是此种情况下不管实参是否有值,形参都不回接收实参的值,形参自己初始化然后运算,最终值会传递给实参。
在类里面定义了某个私有的数组,定义了类的对象之后,对象是无法直接访问这些数组的,因为数组是私有的,但是如果在类里面针对这个数组建立索引,那么那么就可以在外面通过对象来调用数组了:
public class IndexTest
{
private string[] name;
public IndexTest()
{
name = new string[100];
}
public string this[int index]
{
get
{
if (index < 0 || index >= 100)
return null;
else
return name[index];
}
set
{
if (index < 0 || index >= 100)
return;
else
name[index] = value;
}
}
}
(1)、private
这个是成员的默认修饰符,成员被这个关键字修饰之后,该成员就只能在该类中被访问了,在类的外面,即别的类里面定义了该类的对象之后,是无法访问该关键字修饰的成员的;该关键字成员被继承之后还是这个关键字,但是在子类的内部都无法访问了,外部就更不能访问了。
(2)、protected
成员被这个关键字修饰之后,该成员就只能在该类中被访问了,在类的外面,即别的类里面定义了该类的对象之后,是无法访问该关键字修饰的成员的;该关键字成员被继承之后还是这个关键字,在子类的内部是可以访问的,外部是不能访问的。
(3)、internal
成员被这个关键字修饰之后,该成员可以在该类中被访问了,如果是在同一个命名空间下面,在类的外面,即别的类里面定义了该类的对象之后,也可以访问,但是在不同的命名空间下面,定义该类的对象是无法访问该关键字修饰的成员的;该关键字成员被继承之后还是这个关键字。
(4)、public
在任何地方(类的内部、外部、同一个命名空间下、不同的命名空间下)都可以访问该关键字修饰的成员;继承之后,该访问属性不变。
用static修饰的成员是不依赖对象而存在的,是被类的所有对象所共有的。所以调用的时候直接:类名. static成员名(前提是public(internal是针对本命名空间,public是针对整个解决方案))。Static变量在定义的时候一般都要初始化,如果不初始化在后来引用的时候初始化也可以。如果static没有用public或者internal修饰,那么它只能在类的内部引用,否则就是该类的所有的对象公有。
一般都是针对public,定义了static变量之后,程序编译之后,static变量就分配了全局的存储空间,程序就可以不定义对象直接用类名去访问该变量,在过程中可以修改这个变量的值,直到整个程序全部结束之后,该变量才会被释放掉。该变量是针对所有对象的全局变量,在程序的整个运行过程中都是全局的。
静态函数跟静态变量一样,也是不依赖对象而存在的,需要注意的是静态函数只能调用函数外的静态变量和静态函数,在静态函数内部则可以自己定义非静态的成员。非静态的方法则没有任何的限制。
this是在类的函数中,对当前调用该函数的对象的调用。比如:在构造函数的参数与域变量同名的情况下,会使用this来调用域变量从而区别于构造函数的同名参数。
在类的内部,可以用this调用Private的变量,但是在用类的对象来调用类里面的成员的时候,只能是public、internal的。
定义一个类,如果这个类中有很多的函数、属性跟别的类相同,那么这个类可以从别的类中继承过来,这个类会继承除了父类中的构造和析构函数以外的所有的成员,子类中还可以添加新的成员。但是要注意几点:
(1)、子类的访问级别不能超过父类。如果父类为public,那么子类可以是public和internal。如果父类为internal,那么子类就只能是internal。
(2)、private成员也被继承下去了,但是在继承类的内部也无法访问该成员。
(3)、子类只能从一个父类中继承,而不能从多个父类中继承;但是一个类能够被多个子类继承。
(4)、定义一个类继承于别的类
Class subclass : baseClass
{
//类成员
}
在子类中,定义一个子类的对象,并创建的时候,一定会先去调用父类的构造函数来初始化子类中继承过来的父类的成员,然后再调用子类的构造函数。调用父类的构造函数有两种方式:
(1)、隐式的调用,创建子类对象的时候,不用base显式调用父类的构造函数,那么调用的是父类的默认构造函数,此时父类的无参默认构造函数一定要显示的或者隐含的存在。
(2)、用base显式的调用父类的构造函数,那么只要满足base的格式与父类的构造函数匹配即可,不一定需要父类中有默认的构造函数,要根据base调用的构造函数来决定,如果base调用的是默认的那么才要默认的,否则不需要,base调用的构造函数一定要存在。这种方式下,定义子类的构造函数格式如下:public subclass(int a,int b):base(a){……},此处的base是直接调用了父类的构造函数,参数是实参,可以引用前面的形参。在调用构造函数的时候,只用调用子类的构造函数即可,不要加上base,否则会出错,因为系统会默认加上去。
执行顺序如下:先以父类继承过来的成员的默认值为主,然后调用父类的构造函数来初始化,然后以子类中新的成员的默认值为主,然后调用子类的构造函数。
重载:在同一个类中,如果函数名字相同,参数的个数或者类型不相同,那么这个叫做函数的重载,函数重载可以发生在一个类中,也可以在子类中重载从父类继承过来的函数。调用的时候会根据参数的不同来选择最合适的调用。
覆盖和隐藏:函数名字相同,参数的个数和类型也完全相同,如果出现在同一个类中,那么就会有语法错误。如果出现在父子类中,那么子类的该函数可能隐藏父类的该函数,也可能覆盖父类的该函数。
base:可以用base在子类中来调用父类中的函数。在普通情况下,函数从父类中继承到了子类,在子类中就可以直接用函数名访问,也可以用base,但是没必要用base;但是那些父类中继承过来却被子类隐藏或者覆盖的方法,就必须用base才能访问了;父类中的private被继承之后,不论是否被覆盖或者隐藏,都无法以任何的形式在子类中被访问,base也不可以。
new:如果在子类中出现了一个与父类同名同参的函数,子类中的该函数没有用任何的修饰符,那么就是默认隐藏了父类的该函数,此时会有一个警告;如果在子类中加上new的话,警告就会消失。隐藏表示父类的函数在子类中还是存在的,只是隐藏了;如果用父类的引用指向子类的对象,然后调用该函数,调用的是父类的函数,这就是隐藏的特征。New只能隐藏new、overwrite、普通的函数。
如果子类还有下一级子类,在子类中被隐藏的父类的方法会往下一级子类继承,而且在下一级如果有overwrite,也不会被覆盖,被覆盖的是子类中的函数,这个子类中的被隐藏的从父类从继承过来的函数是不会被覆盖的,继续以隐藏的身份往下传递。
virtual:virtual表示的是虚函数,这个函数是有函数体的,而且一定有函数体。Virtual表示该函数一定会在子类中被覆盖。
overwrite:overwrite在子类中会覆盖父类中的同名同参的函数。覆盖之后,父类的该函数在子类中就不存在了。如果用父类的引用指向子类的对象,然后调用该函数,调用的是子类的函数,因为父类的函数在子类中被覆盖了不存在了。Overwrite只能覆盖父类中的virtual,overwrite,abstract。
小结:父类成员继承到子类之后,子类的成员覆盖或者隐藏父类成员,但是访问修饰符号不要轻易改变,因为没有必要。
用父类的引用指向子类的对象,因为一个子类不能继承于多个父类,但是一个父类能够被多个子类继承,所以一个父类的引用可以指向多个子类的对象,这就是多态。通俗的讲就是:单一界面,多实现版本。注意:
(1)、此时父类引用只能调用子类中的在父类中声明过的成员。
(2)、多态的实现中,父类可以是普通类、抽象类、接口。
2009-02-22----2009-02-27