目录
- 6.1被参数化类型(Parameterized Types)
- 6.2Class Template的定义(The Template Class Definition)
- 6.3Template类型参数的处理(Handling Template Type Parameters)
- 6.4实现一个Class Template(Implementing the Template Class)
- 二叉树实现完整代码
- 6.5一个以Function Template完成的Output运算符(A Function Template Output Operator)
- 6.6常量表达式与默认参数值(Constant Expressions and Default Parameters)[template <_type, _name>]
- 6.7以Template参数作为一种设计策略(Template Parameters as Strategy)
- 6.8Member Template Function(Member Template Functions)
在数据结构中,所谓树(tree)乃是由节点(node或vertice)以及连接不同节点的链接(link)组成。所谓二叉树,维护着每个节点与下层另两个节点间的两条链接,一般将此下层二节点称为左子节点(left child)和右子节点(right child)。最上层第一个节点称为根节点(root)。无论是左子节点或右子节点,都可能扮演另一棵“子树(subtree)”的根节点。一个节点如果不再有任何子节点,便称为叶节点(leaf)。
我们的二叉树包含两个class:一个是BinaryTree,用以储存一个指针,指向根节点,另一个是BTnode,用来存储节点实值,以及连接至左、右两个子节点的链接。此处,“节点实值的类型(value type)”正是我们希望加以参数化的部分。
我们的BinaryTree类提供哪些操作行为呢?
用户必须能够将元素插入(insert)到BinaryTree,必须能够从BinaryTree移除(remove)特定元素,也必须能够在树中搜寻(find)某个元素、清除(clear)所有元素、以特定的遍历方式打印(print)整棵树。我们必须三种遍历方式:中序(inorder)、前序(preorder)、后序(postorder)。
在实现中,第一个插入空树(empty tree)的值,会成为此树的根节点。接下来的每个节点都必须以特定规则插入:如果小于根节点,就被置于左子树,如果大于根节点,就被置于右子树。任何一个值只能在树中出现一次,但是此树有能力记录同一值的插入次数。
BinaryTree<string> bt;
bt.insert("Piglet");
程序使Piglet成为二叉树的根节点,接着插入Eeyore:
bt.insert("Eeyore");
由于Eeyore小于Piglet(按字典排列顺序),于是Eeyore便成了Piglet的左子节点。接着插入Roo:
bt.insert("Roo"0;
由于Roo大于Piglet(按字典排列顺序),于是Roo便成了Piglet的右子节点,以此类推。
现在我们插入下列各元素,完成二叉树的构建:
bt.insert("Tigger");
bt.insert("Chris");
bt.insert("Pooh");
bt.insert("Kanga");
任何遍历算法(traversal algorithm)皆由根节点出发。
♦前序(preorder)遍历:节点本身->左子树->右子树
♦中序(inorder)遍历:左子树->节点本身->右节点
♦后序(postorder)遍历:左子树->右子树->节点本身
6.1被参数化类型(Parameterized Types)
template <typename valType>
class BTnode {
public:
// ...
private:
valType _val;
int _cnt;
BTnode *_lchild;
BTnode *_rchild;
};
在实现中,BTnode class template必须和BinaryTree class template配合使用。
BTnode储存了节点实值、节点实值的重复次数、左右子节点的指针。
两个相互合作的class需要建立友谊。针对每一种BTnode实际类型,我们希望对应的BinaryTree实例能够成为其friend
。声明方法如下:
template <typename Type>
class BinaryTree;// 前置声明(forward declaration)
template <typename valType>
class BTnode {
friend class BinaryTree<valType>;
// ...
};
如果要将valType绑定至int,可以这么写:
BTnode<int> bti;
同样,如果将valType绑定至string,这么写:
BTnode<string> bts;
“【代入string】而产生出来的BinaryTree”是“【代入string】所产生出来的BTnode”的friend
。
BinaryTree class仅声明一笔数据:一个BTnode指针,指向二叉树根节点。
template <typename elemType>
class BinaryTree {
public:
// ...
private:
// BTnode必须以template parameter list加以限定
BTnode<elemType> *_root;
};
当我们指定某个实际类型为BinaryTree的参数,例如:
BinaryTree<string> st;
指针_root便指向一个“节点值类型为string”的BTnode。
同理,当我们指定的是int类型:
BinaryTree<int> it;
指针_root便指向一个“节点值类型为int”的BTnode。
6.2Class Template的定义(The Template Class Definition)
以下就是我们的BinaryTree class template的部分定义:
template <typename elemType>
class BinaryTree {
public:
BinaryTree();
BinaryTree(const BinaryTree&);
~BinaryTree();
BinaryTree& operator=(const BinaryTree&);
bool empty() { return _root == 0; }
void clear();
private:
BTnode<elemType> *_root;
// 将src所指子树(subtree)复制到tar所指子树
void copy(BTnode<elemType>* tar, BTnode<elemType>* src);
};
为class template定义一个inline函数,其做法就像为non-template class定义一个inline函数一样;上述的empty()示范了这一点。但是,在类体外,class template member function的定义语法却大相径庭:
template <typename elemType>
inline BinaryTree<elemType>::BinaryTree() : _root(0) { }
inline一词必须紧接在关键字template和参数列表之后。
为什么上述第二次出现的BinaryTree名称就不再需要限定了呢?
因为,在class scope运算符出现之后,BinaryTree< elemType>::其后所有东西都被视为位于class定义范围内。当我们写:
BinaryTree<elemType>:: // 在class定义范围之外
BinaryTree() // 在class定义范围之内
第二次出现的BinaryTree便被视为class定义范围内,所以不需要再加以限定。
以下是BinaryTree的copy constructor、copy assignment operator及destructor的定义:
template <typename elemType>
inline BinaryTree<elemType>::BinaryTree(const BinaryTree &rhs) {
copy(_root, rhs._root);
}
template <typename elemType>
inline BinaryTree<elemType>::~BinaryTree() { clear(); }
template <typename elemType>
inline BinaryTree<elemType>& BinaryTree<elemType>::operator=(const BinaryTree &rhs) {
if (this != &rhs) {
clear();
copy(_root, rhs._root);
}
return *this;
}
6.3Template类型参数的处理(Handling Template Type Parameters)
bool find(const Matrix &val);// by reference
bool find(Matrix val);// by value,没错,但缺乏效率!
当我们处理template类型参数时,我们无法得知用户实际要用的类型是否为语言内置类型:
BinaryTree<int> bti;// 对于内置类型,当然这里可以采用by value方式编写find()的参数列表
如果它是一个class类型:
BinaryTree<Matrix> btm;// 这就应该以by reference方式来编写find()的参数列表
实际运用中,不论内置类型或class类型,都可能被指定为class template的实际类型。在此建议,将所有的template类型参数视为“class类型”来处理。这意味着我们会把它声明为一个const reference,而非以by value方式传递。
在constructor定义中,选择在memeber initialization list内为每个类型参数进行初始化操作:
// 针对constructor的类型参数,以下是比较被大家喜爱的初始化做法:
template <typename valType>
inline BTnode<valType>::BTnode(const valType &val): _val(val) {
_cnt = 1;
_lchild = _rchild = 0;
}
而不选择在constructor函数体内进行:
template <typename valType>
inline BTnode<valType>::BTnode(const valType &val) {
// 不建议这样做,因为它可能是class类型!
_val = val;
// ok: 它们的类型不会改变,绝对是内置类型
_cnt = 1;
_lchild = _rchild = 0;
}
这么一来,当用户为valType指定一个class类型时,可以保证效率最佳。
例如,下面将valType指定为内置类型int:
BTnode<int> btni(42);
那么上述两种形式并无效率上的差异。但如果这么写:
BTnode<Matrix> btnm(transform_matrix);
效率上就有高下之分了。
constructor函数体对_val赋值操作可分解为两个步骤:
(1)函数体执行前,Matrix的default constructor会先作用于_val身上;
(2)函数体内会以copy assignment operator将val复制给_val。
如果我们采用上述第一种方法,在constructor的member initialization list中将_val初始化,那么只需一个步骤就能完成工作:以copy constructor将val复制给_val。
6.4实现一个Class Template(Implementing the Template Class)
每当我们插入某个新值,都必须建立BTnode对象、加以初始化、将它链接至二叉树的某处。我们必须自行以new表达式和delete表达式来管理每个节点的内存分配和释放。
以insert()为例,如果根节点的值尚未设定,它会由程序的空闲空间(free store)分配一块新的BTnode需要的内存空间。否则就调用BTnode的insert_value(),将新值插入二叉树中:
template <typename elemType>
inline void BinaryTree<elemType>::insert(const elemType &elem) {
if (!_root)
_root = new BTnode<elemType>(elem);
else
_root->insert_value(elem);
}
new表达式可分解为两个操作:
(1)向程序的空闲空间(free store)请示内存。如果分配到足够的空间,就返回一个指针,指向新对象。如果空间不足,会抛出bad_alloc异常。
(2)如果第一步成功,并且外界指定了一个初值,这个新对象便会以最适当的方式被初始化。对class类型来说:_root = new BTnode<elemType>(elem);
elem会被传入BTnode constructor。如果分配失败,初始化操作就不会发生。
当根节点存在时,insert_value()才会被调用。小于根节点的所有数值都放在根节点的左子树,大于根节点的所有数值都放在根节点的右子树。insert_value()会通过左右子节点递归(recursively)调用自己,直到以下任何一种情况发生才停止:(1)合乎资格的子树并不存在,(2)欲插入的数值已在树中。
由于每个数值只能在树中出现一次,所以我以BTnode的data member _cnt来记录这个节点的插入次数。以下为实现内容:
template <typename valType>
void BTnode<valType>::insert_value(const valType &val) {
if (val == _val) {
_cnt++;
} else if (val < _val) {
if (!_lchild)
_lchild = new BTnode(val);
else
_lchild->insert_value(val);
} else {
if (!_rchild)
_rchild = new BTnode(val);
else
_rchild->insert_value(val);
}
}
移除某值的操作更为复杂,因为我们必须保持二叉树的次序不变。一般的算法是,以节点的右子节点取代节点本身,然后搬移左子节点,使它成为右子节点的左子树的叶节点。如果此刻无右子节点,那么就以左子节点取代节点本身。为了简化,这里把根节点的移除操作作以特例处理。
template <typename elemType>
inline void BinaryTree<elemType>::remove(const elemType &elem) {
if (_root) {
if (_root->_val == elem)
remove_root();// 根节点的移除操作以特例处理
else
_root->remove_value(elem, _root);
}
}
这里不进行后续的二叉树具体实现的具体解剖了。直接上完整代码:
二叉树实现完整代码
#include <iostream>
#include <vector>
using namespace std;
template <typename elemType>
class BinaryTree;// 前置声明
template <typename elemType>
class BTnode;// 前置声明
template <typename valType>
ostream& foo(ostream& os, const BTnode<valType>& bt);
template <typename valType>
class BTnode {
friend class BinaryTree<valType>;// 友元类,结成友谊:BTnode<->BinaryTree
friend ostream& foo<valType>(ostream&, const BTnode<valType>&);// 友元函数
public:
BTnode(const valType& val);// 构造函数
const valType& value() const { return _val; }
int occurs() const { return _cnt; }
void remove_value(const valType&, BTnode*&);
void insert_value(const valType&);
bool find_value(const valType&) const;
void preorder(BTnode*, ostream&) const;// 前序遍历:节点本身-左子树-右子树
void inorder(BTnode*, ostream&) const;// 中序遍历:左子树-节点本身-右节点
void postorder(BTnode*, ostream&) const;// 后置遍历:左子树-右子树-节点本身
static void lchild_leaf(BTnode* leaf, BTnode* subtree);
private:
int _cnt; // occurrence count
valType _val;
BTnode* _lchild;
BTnode* _rchild;
void display_val(BTnode* pt, ostream& os) const;
BTnode(const BTnode&);// 私有复制构造函数
BTnode& operator=(const BTnode&);// 私有复制赋值运算符重载
};
template <typename valType>
inline BTnode<valType>::
BTnode(const valType& val) : _val(val) {// 构造函数实现
_cnt = 1;
_lchild = _rchild = 0;
}
template <typename valType>
void BTnode<valType>::
insert_value(const valType& val) {// 函数实现
if (val == _val) {
_cnt++;
(*BinaryTree<valType>::os()) << "BTnode::insert_value: increment count( "
<< val << " : " << _cnt << " )\n";
} else if (val < _val) {
if (!_lchild) {
_lchild = new BTnode(val);
(*BinaryTree<valType>::os()) << "ok: BTnode::insert_value at left child( " << val << " )\n";
} else
_lchild->insert_value(val);
} else {
if (!_rchild) {
_rchild = new BTnode(val);
(*BinaryTree<valType>::os()) << "ok: BTnode::insert_value at right child( " << val << " )\n";
} else
_rchild->insert_value(val);
}
}
template <typename valType>
bool BTnode<valType>::
find_value(const valType& val) const {// 函数实现
if (val == _val)
return true;
else if (val < _val) {
if (!_lchild)
return false;
else
return _lchild->find_value(val);
} else {
if (!_rchild)
return false;
else
return _rchild->find_value(val);
}
}
template <typename valType>
void BTnode<valType>::
lchild_leaf(BTnode* leaf, BTnode* subtree) {// static函数实现
while (subtree->_lchild)
subtree = subtree->_lchild;
subtree->_lchild = leaf;
}
template <typename valType>
void BTnode<valType>::
remove_value(const valType& val, BTnode*& prev) {// 函数实现
if (val < _val) {
if (!_lchild)
return; // not present
else
_lchild->remove_value(val, _lchild);
} else
if (val > _val) {
if (!_rchild)
return; // not present
else
_rchild->remove_value(val, _rchild);
} else { // ok: found it
// reset the tree then delete this node
if (_rchild) {
prev = _rchild;
if (_lchild)
if (!prev->_lchild)
prev->_lchild = _lchild;
else
BTnode<valType>::lchild_leaf(_lchild, prev->_lchild);
} else
prev = _lchild;
delete this;
}
}
template <typename valType>
inline void BTnode<valType>::
display_val(BTnode* pt, ostream& os) const {// private函数实现
os << pt->_val;
if (pt->_cnt > 1)
os << "( " << pt->_cnt << " ) ";
else
os << ' ';
}
template <typename valType>
void BTnode<valType>::
preorder(BTnode* pt, ostream& os) const {// 函数实现
if (pt) {
display_val(pt, os);
if (pt->_lchild) preorder(pt->_lchild, os);
if (pt->_rchild) preorder(pt->_rchild, os);
}
}
template <typename valType>
void BTnode<valType>::
inorder(BTnode* pt, ostream& os) const {// 函数实现
if (pt) {
if (pt->_lchild) inorder(pt->_lchild, os);
display_val(pt, os);
if (pt->_rchild) inorder(pt->_rchild, os);
}
}
template <typename valType>
void BTnode<valType>::
postorder(BTnode* pt, ostream& os) const {// 函数实现
if (pt) {
if (pt->_lchild) postorder(pt->_lchild, os);
if (pt->_rchild) postorder(pt->_rchild, os);
display_val(pt, os);
}
}
template <typename elemType>
class BinaryTree {
public:
BinaryTree();// 默认构造函数
BinaryTree(const vector< elemType >&);// 构造函数
BinaryTree(const BinaryTree&);// 复制构造函数
~BinaryTree();
BinaryTree& operator=(const BinaryTree&);// 复制赋值运算符重载
void insert(const vector< elemType >&);
void insert(const elemType&);
void remove(const elemType&);
void clear() { clear(_root); _root = 0; } // remove entire tree ...
bool empty() { return _root == 0; }
void inorder(ostream& os = *_current_os) const {
_root->inorder(_root, os);
}
void postorder(ostream& os = *_current_os) const {
_root->postorder(_root, os);
}
void preorder(ostream& os = *_current_os) const {
_root->preorder(_root, os);
}
bool find(const elemType&) const;
ostream& print(ostream& os = *_current_os,
void (BinaryTree<elemType>::* traversal)(ostream&) const =
&BinaryTree<elemType>::inorder) const;
static void current_os(ostream* os) { if (os) _current_os = os; }
static ostream* os() { return _current_os; }
private:
BTnode<elemType>* _root;
static ostream* _current_os;
// copy a subtree addressed by src to tar
void copy(BTnode<elemType>*& tar, BTnode<elemType>* src);
void clear(BTnode<elemType>*);
void remove_root();
};
template <typename elemType>
ostream* BinaryTree<elemType>::_current_os = &cout;// private static赋值
template <typename elemType>
inline BinaryTree<elemType>::
BinaryTree() : _root(0) {}// 构造函数实现
template <typename elemType>
inline BinaryTree<elemType>::
BinaryTree(const BinaryTree& rhs) {// 复制构造函数实现
copy(_root, rhs._root);
}
template <typename elemType>
inline BinaryTree<elemType>::
~BinaryTree() { clear(); }// 析构函数实现
template <typename elemType>
inline BinaryTree<elemType>& BinaryTree<elemType>::
operator=(const BinaryTree& rhs) {// 重载复制赋值运算符实现
if (this != &rhs) {
clear();
copy(_root, rhs._root);
}
}
template <typename elemType>
inline void BinaryTree<elemType>::
insert(const elemType& elem) {// 函数实现
if (!_root) {
(*BinaryTree<elemType>::os()) << "BinaryTree::insert: root( " << elem << " )\n";
_root = new BTnode<elemType>(elem);
} else
_root->insert_value(elem);
}
template <typename elemType>
BinaryTree<elemType>::
BinaryTree(const vector< elemType >& vec) {// 构造函数实现
_root = 0;
for (int ix = 0; ix < vec.size(); ++ix)
insert(vec[ix]);
}
template <typename elemType>
void BinaryTree<elemType>::
insert(const vector< elemType >& vec) {// 函数实现
for (int ix = 0; ix < vec.size(); ++ix)
insert(vec[ix]);
}
template <typename elemType>
inline void BinaryTree<elemType>::
remove(const elemType& elem) {// 函数实现
if (_root) {
if (_root->_val == elem)
remove_root();
else
_root->remove_value(elem, _root);
}
}
template <typename elemType>
void BinaryTree<elemType>::
remove_root() {// private函数实现
if (!_root) return;
BTnode<elemType>* tmp = _root;
if (_root->_rchild) {
_root = _root->_rchild;
BTnode<elemType>* lc = tmp->_lchild;
BTnode<elemType>* newlc = _root->_lchild;
// if left child of root is non-null
// attach it as leaf to left subtree
if (lc)
if (!newlc)
_root->_lchild = lc;
else
BTnode<elemType>::lchild_leaf(lc, newlc);
}
else
_root = _root->_lchild;
delete tmp;
}
template <typename elemType>
void BinaryTree<elemType>::
clear(BTnode<elemType>* pt) {// private函数实现
if (pt) {
clear(pt->_lchild);
clear(pt->_rchild);
delete pt;
}
}
template <typename elemType>
ostream& BinaryTree<elemType>::
print(ostream& os, void (BinaryTree::* traversal)(ostream&) const) const {// 函数实现
(this->*traversal)(os);
return os;
}
template <typename elemType>
inline ostream&
operator<<(ostream& os, const BinaryTree<elemType>& bt) {// output运算符重载实现
os << "Tree: " << endl;
bt.print(os, &BinaryTree<elemType>::inorder);
return os;
}
template <typename elemType>
inline bool BinaryTree<elemType>::
find(const elemType& elem) const {// 函数实现
return !_root ? false : _root->find_value(elem);
}
template <typename elemType>
void BinaryTree<elemType>::
copy(BTnode<elemType>*& tar, BTnode<elemType>* src) {// private函数实现
if (src) {
tar = new BTnode<elemType>(src->_val);
if (src->_lchild) copy(tar->_lchild, src->_lchild);
if (src->_rchild) copy(tar->_rchild, src->_rchild);
}
}
#include <string>
#include <algorithm>
#include <fstream>
using namespace std;
int main() {
/*
BinaryTree< int > bt;
bt.insert( 7 );
bt.insert( 5 );
bt.insert( 9 );
bt.insert( 6 );
bt.insert( 3 );
*/
/*
BinaryTree< string > bt;
bt.insert( "Piglet" );
bt.insert( "Pooh" );
bt.insert( "Eeyore" );
bt.insert( "Kanga" );
bt.insert( "Tigger" );
*/
ofstream log("logfile.txt");
if (!log) {
cerr << "error: unable to open file!\n";
return -1;
} else
BinaryTree<string>::current_os(&log);
/*
int ia[] = { 24, 18, 36, 12, 14, 8, 24, 1, 42, 24, 8, 8, 16, 55 };
vector< int > ivec( ia, ia + 14 );
BinaryTree<int> bt( ivec );
log << "preorder traversal: \n";
// cout << should see\n\t ";
bt.preorder( log );
bt.clear();
log << "\nbt is now " << ( bt.empty() ? " empty! " : " oops -- not empty!" ) << endl;
sort( ivec.begin(), ivec.end() );
bt.insert( ivec );
log << "\n\ninorder traversal:\n";
bt.inorder( log );
bt.insert( ivec );
log << "\n\npostorder traversal:\n";
bt.postorder( log );
log << endl << endl;
*/
BinaryTree<string> bt;
bt.insert("Piglet");
bt.insert("Eeyore");
bt.insert("Roo");
bt.insert("Tigger");
bt.insert("Chris");
bt.insert("Pooh");
bt.insert("Kanga");
log << "preorder traversal: \n";
bt.preorder(log);
log << "\n\nabout to remove root: Piglet\n";
bt.remove("Piglet");
log << "\n\npreorder traversal after Piglet removal: \n";
bt.preorder(log);
log << "\n\nabout to remove Eeyore\n";
bt.remove("Eeyore");
log << "\n\npreorder traversal after Piglet removal: \n";
bt.preorder(log);
// log << "\n\ninorder traversal:\n";
// bt.inorder( log );
// log << "\n\npostorder traversal:\n";
// bt.postorder( log );
return 0;
}
6.5一个以Function Template完成的Output运算符(A Function Template Output Operator)
我希望为我们的BinaryTree class template提供一个output运算符,针对template class,我们可以明确指出生成的每个class的名称:
ostream& operator<<(ostream&, const BinaryTree<int>&);
比较好的解法是,将output运算符定义为一个function template:
template <typename elemType>
inline ostream&
operator<<(ostream &os, const BinaryTree<elemType> &bt) {
os << "Tree: " << endl;
bt.print(os);
return os;
}
print()乃是BinaryTree class template的一个private
member function。为了让上述的output运算符得以顺利调用print(),output运算符必须成为BinaryTree的一个friend
:
template <typename elemType>
class BinaryTree {
friend ostream& operator<<(ostream&, const BinaryTree<elemType>&);
// ...
};
6.6常量表达式与默认参数值(Constant Expressions and Default Parameters)[template <_type, _name>]
Template参数并不是非得某种类型(type)不可。我们也可以用常量表达式(constant expression)作为template参数。
例如,先前的数列类继承体系可以重新以class template设计,将“对象所含的元素个数”参数化:
template <int len>
class num_sequence {
public:
num_sequence(int beg_pos = 1);
// ...
};
template <int len>
class Fibonacci : public num_sequence<len> {
public:
Fibonacci(int beg_pos = 1) : num_sequence<len>(beg_pos){}
// ...
};
产生Fibonacci对象,像这样:
Fibonacci<16> fib1;
Fibonacci<16> fib2(17);
以上两个Fibonacci对象,由于基类num_sequence会因为参数len而导致元素个数为16。
同理,我们也可以将长度和起始位置一并参数化:
template <int len, int beg_pos>
class NumericSeries;
由于大部分数列对象的起始位置为1,如果我们能为起始位置提供默认值,就更理想了:
template <int len, int beg_pos>
class num_sequence { ... };
template <int len, int beg_pos = 1>
class Fibonacci : public num_sequence<len, beg_pos> { ... };
以下是上述类的对象定义方式:
num_sequence<32,1> *pnslto32 = new Fibonacci<32,1>;
num_sequence<32> *pnslto32 = new Fibonacci<32>;// 意义同上
num_sequence<32,33> *pns33to64 = new Fibonacci<32,33>;// 覆盖掉参数默认值
有了以上知识点,我们可以重新定义num_sequence。不再需要储存“长度”和“起始位置”这两笔data member了。
template<int len, int beg_pos>
class num_sequence {
public:
// ...
protected:
// ...
};
// output运算符的function template定义
template <int len, int beg_pos> ostream&
operator<<(ostream &os, const num_sequence<len, beg_pos> &ns) {
return ns.print(os);
}
template <int length, int beg_pos>
ostream& num_sequence<length, beg_pos>::print(ostream &os) const {
int elem_pos = beg_pos - 1;
int end_pos = elem_pos + length;
if (!check_integrity(end_pos, _pelems->size()))
return os;
os << "( " << beg_pos << " , " << length << " ) ";
while (elem_pos < end_pos)
os << (*_pelems)[elem_pos++] << ' ';
return os;
}
int main() {
Fibonacci<8> fib1;// 长度为8,起始位置为默认值1
Fibonacci<8, 8> fib2;// 长度为8,起始位置为8
Fibonacci<12, 8> fib3;// 长度为12,起始位置为8
cout << "fib1:" << fib1 << '\n'
<< "fib2:" << fib2 << '\n'
<< "fib3:" << fib3 << endl;
return 0;
}
fib1: (1, 8) 1 1 2 3 5 8 13 21
fib2: (8, 8) 21 34 55 89 144 233 377 610
fib3: (8, 12) 21 34 55 89 144 233 377 610 987 1597 2584 4181
全局作用域(global scope)内的函数及对象,其地址也是一种常量表达式,因此也可以被拿来表达这一形式的参数。
例如,以下是一个接受函数指针作为参数的数列类:
template <void (*pf)(int pos, vector<int> &seq)>
class numeric_sequence {
public:
numeric_sequence(int len, int beg_pos = 1) {
if (!pf)
// 产生错误信息并退出
_len = len > 0 ? len : 1;
_beg_pos = beg_pos > 0 ? beg_pos : 1;
pf(beg_pos + len - 1, _elems);
// ...
private:
int _len;
int _beg_pos;
vector<int> _elems;
};
本例中的pf是一个指向“依据特定数列类型,产生pos个元素,放到vector seq内”的函数。用法如下:
void fibonacci(int pos, vector<int> &seq);
void pell(int pos, vector<int> &seq);
// ...
numeric_sequence<fibonacci> ns_fib(12);
numeric_sequence<pell> ns_pell(18, 18);
6.7以Template参数作为一种设计策略(Template Parameters as Strategy)
template <typename elemType>
class LessThan {
public:
LessThan(const elemType &val) : _val(val) {}
bool operator()(const elemType &val) const { return val < _val; }
elemType val() const { return _val; }
private:
elemType _val;
};
LessThan<int> lti(1024);
LessThan<string> lts("Pooh");
上述做法有个潜在问题:一旦用户所提供的类型并未定义less-than运算符,上述做法便告失败。
另一种可行策略便是提供第二个class template,将comparison运算符从类定义中剥离。
template <typename elemType, typename Comp = less<elemType>>
class LessThanPred {
public:
LessThanPred(const elemType &val) : _val(val) {}
bool operator()(const elemType &val) const {
return Comp(val, _val);
}
void val(const elemType &newval) { _val = newval; }
elemType val() const { return _val; }
private:
elemType _val;
};
// 另一个提供比较功能的function object
class StringLen {
public:
bool operator()(const string &s1, const string &s2) {
return s1.size() < s2.size();
}
};
LessThanPred<int> ltpi(1024);
LessThanPred<string, StringLen> ltps("Pooh");
我们可以另一个更通用的名称来命名function object,表示它足以支持任何类型的比较操作。那么本例就不需要提供默认的function object了:
template <typename elemType, typename BinaryComp>
class Compare;
Compare可将任何一种“BinaryComp操作”应用于两个同为elemType类型的对象身上。
在上一章中设计了一个面向对象的数列类体系。请思考以下另一种设计。在其中,我将数列类定义为class template,而将实际的数列类剥离成为参数:
template <typename num_seq>
class NumericSequence {
public:
NumericSequence(int len = 1, int bpos = 1): _ns(len, bpos) {}
// 以下会通过函数的命名规范,调用未知的数列类的同名函数。
// 此处所谓函数命名规范是:每个num_seq参数类都必须提供名为
// calc_elems()和is_elem()的函数。
void calc_elems(int sz) const { _ns.calc_elems(sz); }
void is_elem(int elem) const { return _ns.is_elem(elem); }
// ...
private:
num_seq _ns;
};
这种独特的设计虽然比较高级,所以不要认为class template的类型参数仅能用以传递元素类型——像二叉树或标准库的vector、list等容器那样。
6.8Member Template Function(Member Template Functions)
当然,我们也可以将member function定义成template形式。
class PrintIt {
public:
PrintIt(ostream &os): _os(os) {}
// 下面是一个member template function
template <typename elemType>
void print(const elemType &elem, char delimiter = '\n') {
_os << elem << delimiter;
}
private:
ostream& _os;
};
以下是PrintIt的可能用法:
int main() {
PrintIt to_standard_out(cout);
to_standard_out.print("hello");
to_standard_out.print(1024);
string my_string("i am a string");
to_standard_out.print(my_string);
}
hello
1024
i am a string
Class template内也可以定义member template function。
template <typename OutStream>
class PrintIt {
public:
PrintIt(OutStream &os): _os(os) {}
template <typename elemType>
void print(const elemType &elem, char delimiter = '\n') {
_os << elem << delimiter;
}
private:
ostream& _os;
};
int main() {
PrintIt<ostream> to_standard_out(cout);
to_standard_out.print("hello");
to_standard_out.print(1024);
string my_string("i am a string");
to_standard_out.print(my_string);
}
除了本章所谈到的,template还有许多议题有待进一步研究。