第11章 重载及模板_第1页
第11章 重载及模板_第2页
第11章 重载及模板_第3页
第11章 重载及模板_第4页
第11章 重载及模板_第5页
已阅读5页,还剩46页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第11章重载与模板编译时的多态性是通过函数重载和模板体现的。利用函数重载机制,在调用同名的函数时,编译系统可根据实参的具体情况确定所调用的是同名函数中的哪一个。利用函数模板,编译系统可根据模板实参以及模板函数实参的具体情况确定所要调用的是哪个函数,并生成相应的函数实例;运算符重载是函数重载的一种特殊情况。利用类模板,编译系统可根据模板实参的具体情况确定所要定义的是哪个类的对象,并生成相应的类实例。§11.1重载定义

函数重载(functionoverloading)是指一个函数名称具有不同形式的参数的若干个(两个以上)函数的定义。注意:重载函数可以具有相同的返回类型.但必须有不同类型的参数列表。用不同返回类型和相同参数表生成重载函数将产生语法错误。编译器只用参数列表的数据类型区别同名函数。重载函数不一定要有相同个数的参数,它们可以有不同个数的参数列表。程序员使用带默认值参数的重载函数时要小心,以免出现歧义。一、运算符重载引入类的对象(即抽象数据类型的实例)的操作是通过向对象发送消息完成其功能的(即调用成员函数的形式)。对某些类(特别是有些数值计算类)来说,这种调用方式是繁琐的,而用C++中的丰富的内部运算符来指定对对象的操作会更容易表达和理解。定义把C++中的运算符和类的对象结合在一起形成有意义的表达式,这个过程称为运算符重载。+-*/%^&|~!=<>+=-=*=/=%=^=&=|=<<>>>>=<<===!=<=>=&&||++--,[]()newnew[]deletedelete[]

C++语言中可以重载的运算符

C++语言中不能被重载的运算符

.->::?:sizeof注意:重载不能改变运算符的优先级。虽然重载具有固定优先级的运算符可能会不便使用,但是在表达式中使用圆括号可以强制改变重载运算符的计算顺序。重载不能改变运算符的结合律。重载不能改变运算符操作数的个数。重载的一目运算符仍然是一目运算符,重载的二目运算符仍然是二目运算符,但C++中的唯一的三目运算符(?:)也不能被重载。运算符&、*、+和-既可以用作一元运算符,也可以用作二目运算符,可以分别把他们重载为一目运算符和二目运算符。重载不能创建新的运算符,只有现有的运算符才能被重载。运算符重载不能改变该运算符用于内部类型对象时的含义。例如,程序不能改变运算符+用于两个整数相加的含义。运算符重载通常和用户自定义的类类型的对象一起使用,或者类类型的对象和内部类型的对象混合运算。运算符重载函数说明的一般形式如下:<类类型>operator<运算符>(<参数说明>);

运算符重载函数定义的一般形式如下:<类类型><类类型名称>::operator<运算符>(<参数说明>){<语句序列>}用于类的对象的运算符必须重载,但有两种情况需要注意:赋值运算符“=”无需重载就可用于每一个类类型。在没有重载赋值运算符时,赋值运算符的缺省行为是复制对象的数据成员。但是,这种缺省的复制行为用于带有指针成员的对象时是危险的,该情况通常需要显式重载赋值运算符。地址运算符&也无需重载就可以用于任何类的对象,它返回对象在内存中的地址。地址运算符也可以被重载。

当一个类重载了赋值运算符=和加法运算符+,下列语句是允许的:

object2=object2+object1;但并不意味运算符+=也被自动重载了。因此,下面的语句是错误的:

object2+=object1;然而,显式地重载运算符+=可使上述语句成立。运算符重载函数既可以是类的成员函数,也可以是非成员函数。非成员函数通常是友元函数。成员函数是用this指针隐式地访问类的对象参数,非成员函数的调用必须明确地列出类的对象参数。不管运算符重载函数是成员函数还是非成员函数,运算符在表达式中的使用方式是相同的。

运算符重载函数的实现方式通常根据下列情况来选择:当运算式子的左边操作数(或者只有左边的操作数)是定义类的一个对象(或者是对象的引用),运算符重载函数可以实现为定义类的一个成员函数。当左边的操作数可能是另一个不同类的对象(或者为一个内部类型的操作数),运算符重载函数必须要用一个非成员函数来实现。由于运算符重载函数为类的非成员函数,当需要访问类的对象的隐藏成员时,则必须指定为该类一个友元函数。【例11.1】实现复数类并重载有关的运算符。//Complex.h-存放类定义的文件classCComplex//定义一个复数类{public: CComplex(doublem_re=0,doublem_im=0);//含有缺省值的构造函数 CComplex(CComplex&x);//复制构造函数 CComplexoperator+(doublex);//实现左边为该类对象与一个double数相加 CComplexoperator+(CComplex&x);//实现左边为该类对象与一个该类的对象相加 CComplexoperator-(CComplex&x);//实现左边为该类对象与一个该类的对象相减 CComplexoperator*(CComplex&x);//实现左边为该类对象与一个该类的对象相乘 CComplexoperator/(CComplex&x);//实现左边为该类对象与一个该类的对象相除 voidPrint();private: doublere;//存放实部 doubleim;//存放虚部};//Complex.cpp-存放类定义的实现部分和主函数#include"Complex.h"#include"iostream.h"CComplex::CComplex(doublem_re,doublem_im){ re=m_re; im=m_im;}CComplexCComplex::operator+(doublex){ doubler,i; r=re+x; i=im; CComplexy(r,i); return(y);}CComplexCComplex::operator+(CComplex&x){ doubler,i; r=re+x.re; i=im+x.im; CComplexy(r,i); return(y);}CComplexCComplex::operator-(CComplex&x){ doubler,i; r=re-x.re; i=im-x.im; return(CComplex(r,i));//返回匿名对象}CComplexCComplex::operator*(CComplex&x){ doubler,i; r=re*x.re-im*x.im; i=re*x.im+im*x.re; return(CComplex(r,i));}CComplexCComplex::operator/(CComplex&x){ doubler,i; r=(re*x.re+im*x.im)/(x.re*x.re+x.im*x.im); i=(im*x.re-re*x.im)/(x.re*x.re+x.im*x.im); return(CComplex(r,i));}CComplex::CComplex(CComplex&x){ re=x.re; im=x.im;}voidCComplex::Print(){ cout<<re<<"+"<<im<<"i"<<endl;}voidmain(void){ CComplexa(3,6),b(9,5); a.Print(); b.Print(); CComplexc; c=a+b; c.Print();c=a+6; c.Print();}输出结果:3+6i↙9+5i↙12+11i↙9+6i↙二、类型转换在C++语言中,两个相同的内部数据类型的运算其结果还是这种数据类型,例如,整数加整数还是整数。但是,还有一种需要将一种数据类型的数据转换为另一种数据类型的数据的运算,如赋值、混合数据类型的计算、函数实参传递数值以及函数返回值等等,都可能会发生这种情况。对于内部数据类型的情况,编译器知道如何隐式地进行类型的转换,也可以用强制类型转换运算符来实现内部类型之间的强制转换。但用户自定义数据类型转换,编译器不知道怎样实现与其它数据类型之间的转换,程序必须要明确地指明如何转换。这种转换可以用转换构造函数实现,也可以用单个参数的构造函数实现。这种构造函数仅仅把其他类型(通常是内部数据类型)的对象转换为某个特定类的对象。

强制类型转换运算符(简称为转换运算符)可以把一个类的对象转换为其他类的对象或内部类型的对象。该运算符重载函数必须是一个非static成员函数(不能是友元函数)。例如,下面的强制类型转换的运算符重载函数的函数原型:

A::operatorchar*()const;

说明了一个强制类型转换的运算符重载函数,它根据用户自定义类型A的对象建立一个临时的char*类型的对象。强制类型转换的运算符重载函数不能指定返回类型(返回类型是要转换后的对象类型)。如果s是一个自定义类类型的对象,当编译器遇到表达式(char*)s时,程序会产生函数调用s.operatorchar*(),操作数s是调用成员函数operatorchar*的类对象s。

为了把用户自定义类型的对象转换为内部数据类型的的对象或者为其他用户自定义数据类型的对象,可以定义强制类型转换的运算符重载函数。例如,下面给出两个强制类型转换的运算符重载函数的函数原型:

A::operatorint()const;A::operatorotherClass()const;其中,第一个强制类型转换的运算符重载函数用来把用户自定义类型A的对象转换为一个整数,第二个强制类型转换的运算符重载函数转换为otherClass类型的对象。

强制类型转换的运算符重载函数一个很好的特点是:当需要的时候,编译器可以为建立一个临时对象而自动地调用这个函数。例如,如果用户自定义的类String的一个对象s出现在程序中需要使用char*类型的对象的位置上:Strings;cout<<s;编译器调用重载的强制类型转换运算符函数operatorchar*将对象转换为char*类型,并在表达式中使用转换后的char*类型的结果。String类提供该转换运算符后,不需要重载流插入运算符用cout输出String。三、特殊运算符的重载增一“++”和减一“—”运算符分为前置和后置的增一及减一运算符,它们都可以被重载。下面将介绍编译器如何识别前置和后置的增一“++”运算符及减一“—”运算符。重载既能允许前置又能允许后置的增一运算符,每个这种运算符重载函数必须有一个明确的特征以使编译器能确定要使用哪个“++”版本。前置增一“++”运算符重载函数的方法与重载其他前置一目运算符一样。

【例11.2】用运算符重载实现CDate类的操作。类CDate中,重载的前置和后置增一运算符将一个CDate对象增加1天,必要时使年、月递增。类CDate的Public接口提供了以下成员函数:一个重载的流插入运算符,一个默认的构造函数、一个setDate函数、一个重载的前置自增运算符函数、一个重载的后置自增运算符函数、一个重载的加法赋值运算符(+=)、一个检测闰年的函数和一个判断是否为每月最后一天的函数。#include<iostream.h>classCDate{ friendostream&operator<<(ostream&output,constCDate&d);public:CDate(intm=1,intd=1,inty=1900);voidsetDate(int,int,int); //设定日期CDate&operator++(); //前置自增运算符CDateoperator++(int); //后置自增运算符constCDate&operator+=(int);//增加指定天数到当前日期上boolleapYear(int); //是否闰年 boolendOfMonth(int); //是否为每月最后一天private: intmonth,day,year; staticconstintdays[]; //每月天数 voidhelpIncrement(); //计算日期};//date.cpp-CDate类的成员函数定义和主函数定义#include"date.h"constintCDate::days[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//初始化CDate::CDate(intm,intd,inty){ setDate(m,d,y);}voidCDate::setDate(intmm,intdd,intyy){month=(mm>=1&&mm<=12)?mm:1;year=(yy>=1900&&yy<=2100)?yy:1900;//判断是否闰年if(month==2&&leapYear(year))day=(dd>=1&&dd<=29)?dd:1;elseday=(dd>=1&&dd<=days[month])?dd:1;}CDate&CDate::operator++(){helpIncrement();return*this;//返回当前对象}CDateCDate::operator++(int){CDatetemp=*this;helpIncrement();returntemp;//返回Date类型的对象}constCDate&CDate::operator+=(intadditionalDays)//增加指定天数到当前日期上{for(inti=0;i<additionalDays;i++) helpIncrement();return*this; }boolCDate::leapYear(inty)//判断是否闰年{if(y%400==0||(y%100!=0&&y%4==0))returntrue;//是闰年elsereturnfalse;//不是闰年}boolCDate::endOfMonth(intd){if(month==2&&leapYear(year))returnd==29;//lastdayofFeb.inleapyearelsereturnd==days[month];}voidCDate::helpIncrement()//计算日期函数{if(endOfMonth(day)&&month==12)//年末 {day=1;month=1;++year; }elseif(endOfMonth(day))//月末 {day=1;++month; }else++day;}ostream&operator<<(ostream&output,constCDate&d){staticchar*monthName[13]={"","January","February","March","April","May","June","July","August","September","October","November","December"}; output<<monthName[d.month]<<''<<d.day<<","<<d.year;returnoutput;}voidmain(void){ CDated1,d2(12,27,1992),d3(0,99,8045); cout<<"d1is"<<d1 <<"\nd2is"<<d2 <<"\nd3is"<<d3<<"\n"; cout<<"d2+7is"<<(d2+=7)<<"\n"; d3.setDate(2,28,1992); cout<<"d3is"<<d3; cout<<"\n++d3is"<<++d3<<"\n"; CDated4(3,18,1969); cout<<"Testingthepreincrementoperator:\n" <<"d4is"<<d4<<'\n'; cout<<"++d4is"<<++d4<<'\n'; cout<<"d4is"<<d4<<"\n"; cout<<"Testingthepostincrementoperator:\n"<<"d4is"<<d4<<'\n'; cout<<"d4++is"<<d4++<<'\n'; cout<<"d4is"<<d4<<endl;}输出结果:dlisJanuary1,1900↙d2isDecember27,1992↙d3isJanuary1,1900↙d2+=7isJanuary3,1993↙d3isFebruary28,1992↙++d3isFebruary29,1992↙Testingthepreincrementoperator:↙d4isMarch18,1969↙++d4isMarch19,1969↙d4isMarch19,1969↙Testingthepreincrementoperator:↙d4isMarch19,1969↙d4++isMarch19,1969↙d4isMarch20,1969↙

C++语言的运算符的操作数个数、优先级和结合性是其三个最基本的特性。在重载运算符时,应尽可能保持基原有的特性,而无须采取专门的措施。

重载运算符时需要注意下列问题:是否要求第一操作数就是左值操作数;是否修改其第一操作数;操作的结果是否为左值数据;保证第二操作数不被改变。

§11.2模板函数模板

所谓函数模板是一系列相关函数的模型,这些函数的源代码形式相同,只是所针对的数据类型不同而已。

类模板类模板是一系列相关类的模型,这些类的成员组成相同,成员函数的源代码形式相同,所不同的只是所针对的类型(包括数据成员的类型、成员函数的参数类型以及函数返回值的类型)。一、函数模板函数模板定义的一般形式如下:template〈typename<类型名称1>,typename<类型名称2>,……typename<类型名称n>〉<数据类型>〈函数名称〉(<形式参数说明列表>){函数实现语句序列;}其中,template关键字表示声明的是模板,<

>内是模板的参数,可以有一个或n个,n≥1,斜体尖括弧是语句的成分;<类型名称i>是一个标识符,其命名符合标识符命名规则,是虚拟的类型;函数返回值的<数据类型>可以是普通类型,也可以是模板形式类型参数表中指定的<类型名称i>的数据类型;<形式参数说明列表>中给出了函数的若干个形式参数,他们可以是普通类型,也可以是模板形式类型参数表中指定的<类型名称i>的数据类型;函数体中定义的对象参数,他们可以是普通类型,也可以是模板形式类型参数表中指定的<类型名称i>的数据类型;typename可以用class代替,它们完全等价;也可以是一个实际的数据类型,而其后的<类型名称i>就是该实际的数据类型的类型别名。模板函数的使用形式如下:〈函数名称〉〈<数据类型1>,…<数据类型n>〉(<实参列表>)

或者

〈函数名称〉(<实参列表>)

【例11.3】简单的函数模板实例#include"iostream.h"#include"stdio.h" //函数模版定义template<typenameT>Tabs(Tx)//求绝对值{ Ty; if(x>0) y=x; else y=-x; return(y);}voidmain(void){ intm=-18; cout<<"m="<<m<<endl; cout<<"abs(m)="<<abs(m)<<endl; floatn=3.5; cout<<"n="<<n<<endl; cout<<"abs(n)="<<abs(n)<<endl; doublep=-12e10; cout<<"p="<<p<<endl; cout<<"abs(p)="<<abs(p)<<endl;cout<<"abs<double>(-1979)="<<abs<double>(1979)<<endl;}输出结果:m=-18↙abs(m)=18↙n=3.5↙abs(n)=3.5↙p=-12e010↙abs(p)=12e010↙abs<double>(-1979)=1979↙在使用函数模板时需要注意:函数模板中的每个类型参数前都要放上class关键字,否则属于语法错误。函数模板提供了软件复用的好处。但是请记住:尽管函数模板只编写一次,但程序中仍然实例化多个函数模板的副本。这些副本仍然会占用大量内存。二、类模板类模板定义的一般形式如下:template<typename<类型名称1>,typename<类型名称2>,……typename<类型名称n>>class<类模板名>{成员说明语句序列;};template<typename<类型名称1>,typename<类型名称2>,……typename<类型名称n>><数据类型><类模板名><模板形参表>::<函数名>(<函数形参表>){函数体中的语句序列;}其中,template是关键字,表示后面要定义的是类模板;在template后用〈〉括起的是模板的数据类型参数,参数可以有一个或多个,这些数据类型参数之间要用逗号隔开;typename<类型名称i>中,关键字typename说明了定义的类是一个类模板,<类型名称i>是类模板的类型参数。在类模板的定义中,<类型名称i>可以为任意类型;类定义体中,数据成员类型或成员函数的参数、返回值以及函数体中的局部数据,他们可以是普通类型,也可以是模板参数表中指定的形式化的类型;<类型名称i>是一个标识符,其命名规则符合标识符命名规则;typename可以用class代替,它们完全等价;也可以是一个实际的数据类型,而其后的<类型名称i>就是该实际的数据类型的类型别名。<模板形参表>是〈<类型名称1>,<类型名称2>,...<类型名称n>〉类模板实例化的一般形式如下:<类模板名>〈<类型名称1>,<类型名称2>,...<类型名称n>〉

其中,通过类模板实现一个通用的特定类类型,需要一个或多个实际的数据类型参数,确定实际的数据类型形成特定的类类型,因此,类模板也常称为参数化类类型。【例11.4】简单的类模版实例#include"iostream.h"#include"stdio.h"template<typenameT,typenameP>//类模板的定义部分classCPlus{public: CPlus(); Pplus();public: Tm; Pn;};//类模板的实现部分template<typenameT,typenameP>CPlus<T,P>::CPlus(){}template<typenameT,typenameP>PCPlus<T,P>::plus(){ Ps; s=(P)(m+n); return(s);}voidmain(void)//用于测试类模板的程序{ CPlus<int,double>a; a.m=12; a.n=12.8766;cout<<"结果="<<a.plus()<<endl; CPlus<double,float>b; b.m=3.6543; b.n=5644.76f; cout<<"结果="<<b.plus()<<endl; CPlus<double,int>c; c.m=3.6543e+2; c.n=300; cout<<"结果="<<c.plus()<<endl;}输出结果:结果=24.8766↙结果=5648.41↙结果=665↙从上例中我们看出,模板类与通常的类定义没有什么不同,只是以如下所示的首部开头:template<classT>它指出了这是一个类模板的定义,它有一类型参数T(表示所要建立的类的类型)。编程时不必专门使用标识符T,任何有意义的标识符都可以使用。在主程序定义对象的过程中(如CPlus<int,double>a语句),编译系统会自动地根据需要生成相应的类定义,这种依据类模板生成类定义的过程称为类模板的实例化。类模板所生成的每一个类定义就是相应类模板的一个实例类类型。在用类模板定义对象时,由于没有像函数实参表那样的额外信息渠道,因此无法按函数模板的方式省略模板类型实参。但是,可以为类模板的参数设置默认值。具体地说,在定义类模板时,可以为模板形参表声明的最后若干个参数设置默认值;而这些有默认值的参数中,最后的若干个对应实参可以在定义对象时省略。【例11.5】堆栈类的模板实例。如果由一个数组构成了一个线性表,然后限定在表尾作插入、删除操作就成为栈(也叫堆栈,用数组顺序存储空间表示的栈是一种顺序栈)。//stack.h-堆栈类模板定义#include<iostream.h> template<classT>classStack{public:Stack(int=10); //默认构造函数,堆栈大小为10~Stack(){delete[]stackPtr;} boolpush(constT&); //入栈boolpop(T&); //出栈private:intsize; //堆栈大小inttop; //栈顶元素T*stackPtr; //堆栈指针boolisEmpty()const{returntop==-1;}//是否为空boolisFull()const{returntop==size-1;}//是否已满};//stack.cpp-堆栈类模板实现#include"stack.h"template<classT>Stack<T>::Stack(intS){size=S>0?S:10;top=-1; //堆栈不可用stackPtr=newT[size]; //分配空间} template<classT>boolStack<T>::push(constT&pushValue){if(!isFull()){ stackPtr[++top]=pushValue; //入栈 returntrue; }returnfalse;}template<classT>boolStack<T>::pop(T&popValue){if(!isEmpty()){popValue=stackPtr[top--];

//出栈returntrue;

}returnfalse;

}//mainapp.cpp-测试堆栈模板类#include<iostream.h>#include"stack.h" intmain(){Stack<double>doubleStack(5);doublef=1.1;cout<<"PushingelementsontodoubleStack\n";while(doubleStack.push(f)){

cout<<f<<'';f+=1.1;} cout<<"\nStackisfull.cannotpush"<<f

<<"\n\nPoppingelementsfromdoubleStack\n"; while(doubleStack.pop(f))

cout<<f<<''; cout<<"\nStackisempty.Cannotpop\n"; Stack<int>intStack;inti

=1;cout<<"\nPushingelementsontointStack\n"; while(intStack.push(i)){

cout<<i<<'';

++i;} cout<<"\nStackisfull.Cannotpush"<<i

<<"\n\nPoppingelementsfromintStack\n"; while(intStack.pop(i))

cout<<i<<''; cout<<"\nStackisempty.Cannotpop\n";return0;}输出结果:PushingelementsontodoubleStack↙1.12.23.34.45.5↙Stackisfull.Caunotpush6.6↙ PoppingelementsfromdoubleStack↙5.54.43.32.

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论