模板
泛型(Generic Programming),即是指具有在多种数据类型上皆可操作的含意。泛型
编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件。
泛型编程最初诞生于 C++中,目的是为了实现 C++的 STL(标准模板库)。其语言
支持机制就是模板(Templates)。
模板的精神其实很简单:类型参数化(type parameterized),即,
类型也是一种参数,也是一种静多态。
换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数 T。
重载函数,固然实现了泛化的一面,但是不彻底,且有二义性的存在
函数模板
//在一个函数的参数表,返回类型和函数体中使用参数化的类型。
template<typename/class 类型参数 T1,typename/class 类型参数 T2,...>
返回类型 函数模板名(函数参数列表)
{
函数模板定义体
}
#if 1
#include <iostream>
using namespace std;
class MyType{
public:
MyType(int x, int y): _x(x), _y(y){};
int _x;
int _y;
};
template<typename T>
void myswqp(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
int main(){
int a = 10, b = 20;
myswqp<int>(a, b);
cout << "a = " << a << " b = " << b << endl; //a = 20 b = 10
MyType obj1(10, 20);
MyType obj2(30, 40);
myswqp<MyType>(obj1, obj2);
cout << "obj1._x = " << obj1._x << " obj1._y = " << obj1._y << endl; //obj1._x = 30 obj1._y = 40
cout << "obj2._x = " << obj2._x << " obj2._y = " << obj2._y << endl; //obj2._x = 10 obj2._y = 20
return 0;
}
#endif
模板参数作返回类型
特性
先实例化,再调用。
严格匹配,不存在隐式转化。
模板编译原理
编译器遇到模板方法定义时,会进行语法检查,但是并不编译模板。
编译器无法编译模板定义,因为它不知道使用什么类型。
比如 T a,b; 在 不知晓 T 具体类型时,是
无法分配内存,更无从谈编译 a = b;
T 获取类型的过程,称为模板的实例化,
函数模板通过具体类型产生不同的函数;
编译器会对函数模板进行两次编译:
在声明的地方对模板代码本身进行编译(类型检查),
在调用的地方对参数替换后的代码进行编译(代码生成)。
函数模板应用
数据类型的泛化,使的模板特别适合于作算法的抽象。STL 中的 algorithm 就是
典型的代表。比如,算法 sort 就是支持类型泛化的代表排序算法。
void quickSort(T * array,int left, int right) 将快速排序算法实现模板化。
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T>
void quickSort(T * array,int left, int right)
{
if(left<right)
{
int low = left; int high = right;
T pivot = array[low];
while(low<high)
{
while(array[high] >= pivot && high>low )
high--;
array[low] = array[high];
while(array[low] <= pivot&& high>low)
low++;
array[high] = array[low];
}
array[low] = pivot;
quickSort(array,left,low-1);
quickSort(array,low+1,right);
}
}
int main()
{
int array[10] = {1,3,5,7,2,4,6,8,0,9};
quickSort<int>(array,0,9);
for(auto i:array)
{
cout<<i<<endl;
}
}
函数模板的特化
Template Specialization,函数模板的特化,即函数模板的特性情况,个例行
为。
就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例
版本。
#include <iostream>
#include <string.h>
using namespace std;
template<typename T> int compare( T &a, T &b)
{
if(a > b) return 1;
else if(a < b)return -1;
else return 0;
}
//实参为两个 char*时,比较的是指针的大小,
//而不是指针指向内容的大小,此时就需要为该函数模板定义一个特化版本,即特殊处理的版本:
template<> int compare < const char * >( const char* &a, const char* &b)
{
cout<<"特化的函数模板执行了"<<endl;
return strcmp(a,b);
}
int main()
{
int a = 3; int b = 5;
cout<<compare(a,b)<<endl; // -1
string str1 = "abc",str2 ="abc";
cout<<compare(str1,str2)<<endl; //0
//char * 与 const char* 亦属于不匹配
const char * p1 = "abc",*p2= "def";
cout<<compare(p1,p2)<<endl;//-1
cout<<compare(p2,p1)<<endl;//1
return 0;
}
输出:
-1
0
特化的函数模板执行了
-1
特化的函数模板执行了
1
函数模板适用场景
函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。
如果个数不同,则不能用函数模板。
类模板
Stack 类模板化,可以 push 和 pop 不同的数据类型。主要由几个因素需要把控。
栈中的空间元素类型,压入元素类型,弹出元素类型,三者保持一致即可。
# if 1
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(int size=1024);
~Stack();
bool isEmpty();
bool isFull();
void push(T data);
T pop();
private:
T* space;
int top;
};
template<typename T> Stack<T>::Stack(int size)
{
space = new T[size];
top = 0;
}
template<typename T> Stack<T>::~Stack(){
delete []space;
}
template<typename T> bool Stack<T>::isEmpty(){
return top == 0;
}
template<typename T> bool Stack<T>::isFull(){
return top == 1024;
}
template<typename T> void Stack<T>::push(T data){
space[top++] = data;
}
template<typename T> T Stack<T>::pop(){
return space[--top];
}
int main()
{
Stack<int> s(100);
for(int i=0; i<10; ++i){
if(!s.isFull())
s.push(i);
}
while(!s.isEmpty())
cout<<s.pop()<<endl;
return 0;
}
# endif
输出
9
8
7
6
5
4
3
2
1
0
类模板的友元
hpp
由于编译器需要通过这些"模板"为实例化类型生成实际的方法代码,因此任何使
用了模板的源代码文件中,编译器都应该能同时访问类模板定义和方法定义。
C++中的编译是以文件为单位的,然后链接阶段完成链接。
如果模板的声明与实现分开,这种机制显然会导致看不到模板的全貌,而致编译失败。
所以常将类模板定义和方法定义放到一起,该类模板文件的后缀常为.hpp,以示与普通文件的区别。
在使用的时候,#include"xx.hpp"。
mylist.hpp
#ifndef __MYLIST_HPP__
#define __MYLIST_HPP__
#include <iostream>
using namespace std;
// 定义命名空间 listspace
namespace listspace {
// 声明模板类 GenericList
template<class ItemType>
class GenericList;
// 声明友元函数 operator<< 用于输出 GenericList 对象
template<class ItemType>
ostream& operator<<(ostream& outs, const GenericList<ItemType>& the_list);
// 定义模板类 GenericList
template<class ItemType>
class GenericList {
public:
// 构造函数,初始化列表的最大长度
GenericList(int max);
// 析构函数,释放动态分配的内存
~GenericList();
// 返回当前列表的长度
int length() const;
// 向列表中添加新元素
void add(ItemType new_item);
// 判断列表是否已满
bool full() const;
// 清空列表
void erase();
// 声明友元函数 operator<<,用于输出 GenericList 对象
friend ostream& operator<< <>(ostream& outs, const GenericList<ItemType>& the_list);
private:
// 存储列表元素的数组指针
ItemType* _item;
// 列表的最大长度
int _maxLength;
// 当前元素的索引
int _curIdx;
};
// 定义 GenericList 类的构造函数
template<class ItemType>
GenericList<ItemType>::GenericList(int max)
: _maxLength(max), _curIdx(0) {
_item = new ItemType[max];
}
// 定义 GenericList 类的析构函数
template<class ItemType>
GenericList<ItemType>::~GenericList() {
delete[] _item;
}
// 定义返回当前列表长度的成员函数
template<class ItemType>
int GenericList<ItemType>::length() const {
return _curIdx;
}
// 定义向列表中添加新元素的成员函数
template<class ItemType>
void GenericList<ItemType>::add(ItemType new_item) {
if (full()) {
cout << "Error: adding to a full list.\n";
exit(1);
} else {
_item[_curIdx] = new_item;
_curIdx++;
}
}
// 定义判断列表是否已满的成员函数
template<class ItemType>
bool GenericList<ItemType>::full() const {
return _curIdx == _maxLength;
}
// 定义清空列表的成员函数
template<class ItemType>
void GenericList<ItemType>::erase() {
_curIdx = 0;
}
// 定义友元函数 operator<<,用于输出 GenericList 对象
template<class ItemType>
ostream& operator<<(ostream& outs, const GenericList<ItemType>& the_list) {
for (int i = 0; i < the_list._curIdx; i++) {
outs << the_list._item[i] << endl;
}
return outs;
}
}
#endif
main.cpp
#include <iostream>
#include "mylist.hpp"
using namespace std;
using namespace listspace;
int main( )
{
GenericList<int> first_list(2);
first_list.add(1);
first_list.add(2);
cout << "first_list = \n"
<< first_list;
GenericList<char> second_list(10);
second_list.add('A');
second_list.add('B');
second_list.add('C');
cout << "second_list = \n"
<< second_list;
return 0;
}
输出:
first_list =
1
2
second_list =
A
B
C