1.17k likes | 1.32k Views
第 7 章 C# 面向对象编程技术. 主要内容. 基本思想 类与对象,构造函数与析构函数 继承与多态 接口. 面向过程的程序语言的缺点. 功能与数据分离,不符合人们对现实世界的认识,要保持功能与数据的相容也十分困难。 基于模块的设计方式,导致软件修改困难。 自顶向下的设计方法,限制了软件的可重用性,降低了开发效率,也导致最后开发出来的系统难以维护。. 7.1 面向对象编程 (OOP) 思想. 1) 任何东西在 OOP 中都是通过对象组织起来。 2) 面向对象编程就是通过向对象发送消息 ( 用对象方法 ) 来实现。 3) 可以这样认为:
E N D
主要内容 • 基本思想 • 类与对象,构造函数与析构函数 • 继承与多态 • 接口
面向过程的程序语言的缺点 • 功能与数据分离,不符合人们对现实世界的认识,要保持功能与数据的相容也十分困难。 • 基于模块的设计方式,导致软件修改困难。 • 自顶向下的设计方法,限制了软件的可重用性,降低了开发效率,也导致最后开发出来的系统难以维护。
7.1 面向对象编程(OOP) 思想 1)任何东西在OOP中都是通过对象组织起来。 2)面向对象编程就是通过向对象发送消息 (用对象方法)来实现。 3) 可以这样认为: “面向对象=对象+类+继承+通信”。 如果一个软件系统是使用这样四个概念来设计和实现的,那么我们就认为这个软件系统是面向对象的。
什么是对象(Object)? • 对象是事物的一个抽象,它反映此事物在系统中需要保存的信息和发挥的作用. • 对象是封装体: 封装体 = 一组属性 +对这些属性进行的操作
什么是类(Class)? • 类是一组具有相同数据结构和相同操作的对象的集合。 • 类是对具有相同性质的对象的抽象,是对对象共同特征的描述。 例如:每一辆汽车都是一个对象的话,所有的汽车可以作为一个模板,我们就定义汽车这个类。 • 每个对象都是类的实例,C#中的new操作符可用于建立一个类的实例。 Car car1=new Car(); Car car2=new Car();
什么是继承(Inheritance)? • 继承是使用已存在的类作为基础建立新类的技术。 • 新类复用父类的定义,而不要求修改父类, 父类可以作为基类来引用,而新类可以作为派生类来引用。 这种复用技术大大降低了软件开发的费用。 例如,动物类已经存在,作为具有自身特征的狗就可以从动物类中继承。它同动物一样,具有眼睛、耳朵这些特征,可以行使奔跑和饮食方法。它还具有一般动物不具备的“狗叫”。 class Dog : Animal { void bark(){ } }
类的声明 • 类的声明格式如下: class-modifers class classname { ……} class-modifers为类的修饰符, classname为类的类名。
类的修饰符 • 类的修饰符可以是以下几种之一或者是它们的组合(在类的声明中同一修饰符不允许出现多次): • new——仅允许在继承(嵌套)类声明时使用,表明类中隐藏了由基类中继承而来的,与基类名相同的成员。 • public——表示不限制对该类的访问。 • protected——表示只能从所在类和所在类派生的子类进行访问。 • internal——此成员只在当前编译单元中可见,Internal访问修饰符是根据代码所在的位置而不是类在层次结构中的位置决定可见性。 • private——不能在定义此成员的类之外访问它。因此,即使是派生类也不能访问。 • abstract——抽象类,不允许建立类的实例; • sealed——密封类,不允许被继承;
一个声明以及实例化类的小例子 • 例7.1 创建一个类,并且使用此类创建一个对象。 namespace Example1of7 { //创建classTest类 (定义类) class classTest { } class Program { static void Main(string[] args) { //使用此类创建一个对象 classTest c1 = new classTest(); Console.WriteLine("完成对对象c1的创建"); } } }
程序分析 class classTest { } • 这部分代码定义了一个类,名为classTest。 • 代码 classTest c1 = new classTest(); 建立一个新的对象c1 。
7.3 构造函数和析构函数 • 构造函数和析构函数 是类中两种重要的函数。
7.3.1 构造函数 1) 构造函数用于类的实例的初始化。 每个类都有构造函数,即使我们没有声明它,编译器也会自动为我们提供一个默认的构造函数。 2) 构造函数的名称与类名相同,构造函数没有明确的返回类型。一般地,构造函数是public类型的. • 若是private类型的,表明类不可以被实例化,这通常用于只含有静态成员的类。
例2of7:创建一个类的构造函数。 • class myClass • { • public string s; • //构造函数的定义 • public myClass() • { s = "you are welcome"; } • } • class Program • { • static void Main(string[] args) • { • myClass c1 = new myClass(); • Console.WriteLine(c1.s); • myClass c2 = new myClass(); • Console.WriteLine("Enter your name:"); • string name= Console.ReadLine(); • Console.WriteLine(c2.s+" "+name); • } • }
程序代码解析 • 代码中标为红色的就是构造函数,在实例化类的时候就相当于调用类的构造函数。 • 类的构造函数可以没有参数,例子中就是。当然,也可以拥有多个参数,一个类也可以有多个构造函数,它们的参数个数和类型必须有一个不同的。在类实例化的时候,会自动根据参数的类型和个数去寻找匹配的构造函数。下面就是关于类多构造函数的例子.
一个类多个构造函数的例子3of7 • 例 创建一个有四个构造函数的类,用于计算点、矩形、梯形、圆的面积。 • class Test • { static void Main(string[] args) • { //点面积 • Area pointArea = new Area(); • pointArea.showResult(); • //矩形面积 • Area rectangleArea = new Area(5,4); • rectangleArea.showResult(); • //圆面积 • Console.WriteLine("圆面积为:"); • Area circleArea = new Area(2.5); • circleArea.showResult(); • //梯形面积 • Area trapezoidArea = new Area(2,4,5); • trapezoidArea.showResult(); • } • }
程序代码续 • class Area • { public double myArea = 0; • public void showResult() • { Console.WriteLine(" {0}", myArea); } • //构造函数1--矩形 • public Area(double x,double y) • { myArea = x * y; } • //构造函数2—点 • public Area() • { myArea = 0; } • //构造函数3—圆 • public Area(double r) • { myArea = 3.1415926 * r * r; } • //构造函数4--- 梯形 • public Area(double a,double b,double h) • { myArea = (a + b) * h / 2; } • } • }
程序代码解析 • 上面举的例子中有4个构造函数,它们的名称相同,都与类名一样,但是它们的参数各不相同,编译器可以帮我们识别并且匹配 ,从而找到正确的构造函数的调用。
7.3.2 析构函数 • 在使用New运算符给对象分配动态空间的时候,由于内存也是有限的,可能会被用完,所以在类的实例超出范围时,我们希望确保它所占的存储能被收回。C#中提供了析构函数,用于专门释放被占用的系统资源。
析构函数的基本形式 • 析构函数的基本形式为: ~classname { code; }
7.4 继承与多态 • 面向对象中的两个最为有力的机制——继承与多态。 • 如果所有的类都处在同一级别上,这种没有相互关系的平坦结构就会限制了系统面向对象的特性。继承的引入,就是在类之间建立一种is-a(是一个)关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性,建立起类的层次。 • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。 多态性通过派生类重载基类中的虚函数型方法来实现。
7.4.1继承 • 类继承的基本语法是: class A { Acode; } class B : A // 一定是public继承, 但不要写public A { Bcode; }
类B继承于类A,我们把类A称作父类(也叫基类),类B称作子类(也叫派生类)。类B继承于类A,我们把类A称作父类(也叫基类),类B称作子类(也叫派生类)。 • 不同于C++,C#中派生类只可以从一个类中继承。 • 派生类从基类中继承除了构造函数和析构函数以外的所有成员, 如属性、函数、域、事件和索引器等。
关于类继承的一个例子 do it. namespace Example5of7 { class Fruit // 如果下面三个量是private,则Orange中不能用他们! { public double weight;//重量 public int count; //数量 protected Color color; //添加引用 System.Drawing组件,才能用Color } class Orange : Fruit { public string taste; //口味 public Orange(double Pweight, int Pcount, Color Pcolor, string Ptaste) { //如果构造函数是private => 不能new Orange! weight = Pweight; count = Pcount; color = Pcolor; taste = Ptaste; } public void Description() { Console.WriteLine("wt={0},ct={1},color={2},taste={3}",weight,count,color,taste); } }
程序代码续 static void Main(string[] args) { Orange or = new Orange(2.5,10,Color. Red,"quite good"); or.Description(); } }
程序解析(上面所举的例子) • Orange类继承自Fruit,我们在Orange中使用了类Fruit的字段,并且增加了Descirption()这个方法。 • Orange is-a Fruit, Apple is-a Fruit Fruit 是Apple和Orange 的基类 类继承的用途: • 提高代码的可重用性– Fruit的代码可重用 • 提高动态的多态性 改动代码: public-> private, Fruit 中增加Descirption(), Orange如何用? base.f();
has-a 有一个(Do it!, debug it) //has-a包含关系也可提高代码的可重用性 //Car has-a Radio public class Radio { public void Power(bool on) { Console.WriteLine(“onoff={0}”, on) } } public class Car { Radio myradio=new Radio(); public void TurnOn(bool onoff) { myradio.Power(onoff); } }
Main() { Car mycar=new Car(); mycar.TurnOn(true); // myradio.Power(onoff); } Radio可重用
为什么不对? 1) Main() static void Main() 2) class Program 中无static void Main() 3) class B 中可以定义其他class A, 但 A 只能在B中用。 class B { class A { } } class C { A a=new A(); // error! }
C#中的继承需要符合的规则1 继承是可以传递的。如果类C继承于类B,同时类B又继承于类A,那么类C不仅继承了类B中的声明的成员,同样也继承了类A中的成员。Object类是所有类的基类。 • class C : B • class B : A • C继承于类B, A • 派生类应该是对基类的扩展。派生类可以添加新的成员,但是不能除去已经继承的成员的定义。
C#中的继承需要符合的规则2 • 构造函数和析构函数不可以被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。 • 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为派生类删除了这些成员,只是不能再访问这些成员。 • 类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
7.4.2 多态 • 在面向对象编程思想中,多态是一个非常重要的概念。 如果子类需要对父类中的方法进行修正或者增加新功能,就可以在子类中重新定义原本继承自父类中的方法。
多态例子Ex6of7 do it class Fruit { public double weight; public int count; public Color color; public Fruit(double Pweight, int Pcount, Color Pcolor) { weight = Pweight; count = Pcount; color = Pcolor; } public virtual void Description() { Console.WriteLine("这里是基类(Fruit类)"); } } class Orange : Fruit { public string taste;//口味 public Orange(double Pweight, int Pcount, Color Pcolor, string Ptaste) : base(Pweight, Pcount, Pcolor) { weight = Pweight; count = Pcount; color = Pcolor; taste = Ptaste; } public override void Description() { WriteLine("weight={0},count={1},color={2},taste={3}", weight, count, color, taste); } }
程序代码续 class Apple : Fruit { public string size; public Apple(double Pweight, int Pcount, Color Pcolor, string Psize): base(Pweight, Pcount, Pcolor) { weight = Pweight; count = Pcount; color = Pcolor; size=Psize; } public override void Description() { WriteLine("weight={0},count={1},color={2},size={3}", weight, count, color, size); } }
程序代码续 class Test { static void Main(string[] args) { Fruit fr = new Fruit(); fr.Description(); // 1 fr是基类变量,多态--fruit Orange or = new Orange(2.5, 10, Color.OrangeRed, "good"); or.Description(); //2 • fr = or; fr.Description(); //3=2 fr是基类变量,多态--orange Apple ap = new Apple(3.4, 20, Color.Red, "very big"); ap.Description(); //4 • fr = ap; fr.Description(); //5=4 fr是基类变量,多态--apple } }
有virtual修饰符的函数--虚方法 • 上例中,基类Fruit中的Description()前添加了Virtual修饰符,我们称之为虚方法。 • 使用了virtual后,就不允许再使用Static, Abstract 或者Override修饰符。 • 在派生类中,我们也定义了Description()方法,且在Description()方法之前添加了Override修饰符,这样就完成了派生类对虚方法的重载。 • Fruit类的实例fr先后被赋予Orange类的实例or以及Apple类的实例ap。在执行的过程中,fr先后指代不同的类的实例,从而调用不同的版本。这里fr的Description()方法实现了多态性,并且fr.Description()究竟执行哪个版本,不是在程序编译时确定的,而是在程序的动态运行时,根据fr某一时刻的指代类型来确定的,所以还体现了动态的多态性。 • 编译时的多态主要通过函数的重载以及操作符重载来实现, • 而运行时的重载主要通过虚成员和基类变量引用不同子类的对象(实例)来实现。
7.4.3 抽象类(不能new) • 类中有些方法只有名称,无实现, 我们把这种类称之为抽象类。 让派生类具体实现这些方法的内容。 抽象类使用abstract关键字作为修饰符。
对于抽象类的使用,有以下几点规定: • 抽象类只可以作为其他类的基类,它不能直接被实例化,而且抽象类不能使用能 new操作符。 2) 抽象类包含一些抽象成员,还可包含非抽象成员。 3) 抽象类不可以是封装的。即一个类中不可以abstract 关键字与sealed关键字共存。 4) 如果一个类A派生于一个抽象类B,那么这个类A就要通过重载方法实现所有这个抽象类B中的抽象成员。
抽象类的例子1 abstract class A { public abstract void F(); public void F2() { WriteLine(“F2 is not abstract"); } } class B : A { public override void F() { WriteLine("Reaching B.F() "); } } class Program { static void Main(string[] args) { B b = new B(); b.F(); } }
抽象类的例子 2 abstract public class Fruit {public double weight; abstract public double abs(double a); public void Description() { Console.WriteLine("base”); } } public class Orange : Fruit { override public double abs(double a) { return Math.Abs(a); } // 静态类 public void Description() { base.Description();Console.WriteLine(“orage”); } }
静态类的例子 --函数都是静态的 (不能new) namespace System { // 见 Math的定义 public static class Math { public const double PI = 3.14159; public static double Abs(double value); public staticfloat Abs(float value); ……….. } } 不能: Math h = new Math();
OOP深入探讨 现实世界中的对象:(字典定义) 可为人所感知的物质,思维的精神体。 物质对象:student, teacher, classroom, ….. 精神对象:院系,课程,分数,… 软件对象: 数据和操作合成的软件体。 Student的操作:选课,选老师, …. 软件对象是对现实对象的抽象。
OOP的本质:抽象,封装,继承,多态 1)通过抽象进行简化 2)封装--软件对象的具体实现,把实现细节隐藏起来。 C#把软件对象封装为类, 用类new一个具体对象。 封装—把数据和操作绑定到单一逻辑单元的机制,隐藏细节。 class Student ---类:学生对象的集合 { string Name; void SetName(string st); string GetName(); } Student s1; s1—学生对象变量(声明) s1=new Student(); -- 一个学生对象(具体的) s1. SetName(“wang ming”); --学生对象的操作
类的成员(member):3个 class Student { string name; void SetName(string st); string GetName(); } name是数据成员, SetName(),GetName()是操作成员. (状态,数据, 字段field, 常量,变量, 属性attribute) (behavior, operation, method, function)