《第5章面向对象编程基础.ppt》由会员分享,可在线阅读,更多相关《第5章面向对象编程基础.ppt(49页珍藏版)》请在三一办公上搜索。
1、2023年8月6日,第5章 面向对象编程基础,教学目标,面向对象程序设计最早是在C+中使用的程序设计方法,它尽可能地模拟人类习惯的思维方式,使开发软件的方法和过程尽可能地接近人类认识世界、解决问题的方法与过程。采用面向对象的程序分析和设计技术开发的软件系统,稳定性、可重用性和可维护性都很好。本书前面介绍了C#语法和编程的所有基础知识。本章介绍面向对象的程序设计相关概念。,教学重点,面向对象编程继承重载多态性,面向对象设计的由来,面向过程的设计方法的不足 传统的程序设计思想是面向过程的。面向过程方法适合于描述算法细节,而且功能与数据分离,不能很好的描述现实世界。现实世界应该是数据和行为的统一,具
2、有动态特性。“软件危机”随着计算机硬件技术的发展,计算机应用越来越广泛,这对软件开发提出了更高要求,然而,软件技术进步落后于硬件技术。开发的大规模应用软件往往质量不尽人意,有的甚至无法使用,出现了软件危机。于是提出了面向对象的程序设计思想,面向对象设计思想,在客观世界中,可以把具有相似特征的事物归为一类,也就是把具有相同属性的对象看成一个类(class)。比如,所有的汽车可以归成一个“汽车类”,所有的人可以归成一个“人类”。在面向对象的程序设计中,“类”就是对具有相同属性和相同操作的一组对象的定义。属性一般用数据成员表达,操作用函数成员来表达和实现。对象是类的一个实例。,OOP相关的概念,类和
3、对象静态成员对象的生命周期继承多态性对象之间的关系运算符重载,类和对象,类是面向对象程序设计的基本构成模块。从定义来讲,类是一种数据结构,这种数据结构可能包含数据成员、函数成员以及其它元素。数据成员类型有常量、字段和事件;函数成员类型有方法、属性、索引指示器、操作符、构造函数和析构函数 类的定义方法和结构定义类似。对象是通过类来定义,就像前面定义变量一样。对象的类型就是类,类的声明,类声明是一个类型声明,在C#中定义类的语法很简单,只需在类的名称前加上关键字class,然后在大括号之间插入该类的成员即可,C#中类的一般定义形式如下所示:修饰符 class 类名/类的成员,abstract修饰符
4、,修饰符abstract用来声明类为抽象类,抽象类与非抽象类相比有如下不同点:抽象类不能被实例化。当然可以定义变量,这样的变量的数值必须为null或者是抽象类派生的非抽象类的实例。抽象类可以(但是不需要)包含抽象成员。抽象类不能是密封的。,sealed修饰符,sealed修饰符用来声明一个密封类,一旦在类的声明时使用了sealed修饰符,就不能从这个类派生新类了。封装类不能是任何抽象类,sealed修饰符主要用于防止意外的派生。,成员修饰符,Abstract不允许建立类的实例const应用于字段成员或局部变量event 定义一个域成员或属性为事件extern方法由外部实现override用于改
5、写任何基类中被定义为virtual的方法readonly使用 readonly修饰符的域成员只能在它的声明中被更改static被声明为static的成员属于类,而不属于类的实例virtual说明成员可以被继承类改写。,存取修饰符,public任何地方都可以访问该成员,这是具有最少限制的存取修饰符。protected在类及所有的派生类中可以访问该成员,不允许其他类访问。private仅仅在同一个类的内部才能访问该成员。甚至派生类都不能访问它。internal允许相同组件(应用程序或库)的所有代码访问。在.NET组件级别,你可以把它视为public,而在外部则为private。,类主体,类的主体定
6、义了类的成员,并用一对括起来。类的成员可以分为下面的几种:构造函数:又可分为实例构造函数和静态构造函数,前者对类的实例进行初始化;后者对类本身初始化。析构函数:执行在类的实例要被解析掉前要实现的动作,释放对象占用的资源。字段:包括与类相关的常数数据和变量。方法:实现了可以被类实现的计算和行为。属性:命名的属性和对这个属性进行读写的操作。事件:定义了由类产生的公告。索引:它允许类的实例与数组相同的方法来索引。操作符:可以被应用于类的实例上的操作符。,类成员声明必须遵循下面的规则:,构造函数和析构函数必须与类名相同,所有其他的成员必须与类名不同。字段、事件或类型的名称必须在类声明中是唯一的,即不能
7、与其他成员的名称相同。方法的名称必须与同一个类中声明的其他非方法的名称不同。索引的签名必须与同一个类中声明的所有其他索引的名称不同。操作符的签名必须与同一个类中声明的所有其他操作符的签名不同。,构造函数和析构函数,构造函数是一个特殊的函数,他和类同名,用来完成实例的初始化工作,它在创建实例时自动被调用。如果一个类不包含任何构造函数声明,就会自动提供一个默认的构造函数。默认的构造函数通常是下面的形式public MyClass():base()这里MyClass是类的名称。默认构造函数通过base()调用直接基类的无参数构造函数。,构造函数和析构函数,例:Class TestClass publ
8、ic TestClass(),构造函数,构造函数也可以带有访问关键字,前面定义的构造函数都是公有的,但是private和protected的构造函数也有各自的作用 类可以有一个静态构造函数,它只能被运行时代码所使用,而且不能带有访问修饰符,析构函数,析构函数(destructor)是一种用于实现销毁类实例所需操作的成员。析构函数不能带参数,不能具有可访问性修饰符,也不能被显式调用。垃圾回收期间会自动调用所涉及实例的析构函数。垃圾回收器在决定何时回收对象和运行析构函数方面允许有广泛的自由度。具体而言,析构函数调用的时机并不是确定的。仅当没有其他可行的解决方案时,才在类中实现析构函数。析构函数的基
9、本形式为:classname code;,字段(1),字段存储类所需要的数据。例如,表示日历日期的类可能有三个整数字段:一个表示月份,一个表示日期,一个表示年份。在类中声明字段的方式如下:指定字段的访问级别,然后指定字段的类型,指定字段的名称。例如:public class CalendarDate public int month;public int day;public int year;,字段(2),访问对象中的字段是通过在对象名称后面依次添加一个句点和该字段的名称来实现的,具体形式为 objectname.fieldname。例如:CalendarDate birthday=new
10、CalendarDate();birthday.month=7;声明字段时可以使用赋值运算符为字段指定一个初始值。例如,若要自动将 7 赋给 month 字段,需要按如下方式声明 month:public class Calendar public int month=7;,字段(3),字段在调用对象实例的构造函数之前初始化,如果构造函数为字段分配了值,则它将改写字段声明期间给定的任何值。注意字段初始值不能引用其他实例字段的值。字段可标记为 public、private protected、internal 或 protected internal。这些访问修饰符定义访问字段的方式。,常量,其
11、值在运行过程中不能改变的量即是常量。格式:修饰符 const 类型说明符 常量名;一个常数声明可以包含访问修饰符(public,protected,private,internal)在常数声明中指定的类型必须是sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、枚举类型、引用类型。例子:private const double d=1.0;,变量,类的成员变量前加上static修饰符就构成了静态变量,否则是实例变量。静态变量在类被加载到内存就已经存在,不论类被实例化多少个对象,静态变
12、量只有一个副本。且不能在对象中被引用。相反,实例变量只有在创建了对象后才存在。每创建一个对象,就创建一个新的实例变量。各实例变量的值可以不同,且互不影响。只有在实例化对象之后变量才能够使用。,类的继承,类可以从其他类中继承方法和属性。这是通过以下方式实现的:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类(即基类)例如:public class A public A()public class B:A public B(),类的继承,继承的作用;类用来抽象描述自然界,那么被表述的对象一般具有层次结构关系,(见下例),类也自然应该有一定的层次关系。这就是继承,类的继承,类的继承
13、可以简化程序,并且可以提高代码的重用性。在类之间建立一种继承关系,使得新定义的派生类的实例可以继承已有的基类的方法 和属性,而且可以加入新的成员或者是修改已有的成员,这样就建立起类的层次。比如:上面的例子中,想定义轿车类,就可以继承汽车类,继承其中的方法和属性,然后再定义自己的特殊的方法和属性。,类的继承,常用术语基类:被继承的类,也叫父类派生类:通过继承基类而创建的新类,也叫子类单继承:派生类只有一个父类。多继承:派生类有多个父类。继承机制的特征派生类隐式包含直接父类的除构造函数和析构函数 的所有成员继承具有传递性派生类可以在基类基础上添加自己的成员对派生类实例的引用可以看成是对基类的引用可
14、以通过定义与继承的基类同名的成员来隐藏基类同名成员。,类的继承,派生类的构造和析构 派生类在构造对象时不仅要调用派生类的构造函数,还要调用直接基类的构造函数,即先调用基类的构造函数,在调用派生类的构造函数。析构函数正好和构造函数相反,先调用派生类的析构函数,在调用基类的析构函数例题:,屏蔽基类成员,在派生类中使用new关键字定义与基类成员同名派生类成员可以屏蔽基类成员。这个功能特别有用,它使我们能够在基类中使用和派生类成员相同的名字,但功能却还由派生类成员指定而不受基类同名成员影响。,屏蔽基类成员,class Person public static string fun()return ba
15、seclass;class student:Person new public static string fun()return derived class;,虚拟方法,当一个实例方法的声明中包含一个virtual修饰符时,该方法称为 虚方法。否则是非虚方法。virtual成员允许通过提供该成员的不同实现来更改成员行为。如果派生类要处理给定的特定情况,通常会使用虚成员。,例题:,class Program static void Main(string args)B b=new B();A a=b;a.F();b.F();a.G();b.G();Console.Read();class A
16、public void F()Console.WriteLine(a.f);public virtual void G()Console.WriteLine(a.g);class B:A new public void F()Console.WriteLine(b.f);public override void G()Console.WriteLine(b.g);,覆盖方法,要扩展或修改继承的虚方法,须使用 override 修饰符。实例方法中含有override修饰符时该方法称为覆盖方法。用来覆盖具有相同签名的虚方法。覆盖方法中禁止用new、virtual、static修饰符,可以包含abs
17、tract修饰符。覆盖方法须满足一下条件 被覆盖的方法可以被确定 被覆盖方法是虚拟的、或抽象的、覆盖的 与被覆盖方法具有相同的可访问性。,抽象方法,abstract 修饰符可以和类、方法一起使用。在类声明中使用 abstract 修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。在此例中,类 Square 必须提供 Area 的实现,因为它派生自 ShapesClass:abstract class ShapesClass abstract public int Area();class Square:ShapesClass int x,y;
18、public override int Area()return x*y;,抽象方法,抽象方法具有以下特性:抽象方法是隐式的虚方法。只允许在抽象类中使用抽象方法声明。因为抽象方法声明不提供实际的实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号()。例如:public abstract void MyMethod();实现由一个重写方法提供,此重写方法是非抽象类成员在抽象方法声明中使用 static 或 virtual 修饰符是错的。在静态属性上使用 abstract 修饰符是错误的。通过 override 修饰属性,可以重写抽象的基类属性。,外部方法及方法主体,外部方法
19、 声明中包含external的方法是外部方法。外部方法在程序集外部被执行。外部方法的方法主体只有一个分号。方法主体 类似于c语言中的函数体,类的属性,属性结合了字段和方法的多个方面。对于对象的用户,属性类似于字段。对于类的实现者属性是一个或两个代码块,表示一个 get 访问器和/或一个 set 访问器。当读取属性时,执行 get 访问器的代码块;当向属性分配一个新值时,执行 set 访问器的代码块。不具有 set 访问器的属性被视为只读属性。不具有 get 访问器的属性被视为只写属性。同时具有这两个访问器的属性是读写属性。属性声明:指定字段的访问级别,后面是属性的类型,接下来是属性的名称,然后
20、是声明 get 访问器和/或 set 访问器的代码模块。如下例,类的属性,class myclass private static int myint;public static int Myint get return myint;set myint=value;,基类Object,由于.NET 框架中的所有类均从 Object派生,所以Object 类中定义的每个方法可用于系统中的所有对象。派生类可以而且确实重写其中某些方法,其中包括:Equals支持对象间的比较。Finalize在自动回收对象之前执行清理操作。GetHashCode生成一个与对象的值相对应的数字以支持哈希表的使用。ToS
21、tring生成描述类的实例的可读文本字符串。,对象的生命周期,每个对象都一个明确定义的生命周期,除了“正在使用”的正常状态之外,还有两个重要的阶段:构造阶段:对象最初被创建并实例化的时期。这个初始化过程称为构造阶段,由构造函数来完成。析构阶段:在删除一个对象时,常常需要执行一些清理工作,释放对象占有的资源,这由析构函数来完成。,多态性,继承的一个结果是使得派生类在方法和属性上与基类有一定的重叠,因此,可以使用相同的语法处理从同一个基类实例化的对象的方法。如:Person有一个eat()方法,派生类stuent 和employee的实例都可以调用该方法。而多态性则更推进了一步,它可以把某个派生类
22、型的变量赋值给其基类类型的变量 通过基类变量调用派生类的方法。,多态性,P p=new P2;S s=new S();E e=new E();p0=s;p1=e;p0.Eat();p1.Eat();class P public virtual void Eat()Console.WriteLine(P eat);class S:P public override void Eat()Console.WriteLine(S eat);class E:P public override void Eat()Console.WriteLine(E eat);,对象之间的关系,包含关系:一个类包含另一
23、个类,在一个类中用另外一个类声明字段成员。功能类似于继承关系,但包含类可以控制对被包含类的成员的访问,甚至在使用被包含类的成员前进行其他处理。定义类似于成员变量的声明。集合关系:一个类用作另一个类的多个实例的容器。这类似于对象数组,但集合有其他功能,包括索引、排序、重新设置大小等。,包含关系,class Person public Auto a;public Person()a=new Auto();public int t=new int10;public int thisint index get return tindex;set tindex=value;class Auto publ
24、ic int ca=1133;,class Prgram static void Main(string args)Person p=new Person();Console.WriteLine(p.a.ca);Console.Read();,运算符重载,运算符重载是指通过函数重载使得可以对类的对象应用标准的运算符 运算符重载非常有用,因为我们可以通过运算符重载执行任何操作。要重载运算符,需要给类添加运算符类型成员(它们必须是static)。一些运算符有多种用途,(例如运算符就有一元和二元两种功能),因此我们还需指定要处理多少个操作数,以及这些操作数的类型,运算符重载,Person perso
25、n1=new Person();Person person2=new Person();person1.w=33;person2.w=22;Console.WriteLine(person1*person2);class Person public int w=0;public static int operator*(Person obj1,Person obj2)obj1.w=22;obj2.w=11;return obj1.w+obj2.w;,VS 2005中的OOP工具,“类视图”窗口除了可以查看类的结构信息之外,还可以访问许多项的相关代码 添加新类选择“项目”|“添加类”菜单命令,本
26、 章 小 结,本章介绍了面向对象编程的基础知识。包括面向对象编程的思想,OOP相关的概念,对象声明周期等,最后又介绍了VS 2005中的OOP工具,利用这些工具可以快速完成很多工作,而无须编写大量的代码。通过本章的学习,使读者对OOP有一定的理解和认识,并能自己编写简单的C#类,为深入学习面向对象编程技术作准备。,思考和练习,1.简述面向对象编程思想。2.对象的生命周期是指什么?3.什么是多态性,如何应用多态性?4.查看第4章中部分实例的UML类图。5.下面的代码有什么错误?public sealed class BaseClass/Class members.public class subClass:BaseClass/Class members.6.如何定义不能创建的类?7.定义一个基类MyClass,为该类添加2个int型成员,并编写一个拷贝构造函数MyClass(MyClass obj)。,