C#之面向对象
面向对象
面向对象三大特征:封装、继承、多态
封装:类对外部提供public方法,调用者不用关心类内部的运行机制
继承:子类继承自父类,子类可以继承父类所有非private成员,实现代码重用;
多态:子类中可以覆盖(override)父类中的virtual方法;父类类型的变量可以指向子类类型的对象,调用的方法如果子类override了,则调用的是子类的实现(一个方法在不同环境下不同的表现形态)。
一、封装
1.封装的具体表现
(1)属性封装字段
public class Person { private int _id = -1; public int Id { get { return _id; } set { _id = value; } } }
对内部使用的字段是_id,外部使用的是Id属性。对属性名称进行修改,不需要修改_id名称。
(2)方法的多个参数封装成一个对象
(3)将一堆代码封装到一个方法中
(4)将一些功能封装到一个类中
(5)将一些具有相同功能的代码封装到一个程序集中(dll、exe),并且对外提供统一的访问接口(方法名、属性名等)
二、继承(类与类之间的关系):一个类继承另一个类,那么这个类也就有了另一个类的非private成员
1.判断一个继承是否合理?(子类 is a 父类)
比如:香蕉是水果吗?是。香蕉类继承水果类
2.继承的好处
(1)代码重用
(2)多态
里氏替换原则:需要一个父类类型时,给一个子类对象是可以的。
里氏替换原则就是为了多态=>多态就是为了增加程序的可扩展性,灵活性。
public class Person { public int Id { get; set; } public string Name { get; set; } } public class Student:Person { public string StuId { get; set; } } 调用部分: Person p1 = new Student();
因为有了继承,便有了里氏替换原则,有了里氏替换原则,便有了多态
3.继承中的构造函数问题
public class Person { public Person(int id,string name) { this.Id = id; this.Name = name; } public int Id { get; set; } public string Name { get; set; } } public class Student:Person { public string StuId { get; set; } public Student(int id, string name, string stuid) { this.Id = id; this.Name = name; this.StuId = stuid; } }
当一个子类继承父类以后,该子类中的所有构造函数默认情况下,在自己被调用之前都会先调用一次父类的无参构造函数,如果此时父类中没有无参的构造函数,则提示报错!
解决方法一:在父类中添加一个无参构造函数
解决方法二:在子类的构造函数后面通过:base()的方式,明确指定要调用父类中的那个构造函数。
public class Person { public Person(int id,string name) { this.Id = id; this.Name = name; } public int Id { get; set; } public string Name { get; set; } } public class Student:Person { public string StuId { get; set; } public Student(int id, string name, string stuid):base(id,name) { this.StuId = stuid; } }
:base()表示调用父类的构造函数
三、多态
1.多态的三种表现形态
(1)虚方法
(2)继承
(3)接口
2.多态实行开放封闭原则:对修改封闭,对扩展开放
3.多态的作用:把不同的子类对象当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
4.实现多态的三种方法的区别:
(1)虚方法是实例类,具有默认实现
(2)抽象方法没有默认实现,除非子类也是一个抽象类
(3)接口存在是为了解决:
1.类的多继承问题
2.类继承以后体积庞大的问题
四、接口
接口是一种规范、协议。约定好遵守某种规范就可以写通用的代码了。
接口存在的意义就是为了多态
接口的存在解决了:
(1)类的单继承问题
(2)类继承以后体积庞大的问题
1.接口里面只能包含方法:
(1)方法
(2)属性
(3)索引器
(4)事件
因为属性,索引器,事件本质上都是方法。【字段不能定义在接口中】
2.接口中的所有成员都不能显示的写任何访问修饰符,默认是public的访问修饰符
3.方法,属性,索引器在接口中的定义方式
public interface DefinitionInterface { /// <summary> /// 定义方法 /// </summary> void SayHello(); /// <summary> /// 定义返回值是string类型的方法 /// </summary> /// <returns></returns> string Eat(); /// <summary> /// 定义属性 /// </summary> string Name { get; set; } /// <summary> /// 定义索引器 /// </summary> /// <param name="index"></param> /// <returns></returns> string this[int index] { get; set; } }
4.接口不能被实例化,就是为了让子类来实现的
5.接口中的成员,子类必须实现
五、虚方法与抽象方法
1.虚方法
(1)具有父子类关系
(2)父类需要重写的方法添加virtual
(3)子类重写的方法添加override
class Program { static void Main(string[] args) { Person p1 = new Student(); p1.SayHello(); Console.ReadKey(); } } public class Person { public string Name { get; set; } public virtual void SayHello() { Console.WriteLine("人类说Hello"); } } public class Student:Person { public override void SayHello() { Console.WriteLine("学生说Hello"); } }
Student类重写了子类的SayHello方法,那么在调用p1.SayHello();方法时就会调用重写的方法
2.抽象方法
(1)抽象类中可以有实例成员,也可以有抽象成员
(2)抽象成员必须包含在抽象类中
(3)抽象成员不能有任何实现
(4)抽象类不能用来实例化对象,只能被继承
(5)抽象成员子类继承以后必须重写override,除非子类也是一个抽象类
public abstract class Person { public string Name { get; set; } public abstract void SayHello(); } public class Student:Person { public override void SayHello() { Console.WriteLine("Student类重写Person类"); } }
虚方法和抽象方法的区别:
(1)虚方法有默认实现,抽象方法没有默认实现。
(2)如果虚方法不重写,那么就调用父类的方法;抽象方法中子类必须实现抽象方法,除非子类也是一个抽象方法。
六、静态类
1.静态类的特点
(1)静态类中只能有静态成员。
(2)静态成员不是必须写在静态类中。
(3)在程序中的任何一个地方访问该静态成员,其实都访问的是同一块内存。
(4)静态成员的数据直到程序退出以后才释放资源;实例对象只要使用完毕就可以执行垃圾回收了。
2.静态类和静态成员要使用static修饰
3.静态构造函数
(1)类中的静态成员,在第一次使用静态成员的时候进行初始化
(2)静态构造函数不能手动来调用,而是在第一次使用静态
七、访问修饰符
1.访问修饰符
(1)private 只能在当前类的内部可以访问
(2)protected 只能在当前类内部以及所有子类的内部可以访问
(3)internal 在当前程序集内部访问
(4)public 任何地方都可以访问
2.命名空间中的访问修饰符只能是public或internal,不能是其他访问修饰符(类不加访问修饰符默认是internal)
3.类的成员变量,如果不写访问修饰符默认是private
4.访问级别约束:
(1)子类的访问级别不能比父类的高(会暴露父类的成员)
(2)类中属性或字段的访问级别不能比所对应的类型访问级别高
(3)方法的访问级别不能比方法的参数和返回值的访问级别高