模板概念:
最初设计C++时,template称为被参数化的类型。参数化是因为类型的相关信息可以从template定义中剥离。类型是指每一个类模板或函数模板基本上都随着它作用或它所内含的类型而有性质上的变化。
而后template被通俗的称为模板。作用:可以根据用户指定的特定类型或特定值,自动产生一个函数或类。
实现模板:
在学习或是做题中我们经常使用标准库给我们提供的一些模板类(例如:vector、string等),我们从中可以体会到模板类的一些特性,这在我们实现中有一定参考价值。
为方便说明,之后将实现一个二叉树的类模板来解释模板化编程。
-
被参数化的类型
通常在构造二叉树时,需要定义两个类:
typedef int DataType;
class BTNode{
BTNode(DataType data = 0)
:_data(data)
, _leftChild(NULL)
, _rightChild(NULL)
{}
DataType _data;
int _count;
BTNode* _leftChild;
BTNode* _rightChild;
};
class BinaryTree{
// ...
BTNode* _root;
};
如代码所示:BTNode类用来存储节点实值,BinaryTree类用来存储指向根节点的指针。在没有模板机制下,如果我们为了存储不同类型的数值,就需要对程序代码进行更改。
template机制就是为了解决这种麻烦,将类定义中“与类型相关”和“与“独立于类型之外”的两部分分离开来。我们所要做的只需将与类型相关部分参数化即可。(比如:BTNode类中的_data成员类型便可以被参数化)
对类进行模板化:
template <typename Type>
class BinaryTree;
template <typename elemType>
class BTNode{
// ...
friend class BinaryTree<elemType>;
elemType _data;
int _count;
BTNode* _leftChild;
BTNode* _rightChild;
};
在此定义中,elemType被当作占位符,其名称可以任意定义。在用户指定具体的类型前,它可以指代为任意类型的数据。
由于BTNode类存储BinaryTree类对象的属性,所以其要配合使用,因此将BinaryTree声明为BTNode类的友元。
对其进行实例化的方式是:
BTNode< int > bti; // 类模板名+<实际类型> 对象名;
通过实例化,以 int 取代 _data,代入 int 所产生出来的BTNode的友元。
---什么情况下需要模板参数列表限定?
在class template及其成员的定义中不需要,而其他场合下就需要加以限定。
-
模板的定义
如下实现对BinaryTree模板类的部分定义:
template <typename elemType>
class BinaryTree{
public:
BinaryTree();
BinaryTree(const BinaryTree&);
BinaryTree& operator=(const BinaryTree&);
inline bool empty(){ return _root == 0; }
void clear();
~BinaryTree();
private:
BTNode<elemType>* _root;
void copy(BTNode<elemType>* tar, BTNode<elemType>* src);
};
从上可以看出内联函数在模板类中的定义与一般类的定义一样。但是如果在内外定义就有所不同。
template <typename elemType>
inline bool BinaryTree<elemType>::empty()
{
return _root == 0;
}
可以看出在关键字template和参数列表之后加inline,之后是函数返回值类型,由于在内外定义所以在内联函数名称前加作用域限定。
同样实现其他成员函数:
// 构造函数
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<elemType>::operator=(const BinaryTree& rhs)
{
if (this != &rhs){
clear();
copy(_root, rhs._root);
}
return *this;
}
// 析构函数
template <typename elemType>
inline BinaryTree<elemType>::~BinaryTree()
{
clear();
}
-
模板类型参数处理
1. 对参数的传递方式?
- 在处理内置类型参数时,会以传值方式进行参数传递
void func(int val);
- 在处理自定义类型的参数时,多以传引用的方式传递 (也可以传值方式,但开销大)
void func(const custom& val);
- 不论内置类型还是自定义类型,都可能被指定为类模板的实际类型。建议使用by reference的方式来处理。
2. 在构造函数定义中,如何选择参数初始化?
- 在初始化列表:
template <typename valType>
inline BTNode<valType>::BTNode(const valType& data)
:_data(data)
{
_count = 1;
_rightChild = _leftChild = 0;
}
- 在构造函数体内:
template <typename valType>
inline BTNode<valType>::BTNode(const valType& data)
{
_data = data;
_count = 1;
_rightChild = _leftChild = 0;
}
比较建议使用的是第一种初始化方式。因为效率高。
-
常量表达式作参数
除了数据类型可以作模板参数外,还可以用常量表达式作参数。
- 例如:对一个数列进行模板化:
template <int len, int beg_pos>
class num_sequence{...};
也可以在模板函数的参数列表位置提供默认值:
template <int len = 10, int beg_pos = 1>
class num_sequence{...};
- 全局作用域下的函数及对象,其地址也是一种常量表达式,因此也可以作为参数使用。
template <void (*pf) ()>
class num_sequence{...}