1.明确模板概念:
第一次接触模板,看完第16章后感觉自己都弄清楚了,可是在回想一遍之后,才发现自己的思绪很混乱。所以,我先总结一下有几类模板:
(1).函数模板:
先给出一个例子:
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
这就是一个函数模板,注意在模板定义后面并没有分号(template <typename T>)。
模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。
注意:模板的形参表列不能为空。
(2).类模板:
之前我一直没搞明白,类模板和函数模板到底有什么区别。特意上网查了资料,觉得下面的解释比较易于理解:
(引自博客http://blog.youkuaiyun.com/csw_100/archive/2010/08/27/5844615.aspx,多谢作者)
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。 即函数模板允许隐式调用和显式调用而类模板只能显示调用。
作者接下来给出的例证,是小弟目前还未接触过的知识,只能自己结合着C++primer上的例子来理解。书上给出一个Queue类模板的例子,定义如下:
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:
// ...
};
与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:
Queue<int> qi; // Queue that holds ints
Queue< vector<double> > qc; // Queue that holds vectors of doubles
Queue<string> qs; // Queue that holds strings
我想,这就是类模板和函数模板的区别吧,必须显示调用。类模板和函数模板还有一个区别,比较好理解,那就是类模板的主体是类而函数模板的主体是函数。
3.关于类型形参的实参的受限转换:
这也是个难点,书上讲到:
一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:
const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。
数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
第一个转换容易理解,关键是第二个。看看书上的例子:
template <typename T> T fobj(T, T); // arguments are copied
template <typename T>
T fref(const T&, const T&); // reference arguments
string s1("a value");
const string s2("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
为什么fref(a,b)的调用会出错呢?书上解释道:fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref 的调用是非法的,当形参为引用时,数组不能转换为指针,a 和 b 的类型不匹配,所以调用将出错。为什么会导致ab类型不匹配呢?这是因为通过引用传递数组时,数组大小成为了实参和形参的一部分。而在上面的例子中,a和b的大小并不一样,因此导致类型不匹配。
4.关于函数指针:
不得不说,模板这章真的蛮难的。需要理解的概念很多。接下来说说函数指针的赋值和初始化。
假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化 template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare (const int&, const int&)
int (*pf1) (const int&, const int&) = compare;
pf1 的类型是一个指针,指向“接受两个 const int& 类型形参并返回 int 值的函数”,形参的类型决定了 T 的模板实参的类型,T 的模板实参为 int 型,指针 pf1 引用的是将 T 绑定到 int 的实例化。这里要求能确定模板实参,否则调用将出现错误。
5.实现Queue类:
Queue类的实现将定义两个类,首先我们定义一个QueueItem类,表示Queue的链表中的节点。该类有两个数据成员item和next。item保存Queue中的元素的值,next是队列指向下一个QueueItem对象的指针。Queue中的每个元素保存在一个QueueItem对象中。
编写Queue类:
template <class Type> class QueueItem {
QueueItem(const Type &t): item(t), next(0) { }
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
现在充实 Queue 类:
template <class Type> class Queue {
public:
// empty Queue
Queue(): head(0), tail(0) { }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&);
~Queue() { destroy(); }
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
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
return head == 0;
}
private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy(); // delete all the elements
void copy_elems(const Queue&); // copy elements from parameter
};
这里书上说到一点,应该引起注意的是:
当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字 Queue 是 Queue<Type> 缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。因此,复制构造函数定义其实等价于:
Queue<Type>(const Queue<Type> &Q): head(0), tail(0)
{ copy_elems(Q); }
对于类模板的用法,我还不是很了解,这里先搁一搁。
6.零碎概念:
模板类是类模板实例出来的产物。
调用类模板成员函数比调用函数模板更为灵活。用模板形参定义的函数形参的实参允许进行常规转换。这个在STL第二章有比较详细的讲过。
7.在看到特定模板的友元关系这一节时,我看到一个比较有趣的东东:
template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar {
// grants access to a single specific instance parameterized by char*
friend class Foo2<char*>;
friend void templ_fcn2<char*>(char* const &);
// ...
};
在这段代码中,我发现了这个模板: template <class T> void templ_fcn2(const T&) 的实例化很有趣,是这样子的: friend void templ_fcn2<char*>(char* const &)。char* const&?什么东西?这里会不会是书上写错了呢?其实这样写是对的。要理解这个,我们先来了解const char*, char const*, char*const的区别。
在网上找到一篇博文,写的简单明了:
(摘自http://www.cnblogs.com/jonnyyu/archive/2007/12/04/89766.html感谢原作者!)
原文如下:
const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。
事实上这个概念谁都有只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp; ( * 读成 pointer to )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。
同时,在C++标准规定,const关键字放在类型或变量名之前等价的。
这样三个的区别就清楚了。
不过,我再附上另外两篇博文,帮助大家更深入的理解const:
http://blog.youkuaiyun.com/oury/archive/2005/02/07/283836.aspx
(感谢作者)
所以,这里的const T&是一个引用类型的形参,实例化为char*型,就是一个const的指针指向char型。即为char *const&。
(这部分是我自己总结出来的,如有不足,恳请大家指出。)
8.总结模板的友元声明:(摘自C++primer)
(1)普通友元:
非模板类或非模板函数可以是类模板的友元:
template <class Type> class Bar {
// grants access to ordinary, nontemplate class and function
friend class FooBar;
friend void fcn();
// ...
};
(2)一般模板友元关系:
友元可以是类模板或函数模板:
template <class Type> class Bar {
// grants access to Foo1 or templ_fcn1 parameterized by any type
template <class T> friend class Foo1;
template <class T> friend void templ_fcn1(const T&);
// ...
};
这个友元声明在 Bar 与其友元 Foo1 和 temp1_fcn1 的每个实例之间建立了一对多的映射。对 Bar 的每个实例而言,Foo1 或 temp1_fcn1 的所有实例都是友元。
(3)特定的模板友元关系:
除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权:
template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar {
// grants access to a single specific instance parameterized by char*
friend class Foo2<char*>;
friend void templ_fcn2<char*>(char* const &);
// ...
};
下面形式的友元声明更为常见:
template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar {
// each instantiation of Bar grants access to the
// version of Foo3 or templ_fcn3 instantiated with the same type
friend class Foo3<Type>;
friend void templ_fcn3<Type>(const Type&);
// ...
};
这些友元定义了 Bar 的特定实例与使用同一模板实参的 Foo3 或 temp1_fcn3 的实例之间的友元关系。每个 Bar 实例有一个相关的 Foo3 和 temp1_fcn3 友元:
Bar<int> bi; // Foo3<int> and templ_fcn3<int> are friends
Bar<string> bs; // Foo3<string>, templ_fcn3<string> are friends
这个也好理解,不多说。
想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:
template <class T> class A;
template <class T> class B {
public:
friend class A<T>; // ok: A is known to be a template
friend class C; // ok: C must be an ordinary, nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>; // error: E wasn't declared as a template
friend class F<int>; // error: F wasn't declared as a template
};
如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
9.成员模板:
要注意的是,在类外定义时,成员模板的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign 函数定义的开头为