《C++程序设计教程》课件第4章_第1页
《C++程序设计教程》课件第4章_第2页
《C++程序设计教程》课件第4章_第3页
《C++程序设计教程》课件第4章_第4页
《C++程序设计教程》课件第4章_第5页
已阅读5页,还剩156页未读 继续免费阅读

下载本文档

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

文档简介

4.1概念

4.2函数模板与模板函数

4.3重载模板函数

4.4类模板与模板类

4.5类模板的应用

4.6标准模板库STL

本章要点

练习

若某函数要对一些数据类型进行同样的处理,则可将所处理的数据类型说明为参数,将该函数改写为模板。模板可以让参数表中的数据类型进行同样方式的处理。

比如swap()函数,要求分别交换整型、实型和字符型数据,就要有三个函数:

(1) voidSwap(int&a,int&b)

{inttemp=a;a=b;b=temp;}

(2) voidSwap(float&a,float&b)

{floattemp=a;a=b;b=temp;}

(3) voidSwap(char&a,char&b)

{chartemp=a;a=b;b=temp;}4.1概念从中不难发现,三个Swap的主体是一样的,只是处理的数据类型不同。那么能否编写一个函数,使之对于任何类型的两个对象a、b,总能使编译器理解其意义并实现数据交

换呢?

在C++中引入模板机制,就能解决上述问题。用模板可使一些还未确定数据类型的参数进行一些操作,也就是说把那些完成同样操作功能的函数用模板来代替。这样,不同类型对象的操作就可在一个母体上完成。在C++中有类和函数,模板也分为类模板(classtemplate)和函数模板(functiontemplate)。模板是C++中最复杂且功能最强大的特性之一,它可以帮助用户在编程中创建可重用的代码。使用模板可以创建通用的函数和类,在此称为模板函数或模板类。在模板函数和类中,将数据类型指定为参数,所以该函数和类就能适用于不同的数据类型。4.2函数模板与模板函数4.2.1函数模板

1.一般定义形式

template<类型形式参数表>

函数返回类型FunctionName(函数形式参数表){函数体}

所有函数模板的定义都是以关键字template开始的,关键字之后是用尖括号括起来的类型形式参数表,也可称为模板参数表。该参数表中的参数之间用逗号分隔。类型形式参数表之后是名为FunctionName的函数返回类型,然后是函数FunctionName的形式参数表。

<类型形式参数表>不能为空,其中参数可以是一个或多个,既可以有待确定的类型,也可以是基本类型,可以包括函数的返回类型,也可以包括在本模板函数中要使用的类型。总之,要列出所有现在还未能确定的类型,再用一个自定义的标识符来代替未来的类型名。

在<类型形式参数表>中每个未确定类型的形式参数之前都要加前缀class或typename。当有多个参数时用逗号分隔。在参数表中若包含基本数据类型,则按常规标识。

3.声明函数模板

就像普通函数一样,函数模板也可以分为声明与定义。例如,template<classTA,classTB,intK>voidFun(floatx,TAy,TBz);声明了一个函数模板,该模板中有三个参数,前两个还未决定类型,后一个是整型。在声明时要注意,一般函数模板在声明时必须给出变量名,如Fun中的x,y,z。在函数形式参数表中可以包含基本数据类型,也可以包含<类型形式参数表>中的类型。需要说明的一点是,一般来说,将基本类型放入<类型形式参数表>中的情况并不常用。基本类型参数只是代表了一个常量表达式。对于在<类型形式参数表>中已出现的类型,在函数中都可作为已知类型。

4.定义函数模板

通过下面的模板说明和函数体可完成函数模板的定义:

template<classTA,classTB,intK>voidFun(floatx,TAy)

{TBz;z=x+y;cout<<x<<‘+’<<y<<‘=’<<z<<endl;}

但它只是(函数)模板的定义,编译器不会生成执行代码,它也不占用空间,因为模板是框架,不是函数,y和z是什么类型都未确定。该定义只是描述了该函数模板具有处理一些在<类型形式参数表>中所列出的数据类型的能力。即函数模板定义仅反映了该函数模板的能力。在声明或定义函数模板的参数类型时,用class或typename都可以。关键字typename是最近才被引入到C++中的,引入它的目的是因为有时需要靠它来指导编译器解释模板定义,使得我们可以对模板定义进行分析。这个讨论超出了本书的范围,想进一步了解此内容的读者可阅读Stroustrup的《DesignandEvolutionofC++》一书[4]。4.2.2模板函数

模板函数(templatefunction)是函数模板的一个实例,是一个具体函数的定义,它可由编译系统生成程序代码。模板函数才是真正的函数,是实体。函数模板是一个还没有确定数据类型的框架,而在实现模板函数时则必须确定数据类型。确定方式就是将类型作为实参传给形参。传出实参的方式有两种:

方法一:

函数名(形式参数表);

方法二:

函数名<类型形式参数表>(形式参数表);我们将方法一称为隐式模板实参,方法二称为显式模板实参。若编译器发现有一个函数调用,例如:函数名(实在参数表);,将根据实在参数表中的类型,确认匹配哪个函数模板中对应的形式参数表,然后生成一个重载函数。该重载函数的定义体与函数模板的函数定义体相同,而形式参数表的类型则以实在参数表的实际类型为依据。该重载函数称为模板函数。

1.隐式模板实参

在上面的方法一中,并没有出现类型形式参数表,参数的类型是由函数隐式给出的,称此实参为隐式模板实参。

【例4-1】方法一举例。说明类型参数的确定和实际函数的生成。

#include<iostream>

usingnamespacestd;

template<classT>voidFun(inta,Tb)

{cout<<a<<""<<b<<endl;}voidmain()

{Fun(5,8); //T为int

Fun(4,5.7); //T为float

Fun(6,'A'); //T为char

}注意template<classT>voidFun(inta,Tb),当编译器读到该行时就开始创建函数模板,其中T是以后再确定的类型。在main函数中分别使用了三种不同的数据类型来调用模板函数,编译器自动在原位置处创建三个Fun()的版本,如下所示:

voidFun(inta,intb){cout<<a<<“

”<<b<<endl;}

voidFun(inta,doubleb){cout<<a<<“

”<<b<<endl;}

voidFun(inta,charb){cout<<a<<“

”<<b<<endl;}

也就是说,编译器为我们编写了三个函数。这三个模板函数是实际函数,它们将占用内存空间。由此可见,使用模板是为了简化编程,并没有改变实际代码。【例4-2】演示带返回值的模板函数。

#include<iostream>

#include<string>

usingnamespacestd;

template<classT>TFun(inta,Tb)

{cout<<a<<""<<b<<endl;returnb;}voidmain()

{intx=8;chary[]=“DesignofC++”;

strings=“Language”;

Fun(1,&x);

//将T定为int*,将输出地址

Fun(2,y);

//将T定为char*,将输出DesignofC++

cout<<Fun(3,s)<<endl;//将T定为string,将输出Language

}例4-2定义了一个名为Fun的函数模板,该函数的返回类型为T。该函数具有处理整型和T类型变量的能力。在main函数中定义了一个模板函数Fun,并将T确定为整型指针、字符型指针和string类型。在实例化函数模板时,要求模板函数的函数名与函数模板名同名且有相同的参数个数。通过例4-2可见,T分别成为了int*、char*和string类型的代名词。

在实例化函数模板时,函数实参的类型决定了模板参数的类型和值,当然其中也允许有一些参数类型转换。

【例4-3】说明经过转换来确定类型参数的方法。#include<iostream>

usingnamespacestd;

template<classT>voidFun(inta,constT*b)

{cout<<b<<endl;}

voidmain()

{char*p="DesignofC++";

intz[]={1,2,3,4};

Fun(1,p); //从char*转换到constchar*,将输出“DesignofC++”

Fun(3.3,z); //数组到同类型指针的转换,将输出z的地址

}该例中将double型的3.3传给int型的a,并不是强制转换,而是自动进行的。因为int是一个已知的内部类型,在模板中被看做常量或常量表达式,因此只要能得到一个该类型的数据或者常量便可,但变量不行。

2.显式模板实参

有时编译器不能隐式得到模板实参类型,在这种情况下就要显式指定。指定方法就是前面介绍的方法二。

【例4-4】方法二举例。说明显式模板实参的需要性与提供方法。#include<iostream>

usingnamespacestd;

template<classTA,classTB,charz>voidFun(floatx,TAy);

//声明,必须给出变量名

voidmain()

{constchara='4';charb='4';floatc=3.4f;

Fun<char*,unsignedint,a>(3.4f,"divide"); //ok,a是常量

//Fun<char,int,b>(3.4f,"divide"); //error,b是变量

Fun<char*,int,'4'>(c,"divide"); //ok

}

template<classTA,classTB,charz>voidFun(floatx,TAy)

{cout<<z<<""<<y<<""<<x<<endl;}通过与方法一中的模板函数定义比较发现,在方法二中多用了<类型实在参数表>,例如Fun<char,int,'4'>(3.4f,"divide");。其主要原因是:有两个类型参数TB和z并没有在形式参数表中出现,也就是不能确定类型,那么函数模板就不能成为模板函数,所以要用<类型实在参数表>告知。告知方法是:对于未确定的类型要给出类型,对于基本类型的实参必须给出常量或常量表达式。也可以将例4-4中的程序修改如下,就可省略<类型实在参数表>:

#include<iostream>

usingnamespacestd;

template<classTA,classTB>voidFun(floatx,TAy,TBz); //声明

voidmain(){Fun(3.4f,"divide",4);}

template<classTA,classTB>voidFun(floatx,TAy,TBz)

{cout<<z<<""<<y<<""<<x<<endl;}通过改进我们仍用隐式指定参数类型,但并不是说显式指定就可以不要了。显式指定可以明确地规定函数的实参只能是什么类型。例如:

#include<iostream>

usingnamespacestd;

template<classTA,classTB>voidFun(TAx,TBy)

{cout<<y<<“

”<<x<<endl;}

voidmain()

{Fun<float,char*>(3.4f,"divide");}通过<float,char*>的显式指定,函数的参数只能是float、char*,若再传其他类型给函数就会出错。从中可见,隐式指定就是以默认方式指定;显式指定就是以强制方式指定。有时我们希望函数有不同的返回类型,也可以用显式指定的方法。

【例4-5】函数的返回类型TC并没有在函数参数表中出现,这就要显式指定TC的类型。

#include<iostream>

usingnamespacestd;

template<classTA,classTB,classTC>TCFun(TAx,TBy)

{TCa=x+y;returna;}

voidmain()

{cout<<Fun<int,double,int>(4,3.4)<<endl;

//指定函数类型为int

cout<<Fun<int,double,double>(4,3.4)<<endl;

//指定函数类型为double

}

运行结果:

7

7.44.2.3函数模板的需要性

通常,函数的参数与类型有关,而与数据大小无关,但函数模板却是与数据类型无关。因此不管参数类型如何,函数模板都具有相同行为的操作,可见用函数模板来完成相同的工作既方便又简捷。

1.与宏定义比较

要想实现由一段代码完成多种类型数据的交换,在C中我们只能用宏来实现。它的缺点是不作类型检查。对于同样的数据交换,可用下面带参的宏定义。#include<stdio.h>

#defineFun(a,b,temp)temp=a;a=b;b=temp;

//带参宏定义

main()

{inta=6,b=9,c;

floatu=6.6f,v=9.9f,w;

charx='A',y='a',z;

Fun(a,b,c);

//交换整型

printf("a=%d,b=%d\n",a,b);

Fun(u,v,w); //交换实型

printf("u=%f,v=%f\n",u,v);

Fun(x,y,z); //交换字符型

printf("x=%c,y=%c\n",x,y);

}它们之所以能得到正确的结果,其原因是a、b和temp为同一数据类型。当a、b和temp为不同类型数据时,也会发生数据交换,但结果是错误的。C中的宏不作类型检查,它不管a、b与temp是什么类型,都作交换,其结果有可能是错的。而函数模板具有类型检查的能力,它解决了通用性与严密性之间的矛盾。

2.与重载比较

重载时要求程序员写出多个同名函数,而用模板时只需写一个,即函数模板是一个框架。有了函数模板之后,重载也就显得不那么必要了。4.2.4函数模板的应用

通过前面小节的分析,可以总结出在应用函数模板时要分两步实现:第一步,先定义函数模板,在其中实现所要求的功能,对于一些还不能确定的数据类型只用一个符号作代表;第二步,确定函数模板中可操作的数据类型,从而定义模板函数。

【例4-6】通过数据比较,演示实参是什么数据类型就进行什么类型数据的比较,并返回该类型数据。#include<iostream>

usingnamespacestd;

template<classT>TMax(Ta,Tb)

{returna>b?a:b;}

voidmain()

{cout<<“Max(3.3,8.8)is:”<<Max(3.3,8.8)<<endl;

cout<<“Max(‘3’,‘8’)is:”<<Max(‘3’,‘8’)<<endl;

}

运行结果:

Max(3.3,8.8)is:8.8

Max('3','8')is:8【例4-7】演示用模板函数实现数据交换。

#include<iostream>

#include<string>

usingnamespacestd;

template<classT>

voidFun(T&a,T&b){Ttemp=a;a=b;b=temp;}

//函数模板,参数为引用

voidmain()

{inta=6,b=9;floatc=6.6f,d=9.9f;chare=‘A’,f=‘a’;

stringsa=“Jonas”,sb=“Landy”;

Fun(a,b);Fun(c,d);Fun(e,f);Fun(sa,sb); //模板函数

cout<<“a=”<<a<<“b=”<<b<<endl;

cout<<“c=”<<c<<“d=”<<d<<endl;

cout<<“e=”<<e<<“f=”<<f<<endl;

cout<<"sa="<<sa<<"sb="<<sb<<endl;

}运行结果:

a=9b=6

c=9.9d=6.6

e=af=A

sa=Landy sb=Jonas

该例正确地完成了任意两个数据的交换。在交换时若有两个不同类型的数据要发生交换,编译时就报错,这就解决了类型检查问题。

【例4-8】演示具有多个参数的模板函数的应用。

#include<iostream>

usingnamespacestd;

template<classType1,classType2>

//有多个参数,要用逗号分隔

Type2Fun(Type1&a,Type2&b,floatc)

//返回类型为Type2,它有三个类型形参

{cout<<“a=”<<a<<“

”<<“c=”<<c<<endl;returnb;}

voidmain()

{inta=6;charb=‘A’;floatc=6.6f;

cout<<“b=”<<Fun(a,b,c)<<endl;

}

运行结果:

a=6c=6.6

b=A

【例4-9】演示在类型形式参数表中包含基本数据类型的使用方法。

#include<iostream>

usingnamespacestd;

template<classType,intn> //函数模板

TypeFun(Typea[])

{Typemin=10,max=0;intj=0,k=0;

for(inti=0;i<n;i++)本例在Fun()中应该给出类型;因为形参中有一个是基本类型intn,所以要给出数据。基本类型的实参必须是一个常量或常量表达式,或者必须在编译时就能确定其值。若模板参数不能在编译时确定,就会出错。例如:

#include<iostream>

usingnamespacestd;

template<int*p>voidFun(floaty) //int*是基本类型

{cout<<y<<endl;}

voidmain()

{Fun<newint(57)>(3.4f);} //error,编译时57所在的地址不能被确定修改如下:

#include<iostream>

usingnamespacestd;

template<int*p>voidFun(floaty){cout<<y<<endl;}

intx=57;

voidmain()

{Fun<&x>(3.4f);} //ok,编译时x的地址是常量并且已被确定在类型形式参数表中包含基本类型,这种方法目前不常用,在此仅作为一种讨论。对于例4-9,建议改用如下方法:

#include<iostream>

usingnamespacestd;

template<classType>TypeFun(Typea[],intn){同例4-9}

voidmain()

{floatd[]={9.98f,8.92f,8.87f,9.32f,9.75f,8.15f};

cout<<"最后得分:"<<Fun(d,sizeof(d)/sizeof(d[0]))<<"分"<<endl;

}

【例4-10】求an。第一次调用参数是int型,结果是long型。第二次调用参数和结果都是double型。

#include<iostream>

usingnamespacestd;

template<classBase,classSum>Sumpower(Basea,intn)

{Sumsum=1;

for(inti=1;i<=n;i++)

sum*=a;

returnsum;

}voidmain()

{cout<<power<int,long>(2,10)<<endl;

cout<<power<double,double>(10,9)<<endl;

}函数可以重载,同样地,模板函数也可以被重载,通过重载就可以在不改变原程序的情况下执行新的功能。在模板函数与普通函数之间也能重载,这样在使用模板函数时就如同使用普通函数一样方便,而且有关函数的功能和性质对模板函数都适用。

在模板函数之间也可以实现重载,其中并没有什么新的内容,只要符合重载的规则就可以了。

【例4-11】演示模板函数之间的重载。通过重载,分别从两个数、三个数或一个字符串中找出最大值。4.3重载模板函数#include<iostream>

usingnamespacestd;

template<classT>TMax(Ta,Tb) //两个参数

{returna>b?a:b;}

template<classT>TMax(Tx,Ty,Tz) //三个参数

{Tm;

if(x<y)m=(y<z)?z:y;

elsem=(x<z)?z:x;

returnm;

}charMax(char*p)

{charc;

for(c=*p;*p!='\0';p++)

if(*p>=c)c=*p;

returnc;

}

voidmain()

{cout<<"Themaxis"<<Max(9.7,9.1)<<endl; //Max<float>(float,float)

cout<<"Themaxis"<<Max(9.7,9.1,9.8)<<endl; //Max<float>(float,float,float)

cout<<"Themaxis"<<Max("program")<<endl; //Max(char*p)

}模板函数在重载时不能根据参数类型和参数顺序来区分不同的匹配,而要根据参数个数来区分。对于模板函数之间的重载,匹配时要考虑优先级。若是严格匹配,则优先匹配该函数,否则再寻找转换匹配模板函数。例如:

#include<iostream>

usingnamespacestd;

template<classT>voidFun(constT*b){cout<<b;cout<<“\tIamconsrT*”<<endl;}

template<classT>voidFun(T*b){cout<<b;cout<<“\tIamT*”<<endl;}

voidmain()

{Fun(“DesignofC++”);}//T为char*,将输出DesignofC++

IamT*对于Fun("DesignofC++");来说,Fun(T*b)是严格匹配,Fun(constT*b)是转换后的匹配。对于模板函数与非模板函数之间的重载,在匹配时同样有优先级。若非模板函数是严格匹配,则优先匹配非模板函数,否则匹配模板函数。

【例4-12】演示匹配优先级。#include<iostream>

usingnamespacestd;

template<classT>TMax(Ta,Tb){cout<<“Thetemplateis:”;returna>b?a:b;}

doubleMax(doublea,doubleb){cout<<“TheFuntionis:”;returna>b?a:b;}

voidmain()

{cout<<Max(2.2,4.4)<<endl; //匹配非模板函数Max(double

a,doubleb)

cout<<Max(2,4)<<endl; //匹配模板函数Max<int>(2,4)

cout<<Max(2.2f,4.4f)<<endl;//匹配模板函数Max<float>(2.2f,4.4f)

}4.4.1类模板

1.类模板的定义

类模板定义的一般形式为:

template<类型形式参数表> class类标识{类声明体};

其中,类型形式参数表说明了该类模板可接受的类型。例如:

template<classT>classclassName{voidMemberFuncName(intx,floaty,Tz);}4.4类模板与模板类

2.类模板的实现

类模板的实现即成员函数的定义,一般形式为:

template<类型形式参数表>函数返回类型类标识<类型名表>::函数名(形式参数表){成员函数定义体}

例如:

template<classT>voidclassName<T>::MemberFuncName(intx,floaty,Tz){成员函数定义体};

其中,className<T>是类名。观察上面两步可以看出,类模板的定义就是普通类的定义再加上template<类型形式参数表>标记。定义成员函数时,在原类名处用类标识<类型名表>代替。

经过上面两步,就完成了类模板的说明(包括成员函数的定义),也是类的描述。其中,template<classT>是类模板标记,className<T>是类模板的类名。但是,类模板是一个未确定参数类型的类。4.4.2模板类

1.模板类的定义

类模板定义之后也只是一个框架,是一个没有规定具体类型的框架。类模板不是对象,它不占用内存空间。那么要明确一下该框架是一个什么类,框架中可操作什么类或类型数据,这就是模板类的任务。

模板类明确了类模板可对哪些类型数据进行操作,也可以说模板类是类模板的类型化,它把一个框架变为一个实际的类。

在有了类模板的定义之后,使用className<float>就是定义模板类。该模板类将未知类型明确为float型。但是,要注意它是模板类,还是类,它只不过使得类模板中的未知参数类型得到了确定。

2.模板类对象的创建

将模板类实例化后就成为该模板类的对象。该对象的产生经历了类模板→模板类→对象的过程,从概念上讲还是由类来定义的。

创建模板类对象的方法是:

类名<类型实在参数表>对象名其中,后半部分的对象名由程序员自定义,前半部分的类名<类型实在参数表>称为模板类。

类型实在参数表的作用是说明所建类的类型。例如className<float>object;就确定了float参数的类,并用该类创建了一个对象object。若要创建多个对象可以用逗号分隔,例如className<float>object1,object2,object3;。下面是一个简单的进行数据交换的例子。

【例4-13】交换不同类型的数据。Exch<string>ObjectC("jonas","Landy");

ObjectA.Exchange(); //调用成员函数

ObjectB.Exchange();

ObjectC.Exchange();

ObjectA.Display();

ObjectB.Display();

ObjectC.Display();

}运行结果:输出1.1~6.1(间隔为1)和ASCII值为1~6的字符。

在主函数中的Array<float>floatArray,就是通过模板类Array<float>定义了一个叫floatArray的对象。其中的<类型名表>中的实参是类型,而不是变量或对象。数组类模板是还没有确定类型的数组,在使用中既可以将它作为实型,也可以将它作为字符型等。由例4-14可以得出类模板的应用方法,主要步骤分为下面四步:①先按原来的方法定义一个数组类和一些相关的通用操作,如插入、删除和排序等;②将原数组类的定义改为类模板的定义;③将原数组的类型改为T类型;④将原各函数中参数的数据类型改为T类型。4.5.1实现通用栈

栈是十分常见的一种数据结构形式。在我们使用栈的时候,必须为它制定一种数据类型。当我们要存放不同类型数据时必须建立多个新栈,这样很不方便。下面通过模板来使我们能轻而易举地改变栈的数据类型。

【例4-15】通过类模板创建可分别存放实型数据和字符数据的顺序栈。4.5类模板的应用//********stack.h********创建一个栈类

#ifndefSTACK_H

#defineSTACK_H

#include<assert.h>

template<classT>classStack //定义类模板

{public:

Stack(int=10);

//构造函数

~Stack(){delete[]pTop;} //析构函数从该例可看出,类定义时使用了template<classT>,其余与普通类的定义没有区别。template<classT>的作用就是告诉编译器,在我的类中要用到一个现在还不能确定的类型T。当在主函数中用到了Stack<float>floatStack(8);时,T就被float所取代。到此,栈floatStack才有了确切的数据类型,于是在构造函数中的pTop=newT[maxsize];语句才解释为在堆中申请一个单精度数组。4.5.2实现链表

链表是一种常见的数据结构,一般有两种方法建立链表:复合类和嵌套类。在此介绍嵌套类的实现方法。

【例4-16】创建一个链表模板。

//********list.h********创建一个链表类

#ifndefLIST_H

#defineLIST_H

#include<iostream>

usingnamespacestd;template<classT>

voidList<T>::Add(T&t)

{Node*temp=newNode; //从堆中申请一个结点

temp->pT=&t;

//将该结点中的指针指向t对象

temp->pNext=pFirst; //将该结点作为链表的头结点

pFirst=temp;

//将头指针指向该结点

}在成员函数的实现template<classT>voidList<T>::Add(T&t){…}中,template<classT>是类模板,List<T>是类名,T是类模板形参。

//******list.cpp*******实现文件

#include"list.h"

voidmain()

{List<float>floatList; //创建一个对象floatList运行结果:

实型链表输出

6.65.64.63.62.61.6

整型链表输出

6 5 4 3 2 1

删除3.6后输出

6.65.64.62.61.64.5.3在类模板中使用混合形参

当某类中具有多个未确定的类型时,可以使用具有多个形参的类模板。对于多个自定义的形参,使用中各形参要用逗号分隔。

【例4-17】演示多个形参的类模板。制一张表格,第一次将数据成员指定为double型,第二次将数据成员指定为char*和char型。

#include<iostream>

usingnamespacestd;

template<classT1,classT2>classTable //定义类模板运行结果:

No:3C++:87.5English:99

No:1C++:93.5English:88

No:8特长:打篮球: 性别:M

No:6特长:游泳: 性别:W

【例4-18】类模板的参数也可以使用默认参数。

#include<iostream>

usingnamespacestd;4.5.4在类模板中使用友元

在类模板中也可以声明友元,既可以将普通函数、类和成员函数声明为类模板的友元,也可以将函数模板和类模板声明为另一类模板的友元。

【例4-19】演示函数模板作为友元,实现用普通函数模板访问对象的私有成员。

#include<iostream>

usingnamespacestd;

template<classT>classStud运行结果:

JonasC++program

总成绩4.8

【例4-20】演示类模板作为另一类模板的友元。

#include<iostream>

usingnamespacestd;

template<classT>classStud; //类模板的声明voidmain()

{Stud<double>ObjA;

Teacher<double>ObjB;

ObjB.Set(ObjA,89); //Teacher给出Stud的成绩

ObjA.Show();

//Stud查看成绩

ObjB.Show(ObjA); //Teacher查看成绩

Stud<char*>ObjC;

Teacher<char*>ObjD;

ObjD.Set(ObjC,"C++program");

ObjD.Show(ObjC);

}运行结果:

89

89

c++program

其中语句friendclassTeacher<T>;的语意为Teacher类的实例,即模板类是Stud的友元。由于是实例化,因此在编译器看到friendclassTeacher<T>时,前面必须有Teacher类的定义,而不是声明。4.5.5在类模板中使用函数模板

函数或函数模板可以是一个普通类的成员,函数模板也可以作为类模板的成员。用模板作为类的成员,即为成员模板。成员模板的定义方式是:在成员前加关键字template及类型形式参数表。

【例4-21】演示成员模板的实现。

#include<iostream>

usingnamespacestd;

template<classT>classStud

{private:运行结果:

Landy34

Landyscore4.5

Landyisgood

本例在Stud中声明了一个函数模板作为它的成员,这也就是说,有可能在一个Stud的实例——模板类中包含许多名为Show的成员函数,例如Stud<char*>::Show(3,4)、Stud<char*>::Show("score",4.5)、Stud<char*>::Show("is","good")等。4.6.1vector类

在标准C++中,vector类是C++标准类库的一部分,但它不叫数组,而叫向量(vector)。vector类为内置数组提供了一种替代表示。因为向量是一个类模板,所以为了使用vector,就要包含相关的头文件。例如:

#include<vector>

//要定义向量就要包含头文件

#include<string>

vector<int>a(10);

//定义了包含10个整型对象的向量a

vector<string>b(10);

//定义了包含10个字符串对象的向量b4.6标准模板库STL向量与数组的主要区别是:第一,vector向量可以在运行时动态增长,而数组长度的概念是静态的,定义的同时必须指定大小;第二,vector代表了设计方法的重要转变,它并没有提供一个巨大的操作集,而只提供了一个最小集,包括等于、小于、size()、empty等操作。能应用到向量中的操作是相当多的,但是它们并没有作为vector类模板的成员函数提供,而是以一个独立的泛型算法集的形式由标准库提供,如表4-1所示。表4-1vector类成员函数表(泛型算法集)

1.数组习惯

数组习惯的使用方法如:vector<int>a(9);,即创建了一个整型数组a,这与inta[9]相似,而且其使用方法也如同数组一样。除此之外,我们还可以使用vector类的成员函数。

【例4-22】演示成员函数begin()、size()和泛型算法find()的使用方法。

#include<vector>

#include<algorithm>

#include<iostream>

usingnamespacestd;

voidmain()访问vector中的元素时要注意下标不能越界。例如下面的程序虽然在编译时可以通过,但在运行中就会出错,因为d的最大下标为4。我们只能索引vector中已经存在的元素。

#include<vector>

#include<iostream>

usingnamespacestd;

voidmain()

{vector<int>d(5);d[4]=5;

//d[5]=6; //error,后面将介绍调用函数的方法

cout<<d.size()<<endl; //输出5

d.resize(2*d.size()); //resize为重新设置容器长度

d[5]=6; //正确

}

【例4-23】演示拷贝的方法。

#include<iostream>

#include<vector>

usingnamespacestd;{vector<int>a(10,-1); //把数组a内的10个元素都初始化为-1

Show(a);cout<<endl;

intb[]={9,7,3,6,2,8,5,1,4,3};

vector<int>d(b,b+10);//把数组b内的b[0]~b[9]元素拷贝到d中

Show(d);cout<<endl;

vector<int>e(&b[0],&b[3]);

//将数组b内的b[0]~b[2]元素拷贝到e中

Show(e);cout<<endl;

}经过拷贝后,d的长度为10,e的长度为3。我们也可以将一个vector拷贝给另一个vector,但是不能将数组拷贝给一个vector。例如:

voidmain()

{vector<int>d(10);

intb[]={9,7,3,6,2,8,5,1,4,3};

vector<int>e(d); //ok

vector<int>f(b); //error,b是数组,不是向量,要给出一

段地址空间

}

当我们指定了vector的大小后,任何一个插入操作都将增加vector的长度,而不是覆盖掉某个现有的元素。【例4-24】演示vector的动态增长。

#include<vector>

#include<iostream>

usingnamespacestd;

voidmain()

{vector<int>a(5);

cout<<a.size()<<endl;//输出5

for(inti=0;i<7;++i)

a.push_back(i);//使用推入函数push_back,进行逐个插入

cout<<a.size()<<endl;//输出12

}推入函数push_back的作用是向顺序容器尾部插入一个元素,即只能将新增元素插入到最后。凡是使用push和pop等都是按规定进行操作。程序结束时a中共有12个元素,a中放入的元素从a[5]起,即从最后位置开始。若想要插入到中间位置,可以使用如下方法:

#include<vector>

#include<iostream>

usingnamespacestd;

voidmain()

{intb[]={11,22,33};vector<int>a(5,-1);

a.insert(a.begin(),66); //在首位插入66

a.insert(a.begin()+a.size()/2,b,b+3);//在中间位置插入b的内容

for(inti=0;i<a.size();++i)

cout<<a[i]<<“

”;

}

运行结果:

66-1-1112233-1-1-1

insert函数有两种形式。插入单个内容:insert(参数1,参数2);,其中,参数1给出插入位置,参数2给出插入内容。插入一段内容:insert(参数1,参数2,参数3);,其中,参数1给出插入位置,参数2给出被插入区间的开始位置,参数3给出被插入区间的结束位置。

对于插入一段内容,若参数2给出一个数据而非地址,则表示插入者的个数,同时参数3就成为被插入的值。例如:

vector<int>a(5,-1);

a.insert(a.begin()+1,3,89);

//从第二位,即a[1]起插入3个89

2.STL习惯

vector在STL中的用法完全不同。我们定义一个空的vector,然后用插入函数对它赋值。

【例4-25】演示vector在STL中的用法。

#include<iostream>

#include<string>

#include<vector>

usingnamespacestd;

【例4-26】演示vector的删除。

删除元素用erase()或pop_back。erase删除由iterator指向的一段区间。pop_back删除容器的最后一个元素,它并不返回该元素。

#include<iostream>

#include<algorithm>

#include<vector>

usingnamespacestd;

voidmain()

{vector<int>a(5,-1);4.6.2string类

string类是C++库函数中的字符串类,它包含了字符处理操作。它可提供的操作为:支持用字符序列或字符串对象初始化另一个字符串对象;支持字符串之间的拷贝;支持读/写单个字符;支持字符串的比较(即ASCII码的比较);支持两个字符串的连接;支持对字符串长度的查询;支持字符串的判空。使用时只要包含<string>便可。有关它的使用可以参考C++类库。

【例4-27】演示字符串的连接。

#include<iostream>

#include<string>

usingnamespacestd;

voidmain()

{stringa=“Ilearn”,b=“C++language”;

a=a+b;

cout<<a<<endl;

}

【例4-28】演示一些字符串的常用操作。

#include<iostream>

#include<string>

usingnamespacestd;

voidmain()

{stringa("ThisisC++program");//用字符串初试化对象

cout<<"Thesizeofa:"<<a.size()<<endl;//不含\0

stringb(a); //用对象a初试化对象b

cout<<b<<endl;

if(b==a)cout<<"b=a"<<endl;//字符串比较

elsecout<<"b!=-a"<<endl;

stringc;if(c.empty())cout<<"cisempty"<<endl;

c=a; //拷贝

if(!c.size())cout<<"cisempty"<<endl;

elsecout<<"cisn'tempty"<<endl;

char*p="Jonas";

stringd="study";

stringe("C++");

d=p+d+e+"\n"; //连接cout<<d;

stringf;f=p; //不能是p=f;

cout<<f<<endl;

f=a.substr(8,3); //从第8位置处开始,长度为3的子串

cout<<f<<endl;

a.at(12)='P'; //将12位置处的字符改为P

cout<<a<<endl;

}运行结果:

Thesizeofa:19

ThisisC++program

b=a

cisempty

cisn‘tempty

JonasstudyC++

Jonas

C++

ThisisC++Program4.6.3complex类

标准C++类库提供的complex类(复数类)是基于对象的抽象类的完美模型。通过重载操作符,我们几乎可以像使用内部类型一样使用complex类对象。

【例4-29】演示对complex类的操作。

#include<iostream>

#include<complex>

usingnamespacestd;

voidmain(){complex<double>a(2,3); //a=2+3i

complex<float>b(0,3); //b=0+3i

complex<int>c; //c=0+0i

complex<double>d(a); //d=2+3i

complex<double>e[2]={complex<double>(1,2),complex<double>(3,4)};

complex<double>*p=&e[0]; //指针a+=d; //支持复合运算符

cout<<a<<endl;

//cout<<*p<<endl; //输出(1,2)

//cout<<*++p<<endl;//输出(3,4)

a=*p+*++p;

cout<<a<<endl; //输出(6,8)

doublex=a.real(); //用doublex=real(a);也可

doubley=a.imag(); //与doubley=imag(a);等价

cout<<x<<""<<y<<endl;

}4.6.4stack类

stack类是堆栈类。标准C++类库提供的stack类是基于对象的抽象类的类模板。它的操作原则是后进先出。通过包含相关头文件#include<stack>,我们几乎可以像使用内部类型一样使用stack类对象。在C++中我们称stack类为栈容器。表4-2列出了stack类的成员函数。表4-2stack类成员函数表【例4-30】演示对栈类的操作。

#include<iostream>

#include<stack>

usingnamespacestd;

voidmain()

{stack<int>a;

for(inti=1;i<8;++i)

a.push(i);

cout<<a.size()<<endl;

while(!a.empty())

{cout<<a.top()<<"";a.pop();}

cout<<'\n'<<a.size()<<endl;

}4.6.5queue类

queue是队列类。标准C++类库提供的queue类是基于对象的抽象类的类模板。它的操作原则是先进先出。通过包含相关头文件#include<queue>,我们几乎可以像使用内部类型一样使用queue类对象。在C++中我们称queue类为队列容器。表4-3列出了queue类的成员函数。表4-3queue类成员函数表【例4-31】演示队列类的操作。

#include<iostream>

#include<queue>

usingnamespacestd;

voidmain()

{queue<int>a;

温馨提示

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

评论

0/150

提交评论