C++ -- 模板的基础知识
模板的由来
1.对于交换函数Swap来说,
(1)如果用于交换两个整型,则代码如下:
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
(2)当用于交换两个整型指针时,代码如下:
void Swap(int** a, int** b)
{
int* temp = *a;
*a = *b;
*b = temp;
}
2.对于交换函数Swap,当用于交换两个整型和两个整型指针时,代码基本一致,功能也是一样(都是用于交换)。对于这种代码基本一致,且只与类型有关的函数(或类),我们可以想到只要能够编写与类型无关的函数(或类)就可以达到任意类型都可以共用同一个函数。
3.模板的定义:模板是一种复用手段,模板是泛型编程的基础(泛型编程就是编写与类型无关的代码)。
4.模板的分类:模板分为函数模板与类模板。
函数模板的编写
1.用template关键字,template后面带一对尖括号,尖括号里用class或typename定义模板参数T,T表示数据的类型
template<class T>
template<typename T>
template<class T, class M> //也可以定义多个模板参数
2.对于上面的代码,采用函数模板编写,代码如下:
3.则对于任意类型都可以用于交换
4.对于函数模板来说,虽然函数模板只有一份,但是当参数类型不同时,调用的不是同一个函数。
- 对于上面的程序,汇编代码如下:(可以看到当调用同一个函数模板时,由于参数的类型不同,call的地址也不同,说明调用的不是同一个函数模板)
函数模板的实例化
1.模板实例化的过程
2.模板的显示实例化(在函数模板的调用时,显示指定类型)
代码如下:
template<class T>
void Swap(T* a, T* b)
{
T temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
Swap<int>(&a, &b);
system("pause");
return 0;
}
3.重载函数模板(函数模板的函数名相同,模板参数不同)
(1)测试代码:
template<class T>
bool Equal(const T& left, const T& right)
{
cout << "Equal(const T& left, const T& right)" << endl;
return left == right;
}
template<class T1,class T2>
bool Equal(const T1& left, const T2& right)
{
cout << "Equal(const T1& left, const T2& right)" << endl;
return left == right;
}
int main()
{
Equal(1, 2);
Equal(1, 1.2);
system("pause");
return 0;
}
(2)运行结果:(可以看到当参数不同时,调用的是不同的模板函数)
4.模板只有在实例化时才生成相应的代码,才去检查相应的错误
(1)例如:下面的模板函数内部缺少分号,但是该函数模板没有被调用也就没有进行实例化,因而没有检查出错误,所以也能成功编译。
(2)如果函数模板框架错误,编译器会检查出相应的错误
总结:模板只有在调用时才会实例化,才会生成相应的代码。
类模板
1.以顺序表为例:
#include<iostream>
using namespace std;
template<class T>
class SeqList
{
public:
SeqList();
~SeqList();
private:
int _size;
int _capacity;
T* _data;
};
2.对于类模板来说,它的类型为类名加上<T>
,例如上面SeqList类的类型为SeqList<T>
。
3.因此对于类模板来说,如果将其成员函数的声明与定义分开,则定义时必须指明类型:
template<class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(0)
, _data(new T[_capacity])
{}
template<class T>
SeqList<T>::~SeqList()
{
delete[] _data;
_size = 0;
_capacity = 0;
}
int main()
{
SeqList<int> s;
system("pause");
return 0;
}
实现适配器模式的栈和队列
- 因为栈要满足后进先出的性质,栈只能在栈顶进行插入和删除,栈顶类似于一个数据结构的尾巴处,而顺序表的尾插和尾删比较方便,所以可以采用顺序表实现栈。而队列是在队尾入,队头出,如果采用顺序表,就会挪动大量的数据,所以队列可以采用链表进行适配。
- 缺省模板参数
①对于如下代码:
template<class T,class Container = SeqList<T>>
②Container为模板参数,后面带一个等号,表示Container为缺省的模板参数,表示如果自己传两个参数时,就会用自己传的,如果没有给就用缺省的类型。
③模板参数只能从后向前缺省,不能再中间进行缺省,因为编译器无法识别。
(1)利用适配器模式实现栈(模板参数Container表示容器,当指定为list时就用list实现栈,当用vector时就用vector实现栈。(这里的SeqList是自己实现的顺序表,也可以用标准库里面的)
template<class T,class Container = SeqList<T>> //Container为缺省的模板参数
class Stack
{
public:
void Push(const T& x)
{
_con.PushBack(x);
}
void Pop()
{
_con.PopBack();
}
const T& Top()
{
return _con.Back();
}
bool Empty()
{
return _con.Empty();
}
size_t Size()
{
return _con.Size();
}
private:
Container _con;
};
void TestStack()
{
Stack<int> s;
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
while (!s.Empty())
{
cout << s.Top() << " ";
s.Pop();
}
cout << endl;
}
(2)利用适配器模式实现队列
template<class T,class Container>
class Queue
{
public:
void Push(const T& x)
{
_con.PushBack(x);
}
void Pop()
{
_con.PopFront();
}
const T& Front()
{
return _con.Front();
}
size_t Size()
{
return _con.Size();
}
bool Empty()
{
return _con.Empty();
}
private:
Container _con;
};
模板的模板参数
1.模板的模板参数的引入
(1)对于如下代码,如果不用缺省的模板参数,而是自己指定类型,就有可能第一个模板参数传int,第二个模板参数传char,这样就会造成类型不匹配。
Stack<int,List<char>> s;
(2)只要能够想到一个方法,使得显示实例化时某些模板参数不用指定类型,只用传一个类名就可以,这样就不会出错。
template<class T,template<class> class Container = List>
//或者可以加上T,这两句等价
template<class T,template<class T> class Container = List>
//也可以不用缺省参数,此时实例化时Container对应的参数传的是类名而不在是类型
template<class T,template<class T> class Container>
例如:
template<class T,template<class> class Container> //Container为模板的模板参数,则Container就是一个类名,如果定义其类型,则必须在类名前面加上Container<T>,
class Stack
{
public:
void Push(const T& x)
{
_con.PushBack(x);
}
private:
Container<T> _con;
};
void TestStack()
{
Stack<int,List> s; //例如第二个参数传的是List,这是一个类名
}
非类型的模板参数
- 模板参数表示的不是类型
1.非类型模板参数的编写
template<size_t M> //这里的M为非类型的模板参数,表示A里面数组a的大小
class A
{
public:
int a[M];
};
2.非类型的模板参数只能是整型(如果不是整型就会出错)
3.非类型的模板参数实际上就是一个常量
由上面可以看出,在A类里面定义了一个数组,M作为该数组的大小,只有常量才能作为数组的大小。