类是C#中的两种基本封装构造之一(另一个是结构),类是逻辑相关的数据和函数的封装,通常代表真是世界中的或概念上的事物。
类包括数据成员和函数成员两项内容。其中数据成员用于存储数据包括字段和常量,函数成员用于执行代码包括方法、属性、构造函数、析构函数、运算符、索引和事件。(类中允许嵌套类)
一、类定义
类基础声明:<修饰符> class 标识符 : 基类,接口1,接口2,……
修饰符 说明 abstract 抽象类 于作为基类不能创建该类的实例,不能同时定义为sealed 抽象类中允许有抽象成员(不是必须的但有抽象成员的类必须为抽象类) internal 访问修饰符 类只能从同一个程序集的其他类中访问 他是非嵌套类的默认访问方式 new 只能用于嵌套类 他指明类隐藏一个同名的被继承成员 private 访问修饰符 用于嵌套类表明此类只能在定义它的类中访问 protected 访问修饰符 用于嵌套类表明此类只能在定义它的类或由此派生的类中访问 public 访问修饰符 该类的实例可以被任何其他类访问 sealed 密封类 该类不能作为其他类的基类即不能被继承 不能同时定义abstract
二、成员定义
2.1.成员的访问级别
访问修饰符 说明 public 成员可以由任何代码访问 private 成员只能由类中的代码访问(如果没有使用任何关键字这为默认的) internal 成员只能由定义它的程序集(项目)内部的代码访问 protected 成员只能由类或者派生类中的代码访问
2.2.常量: <修饰符> const 类型 标识符 = 常量表达式;
·修饰符只能为new和访问修饰符(常量默认为static不用且不能显式声明)
new用作修饰符与用作操作符是两个概念,用作修饰符时可以显式隐藏从基类继承的成员,隐藏继承的成员的同时该成员的派生版本将替换基类版本(虽然不用new也可以隐藏但会生成警告)这里隐藏的意思是指隐藏从基类中继承了的成员(可以理解为虽然子类从基类中继承类该成员但该成员对子类不可见或者说子类不认为该成员是从父类继承得到的而是认为是自己新建的一个成员)主要用于基类设计的不足
using System;public class Derived:Base {new public const double PI = 3.14159;new public static int x = 100;new public class Nested{public int x = 100;public int y;public int z;}new public void Say(){Console.WriteLine("derived");}public static void Main(string[] args){Derived der = new Derived();Base ba = new Base();Console.WriteLine("{0},{1}", Derived.PI, Base.PI);//输出3.1415,3.14der.Say();//输出derivedba.Say();//输出baseNested nes = new Nested();Base.Nested banes = new Base.Nested();Console.WriteLine("{0},{1}",nes.x,banes.x);//输出100,200 Console.ReadKey();} } public class Base {public const double PI = 3.14;public static int x = 55;public class Nested{public int x = 200;public int y;}public void Say(){Console.WriteLine("base");} }
·常量声明中指定的类型必须是内置类型枚举类型或者引用类型且每个常量表达式所产生的值必须属于目标类型的或者可以通过一个隐式转换为目标类型的(常量表达式中不允许使用new运算符所以除了string以外的引用类型常量的唯一可能值为null)
2.3.字段:<修饰符> 类型 标识符 <= 初始值设定项>;
·字段修饰符除了成员访问修饰符外有new,static,readonly,volatile
static用于定义静态变量即可以用类名引用她相对于实例变量(实例变量必须用实例名引用),一个静态字段只标识一个存储位置无论一个类创建多少个实例他的静态字段永远都只有一个副本,静态函数成员(方法、属性、事件、运算符、或者构造函数)不能作用与具体的实例且在静态函数中用this引用会导致编译错误
只读字段用readonly修饰符声明 只能在声明阶段个只读字段进行直接赋值或者在同一个类中的实例构造函数或静态构造函数中赋值只有在这两种情况下(声明时,调用构造函数时)才允许对只读变量赋值其他的任何赋值都会导致编译时错误(可以有static readonly字段通常用于需要一个具有常数值的符号名称,但该值类型不能用const声明或者无法在编译时计算出该值就可以用此字段)
易失字段(用volatile声明)用于多线程
·字段的初始化 字段不同于局部变量编译器会自动给字段赋予初值(为字段类型的默认值通过构造函数)也可以指定一个初始值
2.4方法 <修饰符> 返回类型 标识符(<形参表>){<方法体>;}
·修饰符除了成员访问修饰符还有new,static,virtual,sealed,override,abstract,extern
·返回类型必须有如果没有返回值则显示声明为void
·形参定义可以有多个用逗号分开定义是必须显式写出个元素类型
static定义一个静态方法 它不能对实例成员进行操作在静态方法中引用this会导致错误 静态方法直接有类名调用
外部方法(extern修饰) 外部方法是在外部实现的通常使用C#以外的编程语言不提供任何实现通常与DllImport特性一起使用从而使外部方法可以由DLL实现
虚拟方法(用virtual修饰符定义的方法) 非虚拟方法的实现是不变的(无论是在声明它的类的实例上还是在派生类的实例上调用该方法实现都是相同的) 而虚拟方法的实现可以由派生类取代(取代所继承的虚拟方法的实现的过程称为重写该方法)
重写方法(用override修饰符定义的方法) 重写方法是要满足的要求1.可以从其继承链中找到一个已重写的基方法2.该已重写的基方法是 一个模拟、抽象或重写方法3.已重写的基方法不能是密封方法4.重写声明和已重写了的基方法具有相同的返回类型5.重写声明与已重写的基方法具有相同的声明可访问性 重写方法可以用base关键字调用已重写的基方法(有点像this)
密封方法(一个实例方法用sealed修饰) 如果实例方法声明中包含sealed则必须也包括override修饰符用于防止派生类进一步重写该方法
抽象方法(用abstract声明) 抽象方法同时隐式声明为虚拟方法(不能同时用virtual修饰)抽象方法不提供任何实现只有方法定义,抽象方法只允许定义在抽象类中
using System;public class Text {public static void Main(string[] args){D d = new D();A a = d;B b = d;C c = d;A a1 = new A();a1.F();//输出A.F 正常(没有体现多态性)输出还是A.Fa.FF();//输出A.FF 对于new关键字是隐藏了基类的方法定义自己的方法只是两者名字相同而已a.F();//输出B.F 调用那个实际方法实现其决定作用的是该实例的运行是类型(B)而不是编译时类型(A)b.FF();//输出B.FF 隐藏时访问父类则调用父类的方法 子类调用子类的方法b.F();//输出B.F 重写时访问父类子类都调用子类重写的方法它们的体现是在多态中的b.AF();//输出A.Fc.FF();//输出C.F 隐藏方法只要求方法名相同c.F();//输出D.F 重写方法要求方法声明相同(相同返回值和参数列表)d.FF();//输出D.FFd.F();//输出D.F Console.ReadKey();} } class A {public virtual void F() { Console.WriteLine("A.F"); }public void FF() { Console.WriteLine("A.FF"); }public virtual void FFF() { Console.WriteLine("A.FFF"); } } class B : A {public override void F(){Console.WriteLine("B.F");}public void AF(){base.F();//base语言类似与this base调用的是基类的方法 }new public void FF() { Console.WriteLine("B.FF"); }public sealed override void FFF() { Console.WriteLine("A.FFF"); }//密封重写方法防止进一步重写 } class C : B {new public virtual void F() { Console.WriteLine("C.F"); }new public void FF() { Console.WriteLine("C.FF"); }//public override void FFF() { Console.WriteLine("A.FFF");} 无法重写已密封的方法 } class D : C {public override void F() { Console.WriteLine("D.F"); }new public void FF() { Console.WriteLine("D.FF"); } } /*C类和D类均含有两个具有相同签名的虚拟方法:A引入的虚拟方法和C引入的虚拟方法。*但是,由于C引入的方法隐藏了从A继承的方法因此D中的重写声明所重写的是由C引入*方法,D不可能重写由A引入的方法*/方法重载: 用于在给定的参数列表和一组候选函数成员的情况下选择一个最佳函数成员来实施调用。重载的方法必须保证形参的类型或者数目不一样才能让编译器判断需要调用那个方法(不能仅返回值不同)
2.5.属性 <修饰符> 类型 标识符 {访问器声明}
修饰符除了访问修饰符还有new,static,virtual,sealed,override,abstract,extern (修饰符与方法基本相同)
访问器声明包括一个get或者set或两者都有 get访问器相当于一个具有属性类型返回值的无参数方法其中有return语句返回一个值作为赋值的目标,set访问器相当于一个具有单个属性值参数和void返回类型的方法其隐式参数始终为value当作一个属性接受赋值是调用 只有get为只读属性 只有set为只写属性 两者都有为读写属性(通常一个类将字段都设置为私有的而定义属性公开 属性中可以包括读写执行的过程以引导或者判断赋值)
2.6.实例构造函数 <修饰符> 标识符(<参数列表>){函数体;}:base(参数列表):this(参数列表)
修饰符包括访问修饰符和extern 构造函数名必须与定义他的类的类名相同 实例构造函数不会被继承 如果一个类中没有声明任何构造函数那么编译器自动为其生成一个默认构造函数 如果一个类中只用private定义的私有构造函数则此类不能实例化(只包含静态成员的类可以这样定义)
除了object类的实例构造函数外所有其他的实例构造函数都隐式的包含一个对另一个实例构造函数的调用该调用紧靠在构造函数体的前面,可以显式声明(base()和this()为初始值设定项)
base(参数列表)形式的实例构造函数初始值设定项导致调用直接基类中的实例构造函数 this()形式调用该类本身所声明的实例构造函数 他们都是根据参数列表来决定用哪个构造函数 如果没有初始值设定项则隐式添加一个base()形式的构造函数初始值设定项
注意构造函数没有返回值类型而且不能有显式声明void
2.7.静态构造函数用static修饰符定义的构造函数 静态构造函数不可继承并且不能被直接调用类的静态构造函数在给定应用程序域中至多执行一次 应用程序域中第一次发生创建类的实例或者引用类的任何静态成员时执行静态构造函数 静态函数中指定当初始化该类时需要执行的语句通常用初始化静态成员
2.8.this和base访问 this用于访问被同名局部变量隐藏的类的实例成员(不能出现在静态成员中) base用于访问被当前类或者结构中名称相同的成员隐藏的基类成员(也不能用于静态成员中)
三、程序示例
using System;public class Example {public static void Main(string[] atgs){Circle cir = new Circle(12);Rectangle rec = new Rectangle(12,21);Square squ = new Square(21);Console.WriteLine("{0},{1}",rec.area(),squ.area());Console.ReadKey(); } } public abstract class Sharp {public abstract double area(); } public class Circle : Sharp {public const double PI = 3.1415;//静态字段private double radius;//实例字段最好声明为私有的public override double area()//重写了抽象方法 {return radius * radius * PI;}public double Radius//属性提供公开的方法用于读写私有字段 {get //读访问器 {return radius;}set //写访问器 {if (value <= 0)//简单逻辑用来判断传入值的正负radius = 0;elseradius = value;//value是传入属性的值 }}public Circle(double radius)//实例构造函数初始化radius字段 {this.radius = radius;//this关键字用以引用实例字段(因为局部变量与字段重名) } } public class Rectangle:Sharp {public double Height {set;get;}private double length;public double Length {set{length = value;}}public double Width { set; get; }//默认属性相当于定义一个公开的字段public override double area()//继承sharp抽象类必须实现其中的抽象类 {return length * Width;}public Rectangle(double length, double width):this(length,width,0)//this()相当调用重载的此类的其他构造函数(由参数不同来判断用哪个)//可以用base()来调用直接基类的构造函数用于初始化基类的成员 {//由this()调用其他构造函数实现 }public Rectangle(double length, double width, double height){this.length = length;this.Height = height;this.Width = width;}public Rectangle()//默认构造函数如果定义了其他构造函数后还想用默认构造函数必须显式定义: base()//可以没有实例构造函数都隐式包含一个对另一个实例构造函数的调用 如果有参数必须显式调用 { } } public class Square : Rectangle {public Square(double length): base(length, length, 0)//用基类的构造函数来初始化派生类 {}public Square(double length, double width): base(length, length, width){} }