C面向对象程序设计.ppt
1,C+面向对象程序设计,第十章 运算符重载与类模板,2,学习目标,理解为什么要进行运算符重载,在什么情况下要进行运算符重载 掌握通过成员函数重载运算符,借助友元函数实现运算符重载 理解引用在运算符重载中的作用,引用作为参数和返回值的好处和用法 理解类型转换的必要性,能够在程序设计中正确应用类型转换 理解为什么要引入类模板的概念,掌握类模板的应用,3,10.1 为什么要进行运算符重载,10.1.1 运算符重载的例子 在程序中,经常会使用运算符,但C+中已经定义的运算符都是针对基本数据类型的,那么能否将它们用于复杂的类对象呢?在学习本章之前,我们往往是编写实现相应运算功能的函数来解决复杂类型的运算问题。首先来看一个复数加法运算的例子,请注意程序中,加法运算时表达式的书写形式。 【例6-1】复数的加运算。见教材P133,4,程序说明: 在【例10-1】为了解决复数的存储问题,在复数类Complex中,定义了两个私有成员变量:real(复数的实部)和imag(复数的虚部);并定义了公有的复数加法函数add()和友元输出函数print()显示复数。在main函数中定义了三个复数对象,使用加法函数add进行3个复数的相加,并输出结果。 在程序中三个复数相加的表达式为: c4 = c1.add(c2).add(c3); 用这种方式书写表达式,不仅形式复杂、不符合人们的日常书写习惯,而且不易理解;复数的运算越复杂,书写的表达式越困难,给使用自定义类编写程序带来诸多不便。如果能够像使用基本类型那样用运算符来书写复数运算表达式(如c4 = c1+c2+c3;),不仅书写简单,也更容易理解;这正是在C+中引入了运算符重载的意义所在。下面通过使用运算符重载进行复数加运算例子来了解如何进行运算符的重载。,5,【例10-2】使用运算符重载进行复数加运算,见教材P134 输出结果: 11+2i 程序说明: 该程序的作用和【例10-1】相同,只是用运算符重载函数代替了加法函数。 比较两个程序不难发现,【例10-2】中书写的复数相加表达式,含义清晰,便于书写。让复数类的使用变得更加简易。 程序中“+”运算符重载的定义如下: Complex operator +(Complex 它与一般函数的定义非常相似,不同的是运算符重载函数的名字(“operator +”)是由“operator”关键字和紧随其后的运算符“+”组成;关键字“operator”表明该函数是一个运算符重载函数。在【例10-2】中,该函数是作为类Complex的成员函数,因此把以这种方式的运算符重载称作重载为类运算符。,6,【例10-3】重载为友元运算符进行复数加运算,见教材P136 输出结果:11+2i 程序说明: 本例实现的功能与【例10-1】和【例10-2】完全相同。 本例中,“+”运算符重载函数的定义如下: Complex operator +(Complex 由于该函数是一般的普通函数,不是类的成员函数,只是为能够访问类的私有数据成员,而把其声明为类的友元函数,因此在定义时参数表中必须指定两个参数:第1个参数作为运算符的左操作数,第2参数作为运算符的右操作数。,7,10.1.2 注意事项,C+对运算符重载作出了一定的限制和规定,在重载运算符时,需要注意以下几点: 不是所有运算符都可以被重载。 C+的大部分运算符都可以被重载。可以重载的运算符如下: new new delete delete + - * / % answer = c1 + c2 * c3; 在这个表达式中,乘法运算优先级高于加法运算,它等效于 answer = c1 + (c2 * c3); 如同系统定义数据类型一样,可以使用括号强制改变重载运算符的计算顺序,例如: answer = (c1 + c2) * c3; 重载不能改变运算符的结合律。,9,重载不能改变运算符的操作数个数。 重载的一元运算符仍然是一元运算符,重载的二元运算符仍然是二元运算符。C+中唯一的三元运算符(?:)不能被重载。运算符 +、-、*、 /重载前增1运算符 T operator +(int); /重载后增1运算符 T operator -(); /重载前减1运算符 T operator -(int); /重载后减1运算符 为了说明增1运算符和减1运算符重载,下面给出一个分数类作自增,自减的例子。【例6-5】分数类的自增,自减。 见教材P142,12,10.4 关系运算符的重载,在C+中对于使用内部数据类型定义的变量,可以使用六个关系运算符“、=、 = = 和 !=”进行比较运算。而要对自定义类定义的变量进行比较运算,用户就必须重载这些运算符。 下面以分数的 为例说明关系运算符的重载。 【例10-6】比较两个分数大小,输出值大的分数。见教材P144,13,10.5 算术赋值运算符的重载,在C+中允许把算术运算符和赋值运算符组合在一起使用,这种书写方法简洁高效;同样也可以在自定义类型中重载这些运算符。为了简单起见,仍然使用分数的例子。 【例10-7】分数的加法运算。见教材P145,14,10.6 下标运算符的重载,“”下标运算符通常用于数组,c+也允许重载这个运算符,在重载这个运算符时,需要注意的是:当“”运算符位于“=”左边时,是修改数组中元素的值,需要返回这个元素的指针或引用;当“”运算符位于“=”右边时,是取得数组中元素的值,需要返回这个元素的值或引用;为了能使“”运算符即可用于“=”左边,也可用于“=”右边,在重载“”运算符时,必须返回该元素的引用。 下面给出一个数组类的例子来说明这一问题。 【例10-8】一个数组类的例子。见教材P147,15,10.7 插入与抽取运算符的重载,10.7.1 插入运算符的重载 C+中对左移运算符 “<<” 进行重载,以便输出C+内部类型的数据。对于自定义类型也可以重载运算符 “<<”,按照合适的方式输出自定义类型的对象。下面以分数的输出为例介绍。 【例10-9】分数的输出。见教材P149,16,10.7.2 抽取运算符的重载,C+中对右移运算符“”进行重载,以便把外来的信息输入到所有的C+内部数据类型的变量,这些重载函数作为系统类的成员函数。例如,如果i是一个整型变量,C+便将输入语句cini;翻译为cin.operator(i);然后调用此函数,读取一个值,存入变量i中。 为了支持自定义类型,用户应重载输入运算符“”。在重载时,如果作为一般的函数来定义(而非类的成员函数),则这个重载运算符在被使用时应被声明为类的友元运算符。因为“”的第一个操作数是系统类的对象cin(系统类iostream的对象),因此,作为友元函数,类X的输入运算符重载函数的原型应为: istream 请注意:这个函数的第一个参数是输入流的引用,第二个参数是类X的引用。函数返回输入流的引用,其目的是为了能连续输入。对此,可以仿照6.7.1节做出解释。 【例10-10】分数的输入。见教材P151,17,10.8 类型转换,在C+中,当使用数值类型进行计算时,各种类型之间可以隐式或强制转换;例如: double d; int a = 2; d = a + 5.3; 这种书写方式,使代码看起来优雅而自然。在用户自定义类中,没有进行类似的定义而无法使用这种方法。,18,10.8.1 基本类型转换和自定义类型的相互转换,在定义了类型转换后,对所有TRangeInt类型变量的计算都可以先转换成int类型,再进行计算,因此,可以不需要进行运算符的重载(如下面例子中的“+”、“-”运算符)。 【例10-11】TRangeInt类型的加、减运算。 见教材P154,19,10.8.2 自定义类型之间的转换,有两种方法可以实现自定义类型之间的转换:使用类型转换符函数和构造函数。不仅可以在基本类型转换和自定义类型之间实现类型的转换,也可以在两个自定义类型之间实现。 【例10-12】使用类型转换符函数实现二维向量类型和复数类型的相互转换。 见教材P155,20,【例10-13】使用构造函数实现二维向量类型和复数类型的相互转换。,程序见教材P157,21,10.9 类模板与模板类,在编写程序时,有许多类的工作机制是相同的,只是其使用的数据类型不同。链表就是一个较为典型的例子,可以有整型链表、结构链表或自定义类的链表,但就链表本身的操作来说是相同的。在链表操作时,如果能够把要处理的类型当作参数,就可以构建一个通用的链表类。 类模板的定义格式如下: template class 类名 ,22,这里template是指出在所定义的类中,将使用到一个参数的类型为T;编译时,类型T将被调用时的具体类型替换,产生一个具体的类,称为模板类(由模板产生的类)。 来看一个具体的例子:单向链表,其结构图6.1所示。 这里的数据可以是任意类型的,而对于任一个单向链表,其操作都是相同的;单向链表都具有增加、删除、寻找、遍历等操作,图10.1单向链表,23,【例10-14】通用链表类的定义。,程序见教材P159,24,本章小结,本章介绍了C+中,运算符重载和类型转换的概念,举例说明了运算符重载和类型转换的用法。 使用运算符重载可以使程序易于理解并易于对对象进行操作。几乎所有的C+运算符都可以被重载。 如果在类中没有说明本身的拷贝构造函数和赋值运算符,编译程序将会提供,但它们都只是对对象进行成员浅拷贝。 this指针指向当前的对象,它是所有成员函数的不可见的参数,在重载运算符时,经常返回this指针的间接引用。,25,在实际应用中应注意以下几点: 设计和实现使用重载运算符,需要深入了解语言、运算符和类。合理地使用运算符重载,使它成为程序设计的有力工具。不要仅仅为了证明语言的威力而使用重载运算符。 不能改变运算符操作数的数量。 大多数运算符属于某个运算符系列。如果实现了系列中的某个成员,则需要实现其他的成员(以保持接口完整性和一致性)。 任何不需要左值并具有交换性的运算符都最好作为非成员函数(+、- 等)实现。这允许编译器在参数不匹配的情况下对第t个参数进行转换。 任何需要左值的运算符最好作为成员函数实现。这明确说明它只能在现存的、可以修改的对象上调用。,26,通过转换运算符可以在表达式中使用不同类型的对象。转换运算符不遵从函数应有返回值类型的规定,没有返回值。 在前缀增量和后缀增量运算符定义中,int参数只是标志是前缀还是后缀,没有其他作用。 拷贝构造函数用已经存在的对象来创建一个相同类型的新对象,而赋值运算符把一个对象的成员变量赋予一个已经存在的同类对象的同名变量。 使用模板,可以避免重复编写具有相同功能只是类型不同的类或函数代码,提高代码的编写效率,简化程序编写。在编译时,C+采用代码复制技术,生成相应的模板类或模板函数,这种方式存在的缺点是容易造成代码的膨胀,因此,在编写类模板或函数模板时应尽量减少其代码量。,