首页 > 代码库 > C++学习笔记12-模板1
C++学习笔记12-模板1
1. 函数模板
函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。
// implement strcmp-like generic compare function
// returns 0 if thevalues are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T&v1, const T &v2)
{
if (v1 < v2)return -1;
if (v2 < v1)return 1;
return 0;
}
2. 类模板
类模板也是模板,因此必须以关键字template 开头,后接模板形参表。Queue 模板接受一个名为Type 的模板类型形参。
template <class Type> class Queue {
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front() const;
void push (const Type&); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
3. 模板形参
像函数形参一样,程序员为模板形参选择的名字没有本质含义。在我们的例子中,将compare 的模板类型形参命名为T,但也可以将它命名为任意名字:
// equivalent template definition
template <class Glorp>
int compare(const Glorp &v1, const Glorp &v2)
{
if (v1 < v2)return -1;
if (v2 < v1)return 1;
return 0;
}
该代码定义的compare 模板与前面一样。可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
4. 模板形参作用域
模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字:
typedef double T;
template <class T> T calc(const T &a, const T &b)
{
// tmp has the typeof the template parameter T
// not that of the global typedef
T tmp = a;
// ...
return tmp;
}
将T 定义为double 的全局类型型别名将被名为T 的类型形参所屏蔽,因此,tmp不是double 型,相反,tmp的类型是绑定到模板形参的任意类型。使用模板形参名字的限制用作模板形参的名字不能在模板内部重用。
template <class T> T calc(const T &a, const T &b)
{
typedef double T; //error: rede clares template parameter T
T tmp = a;
// ...
return tmp;
}
这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次:
// error: illegalre use of template parameter name V
template <class V,class V> V calc(const V&, const V&) ;
当然,正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
// ok: reuses parameter type name across different templates
template <class T> T calc (const T&, const T&) ;
template <class T> int compare(const T&, const T&) ;
5. 模板声明
像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函
数或类是一个模板:
// declares compare but does not define it
template <class T> int compare(const T&, const T&) ;
同一模板的声明和定义中,模板形参的名字不必相同。
// all three uses of calc refer to the same function template
// forward declarations of the template
template <class T> T calc(const T&, const T&) ;
template <class U> U calc(const U&, const U&) ;
// actual definitionof the template
template <class Type>
Type calc(const Type& a, const Type& b) { /* ... */ }
每个模板类型形参前面必须带上关键字 class 或 typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:
// error: must precede U by either typename or class
template <typename T, U> T calc (const T&, const U&) ;
6. typename 与 class 的区别
在函数模板形参表中,关键字typename 和class 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:
// ok: no distinction between typename and class in template parameter list
template <typename T, class U> calc (const T&, const U&);
使用关键字typename 代替关键字class 指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚地指明后面的名字是一个类型名。但是,关键字typename 是作为标准C++ 的组成部分加入到C++ 中的,因此旧的程序更有可能只用关键字class。
7. 编写泛型代码的两个重要原则:
? 模板的形参是const 引用。
? 函数体中的测试只用< 比较。
8. 类模板形参是必需的
想要使用类模板,就必须显式指定模板实参:
Queue qs; // error:which template instantiation?
类模板不定义类型,只有特定的实例才定义了类型。特定的实例化是通过提供模板实参与每个模板形参匹配而定义的。模板实参在用逗号分隔并用尖括号括住的列表中指定:
Queue<int> qi;// ok: defines Queue that holds ints
Queue<string>qs; // ok: defines Queue that holds strings
9. 类型形参的实参的受限转换
考虑下面的compare 调用:
short s1, s2;
int i1, i2;
compare(i1, i2); //ok: instantiate compare(int, int)
compare(s1, s2); //ok: instantiate compare(short, short)
一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:
?const 转换:接受const 引用或const 指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略const,即,无论传递const 或非const 对象给接受非引用类型的函数,都使用相同的实例化。
? 数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
例如,考虑对函数fobj 和fref 的调用。fobj函数复制它的形参,而fref
的形参是引用:
template <typename T> T fobj(T, T); // arguments are copied
template <typename T> T fref(const T&,const T&); // reference arguments
string s1("avalue");
const strings2("another value");
fobj(s1, s2); // ok:calls f(string, string), const is ignored
fref(s1, s2); // ok: non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b); // ok:calls f(int*, int*)
fref(a, b); // error:array types don‘t match; arguments aren‘t converted to pointers
第一种情况下,传递string 对象和const string 对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在fobj 的调用中,实参被复制,因此原来的对象是否为const 无关紧要。在fref 的调用中,形参类型是const引用,对引用形参而言,转换为const 是可以接受的转换,所以这个调用也正确。
在第二种情况中,将传递不同长度的数组实参。fobj的调用中,数组不同无关紧要,两个数组都转换为指针,fobj的模板形参类型是int*。但是,fref的调用是非法的,当形参为引用时数组不能转换为指针,a和b 的类型不匹配,所以调用将出错。
10. 类型转换的限制只适用于类型为模板形参的那些实参。
C++学习笔记12-模板1