




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
两种面向对象语言的比较,C+和Java1概述面向对象编程的基本思想是把软件(尤其是大型软件)看成是一个由对象所组成的社会。对象拥有足够的智能,能够理解从其它对象接收到的信息,并且以适当的行为对此做出反应;对象能够从上一层对象继承属性和行为,并允许下一层对象从自己继承属性和行为等。拥有相同属性,展示相同行为对象被分为一组,我们就得到了一个类。实际中,我们首先定义一个类,然后通过对类进行实例化来创建这个类的若干对象。目前,C+和Java 是两种被广泛使用的面向对象程序设计语言。C+是从C 语言发展而来的。贝尔实验室的Dennis Ritchie 于1972 年在一台DEC PDP-11上实现了C 语言。最初,C 语言是作为UNIX 系统的开发语言而被人们广泛了解。在过去的20 年中,大多数计算机上都实现了对C 语言的支持。作为C 语言的扩展,C+是贝尔实验室的Bjarne Stroustrup 于20 世纪80 年代开发出来的。C+提供了大量完善的C 语言的特性,但更重要的是它提供了面向对象的编程功能。今天,基本上所有的操作系统都是用C 或C+编写的。Java 是由美国Sun Microsystems 公司开发的一种能在Internet 上具有“硬件/软件独立性”和交互能力的新型面向对象的程序设计语言。Java 不同于Pascal 这样的个人开发的语言,也不同于一个小组为了自己的使用目的而开发出来的C 或C+,它纯粹是为了商业目的而开发。Java 的一个突出的特性是它的平台无关性,Java 程序可以一次编写而到处使用。值得指出的是,C+与Java 这两种面向对象语言具有许多的相似之处,将这两种语言放在一起比较是一件有趣的事。然而,将它们的所有特征都拿出来相比是十分困难的。限于篇幅,本文主要就基本语言特征、面向对象机制及相关方面对它们做一个比较。2. 基本语言特征2.1 字符集、标识符字符集是指允许出现在一个语言的程序里的字符的全体。C+采用7 位的ASCII 字符集。我们知道,ASCII 码把每个字符与一个二进制代码相关联,它的范围是0000000 至1111111 之间,也就是十进制的0 至127 之间。与C+不同的是,Java 采用的是16 位的Unicode 字符集,包含65536 个字符。其中,前127 个字符与7 位的ASCII 字符集相同。其它的字符可用于注释、标识符、字符和字符串字面量。当然,用于标识符可能并不好。与C+相比,Java 采用了更大的Unicode 字符集。这样做的优点是明显的,一个大的字符集其表达能力会很强。例如,字符的16 位表示形式允许Java 标识符包括世界上许多不同地理区域的字母和数字。另一方面,这样也会带来一些问题。比如说,标识符的处理效率低,可读性差等等。标识符是指用作语言里的关键字以及程序对象的命名的字符序列。对于C+和Java,标识符中的字符必需是字母、数字或者下划线。需要说明的是,这两种语言的标识符都是大小写区别对待的。例如,apple 和AppLE 和APPLE 是3 个不同的标识符。对于标识符的第一个字符,C+和Java 的规定有一点不同。C+要求起始字符必须是字母或下划线,而Java 既允许起始字符是字母或下划线,也允许是美元符号“$”。2.2 基本类型C+和Java 的基本类型包括:布尔类型、字符类型、整数类型、浮点类型。2.2.1 布尔类型C+中的bool,Java 中的boolean 为布尔类型。一个布尔类型的值只可以是true 或false 二者之一。布尔值用来表示逻辑运算的结果。在C+中,整数和指针都可以隐式地转换为布尔值:非零整数或非空指针转换为true,0 或空指针转换为false。布尔值也可以隐式地转为整数:true 转为1,false 转为0。然而,Java 既不允许将一个布尔类型的数据转为其他类型的数据,也不允许将其它类型的数据转为布尔类型的数据。2.2.2 字符类型C+允许我们使用三种不同的字符类型:char、unsigned char 和signed char。一个字符类型的变量占用一个字节的存储,它可以容纳256 个值之一。每个字符常量都对应一个整数值。对于unsigned char,其十进制值的范围是-128 至127;对于signed char,其范围则是0 至255。然而,对于普通的char,其十进制值的范围到底是-128 至127 还是0 至255 呢?这取决于具体的编译器。不管在哪种情况下,差异只出现在那些超出127 的值,而最常用的字符都在127 之内。在Java 中,基本的字符类型只有一个,那就是char。一个char 变量代表一个16位的Unicode 字符,占用两个字节的存储。Java 的char 变量是无符号的,这表示它相应的整数值的范围是0 至65535。2.2.3 整数类型C+和Java 都支持这三种整数类型:short、int 和long。除此之外,C+还支持无符号整数(unsigned)和有符号整数(signed)类型,Java 还支持一种额外的整数类型byte。在C+中,与char 不同的是,普通的int 总是有符号的,因此有符号整数类型只不过是其对应的普通int 类型的一个同义词罢了。C+提供了这么多个整数类型,是为了支持系统程序设计中的细致选择。C+标准要求一个short 类型的变量占用的空间不小于char 类型,一个int 类型的变量占用的空间不小于short 类型,一个long 类型的变量占用的空间不小于int 类型。但是它并没有准确规定各种类型的整数应分别占用多少个字节,把这些问题留给了实现。具体实现可以选择运行平台上最合适的编码形式,以提高实现效率,得到适应各种新环境的能力和需要。当然,这样做的缺点是会损害程序的可移植性。在这一点上,Java 与C+形成了鲜明的对比。Java 明确规定了一个byte、short、int、long 类型的整数分别占用1、2、4、8 个字节。此外,这四种类型都是有符号的。2.2.4 浮点类型C+和Java 都支持这两种浮点类型:float 和double。此外,C+还支持long double 类型,用于对浮点数的精度进行扩展。在C+中,float、double 和long double 的确切意义是由实现决定的。比如说,一个双精度浮点数到底占几个字节可能因编译器而异。反之,Java 浮点数的格式由IEEE 754 严格定义。该标准明确规定一个float 类型的变量占4 个字节,一个double 类型的变量占8 个字节。通过比较可以看出,无论对于是整数类型还是浮点类型,C+标准都没有规定其确切意义,而Java 则把一切都规定清楚了。Java 做出这样严格的规定,优点是有利于程序在不同平台之间的移植,这于Java 的平台无关性需求是相一致的。当然,这样做也会带来一定的缺点,那就是某些机器上可能难以有效实现这些类型。2.3 声明、定义、初始化、作用域2.3.1 声明是否定义无论是在C+还是Java 中,我们在使用一个标识符之前必须对它进行声明。在某些情况下,一个声明同时也是一个定义,然而声明和定义并不等同。在声明中,除非我们为一个标识符分配了足量的内存,否则这个标识符便没有被定义。一个声明是否同时也是一个定义?这个问题在C+和Java 中的答案并不相同。在C+中,绝大多数的声明同时也是定义,不管变量是全局的、局部的还是一个类的对象的数据成员。但是,也有一些C+声明不是定义,这里有几个例子:extern int num;变量的extern 存储类型能够使几个不同的源文件共享同一个变量。上面的extern 声明通知编译器num 是一个int 变量,但不必为这个变量分配内存;class Student;这条声明是Student 类的不完整定义。编译器将期望在别的地方看到一个完整的定义。int f(double);这个声明是一个函数原型。它表示f 是一个函数,接收一个double 类型的参数并返回一个int类型的值。编译器将在其它地方寻找它的定义。在Java中,一个变量声明是否同时也是定义取决于这个标识符是一个方法的局部变量还是一个类的数据成员(注意在Java 中不允许有全局变量)。下面这一声明:int num;如果位于一个方法内部,那么它就不是一个定义。这个声明不会为num 分配任何内存。这一点与C+是不同的。如果希望在声明一个局部变量时同时对它进行定义,就必须给它一个初始值,如下所示。int num = 0;反之,如果一个变量是一个Java类对象的数据成员,那么它的声明也同时是它的定义。2.3.2 是否默认初始化假如我们定义了一个变量,但没有为这个变量提供初始值,那么编译器会不会给这个变量提供一个初始值?同样地,这个问题的答案在C+和Java 中也是不相同的。在C+中,对于基本类型的变量,有下述两种情形:(1)对于全局变量,用全0的二进制序列进行默认初始化。例如,数值变量被初始化为0,指针变量被初始化为空指针。(2)对于局部变量,不提供默认初始化。这样做是为了保证执行效率。缺点在于,如果未赋值就使用这些变量,后果是无法预料的。对于一个类类型的对象,如果这个类有一个无参构造函数,那么不管该对象是全局的还是局部的,它都会根据这个无参构造函数进行默认的初始化。如果这个类没有提供无参构造函数,我们又要考虑两种不同的情况:(1)如果这个类没有提供任何构造函数,那么系统会为这个类提供一个默认的无参构造函数。然而,这个无参构造函数到底做什么,这是因编译器而异的。有可能只进行内存分配而不进行初始化。(2)如果这个类提供了其它的构造函数,只是没有一个无参的构造函数,那么无参构造函数就不存在。如果我们对某对象只进行定义而不进行显式的初始化,就会导致一个编译错误。在Java 中,一个基本规则是,当一个变量被定义时(而不是只声明),它总是被默认的初始化为适当的“0”。例如,boolean 被初始化为false,double 被初始化为0.0,对象引用被初始化为空引用null。Java 要求在每个变量使用前,都必须给它“定义性”赋值(初始化)。即在到达变量的每个使用点的每条控制路径上,必须出现对这个变量的赋值这种规定是为了防止出现使用未初始化的变量的值的情况。这个规定是静态的要求,是充分条件,可能并不必要。2.3.3 作用域标识符与被定义对象之间的约束由定义建立,在续存期间固定不变。这一约束在源程序里的作用范围(在该范围里,这个名字指称这个对象)称为这一约束的作用域。作用域单位是指一种语言所规定的可以作为作用域的结构。C+常见的作用域单位有:全局的外部作用域、子程序定义的局部作用域(不允许局部嵌套定义的子程序)、子程序里任意嵌套的复合语句(块)作用域、类定义形成的作用域、方法的作用域嵌套在类作用域里、名字空间(namespace)定义的模块作用域。Java 常见的作用域单位有:全局作用域(只能定义类)、类定义形成的作用域、方法作用域嵌套在类作用域里、包(package)形成的模块作用域。关于作用域,Java 与C+有两点明显的不同之处。一是Java 的全局作用域里只能定义类,不能定义全局变量或是全局子程序。此外,Java 中如果一个标识符已经在一个外层代码块中进行了声明,那么它不允许在内层代码块中重新声明。例如,下列代码段在Java 中是非法的,而在C+中没有问题。int i = 1;int i = 2;/.i.2.4 表达式我们主要讨论一下运算对象的计算顺序以及表达式的副作用。在这两个方面,C+与Java有着明显的差异。2.4.1 运算对象的计算顺序运算对象的计算顺序是一个有趣的问题。例如,对于表达式(a - b) * (c + d),(a - b)与(c + d)中哪个会先被计算?对于这一问题,C+与Java 有着不同的回答。C+对运算对象的求值顺序不予规定,目的是允许编译器采用任何求值顺序,允许在表达式编译中做更多优化。而Java 则明确规定一律按照从左到右的顺序计算各个运算对象。这样做的优点在于,提高了程序的可移植性和安全性。然而,这样做也限制了语言的新实现方式和实现技术的应用,限制了目标代码优化的可能性。2.4.2 表达式的副作用C+和Java 都允许一个表达式产生副作用。一个问题是,如果一个表达式产生了副作用,什么时候能看到这个作用的效果?通常而言,语言规定了变量修改的最晚实现时间,称为顺序点或执行点。编译器保证到每个执行点时,此前的所有修改都体现到内存,此后出现的修改都没有发生。然而,在执行点之间,副作用是否体现到内存是没有任何保证的。关于顺序点,C+和Java 有着不同的规定。C+的顺序点有如下几种:(1)每个完整表达式结束时。包括变量初始化表达式,表达式语句,return 表达式,条件、循环和switch 的控制表达式。(2)运算符“&”、“|”、“?:”和逗号运算符“,”的第一个运算对象之后。(3)函数调用中对所有实参和函数名表达式的求值完成之后,进入函数体之前。与此不同的是,Java 的每个计算动作之后都有顺序点。也就是说,Java 中每个计算的副作用将立即得到体现。Java 的这种做法达到了语义的确定性,却降低了实现效率。2.5 变量的语义模型2.5.1 变量的值模型和引用模型我们讨论两种变量的语义模型:值模型和引用模型。在变量的值模型中,值保存在变量的存储区里;而在变量的引用模型中,变量的值需要用另一个值对象表示,变量的存储区里存放的是对值对象的引用。C+里的变量都采用值模型。Java 里基本类型的变量采用值模型,其他类型的变量都采用引用模型。2.5.2 C+中的引用变量与指针变量C+提供了引用变量。例如,我们可以写:int n = 0;int &r = n;对于引用变量,我们应当把它看作是被引用变量的一个别名。对引用变量本身不能做任何操作,只能通过它操作被引用的对象。C+中还支持指针变量,即以地址作为值的变量。在C+中引入了指针变量,地址就成为可以操作的了。我们可以对地址进行赋值和运算。例如,一个地址值可以加上(或减去)一个偏移量,得到一个新的地址。C+允许指针运算,这带来了许多的灵活性,但另一方面,也有可能导致意想不到的严重后果。需要指出的是,指针是采用值语义的语言里的一种机制,用于模拟引用语义。设想一下,如果在C+中没有指针,我们就无法进行动态存储管理,无法建立起复杂的数据结构(例如链表)。Java 中除基本类型外,变量采用的都是引用模型,因此Java 没有必要再另外提供引用变量或指针变量机制。2.5.3 废料、废料收集废料是指丧失了引用途径,已经不可能在程序里访问,但仍然占据着存储的“死”对象。在C+中,由于程序员的不当疏忽,或由于情况复杂不能确认而未能释放,可能导致废料的产生。而在Java 中,这一问题显得更为突出:所有赋值都是修改引用,要求程序员去确认某个操作可能导致对象丢掉所有引用,将成为很大负担,甚至根本不可能。针对这一问题,Java提供了自动废料收集机制,它能够自动识别废料,并把它们送回自由空间。提供自动废料收集机制,优点是明显的。对于面向对象程序的开发,自动废料收集能大大地降低开发的代价。程序员在编程时的负担减轻了许多,不必去考虑废料问题。然而,这样做的缺点也是不可避免的。自动废料收集不仅在时间和空间上的开销很大,而且可能会导致程序执行的时间无法预期。值得庆幸的是,近年来随着CPU 速度的提高和内存容量的增大,废料收集的开销已经不是大问题。尽管C+中也存在类似的废料问题,但是C+没有提供自动废料收集。其原因在于,C+语言设计者的一个目标就是尽可能避免对于自动存储回收的依赖性,目标是支持系统程序设计,提高效率,减少不确定性。2.5.4 两种变量的语义模型的优缺点比较变量的值模型和引用模型各有其优缺点,主要体现在下述几个方面:(1)变量的值模型实现简单,易使用,易理解。而引用模型实现复杂,概念的理解也比较困难。(2)采用变量的值模型,值可以直接访问,效率高。而采用变量的引用模型,需要多做一次间接访问,效率有所降低。(3)对于变量的值模型而言,赋值是值的拷贝,语义清晰,易于理解,但对于大对象可能耗费时间。而对于变量的引用模型,赋值是引用共享,语义比较复杂,但赋值只修改引用,效率高(尤其对于大的对象)。(4)采用变量的值模型,存储管理方便,可根据变量的作用域,利用栈机制统一进行管理,存储管理简单,管理的开销少。而采用变量的引用模型,由于值对象的复杂引用关系,必须有堆存储管理的支持和废料收集,管理复杂,开销大。(5)采用变量的值模型,需要静态确定变量的类型,静态分配存储,带来高的执行效率,但较难支持动态确定大小的数组和其他具有动态性质的数据结构,包括字符串等。而采用变量的引用模型,变量可以无类型,变量许多特征不必静态确定,可自然地支持动态大小的数组和字符串等,但可能会增加一些运行时开销。(6)变量的值模型需要另提供动态存储分配和指针概念,支持动态数据结构等高级程序设计技术。而与引用相比,指针是更危险难用的机制,是程序错误的重要根源。采用变量的引用模型,能方便自然地支持各种高级程序设计技术,包括动态数据结构,面向对象的程序设计等等,不需要另外的指针概念。3. 面向对象机制3.1 类的基本概念3.1.1 类的定义、构造函数下面是一个C+类的简单例子。class User string name;int age;public:User(string str, int i) name = str; age = i; /Avoid print() cout name: name age: age;这个类包含两个数据成员,前者的类型是string,后者是int。此外,它还包括一个成员函数(方法)print,用来将该类对象的信息打印出来。注意在C+中,我们需要用一个分号来表示类定义的结束。在第A行,我们提供了User 类的一个构造函数。构造函数是与类同名的特殊成员函数,没有返回值,用来对该类的对象进行初始化。有了这一构造函数,我们就可以用下列两种方法之一来创建这个类的一个对象实例:User u(Bob, 20);User *p = new User(Bob, 20);第一种方法在栈上为对象u分配内存,当u 离开它的作用域之后,其内存会自动释放。第二种方法在堆上为对象分配内存,并将这块内存的地址赋给指针p。以后这块地址需要调用delete操作符显式地进行释放。需要指出的是,C+允许类的一个成员函数的实现代码可以位于类定义的外部。在C+中,如果一个成员函数的实现代码是在类定义本身提供的,这个函数便被认为是内联的(inline)。函数是否为内联的会影响编译器执行某些优化措施的能力。通过显式地声明,一个在类定义外部定义的成员函数也可以是内联的。在Java 中,我们可以作出一个等价的类定义。class User private String name;private int age;public User(String str, int i) name = str; age = i;public void print() System.out.print (name: + name + age: + age);在这个类中,我们同样定义了两个数据成员。其中,name 成员的类型是String,这是Java的字符串类型。与C+不同的是,Java 的类定义不需要以一个分号结束。我们在上述的类中提供了一个构造函数。有了这一构造函数,我们就可以象下面这样而且只能这样创建这种类型的对象。User u = new User(Bob, 20);该表达式右边部分的调用创建了一个User类型的新对象并返回对该对象的引用。3.1.2 访问控制类的每个成员都有一个相关的访问控制属性。在C+中,成员的访问控制属性是private(私有)、protected(保护)、public(公共)三者之一。如果没有显式地声明一个成员的访问控制属性,那么它就按照缺省情况作为这个类的私有成员。因此,在上面User 类的例子中,数据成员name 和age 的访问控制属性是private,而构造函数和方法print 的访问控制属性是public。对于某个类的private 成员,它的名字将只能由该类的成员函数和友元使用。对于某个类的protected 成员,它的名字只能由该类的成员函数和友元,以及由该类的子类的成员函数和友元使用。而对于某个类的public 成员,它的名字可以由任何函数使用。在Java 中,限定符public 和private 与它们在C+中的含义相同。至于protected,含义与C+中的类似,只不过在同一个程序包中这类成员就像是公共成员一样。也就是说,我们可以在同一个程序包的所有类中访问一个类的保护成员,但是在这个包之外,只有这个类的子类可以访问这些成员。除了private、protected 和public 之外,Java 中还有一个访问控制限定符package。对于package 成员,在同一个程序包内它们就像是公共成员,而在包的外部它们就像是私有成员。此外,如果我们没有为一个成员指定访问限定符,那么这个成员的访问控制属性就是package。3.1.3 对象的复制与赋值在C+中,按照默认规定,类对象可以进行复制。特别是可以用同一个类的对象的复制对该类的其他对象进行初始化。例如:X a = b; /通过复制初始化按照默认方式,类对象的复制就是其中各个成员的复制。如果某个类X 所需要的不是这种默认方式,那么就可以定义一个复制构造函数X:X(const X&),由它提供所需要的行为。类似地,类对象也可以通过赋值进行按默认方式的复制。例如:a = b;其中a 和b 都是类X 的对象。在这里也一样,默认的语义就是按成员复制。如果对于某个类而言这种方式不是正确选择,那么用户可以重载赋值运算符,由它提供所需要的行为。在Java 中,情况要简单一些。对于语句X a = b;其语义并不是对象的复制,而是变量a 得到了变量b 所保存的那个对象的引用的一份拷贝。也就是说,在初始化之后,a 和b 都是对同一个对象的引用。如果我们希望a 所引用的是b所引用的那个对象的一份拷贝,那么我们可以通过调用clone 方法来实现。该方法是从根类Object 继承而来的,这里就不细述了。3.1.4 对象的终结处理在对象销毁之前,可能需要做一些最后动作,例如释放对象所占用的各种资源,维护有关状态等等。在C+中,终结动作以类的析构函数的形式定义。析构函数的名称就是类名前面加一个波浪号。它不应接受任何参数,也不应返回任何值。对于堆栈上的类的对象,在其作用域退出时,它的析构函数会被自动调用。对于堆上的类的对象,我们需要显式地释放它们所占的存储。在释放之前,析构函数会被自动调用。如果一个类没有显式地提供一个析构函数,那么系统在销毁这个类的对象之前会执行析构函数的缺省行为。这个缺省行为就是依次调用类的每个数据成员的析构函数。Java 的对象销毁方式与C+有着很大的不同。前面说过,Java 提供了自动废料收集的机制。在一个Java 程序中,如果没有任何变量保存了对一个对象的引用,那么这个对象就成为了废料收集的候选目标。在实际销毁一个对象之前,如果该对象存在finalize 方法,那么废料收集器就会执行其中的代码。Java 的finalize 方法所执行的任务类似于C+中程序员所提供的析构函数。然而,执行终结动作的时间是不可预计的,存在时间上和顺序上的不确定性。3.2 类继承3.2.1 子类的定义、相关概念继承是面向对象程序设计的基本概念之一。通过继承一个已有的类,我们可以对类进行扩展。对于上面给出的User 类的例子,我们可以定义该类的一个子类StudentUser。/在C+中定义子类class StudentUser : public User string school;public:StudentUser(string str, int i, string s) : User(str, i) school = s;void print() User:print();cout School: school;/比较。在Java 中定义类似的子类class StudentUser extends User private String school;public StudentUser(String str, int i, String s) super(str, i);school = s;public void print() super.print();System.out.print( School: + school);在上述定义的基础上,我们称User是一个父类、基类或超类,StudentUser 是一个子类、派生类或扩展类。在定义子类时,C+和Java 中的书写格式有所不同。C+中的类定义的头部“class StudentUser : public User”表示StudentUser 类是User 类的一个子类。在Java 中的写法则是“class StudentUser extends User”。子类能够继承基类的所有成员,但它不能访问基类的私有成员。在上面的例子中,由于name和age是基类User的私有成员,因此子类StudentUser中的任何成员函数(方法)都无法访问它们。注意子类StudentUser 的构造函数。在C+和Java 的例子中,我们分别通过“User(str, i)”和“super(str, i)”调用了父类的构造函数,对那些只能由基类的成员函数进行访问的数据成员进行赋值。实际上,一个派生类的构造函数在执行任何任务之前必须调用它的基类的构造函数。如果在派生类的构造函数中并没有显式地调用基类的构造函数,系统将会试图调用基类的无参构造函数(缺省构造函数)。在上面的例子中,子类StudentUser 的print 函数的定义覆盖了它从基类User 继承而来的同名函数的定义。然而,我们仍然可以访问被隐藏的基类函数,例如C+中的“User:print();”以及Java 中的“super.print();”。最后需要指出的是,基类的指针或引用可以安全引用子类的对象,但子类的指针或引用不能引用基类的对象。这一点在C+和Java 中是一致的。3.2.2 C+的不同继承方式C+提供了三种继承方式,以控制对基类成员的访问。第一种是public 继承,也就是通常情况下的继承方式。记子类为D 而基类为B,那么子类头部的写法是“class D : public B”。在public 继承中,基类的public 成员变成子类的public 成员,protected 成员变成子类的protected成员。注意基类的private 成员在子类中不可访问。第二种是protected 继承,子类头部的写法是“class D : protected B”。在protected 继承中,基类的public 成员和protected 成员都变成派生类的protected 成员。第三种是private 继承,子类头部的写法是“class D : private B”。在private 继承中,基类的public 成员和protected 成员都变成派生类的private 成员。在编程中,我们可以根据实际的需要来选择适当的继承方式。其中,public 继承是最为常用的。3.2.3 阻断继承、Java 中的关键字final有的时候,我们会有这样的一种需要,就是希望某个类不能被继承。比如说,编译程序如果预先能够知道一个类不能被扩展,那么它可能会在函数调用的数据访问方式的优化上做得更好。另外,出于安全方面的一些考虑,我们也有可能决定不让用户对销售商所提供的类进行扩展。Java 提供了一种方便的方法,如果我们在一个类的头部以关键字final 作为前缀,那么这个类就不能被扩展。在Java 中,我们也可以只把一个类的其中一些方法声明为final,从而实现有选择地控制继承的目的。当超类中的一个方法被声明为final 时,我们就无法在子类中对它进行覆盖。相比之下,C+并没有提供一个类似final 的关键字来防止一个类被扩展或者有选择地对覆盖机制进行阻断。然而我们知道,一个派生类的构造函数在执行任何任务之前必须调用它的基类的构造函数。因此,如果我们把基类的构造函数放在类的私有部分,那么这个类就不能被扩展。3.3 方法的动态约束面向对象语言里的方法调用通常采用“x.m(.)”的形式。其中,x 是一个指向或者引用对象的变量,m 是x 的定义类型的一个方法。现在的问题是,“x.m(.)”所调用的方法是根据什么确定的?有两种可能性。如果是根据变量x 的类型静态确定,我们就称之为静态约束;如果是根据方法调用时被指向或引用的对象的类型确定,我们就称之为动态约束。动态约束是面向对象程序设计的一个非常重要的概念。动态约束带来的优点是明显的,它使得我们能够编写程序来通用化地处理一个层次中的所有类的对象。然而,采用动态约束调用时会带来一些额外开销,如果需要调用的方法能够静态确定的话,采用静态约束有速度优势。动态约束方法的另一个缺点是不能做在线展开(inline)处理。在Java 语言里,所有方法都采用动态约束。与此不同的是,C+提供了静态约束和动态约束两种方式,以静态约束作为默认方式,而把动态约束的方法称为虚方法(virtual 方法)。C+之所以会这么做,是与它的C 基础有关。我们来举一个简单的Java 例子来说明方法的动态约束。/Test.javaclass User private String name;private int age;public User(String str, int i) name = str; age = i;public void print() System.out.print (name: + name + age: + age);class StudentUser extends User private String school;public StudentUser(String str, int i, String s) super(str, i);school = s;public void print() super.print();System.out.print( School: + school);class Test public static void main(String args) User u = new User(Bob, 20);User su = new StudentUser(Chris, 25, Math);u.print();System.out.println();su.print();System.out.println();这是一个完整的Java 程序。在main 方法中,我们定义了两个User 类型的变量。变量u 引用了一个User 类型的对象,而变量su 则引用了一个StudentUser 类型(User 的子类)的对象。我们对变量u 调用方法print,与对变量su 调用方法print,调用的是不同的方法。这是由于Java 采用的是方法的动态约束(假如采用的是静态约束的话,调用的就是同一个方法)。该程序输出的结果是:name: Bob age: 20name: Chris age: 25 School: Math(假如采用的是静态约束的话,输出结果中就不会有“School: Math”)。3.3.1 方法覆盖的限制在C+中,当一个方法在基类中被声明为虚拟方法时,我们可以在派生类中用一个同名的方法对它进行覆盖。然而,覆盖不是任意的,要受到一些条件的限制。第一,当基类方法的返回值是基本类型时,派生类的覆盖方法的返回类型必须和基类方法相同;当方法的返回类型是一个指向类B 类型的指针或引用时,派生类的覆盖方法的返回类型可以是指向类B 的子类型的指针或引用类型。第二,基类虚拟方法的访问控制属性对于派生类的覆盖定义是否合法没有影响。即使基类的虚拟方法位于保护或私有部分,派生类的覆盖方法也可以出现在派生类的任何一个部分。第三,如果基类的虚拟方法带有异常说明,那么派生类的覆盖方法就不允许抛出这个异常说明之外的异常。在Java 中,关于方法的覆盖也有一些限制。第一,派生类覆盖方法的返回类型必须和基类中被覆盖方法的返回类型相同。与C+的规定不同,Java 对覆盖方法的返回类型的限制与返回类型是基本类型还是类类型无关。第二,覆盖方法的访问限制不能比基类的被覆盖方法的访问限制更严格。因此,如果基类方法的访问限制属性是保护,那么派生类的覆盖方法的访问限制可以是保护或公共,但不可以是私有。与C+不同的是,Java 中在类的私有部分声明的方法是不能被覆盖的。第三,派生类中一个覆盖方法的异常说明必须是基类中被覆盖方法的异常说明的一个子集。这一点与C+中的相应限制是类似的。3.4 抽象类、接口一般而言,如果我们无法从一个类创建对象,那么这个类就是抽象类。导致这个结果的原因不止一种。在C+中,如果一个类的一个或多个成员函数被声明为纯虚函数,那么我们就无法从这个类创建对象。在Java 中,如果我们对一个类显式地使用关键字abstract 进行声明,那么我们也不能从这个类创建任何对象。如果我们无法由一个类创建对象,那么这个类有什么用呢?首先,抽象类可以在一个类层次体系中起到组织其它类的作用。其次,抽象类可以表示一种特殊的行为,当它与其它类混合使用时,可以允许我们创建具有这种行为的类。此外,抽象类可以帮助我们以增量的方式创建类的实现代码。在面向对象程序设计中,抽象类可以作为一个包含了一些具体类的类层次体系的根系,把那些分散封装在各个子类中的共同概念归纳在一起。例如,圆形和矩形等都是形状,我们可以写一个具体类Circle 来描述圆形,写一个具体类Rectangle 来描述矩形,我们也可以写一个抽象类Shape 来描述形状。显然,为Circle 和Rectangle 类创建对象是合理的,而为Shape 类来创建对象就显得没有意义。Shape 类在这里扮演了一个关键的角色,它把其它的类组织到一个单一的类层次体系中。需要说明的是,“组织在一起”这个概念实际上比看上去要复杂的多,它涉及到了继承和动态约束。利用继承,我们可以把各个子类中的公共代码放在Shape 类中,使程序的效率更高。假如我们有一个Shape 变量(引用或指针)的列表,其
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 汽车配件的供应链智能化升级实施方案研究报告
- 物流行业仓储管理与配送路线优化方案
- 自考专业(护理)试题附答案详解【基础题】
- 中考数学总复习《 圆》能力检测试卷含答案详解(突破训练)
- 中级银行从业资格之中级银行业法律法规与综合能力强化训练题型汇编附完整答案详解【夺冠】
- 电竞公司电商数据分析规定
- 电竞公司系统升级操作规定
- 安徽省黄山市黟县中学2026届化学高二第一学期期末教学质量检测模拟试题含答案
- 矿车自动驾驶仿真与验证平台创新创业项目商业计划书
- 采矿无人机巡检创新创业项目商业计划书
- 社保补助代理协议书
- 2物流行业2025年人力资源招聘策略研究
- 大题04 板块模型(解析版)-【三轮冲刺】2025高考物理大题突破
- 统计分析在资产评估中的运用
- 个人提供技术与公司合作协议书范本
- APP融资方案模板
- 支气管哮喘防治指南(2024年版)解读
- 2025年镍厂招工考试题及答案
- 财经法规与会计职业道德(第5版)课件 第一章 总论
- 舞蹈艺术概论
- 装卸劳务外包服务项目方案(技术标)
评论
0/150
提交评论