六、以template进行编程

本文深入探讨了C++中的模板(template)编程,介绍了如何使用template创建函数和类,以实现泛型编程。通过二叉树类模板示例,阐述了类型参数化、成员函数定义以及模板参数在不同场景下的应用。同时,讨论了template与类型相关和独立于类型之外的部分,以及template类型参数的处理方式。
  • Bjarne Stroustrup(C++创造者)最初将template称为被参数化的类型(parameterized type):称其参数化是因为类型相关信息可自template定义中剥离,称其类型则因为每个class template或function template基本上都随着他所作用或它所包含的类型而有性质上的变化,本身就像某种类型。
  • template(模板)能根据用户指定的特定值或特定类型,自动产生一个函数或类。
  • 二叉树(binary tree)class template包含两个class,BinaryTree用来储存指向根节点的指针,BTnode用来储存节点实值以及连接至左、右两个节点的链接。节点实值的类型(value type)正是我们希望加以参数化的部分。
  • BinaryTree类提供的操作行为有:插入(insert)、移除(remove),搜寻(find),清除(clear)、以特定遍历方式打印(print),遍历方式有:中序(inorder)、前序(preorder)、后序(postorder)。
  • template机制将类定义中 与类型相关(type-dependent)独立于类型之外 的两部分分离开来。
template <typename Type>
class BinaryTree; //前置声明 

template <typename valType>
class BTnode {
	//针对每一种BTnode实际类型,
	//使对应的BinaryTree实例成为其friend 
	friend class BinaryTree<valType>; 
public:
	//...
private:
	valType 	_val;
	int 		_cnt; //同一值的插入次数 
	BTnode 		*_lchild;
	BTnode 		*_rchild;
};
  • BinaryTree class仅声明一个 BTnode 指针,指向二叉树的根节点:
template <typename elemType>
class BinaryTree {
public:
	//...
private:
	//BTnode必须以template parameter list加以限定
	BTnode<elemType> *_root;
};
  • 在class template及其member的定义中,不必以template parameter list进一步限定class template,除此之外的场合均要限定(如上所示)。
  • 为class template定义一个inline函数,做法和为non-template class定义一个inline函数一样。但是,在类体外,class template member function的定义语法有所不同:
template <typename elemType> 
inline BinaryTree<elemType>:: //在class定义范围之外
BinaryTree() : _root(0)		  //在class定义范围之内	
{}

/*
开始于关键字template和一个参数列表,然后便是函数定义本身,
并带有关键字inline和class scope运算符。
inline必须紧跟在template和参数列表之后。
*/

template <typename elemType>
inline BinaryTree<elemType>& //inline + 返回类型 
BinaryTree<elemType>:: //class scope限定 
operator=(const BinaryTree &rhs)
{
	if(this != &rhs)
	{
		clear();
		copy(_root, rhs>_root);
	}
	return *this;
}

在class scope运算符出现之后,其后所有东西都被视为位于class定义范围内,所以第二次出现的BinaryTree就不再需要限定了。

  • 不论内置类型或class类型,都可能被指定为class template的实际类型。建议将所有的template类型参数视为class 类型来处理,这意味着,我们会把它声明为一个const reference,而非以传值(by value)方式传递:(即使内置类型的值传递效率往往高于引用传递
//constructor定义中在member initialization list初始化
template <typename valType> 
inline BTnode<valType>::
BTnode(const valType &val) 
	//将valType视为某种class类型 
	: _val(val)
{
	//_val = val; //不这样做,因为val可能是class类型 
	_cnt = 1;
	_lchild = _rchild = 0;
}

_val是class类型MatrixBTnode<Matrix> btnm( matrix_a );
constructor函数体内对_val的赋值操作可分为两个步骤:
(1)函数体执行前,Matrix的default constructor会先作用于_val身上;
(2)函数体内以copy assignment operator将val复制给_val
而member initialization list只需一步:
(1)以copy assignment operator将val复制给_val

  • new表达式可分解为两个操作:
    • 向程序的空闲空间请求内存。如果分配到足够的空间,就返回一个指针指向新对象(如果空间不足,会抛出 bad_alloc 异常( 第七章))
    • 如果第一步成功,并且外界指定了一个初值,这个新对象便会以最适当的方式被初始化。
  • 以pointer来传递参数,只能更改pointer所指的对象,不能更改pointer本身。reference to pointer(*&)使我们不但可以改变pointer本身,也可以改变此pointer指向的对象。
  • 将output运算符定义为一个function template:
template <typename elemType>
inline ostream&
operator<<(ostream &os, const BinaryTree<elemType> &bt)
{
	os << "Tree: " << endl;
	bt.print(os);
	return os;
} 

BinaryTree<string> bts;
cout << bts <<endl; //第二参数为 BinaryTree<string> 

BinaryTree<int> bti;
cout << bti <<endl; //第二参数为 BinaryTree<int>
  • template参数并不仅是类型(type),也可以使用常量表达式:
//将对象所含的元素个数参数化
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<16> fib1; //beg_pos = 1,len = 16
Fibonacci<16> fib2(17); //beg_pos = 17,len = 16
  • 全局作用域内的函数及对象,其地址也是一种常量表达式,因此也可以用来作为template的参数 :
//pf是一个指向 依据特定数列类型,产生pos个元素,放到vector seq内 的函数 
template<void (*pf)(int pos, vector<int> &seq)>
class numeric_sequence {
public:
	numeric_sequence(int len, int beg_pos = 1)
	{
		//检查pf是否为null
		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;
};


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, 8); 
  • class template无法基于参数列表的不同而重载。
  • 判断:class template的类型参数仅能用以传递元素类型——像二叉树或标准库的vector、list容器那样。 ( ×
//将数列类定义为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);}
	bool is_elem(int elem) const {return _ns.is_elem(elem);}
	
	//...

private:
	num_seq _ns; 
}; 
  • member function也可以定义为template形式;class template内也可以定义member template function。
  • ofstream是ostream的子类。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值