模板与泛型编程
--实例化
引言:
模板是一个蓝图,它本身不是类或函数。编译器使用模板产生指定的类或函数的特定版本号。产生模板的特定类型实例的过程称为实例化。
模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。
1、类的实例化
当编写Queue<int>qi时,编译器自己主动创建名为Queue<int>的类。实际上,编译器通过又一次编写Queue模板,用类型int取代模板形參的每次出现而创建Queue<int>类。实例化的类就像已经编写的一样:
template <typename Type> class Queue<Type> { public: Queue(); int &front(); const int &front() const; void push(const int &); void pop(); bool empty() const; private: //... };
假设要为string类型的对象创建Queue类,能够编写:
Queue<string> qs;
在这个样例中,用string取代Type的每次出现。
【重点理解:】
类模板的每次实例化都会产生一个独立的类类型:为int类型实例化的Queue与随意其它Queue类型没有关系,对其它Queue类型的成员也没有特殊訪问权限!
2、类模板实參是必需的
想要使用类模板,就必须显式指定模板实參:
Queue qs; //Error
类模板不定义类型,仅仅有特定的实例才定义了类型。特定的实例是通过提供模板实參与每一个模板形參匹配而定义的。模板实參在用逗号分隔并用尖括号括住的列表中指定:
Queue<int> qi; Queue<string> qs;
用模板类定义的类型总是包括模板实參。比如,Queue不是类型,而Queue<int>,Queue<string>是类型。
3、函数模板实例化
使用函数模板时,编译器一般会为我们判断模板实參:
int main() { compare(1, 0); //将模板形參替换为int compare(3.14, 2.7); //将模板形參替换为double }
这个程序实例化了compare的两个版本号:一个用int取代T,还有一个用double取代T,实质上是编译器为我们编写了compare的这两个实例:
int compare(const int &v1, const int &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } int compare(const double &v1, const double &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; }
一、模板实參判断
要确定应该实例化哪个函数,编译器会查看每一个实參。假设相应形參声明为类型形參的类型,则编译器从实參的类型判断形參的类型。从函数实參确定模板实參的类型和值的过程叫做模板实參判断。
1、多个类型形參的实參必须全然匹配
模板类型形參能够用作一个以上函数形參的类型。在这种情况下,模板类型判断必须为每一个相应的函数实參产生同样的模板实參类型。
template <typename T> int compare(const T &val1,const T &val2) { if (val1 < val2) return -1; else if (val2 < val2) return 1; return 0; } int main() { short si; compare(si,1024); //Error:调用compare的实參类型不同,无法完毕正确判断 }
假设compare的设计者想要同意实參的常规转换,则函数必须用两个类型形參来定义:
template <typename T,typename U> int compare(const T &val1,const U &val2) { if (val1 < val2) return -1; else if (val2 < val2) return 1; return 0; } int main() { short si; compare(si,1024); //OK }
2、类型形參的实參的受限转换
一般而言,不会转换实參以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器仅仅会运行两种转换:
1)const转换:接受const引用或const指针的函数能够分别用非const对象的引用或指针来调用,无须产生新的实例化。假设函数接受非引用类型,形參类型实參都忽略const,即,不管传递const或非 const对象给接受非引用类型的函数,都使用同样的实例化。
2)数组或函数到指针的转换:假设模板形參不是引用类型,则对数组或函数类型的实參应用常规指针转换。数组实參将当作指向其第一个元素的指针,函数实參当作指向函数类型的指针。
template <typename T> T fobj(T,T); template <typename T> T fref(const T &,const T &); string s1("a value"); const string s2("another value"); fobj(s1,s2); //OK:call fobj(string,string),实參被复制 fref(s1,s2); //OK:cal fref(const string &,const string &) int a[10],b[42]; fobj(a,b); //OK:call fobj(int *,int *) fref(a,b); //Error:实參没有办法转换成为pointer[指针]
3、应用于非模板实參的常规转换
注意以下的一段程序:
template <typename Type> Type sum(const Type &op1,int op2) { return op1 + op2; }
由于op2的类型是固定的,在调用sum的时候,能够对传递给op2的实參应用常规转换:
double d = 3.14; string s1("hiya"),s2("world"); cout << sum(1024,d) << endl; cout << sum(1.4,d) << endl; cout << sum(s1,s2) << endl; //Error:没有从string到int的转换
4、模板实參判断与函数指针
能够使用函数模板对函数指针进行初始化或赋值,此时,编译器使用指针的类型实例化具有适当模板实參的模板版本号。
比如,假定有一个函数指针指向返回int值的函数,该函数接受两个形參,都是 constint 引用,能够用该指针指向compare的实例化:
template <typename T> int compare(const T &,const T &); int (*pf1)(const int &,const int &) = compare;
pf1的类型是一个指针,指向“接受两个constint & 类型形參并返回int值的函数”,形參的类型决定了T的模板实參的类型:T的模板实參为int型,指针 pf1引用的是将 T绑定到 int的实例化。
获取函数模板实例化的地址的时候,上下文必须是这种:它同意为每一个模板形參确定唯一的类型或值。
假设不能从函数指针类型确定模板实參,就会出错:
void func(int (*)(const string &,const string &)); void func(int (*)(const int &,const int &)); func(compare); //Error:通过查看func的形參类型不可能确定模板实參的唯一类型。
对func的调用能够实例化下列函数中的随意一个:
compare(const string&, const string&) compare(const int&, const int&)
由于不能为传给func的实參确定唯一的实例化,该调用会产生一个编译时(或链接时)错误!
//P540 习题16.22 template <class Type> Type calc(const Type *arr,int size); template <class Type> Type fcn(Type p1,Type p2); int main() { double dobj; float fobj; char cobj; int ai[5] = {511,16,8,64,343}; calc(&cobj,‘c‘); calc(&dobj,fobj); fcn(ai,ai + sizeof(ai)/sizeof(*ai)); }