第16章 模板与泛型编程
【111】16.2编写一个函数模板,接受一个ostream引用和一个值,将该值写入流。用至少4种不同类型调用函数,通过写至cout、文件和stringstream来测试你的程序。
答:#include <iostream>
#include<string>
#include<fstream>
#include<sstream>
usingnamespace std;
template<typename T1,typename T2>
T1&print(T1& s,T2 val)
{
s<<val;
returns;
}
intmain()
{
doubledval=0.88;
floatfval=-12.3;
stringoristr=”this is a test”,desstr;
ostringstreamoss(desstr);
ofstreamoutFile(“result.dat”);
//写至cout
print(cout,-3)<<endl;
print(cout,dval)<<endl;
print(cout,fval)<<endl;
print(cout,oristr)<<endl;
//写至文件
print(outFile,-3)<<endl;
print(outFile,dval)<<endl;
print(outFile,fval)<<endl;
print(outFile,oristr)<<endl;
outFile.close();
//写至stringstream
print(oss,-3)<<endl;
print(oss,dval)<<endl;
print(oss,fval)<<endl;
print(oss,oristr)<<endl;
//将stringstream中的字符输出到cout以进行验证
cout<<oss.str()<<endl;
return0;
}
【112】16.3当调用两个string对象的compare时,传递用字符串字面值初始化的两个string对象,如果编写以下代码会发生什么?
compare(“hi”,”world”);
答:该代码会出现编译错误。因为根据第一个实参”hi”可将模板形参T推断为char[3],而根据第二个实参”world”可将模板形参推断为char[6],T被推断为两个不同的类型,所以编译器无法使用函数模板compare进行适当的实例化以满足需求。
【113】16.7解释下面每个函数模板的定义并指出是否有非法的。
a)template<class T,U, typename V> voidf1(T,U,V);
非法,模板类型形参前必须带有关键字typename或class,模板非类型形参前必须带有类型名,而这里U作为f1的形参类型使用,应该是一个类型形参,所以应在模板形参表中U的前面加上class或者typename;
b)template<classT>T f2(int &T);
非法,如果单纯从函数模板定义的语法来看,该定义是合法的。但是,模板形参T没有作为类型在模板函数的形参表中出现,因此将无法对其进行模板实参推断,所以,该模板函数的定义是错误的;
c)inlinetemplate<class T> T foo(T,unsigned int*);
非法,inline不能放在关键字template之前,应放在模板形参表之后,函数返回类型之前;
d)template<classT> f4(T,T);
在标准C++中非法;没有指定函数f4的返回类型。
e)typedefchar Ctype;
template <typename Ctype> Ctypef5(Ctype a);
合法。定义了一个模板函数f5,该函数的返回类型与形参类型相同,均可绑定到任意类型。
【114】16.10声明为typename的类型形参与声明为class的类型形参有区别吗?区别在哪里?
答:在标准C++中,声明为typename的类型形参与声明为class的类型形参没有区别。但是,标准C++之前的系统有可能只支持使用关键字class来声明模板类型形参。
【115】16.11何时必须使用typename?
答:如果要在函数模板内部使用在类中定义的类型成员,必须在该成员名前加上关键字typename,以告知编译器将该成员当作类型。
【116】16.15编写可以确定数组长度的函数模板
答:可以使用非类型模板形参编写如下函数模板
template<typename T,std::size_t N>
std::size_tsize(T (&arr)[N])
{
returnN;
}
【117】16.20在模板实参推断期间发生什么?
答:根据函数调用中给出的实参确定模板实参的类型和值;
【118】16.21指出对模板实参推断中涉及的函数实参允许的类型转换。
答:const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无需产生新的实例化。如果函数接受非引用类型,形参类型和实参都忽略const,即无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化。
数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
【119】16.22对于下面的模板:
template<class Type>
Typccalc(const Type* array,int size);
template<class Type>
Typefcn(Type p1,Type p2);
下面这些调用有错吗?
doubledobj; float fobj; char cobj;
intai[5]={22,13,4,12,24};
a)calc(cobj,’c’);
实参cobj的类型为char,但是,不能使用函数模板calc产生第一个形参为非指针类型的函数实例;
b)calc(dobj,fobj);
实参dobj的类型为double,但是,不能使用函数模板calc产生第一个形参为非指针类型的函数实例;
c)fcn(ai,cobj);
函数模板fcn中两个形参的类型必须是相同的,而函数调用fcn(ai,cobj)中给出的两个实参类型不同,不能进行实例化。
【120】16.23标准库函数max接受单个类型形参,可以传递int和double对象调用max吗?
答:可以,只需使用强制类型转换将其中一个对象转换为int或double类型,使其与另一对象类型相同即可。
【121】16.24对于具有单个模板类型形参的compare版本,传给它的实参必须完全匹配,如果想要用兼容类型如int和short调用该函数,可以使用显式模板实参指定int或short作为形参类型。编写程序使用具有一个模板形参的compare版本,使用允许你传递int和short类型实参的显式模板实参调用compare。
答:#include <iostream>
usingnamespace std;
template<typename T>
intcompare(const T& v1,const T& v2)
{
if(va<v2)return -1;
if(v2<v1)return 1;
return0;
}
intmain()
{
shortsval =123;
intival=1024;
cout<<compare(static_cast<int>(sval),ival)<<endl;
cout<<compare(sval,static_cast<short>(ival))<<endl;
cout<<compare<short>(eval,ival)<<endl;
cout<<compare<int>(sval,ival)<,endl;
return0;
}
【122】16.25使用显式模板实参,使得可以传递两个字符串字面值调用compare
答:只需按如下方式使用显式模板实参:
compare<std::string>(“mary”,”mac”);
亦可采用如下强制类型转换方式:
compare(static_cast<std::string>(“mary”),static_cast<std::string>(“mac”))
或 compare(std::string(“mary”),std::string(“mac”))
【123】16.26对于下面的sum模板定义:
template<classT1, class T2, class T3>T1 sum(T2,T3);
解释下面的每个调用。如果有,指出哪些是错误的。
doubledobj1,dobj2; float fobj1,fobj2; char cobj1,cobj2;
a)sum(dobj1,dobj2);
错位,没有为模板类型形参T1指定相应的类型实参;
b)sum<double,double,double>(fobj1,fobj2);
正确,编译器将根据显式模板实参为该调用产生函数实例double sum(double,double),并将两个函数实参由float类型转换为double类型来调用该实例;
c)sum<int>(cobj1,cobj2);
正确,编译器将根据显式模板实参及函数实参为该调用产生函数实例int sum(char,char).
d)sum<double,, double>(fobj2,dobj2);
错位,只有最右边形参的显式模板实参可以省略,不能用”,”代替被省略的显式模板实参。
【124】16.36每个带标号的语句,会导致实例化吗?
template<class T> class Stack{};
voidf1(Stack<char>); //a)
classExercise{
Stack<double>&rsd; //b)
Stack<int>s1; //c)
};
intmain(){
Stack<char>*sc; //d)
f1(*sc); //e)
intiObj=sizeof(Stack<string>); //f)
}
答:a)不会导致实例化,函数声明不会导致实例化;
b)不会导致实例化,定义引用不会导致实例化;
c)会导致实例化,在创建Exercise对象时会实例化Stack<int>类及其默认构造函数(严格来说这样的实例化不是由语句c)直接导致的,而是由定义Exercise对象的语句导致的;
d)不会导致实例化,定义指针不会导致实例化;
e)会导致实例化,调用函数f1时需创建形参对象,此时导致实例化Stack<char>类及其默认构造函数。注意,题目此处有误,指针sc尚未赋值就使用了。
f)会导致实例化,sizeof操作需要使用具体类,此时导致实例化Stack<string>.
【125】16.37下面哪些模板实例化是有效的?解释为什么实例化无效。
template<class T,int size> class Array{…};
template<inti,int w>class Screen{…}
a)constint i=40,w=80; Screen<i,w+32> sObj;
b)constint arr_size =1024; Array<string , arr_size> a1;
c)unsignedint asize=255; Array<int,asize> a2;
d)constdouble db=3.1222; Array<double,db> a3;
答:有效的模板实例化包括a)和b);
c)之所以无效,是因为非类型模板实参必须是编译时常量表达式,不能用变量asize作模板实参;
d)之所以无效,是因为db是double型常量,而该模板实例化所需的非类型模板实参为int型常量。
【126】16.42编写一个输入操作符,读入一个istream对象并将读取的值放入一个Queue对象中。
答:template<class Type>
istream&operator>> (istream &is,Queue<Type> &q)
{
Typeval;
while(is>>val)
q.push(val);
returnis;
}