C++笔记(1) —— 模板
简述
面向对象编程(OOP)和泛型编程(GP)是C++语言的两个不同分支,而模板就是C++泛型编程的基础。模板就等于是一个类或者函数的基本公式,在编译时就能获知具体类型从而展开成对应的一份代码。
模板在什么地方使用,如何使用,想法也很简单。在设计一个类或者函数的时候,如果认为哪一些类型可以抽出来,允许使用者任意指定,那么就可以抽出这些类型,然后将该类型或者函数抽象成一个模板,在使用的时候再去指定类型即可。
用法
模板定义可以通过使用
template <typename T>
这样的形式,声明在类或函数前,然后里面所有用到的类型都用 T 替代即可。
1. 类模板
类模板中,一般是将类的数据类型抽出来,在不同的类型下可以同样定义出这个类。例如下面这个二叉搜索树的代码,就是将元素类型抽象成为一个模板 Comparable,在实际使用的时候就可以对应不同的类型生成树。
template <typename Comparable>
class BinarySearchTree
{
public:
BinarySearchTree() : root{nullptr} {}
BinarySearchTree(const BinarySearchTree& rhs) { root = clone(rhs.root); }
BinarySearchTree(BinarySearchTree&& rhs) : root{ std::move(rhs.root) } { rhs.root = nullptr; }
~BinarySearchTree();
BinarySearchTree& operator= (const BinarySearchTree& rhs);
BinarySearchTree& operator= (BinarySearchTree&& rhs);
const Comparable& findMin() const;
const Comparable& findMax() const;
bool contains(const Comparable& x) const { return contains(x, root); }
bool isEmpty() const { return root == nullptr; }
void makeEmpty() { makeEmpty(root); }
void insert(const Comparable& x) { insert(x, root); }
void insert(Comparable&& x) { insert(std::move(x), root); }
void remove(const Comparable& x) { remove(x, root); }
void print(ostream& out = cout) const { print(root, out); out << endl;}
private:
struct BinaryNode
{
Comparable element;
BinaryNode* left;
BinaryNode* right;
BinaryNode(const Comparable& x, BinaryNode* lt, BinaryNode* rt)
: element{x}, left{lt}, right{rt} {}
BinaryNode(Comparable&& x, BinaryNode* lt, BinaryNode* rt)
: element{std::move(x)}, left{lt}, right{rt} {}
};
BinaryNode* root;
}
使用方法也很简单,如下所示,只需要先指定类型然后直接使用即可:
BinarySearchTree<double> bst;
bst.insert(3.14);
2. 函数模板
函数模板和类模板基本也是一样的,同样是将类型抽出来。而使用的时候则更方便,不需要指定类型,编译器会对函数模板进行实参推导。
template <typename T>
inline const T& min(const T& a, const T& b)
{
return a < b ? a : b;
}
int r1 = 1, r2 = 2, r3;
r3 = min(r1, r2);
还有一种比较特别一点的用法,非类型模板函数:
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char(&p2)[M])
{
return strcmp(p1, p2);
}
compare("hi", "mom");
此时,参数代表了一个值而不是类型,需要通过特定的类型名而不是使用typename或class来指定。
3. 成员模板
一个类可以包含本身是模板的成员函数,这个成员就被称为成员模板。
template <typename T1, typename T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
pair(const T1& a, const T2& b) : first(a), second(b) {}
template<typename U1, typename U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
}
成员模板的用途一般在一些模板的模拟up-cast上。基类和继承类之间可以有一种up-cast的关系,同样,如果一个模板类可以用任意的T1, T2 构造,那么应该也允许继承自T1,T2的类D1, D2来构造。例如:
Derived1 dr1;
Base1 base1(dr1);
Derived2 dr2;
Base2 base2(dr2);
pair<Derived1, Derived2> p1;
pair<Base1, Base2> p2(p1);
模板特化
特化,就是泛化的反面。有时单一的模板可能会无法适合某些实参,这个时候就需要通过特化来满足模板定义这些特定类型。又或者对于某些特定的类型,可以有更好的算法或者更高效的代码,这个时候,也可以通过模板特化来实现。
如何模板特化也很简单,只需要多加一个特殊版本就可以实现了:
template <typename T>
int compare(const T& a, const T& b)
{
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
template<>
int compare(const char* const &p1, const char* const &p2)
{
return strcmp(p1, p2);
}