6-以template进行编程

本文主要围绕C++展开,详细介绍了二叉树的实现,包含BinaryTree和BTnode两个类。阐述了类模板的定义、类型参数处理、内存管理等内容,还提及了输出运算符、常量表达式与默认参数值等相关知识,给出了二叉树实现的完整代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在数据结构中,所谓树(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");
左子节点
右子节点
叶节点
叶节点
叶节点
叶节点
Piglet
Eeyore
Roo
Chris
Kanga
Pooh
Tigger

任何遍历算法(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还有许多议题有待进一步研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值