模板
模板是一种通用的描述机制,也就是说,使用模板允许使用通用类型来定义函数或类等,在使用时,通用类型可被具体的类型,如int、double甚至是用户自定义的类型来代替。简单来说,模板就是数据类型参数化,当然不光是数据,算法也行。
为什么要模板:
形象地说,把函数比喻为一个游戏过程,函数的流程就相当于游戏规则,在以往的函数定义中,总是指明参数是int型还是double型等等,这就像是为张三(好比int型)和李四(好比double型)比赛制定规则。可如果王五(char*型)和赵六(bool型)要比赛,还得提供一套函数的定义,这相当于又制定了一次规则,显然这是很麻烦的。模板的的引入解决了这一问题,不管是谁和谁比赛,都把他们定义成A与B比赛,制定好了A与B比赛的规则(定义了关于A和B的函数)后,比赛时只要把A替换成张三,把B替换成李四就可以了,大大简化了程序代码量,维持了结构的清晰,大大提高了程序设计的效率。该过程称为“类型参数化”。
强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决强类型的严格性和灵活性的冲突。有以下3中方式解决:
- 宏替换:不做类型检查
- 函数重载:为每个类型提供一个重载版本,程序自己维护这些重载的版本。是一种’类型参数化’的多态。函数版本编译器决定,属于静态多态。
- 函数模板:为相同逻辑提供一个模板,将类型当做参数来传递,让编译器实例化对应版本的函数 来处理(让编译器维护不同的重载函数,程序维护一份函数模板),结合了宏替换和函数重载。
模板形参在模板中作为一种类型使用,可以用于函数形参、函数返回值和函数局部变量;
每个模板形参都要在函数的“形参列表”中至少出现一次;
模板参数名的作用域局限于函数模板范围内。
关键字typename
用以说明后面的东西是个类型,不是数据成员
template<typename T>
class MyClass3
{
private:
// T::SubType * ptr1_; // T里面的静态数据成员与ptr_相乘,与意图不符
typename T::SubType * ptr_; // 加上typename关键字后,就会被解析成T里面的'子类型'的一个指针
};
派生类和模板
- 为了运行效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配器(adapter)来完成的。通用性是模板库的设计出发点,这是由泛型算法和函数对象等手段达到的。
- 派生类的目标之一是代码的复用和程序的通用性,派生类的优点是可以由简到繁,逐步深入,程序编制过程中,可以充分利用前面的工作,一步步完成一个复杂的任务,这种方式有一定的开销。
- 模板最求的是运行效率。而派生追求的是编程的效率.
面向对象与泛型
- 面向对象,通过基类接口,使用一套代码,处理不同的派生类;泛型是使用一套代码[模板]处理不同的数据类型。
函数模板:
函数模板的使用规则和普通函数是相同的,在使用函数模板之前,必须对函数模板进行声明,此声明必须在外部进行,也就是说不能在任何一个函数(包括main函数)中声明,声明的格式为:
template <class T1[,class T2,……]>
函数原型
函数模板的模板参数可以是:类型参数(数据结构/容器也可以看成类型)、非类型参数(只能是int),需要注意的是,函数模板的模板参数不能有默认值。
函数模板实际上不是个完整的函数定义,因为其中的类型参数还不确定,只是定义了某些类型的角色(或变量)在函数中的操作形式,因此,必须将模板参数实例化才能使用函数,用模板实例化后的函数也称为模板函数。分为隐式实例化(依靠编译器的类型推导)和显式实例化
整个编译过程中,模板被编译了两次:
- 实例化之前,检查模板代码本身,查看语法是否正确;
- 实例化期间,检查模板代码,查看是否所有调用都有效。
普通函数只需要声明,即可顺利编译,而模板的编译需要查看模板的定义。C++是分离编译,头文件和实现文件是单独编译的,如果把模板的声明和实现分开,在编译的时候就没法推导出模板函数,即无法实例化,链接的时候就会出错。 因此模板的声明和实现必须在一起,不能像一般函数那样分开。
函数模板特化:
针对某种数据类型进行特化,还是模板函数
重载函数模板:
函数模板支持重载,既可以模板之间重载(同名模板),也可以是模板和普通函数间的重载
遇到重载时,一般函数优先于模板函数的执行
int Max(int i1,int i2)
{
cout<<"Normal Max"<<endl;
return i1>i2? i1:i2;
}
template<class T>
T Max(T t1, T t2)
{
cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl;
return t1>t2? t1:t2;
}
// 与上面的构成重定义
// template<class T1>
// T1 Max(T1 t1, T1 t2)
// {
// cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl;
// return t1>t2? t1:t2;
// }
// Max<int>(int, int) 与 Max(int, int)不构成重定义,遇到这种情况,优先调用普通(非模板)函数。
再看个例子:
max.hpp:
#include <cstring>
template <typename T> // 函数模板的参数列表
const T& Max(const T& a, const T& b)
{
return a > b ? a : b;
}
// 函数模板重载,不同的形参个数
template <typename T>
const T& Max(const T& a, const T& b, const T& c)
{
return Max(a,b) > c ? Max(a,b) : c;
}
// 模板特化,针对char*这种数据类型。
template<> // 也可以不加这个,那就成了非模板函数,只是构成重载
const char* const& Max(const char* const& a, const char* const &b)
{
return strcmp(a, b) < 0 ? b : a;
}
// 非模板函数,与函数模板构成重载
const int& Max(const int& a, const int& b)
{
cout << "no template function" << endl;
return a > b ? a : b;
}
main.cpp:
#include <iostream>
using std::cout;
using std::endl;
#include "max.hpp"
class Test
{
};
class Test2
{
public:
friend bool operator> (const Test2& a, const Test2& b)
{
return true;
}
};
int main(void)
{
// ::表示全局函数,一般用于自己函数与库函数名相同,调用自己的函数。
// 这里没有在<>中实例化数据类型,实际上是编译器自己根据传的实参推导出了模板函数。
double res = ::Max(3.6, 5.4);
char ch = Max('A', 'a');
cout.setf(std::ios::fixed);
cout.precision(4);
cout << res << ch << endl;
// Test t1, t2;
// Test ret = Max(t1, t2); // 调用无效,Test不支持>运算符
Test2 t1, t2;
Test2 ret = Max(t1, t2); // 调用有效效,Test2支持>运算符
const char* str1 = "zzz";
const char* str2 = "aaa";
// 此时比较的是指针变量,而不是字符串内容,与预期不符,可以通过模板特化来修正。
cout << Max(str1, str2) << endl;
cout << Max(1.0, 5.0, 3.0) << endl;
cout << Max('a', 50) << endl; // 调用的是重载的非模板函数
// 调用的是模板函数,数据类型是显示给定的,不是编译器自己推导的
cout << Max<int>('a', 50) << endl;
// cout << Max<>('a', 50) << endl; // 调用的是模板函数,只是要由编译器自己推导数据类型,
// 如果推导不出来,就会编译出错。
return 0;
}
类模板
模板的引入使得类的定义更为灵活,可以在类创建时指明其中的元素类型T,以及非类型常量num的大小,需要注意的是,不管是类定义还是成员函数定义,都要遵守模板的定义形式。
类模板的模板参数可以是:类型参数(数据结构也算类型,如vector<int>
)、非类型参数(只能是int)。需要注意的是,类模板的模板参数可以有默认值,而函数模板的模板参数不能有默认值。
类模板和成员函数模板不是传统意义上的类定义和成员函数定义,由类模板实例化而生成的具体类称为模板类,然后使用模板类才可以实例化对象:类名<数据类型实参表> 对象名称
,包含两个过程:类模板实例化为模板类 -> 模板类实例化一个对象。
注意:类模板只能显示实例化为模板类,不能像函数模板那样由编译器自动推导(隐式)。
stack.hpp:
普通版本:
#include <stdexcept>
template<typename T>
class Stack
{
public:
explicit Stack(int max_size); // 最大值通过构造函数传入,存入数据成员
~Stack();
void Push(const T& elem);
void Pop();
T& Top();
const T& Top() const;
bool IsEmpty() const;
private:
T* elems_;
int max_size_;
int top_;
};
// 成员函数的定义格式,注意类名限定是Stack<T>,也就是模板类名,而不是类模板名
template<typename T>
Stack<T>::Stack(int max_size) : max_size_(max_size), top_(-1)
{
elems_ = new T[max_size_];
}
template<typename T>
Stack<T>::~Stack()
{
if (elems_)
{
delete[] elems_;
}
}
template<typename T>
void Stack<T>::Push(const T& elem)
{
if (top_ + 1 >= max_size_) {
throw std::out_of_range("Stack<>::Push() stack full");
}
elems_[++top_] = elem;
}
template<typename T>
void Stack<T>::Pop()
{
if (top_ < 0) {
throw std::out_of_range("Stack<>::Pop() stack empty");
}
--top_;
}
template<typename T>
T& Stack<T>::Top()
{
if (top_ < 0) {
throw std::out_of_range("Stack<>::Pop() stack empty");
}
return elems_[top_];
}
template<typename T>
const T& Stack<T>::Top() const
{
if (top_ < 0) {
throw std::out_of_range("Stack<>::Pop() stack empty");
}
return elems_[top_];
}
template<typename T>
bool Stack<T>::IsEmpty() const
{
return top_ < 0;
}
int main()
{
Stack<int> stack(10); // 必须显示实例化
stack.Push(1);
stack.Push(2);
stack.Push(3);
while(!stack.IsEmpty()) {
cout << stack.Top() << endl;
stack.Pop();
}
return 0;
}
非类型模板参数版本:
#include <stdexcept>
// 最大值改为作为模板参数,编译期替换,不需要额外的数据成员来保存,与宏类似。
// 类模板的模板参数可以指定默认值,默认值只需要在声明处提供一次就行,类成员
// 函数定义处就不需要了。
template<typename T, int MAX_SIZE = 10>
class Stack2
{
public:
explicit Stack2(); // 不需要再传最大值这个参数
~Stack2();
void Push(const T& elem);
void Pop();
T& Top();
const T& Top() const;
bool IsEmpty() const;
private:
T* elems_;
int top_;
};
// 末模板参数变了,这里的形式也就跟着变了,但格式是一致的
// 这里不用再指定默认值
template<typename T, int MAX_SIZE>
Stack2<T,MAX_SIZE>::Stack2() : top_(-1)
{
elems_ = new T[MAX_SIZE];
}
template<typename T, int MAX_SIZE>
Stack2<T,MAX_SIZE>::~Stack2()
{
if (elems_)
{
delete[] elems_;
}
}
template<typename T, int MAX_SIZE>
void Stack2<T,MAX_SIZE>::Push(const T& elem)
{
if (top_ + 1 >= MAX_SIZE) {
throw std::out_of_range("Stack2<>::Push() stack full");
}
elems_[++top_] = elem;
}
template<typename T, int MAX_SIZE>
void Stack2<T,MAX_SIZE>::Pop()
{
if (top_ < 0) {
throw std::out_of_range("Stack2<>::Pop() stack empty");
}
--top_;
}
template<typename T, int MAX_SIZE>
T& Stack2<T,MAX_SIZE>::Top()
{
if (top_ < 0) {
throw std::out_of_range("Stack2<>::Pop() stack empty");
}
return elems_[top_];
}
template<typename T, int MAX_SIZE>
const T& Stack2<T,MAX_SIZE>::Top() const
{
if (top_ < 0) {
throw std::out_of_range("Stack2<>::Pop() stack empty");
}
return elems_[top_];
}
template<typename T, int MAX_SIZE>
bool Stack2<T,MAX_SIZE>::IsEmpty() const
{
return top_ < 0;
}
int main(void)
{
Stack2<int, 10> stack; // max_size以模板参数的形式传递给类模板
stack.Push(1);
stack.Push(2);
stack.Push(3);
while (!stack.IsEmpty()) {
cout << stack.Top() << endl;
stack.Pop();
}
return 0;
}
使用标准库已有数据结构的版本:
#include <stdexcept>
#include <deque> // 对标准库容器封装一下
// 可以传数据结构(这里的容器,其实也是一个类型)进来
template<typename T, typename CONT = std::deque<T>>
class Stack3
{
public:
explicit Stack3();
~Stack3();
void Push(const T& elem);
void Pop();
T& Top();
const T& Top() const;
bool IsEmpty() const;
private:
CONT container_;
};
template<typename T, typename COUNT>
Stack3<T,COUNT>::Stack3()
{
container_.clear();
}
template<typename T, typename COUNT>
Stack3<T,COUNT>::~Stack3()
{
}
template<typename T, typename COUNT>
void Stack3<T,COUNT>::Push(const T& elem)
{
container_.push_back(elem);
}
template<typename T, typename COUNT>
void Stack3<T,COUNT>::Pop()
{
container_.pop_back();
}
template<typename T, typename COUNT>
T& Stack3<T,COUNT>::Top()
{
return container_.back();
}
template<typename T, typename COUNT>
const T& Stack3<T,COUNT>::Top() const
{
return container_.back();
}
template<typename T, typename COUNT>
bool Stack3<T,COUNT>::IsEmpty() const
{
return container_.empty();
}
模板嵌套-成员模板
模板的套嵌可以理解成在另外一个模板里面定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称成员模板。
成员模版是不能声明为virtual的。
函数成员模板
在成员函数中使用类模板形参列表中未声明的参数类型(如不同的模板参数的模板类对象之间相互赋值时的赋值运算符),需要将成员函数写成函数模板:
template<typename T>
class MyClass
{
public:
// 相同类型的类(模板类)对象赋值,可以访问私有成员
void Assign(const MyClass<T>& other) {
value_ = other.value_;
}
private:
T value_;
};
template<typename T>
class MyClass2
{
public:
// 函数成员模板
template<class X>
// MyClass2<X>与MyClass2<T>有可能是不同类型,所以不能直接访问私有成员,
// 需要提供公有接口用于访问私有成员,可以访问私有成员.
void Assign(const MyClass2<X>& other) {
value_ = other.GetValue();
}
T GetValue() const {
return value_;
}
private:
T value_;
};
对象成员模板
类模板的定义可以放在另一个类中,实例化后的模板类对象可以作为另一个类的成员:
#include <iostream>
using namespace std;
template<class T>
class Outside
{
public:
template <class R>
class Inside
{
private:
R r;
public:
Inside(R x) // 模板类的成员函数可以在定义时实现
{
r = x;
}
void disp()
{
cout << "Inside: " << r << endl;
}
};
Outside(T x) : t(x)
{}
void disp()
{
cout << "Outside:";
t.disp();
}
private:
Inside<T> t;
};
// 模板类的成员函数也可以在定义外实现,
// 但必须是在所有类定义的外边,不能放在Outside内Inside外去实现.
//template<class T>
//template<class R>
//void Outside<T>::Inside<R>::disp()
//{
// cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl;
//}
//template<class T>
//void Outside<T>::disp()
//{
// cout<<"Outside:";
// t.disp();
//}
int main()
{
Outside<int>::Inside<double> obin(3.5); // 声明Inside类对象obin
obin.disp();
Outside<int> obout(2); //创建Outside类对象obout
obout.disp();
getchar();
return 0;
}
模板类的成员函数可以在定义时实现,也可以在定义外实现,但必须是在所有类定义的外边,不能放在Outside内Inside外去实现。注意在外部实现时的格式。
在Outside类内使用Inside<T> t;
语句声明了Inside<T>
类的对象,在Outside模板类对象创建时,首先采用隐式实例化先生成Inside<T>
类的定义,而后根据此定义创建对象成员t。
模板做模板参数
模板包含类型参数(如class Type)和非类型参数(如int NUM,NUM是常量),实际上,模板的参数可以是另一个模板,也就是说,下述形式是合法的:
template<template <typenameT1> typenameT2, typenameT3, int Num>;
#include <iostream>
using namespace std;
template <typename T, int num>
class Stack
{
private:
T sz[num];
public:
int ReturnNum();
};
template<typename T1, int num1> // 参数列表不要求字字相同,但形式要相同
int Stack<T1, num1>::ReturnNum()
{
return num1;
}
// 注意模板做模板参数时的格式
template<template<typename Type, int NUM> typename TypeClass, typename T1, int N>
void disp()
{
TypeClass<T1, N> ob;
cout << ob.ReturnNum() << endl;
}
int main()
{
disp<Stack, int, 8>();
return 0;
}
模板实现的单例模式
#include <cstdlib>
// 模板类的包装器,用它可以产生单例模式的类,返回的是T
template<typename T>
class Singleton
{
public:
static T& GetInstance()
{
Init();
return *instance_;
}
private:
static void Init()
{
if (instance_ == NULL) {
instance_ = new T;
atexit(Destroy); // 每注册一次,就会在程序结束的时候调用一次;可以注册多个,后注册的会先调用
}
}
static void Destroy()
{
delete instance_;
}
Singleton();
Singleton(const Singleton& other);
Singleton&operator=(const Singleton& other);
~Singleton();
static T* instance_;
};
template<typename T>
T* Singleton<T>::instance_ = NULL;
class ApplicationImpl
{
public:
ApplicationImpl()
{
cout << "Application ..." << endl;
}
~ApplicationImpl()
{
cout << "~Application ..." << endl;
}
void Run()
{
cout << "Run ..." << endl;
}
};
// ApplicationImpl本身不是单例模式,但是经过包装器Singleton包装后就是单例模式了.
typedef Singleton<ApplicationImpl> Application;