封装,就是将类的属性和方法封闭起来,外部成员不可直接调用,只能通过预留的接口访问。
封装允许类自身的属性和方法被一些可信对象操作,如继承自该类的对象,而一些不可信的类则不允许其修改。在一定程度上保证了类内数据的稳定性。
同时,封装也能够简化编程——有时候我们并不想知道其原理,只需要会调用即可。外部的对象只需要访问一个简单的接口函数,就可以完成一件任务,很轻松的一件事。
访问修饰符 class 类名
{
访问修饰符 类的成员1;
…
访问修饰符 类的成员n;
}
类的访问修饰符可以定义为private、protected、public,通过访问修饰符,类的属性或方法可以选择是否允许被外部访问,从而限制了外部的权限,保护了内部的数据的稳定性。
访问修饰符 class 类名
{
private 数据类型 字段名;
public 数据类型 属性名
{
get { return 字段名;}
set {字段名=value;}
}
…
类的成员;
}
属性是对象的性质与对象之间关系的统称。和字段一样,属性是类的成员,有访问修饰符,有类型。
通常,定义字段为私有,然后再定义一个与该字段对应的属性,具有对外的set和get方法。如下图:
public class Guy
{
private string name;
public string Name
{
set
{
name = value;
}
get
{
return name;
}
}
}
Guy是一个类,name是其字段,而Name是与其对应的属性,Name能够被外部访问,就好像对应name为public一样,直接访问。
get和set是属性访问器,get和set访问器有预定义的语法格式,可以把属性访问器理解为方法。
set访问器用于设置数据,value是调用属性是赋给属性的值;get访问器用于返回数据,必须有return语句,其返回值与属性类别是一致的。
在set和get方法里,可以做相应的判断和操作,可以将set和get看做一个方法,在里面做需要的操作。
当然,属性也可以简化:
public class Guy
{
public string Name { set; get; }
}
继承就是在已存在的类基础上,建立一个新类,保留原有类的属性和方法,同时又可以增加新的内容。
已存在的类称为基类或父类,新建的类称为派生类或父类。
//基类
public class Animal
{
public string Name { get; set; }
public Animal(string name) //构造函数
{
Name = name;
}
}
//派生类
public class Dog : Animal
{
public Dog(string name) : base(name) //调用基类的构造函数
{
}
}
上面的代码实现了Dog类继承自Animal类,子类自动继承了基类的属性Name。
调用base(参数列表)实现调用基类的带参数的构造函数。即使不使用base(),编译器也会先调用基类的不带参构造函数,再执行子类的构造函数。
和C++不同,在C#中一个子类只能有一个父类,而C++是可以继承多个父类的。继承的单一性也使得C#的基础关系比较简单,易于管理维护。
Object类是所有类的基类。CLR运行时,要求所有的类型,不管是系统定义的类还是用户自定义的类,都必须从Object派生。
//1.隐式派生
class Guy
{
}
//2.显式派生
class Guy : System.Object
{
}
//两者实际上是一样。
当一个子类被一个新类继承时,新的类不仅继承子类的属性和方法,子类的父类的内容也将继承下来。
C#继承的单根性与传递性的继承关系,形成了树状层次结构,基类和它的派生类存在一种层次关系。
之前,关于类的定义时有讲到访问修饰符。在C#中,有4中不同的访问修饰符:
(1)pirvate:只能在本类中被访问,只可以被本类所存取,不能被继承。
(2)public:可以在项目外被访问,任意存取,可以被继承。
(3)protected:只能在本类中被访问,可以被继承。
(4)internal:只能在同一程序集中被访问,可以跨类。可以被继承。
在C#中,新增了internal这一关键字。internal的作用域为同一程序集。程序集的概念可以理解为一个工程、项目,编译后的dll文件或exe文件。也就是说,internal修饰符修饰后,该类只能在单一工程内访问,如果其他项目访问是不行的。
这位博主关于internal的总结不错
如果没有显式地定义访问修饰符,则C#的默认访问权限如下。
(1)在namespace中的类、接口默认为internal。如果不是嵌套的类,命名空间或编译单元内的类只可以显式地定义为public、internal类型,不允许是其他访问类型。
(2)在类中,所有的成员均默认为private。可以显式地定义为public、private、protected、internal或protected internal访问类型。
任何基类可以出现的地方,派生类一定可以出现。也就是说,可以用派生类对象代替基类对象,可以让一个基类的引用指向一个派生类的对象。
//基类
public class Animal
{
public string Name { get; set; }
public Animal(string name) //构造函数
{
Name = name;
}
}
//派生类
public class Dog : Animal
{
public Dog(string name) : base(name) //调用基类的构造函数
{
}
}
//栗子
Dog dog = new Dog(); //实例化对象
Animal animal = new Dog(); //使用里氏替换原则,基类对象指向派生类
在面向对象编程中,多态(Polymorphism)是类的三大特性之一,从一定角度来看,封装和继承几乎都是为多态而准备的。
封装、继承、多态这三大特性是相互关联的,封装是基础,继承是关键,多态性是补充。
多态性存在于继承性之中,它是继承性的进一步扩展。没有继承就没有多态。通过多态可以实现代码重用,减少代码量,提高代码的可扩展性和可维护性。
多态性分为编译时的多态性和运行时的多态性
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。具有运行速度快的特点。
//基类
public class Animal
{
public void Hello()
{
Console.WriteLine("我是一只小动物");
}
}
//派生类
public class Dog : Animal
{
public new void Hello()
{
Console.WriteLine("我是一只小狗");
}
}
重载是通过new修饰符来实现的,这样Dog类调用hello()时将覆盖掉Animal类的hello函数,调用自身的hello()函数。
运行时的多态性是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过重写实现。具有高度灵活和抽象的特点。
//基类
public class Animal
{
public virtual void Hello()
{
Console.WriteLine("我是一只小动物");
}
}
//派生类
public class Dog : Animal
{
public override void Hello()
{
Console.WriteLine("我是一只小狗");
}
}
重写,需要将父类的Hello()加上修饰符virtual,而子类的Hello()加上修饰符override,表示重写基类的虚方法。
如果使用里氏替换原则,即使用基类对象指向子类对象,那么会出现问题:究竟是调用哪个函数呢?
(1)使用new重载,那么还是调用基类的方法。
(2)使用virtual和overide重写,那么将调用子类自己的方法。
也就是说,基于重写可以实现多态,而重载方法不行。