C#中的面向对象编程是指3个基本特征:封装、继承、多态。
封装:
封装是指把类的内部数据隐藏起来,不让对象直接进行操作。C#中可用属性来对类内部的状态进行操作,使用public、private、protected、internal等关键词来实现。
1.为何要封装
当类的内部数据没有被封装时,若把字段定义为公共字段,则外部对象可以对内部数据进行任意的操作,很可能导致不当的操作结果。例如:
public class Person
{
public int age;
public string name;
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.name = "张三";
person.age = -2;
Console.Read();
}
}
以上代码中,虽然程序可以正常运行,但是逻辑上有问题,因为人的年龄不可能为负。
这就是外部对象对类内部数据进行任意操作的产生的不当结果。为了避免这种情况,可以将类进行封装。
2.如何封装
C#提供了属性机制来对私有字段数据进行间接的操作,并且可以在属性中加入判断机制来避免逻辑错误。因此,在面向对象编程中,应更多的定义私有字段。例如:
public class Person
{
//定义私有字段
private int age;
private string name;
//定义公有属性
public int Age
{
get
{
return age;
}
set
{
//加入逻辑判定
if(value < 0 || value > 100)
{
throw(new ArgumentOutOfRangeException("AgeIntPropery", value, "年龄必须在0到100之间"));
}
age = value;
}
}
public string Name
{
get{return name;}
set{name = value;}
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "张三";
person.Age = 20; //设置了一个合理的值
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
}
}
以上代码中,person.Age设置为一个不合理的数(小于0,大于100)时,执行时程序会中断,并抛出ArgumentOutOfRangeException。
继承:
在C#中,一个类可以继承另一个已有类(密封的除外),被继承的类称为基类(或父类),继承的类称为派生类(或子类),子类将获得基类除构造函数和析构函数以外的所有成员。此外,静态类是密封类,不能被继承。
1.什么是继承
简单来说,一个类定义了一些方法、属性等,若另一个类也要定义同样的方法、属性等,可以直接通过继承的方式来获得基类的所有方法、属性等,避免了代码重复。并且同时,子类可以定义另外一些自有的方法、属性等。这就好比男人女人都先拥有人的属性,然后还可以定义自有的属性。
2.如何继承
在C#中,子类仅支持派生于一个基类,也就是一个子类仅可有一个父类。但一个父类可以有多个子类。这些子类都继承父类相同的成员。C#中继承的方法为:
//定义一个基类
public class Father
{
//基类成员定义
}
//子类继承父类
public class ChildA : Father
{
//子类自有成员定义
}
public class ChildB : Father
{
//子类自有成员定义
}
需要注意的是,子类并不能对父类的私有成员进行直接访问,它只可对保护成员和公有成员进行访问。但是子类会继承基类的私有成员,子类可以通过调用公有或保护方法间接的对自有成员进行访问。
密封类:
上面说到,一个类可以继承另一个除密封类以外的类。所以,若不想被继承,则可以使用sealed关键字将其定义为密封类。方法为:
public sealed class SealedClass
{
//密封类成员定义
}
若有类要继承该密封类时,编译器会报错。
子类的初始化顺序:
上面说到,子类不会继承基类的构造函数和析构函数。但是当子类进行初始化时,任然会调用基类的构造函数。
初始化顺序为:
①初始化子类的实例字段;
②调用基类的构造函数,若没有指明基类,则调用System.Object的构造函数;
③调用子类的构造函数;
例如:
class Program
{
static void Main(string[] args)
{
//初始化子类实例
Child child = new Child();
//调用子类方法
child.Print();
Console.Read();
}
}
//创建一个父类Father
public class Father
{
//初始化它的实例字段;
private string name = "张三";
//定义一个方法输出实例字段
public void Print()
{
Console.WriteLine(name);
}
//调用基类构造函数
public Father()
{
Console.WriteLine("基类构造函数被调用了");
}
}
//创建一个子类Child
public class Child : Father
{
//初始化它的实例字段
private int Age = 3;
//定义一个方法输出实例字段
public void Print()
{
Console.WriteLine(Age);
}
//调用子类构造函数
public Child()
{
Console.WriteLine("子类构造函数被调用了");
}
}
运行以上代码,结果为:
需要注意,运行结果显示,初始化子类实例时,先执行了基类的构造函数,再执行了子类的构造函数,最后执行了子类的方法。并未执行基类的方法,且子类执行顺序并不是按代码顺序先执行方法再执行构造函数。因此可见总是要并先要执行父类的构造函数,第二再执行子类的构造函数。
多态:
简单来说,如果一个子类继承了一个基类,则获得了基类中的某些行为(如方法、属性等),但是如果子类想改变继承的方法,就需要覆写基类的方法,这种技术就称为多态。
1.用virtual和override关键字实现
基类成员声明为virtual(称为虚方法)时,子类可以重写,如果想改变虚方法的实现行为,要使用override关键字。例如:
class Program
{
static void Main(string[] args)
{
//初始化子类实例
Son son = new Son();
Daughter daughter = new Daughter();
//调用子类的方法
son.Age();
daughter.Age();
Console.Read();
}
}
public class Father
{
//定义输出年龄的方法
public virtual void Age()
{
Console.WriteLine("父亲的年龄为50岁");
}
}
public class Son : Father
{
//覆写输出年龄的方法
public override void Age()
{
//调用基类的方法
base.Age();
Console.WriteLine("儿子的年龄为20岁");
}
}
public class Daughter : Father
{
public override void Age()
{
//base.Age();不调用基类的方法
Console.WriteLine("女儿的年龄为18岁");
}
}
运行以上代码,结果为:
由此可见,子类继承了基类虚方法后对虚方法进行了覆写,在儿子类中覆写时调用了基类方法,因此运行结果仍然有基类方法。女儿类中覆写时未调用基类方法,运行结果则无基类方法。
可见,多态的精髓是相同类型的对象调用相同的方法却表现了不同的行为。
2.用abstract关键字防止创建类的实例
用1实现多态时会存在一个问题,即基类可以通过new操作符创建基类的实例。可有的情况下,基类是一个抽象概念(如:人、动物、天气),我们希望避免创建这种抽象的实例。此时可用abstract关键字来防止在代码种直接创建这样的实例,然后仍然可用1实现多态。例如:
public abstract class Wether
{
public virtual void Print()
{
Console.WriteLine("天气状况为:");
}
}
public class Rain : Wether
{
public override void Print()
{
base.Print();
Console.WriteLine("雨");
}
}
public class Sun : Wether
{
public override void Print()
{
base.Print();
Console.WriteLine("晴");
}
}
class Program
{
static void Main(string[] args)
{
Rain rain = new Rain();
Sun sun = new Sun();
//无法创建基类的实例
//Wether wether = new Wether();
rain.Print();
sun.Print();
Console.Read();
}
}
运行以上代码,结果为:
若将Main中的创建基类实例的注释取消,则会报错:
3.用sealed关键字防止子类被覆写
前面讲到可以用sealed关键字来密封类,同理,可以用sealed关键字来密封子类方法防止子类被重写。例如:
public abstract class Parent
{
public virtual void Marry()
{
Console.WriteLine("父母已经结婚25年");
}
}
public class Son : Parent
{
//用sealed关键字防止Son类被覆写
public sealed override void Marry()
{
base.Marry();
Console.WriteLine("儿子已经结婚2年");
}
}
以上代码的类Son无法被覆写,若添加以下代码:
public class GrandSon : Son
{
public override void Marry()
{
base.Marry();
Console.WriteLine("孙子还小,不能结婚");
}
}
则会显示错误信息:无法对密封成员进行复写。
4.用new关键字隐藏基类成员
有的情况下,子类需要定义与基类相同名字的成员,此时可以用new关键字将基类成员隐藏起来。若不使用new关键字,则会报错。例如:
public class Father
{
public void Name()
{
Console.WriteLine("爸爸叫大明");
}
}
public class Son : Father
{
//子类中仍然想定义一个叫Name的方法,使用new关键字
public new void Name()
{
Console.WriteLine("儿子叫小明");
}
}
如果此时仍然想要访问基类的成员,可使用强制类型转换,把子类强制转换成基类类型,从而访问隐藏的基类成员。例如:
class Program
{
static void Main(string[] args)
{
Son son = new Son();
son.Name();
//调用基类的Name方法
((Father)son).Name();
Console.Read();
}
}
以上代码,结果为:
所有类的父类:System.Object
C#中,所有的类都派生自System.Object类。如果类没有指定任何的基类,则编译器就自动吧Object当作它的基类。
详细信息请参考:Object类(System)