如何编写一个加法的通用函数,使它试用于各种类型的参数。c++中提供了一种方法——泛型编程
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。
模板:1.函数模板; 2.类模板。
一.函数模板
1.函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
格式:template <typename T, typename T1, ......,class Tn> (其中T,T1,.......,Tn是模板形参名字,它可以任意命名)
(inline) 返回值类型 函数名(参数列表) (模板函数也可以定义为inline函数,但要注意关键字inline的位置)
{
...............
}
typename是用来定义模板参数的关键字,也可以使用class。建议尽量使用typename。
注意:不能使用struct代替typename。
2.模板是一个蓝图,本身它不是类或函数。
函数模板实例化:编译器用模板产生指定的类或函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
<span style="font-size:14px;">template <typename T>
(inline)T Add(const T& left, const T right)
{
return left + right;
}
int main()
{
Add(10, 20); //int Add(int left, int right)
Add('a', 'c'); //char Add(char left, char right)
Add(3.4, 1.2); //double Add(double left, double right)
return 0;
}</span>
注意:模板会被编译两次:
(1).在实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号;
(2).在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用。
3.实参推演
从函数实参确定模板形参类型和值的过程称为模板实参推断
多个类型形参的实参必须完全匹配
4.类型形参转换
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
(1)const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
(2)数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
5.模板参数有两种:模板参数和调用参数
A.模板类型形参
(1)模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则:
<span style="font-size:14px;">#include<iostream>
using namespace std;
typedef int T;
template <typename T>
(inline)T Add(const T& left, const T right)
{
return left + right;
}
int main()
{
Add('a', 'c');
T a = 10;
cout << typeid(a).name() << endl; //int(typeid用于c++中获知一个变量的类型)
getchar();
return 0;
}</span>
(2)模板形参的名字在同一模板形参列表中只能使用一次
<span style="font-size:14px;color:#000000;">template < typename T, typename T> //error 重定义 模板 参数 T
void FunTest(T t1,T t2)
{
}</span>
(3)所有模板形参前面必须加上class或者typename关键字修饰
<span style="font-size:14px;color:#000000;">template < typename T, U> //语法错误,标识符"U"
void FunTest(T t,U u)
{
}</span>
(4)在函数模板的内部不能指定缺省的模板实参
B.非模板类型参数
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
<span style="font-size:14px;">template < typename T, size_t N>
void FunTest(T(&array)[N])
{
for (int i = 0; i < N; i++)
{
array[i] = i;
}
}
int main()
{
int a1[3];
int(&a2)[3] = a1;
FunTest(a1); //a1[3]={0,1,2};a2[3]={0,1,2} 【void FunTest(int(&array)[3])】
float a3[3];
FunTest(a3); //a3[3]={0.00000000,1.00000000,2.00000000)【void FunTest(float(&array)[3])】
int a4[10];
FunTest(a4); //a4[10]={0,1,2,3,4,5,6,7,8,9} 【void FunTest(int(&array)[10])】
getchar();
return 0;
}</span>
模板形参说明
(1)模板形参表使用<>括起来
(2)和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
(3)模板形参表不能为空
(4)模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
(5)模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
(6)模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
6.模板函数重载
#include<iostream>
using namespace std;
template<typename T>
bool IsEqual(T left, T right)
{
return left == right;
}
template<typename T>
bool IsEqual(T t1, T t2, T t3)
{
return true;
}
bool IsEqual(int left, int right)
{
return left == right;
}
template<typename T,typename T1>
bool IsEqual(T left, T1 right)
{
return left == right;
}
int main()
{
cout << IsEqual(10, 20) << endl; //调用bool IsEqual(int left, int right)函数
cout << IsEqual(10.0, 20.0) << endl; //调用bool IsEqual(T left, T right)函数
cout << IsEqual(10,20,30) << endl; //调用bool IsEqual(T t1, T t2, T t3)函数
cout << IsEqual<>(10, 20) << endl; //调用bool IsEqual(T left, T right)函数
cout << IsEqual(10, 20.0) << endl; //调用bool IsEqual(T left, T1 right)函数
return 0;
}
总结:
(1)一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
(2)对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。 如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
(3)显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演 绎出来。
(4)模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
#include<iostream>
using namespace std;
template<typename T>
bool IsEqual(T left, T right)
{
return left == right;
}
int main()
{
cout << IsEqual(10, 20) << endl; //调用bool IsEqual(T left,T right)函数
return 0;
}
bool IsEqual(int left, int right)
{
return true;
}
注意:函数所有重载版本的声明都应该位于该函数被调用位置之前 。
7.模板函数特化
template<> template<>
返回值 函数名<Type>(参数列表) int compare<const char*>(const char* left, const char* right.......)
{ {
//函数体 //函数体
} }
模板函数的格式:
(1)关键字template后面接一对空的尖括号<>
(2)再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
(3)函数形参表
(4)函数体
<span style="font-size:14px;"><span style="font-size:12px;">#include<iostream>
#include<string>
using namespace std;
template<typename T>
int compare(const T t1, const T t2)
{
return strcmp(t1, t2);
}
template<>
int compare <const char*>(const char* const p1, const char* const p2)
{
return strcmp(p1, p2);
}
int main()
{
const char* s1 = "1234";//const char*与特化版本的参数列表完全匹配,调用了特化版本
const char* s2 = "abcd";
char* s3 = "1234";//char*与特化版本的形参列表不匹配,在编译期间通过参数推断,编译器通过模板生成一个char*类型的compare()函数
char* s4 = "abcd";
cout << compare(s1, s2) << endl;
cout << compare(s3, s4) << endl;
getchar();
return 0;
}</span>
</span>
注意:
(1)在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
(2)特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件。
二.模板类
模板类的实例化:只要有一种不同的类型,编译器就会实例化出对应的类。
template<class 形参名1,class 形参名2,...........,class 形参名n>
class 类名
{
................
};
1.用模板实现动态顺序表:
#include<iostream>
#include<string>
using namespace std;
template <typename T>
class SeqList
{
public:
SeqList()
:_pData(NULL)
, _size(0)
, _capacity(0)
{}
~SeqList();
SeqList(const SeqList<T>& seq);
SeqList<T>& operator=(const SeqList& seq)
{
if (this != &seq)
{
delete[] _pData;
_pData = new T[s._size];
for (int i = 0; i<s._size; ++i)
{
_pData[i] = seq._pData[i];
}
_size = seq._size;
_capacity = seq._size;
}
return *this;
}
public:
bool Empty()const
{
return _size == 0;
}
void Print()
{
for (int i = 0; i < _size; ++i)
{
cout << _pData[i] << " ";
}
cout << endl;
}
T& operator[](size_t index)
{
assert(_pData);
assert(index < _size);
return _pData[index];
}
void PushBack(const T& d)
{
_CheckCapacity();
_pData[_size++] = d;
}
void PopBack()
{
if (!Empty())
{
_size--;
}
}
void PushFront(const T &d)
{
_CheckCapacity();
for (int i = _size - 1; i >= 0; i--)
{
_pData[i + 1] = _pData[i];
}
_pData[0] = d;
_size++;
}
void PopFront()
{
for (int i = 0; i <= _size - 1; i++)
{
_pData[i] = _pData[i + 1];
}
_size--;
}
void Insert(size_t pos, const T& d)
{
_CheckCapacity();
if ((pos<0) || (pos>_size))
{
return;
}
else
{
if (_size)
{
for (int i = _size-1; i >= pos; i--)
{
_pData[i+1] = _pData[i];
}
_pData[pos] = d;
}
_size++;
}
}
void Find(const T& x)
{
_CheckCapacity();
for (int i = 0; i < _size; i++)
{
if (_pData[i] == x)
{
printf("这个数在%d个位置\n", i);
}
}
}
void Erase(size_t pos)
{
_CheckCapacity();
for (int i = pos; i < _size-1; i++)
{
_pData[i] = _pData[i + 1];
}
_size--;
}
private:
void _CheckCapacity()
{
if (_size >= _capacity)
{
_capacity = _capacity * 2 + 3;
T *pTemp = new T[_capacity];
if (_pData)
{
for (int idex = 0; idex < _size; ++idex)
{
pTemp[idex] = _pData[idex];
}
delete[]_pData;
}
_pData = pTemp;
}
}
private:
T * _pData;
size_t _size;
size_t _capacity;
};
template<typename T>
SeqList<T>::~SeqList()
{
{
if (_pData)
{
delete[] _pData;
}
_size = 0;
_capacity = 0;
}
}
template<typename T>
SeqList<T>::SeqList(const SeqList<T>& seq)
{
{
_pData = new T(seq._size);
for (int i = 0; i < seq._size; ++i)
{
_pData[i] = seq._pData[i];
}
_size = seq._size;
_capacity = seq._size;
}
}
int main()
{
SeqList<int> seq1;
seq1.PushBack(1);
seq1.PushBack(2);
seq1.PushBack(3);
seq1.PushBack(4);
seq1.PushBack(5);
seq1.PushBack(6);
seq1.Print();
seq1.PopBack();
seq1.Print();
seq1.PushFront(7);
seq1.Print();
seq1.PopFront();
seq1.Print();
seq1.Insert(2, 9);
seq1.Print();
seq1.Find(2);
seq1.Print();
seq1.Erase(2);
seq1.Print();
getchar();
return 0;
}
2.用模板实现双链表:
#include<iostream>
using namespace std;
template <typename T>
struct Node
{
Node(const T& d)
:_pre(NULL)
, _next(NULL)
, _data(d)
{}
Node<T>* _next;
Node<T>* _pre;
T _data;
};
template <typename T>
class List
{
public:
List()
:_head(NULL)
, _tail(NULL)
{}
~List()
{
SListClear();
}
List(const List<T>& l)
{
Node<T>* cur = l._head;
while (cur)
{
this->PushBack(cur->_data);
cur = cur->_next;
}
}
List& operator=(const List<T>& l)
{
if (this != &l)
{
SListClear();
Node<T>* begin = l._head;
while (begin)
{
PushBack(begin->_data);
begin = begin->_next;
}
}
return *this;
}
public:
void SListClear()
{
Node<T>* cur = _head;
while (cur)
{
Node<T>* del = cur;
cur = cur->_next;
delete del;
}
_head = _tail = NULL;
}
void Print()
{
Node<T>* begin = _head;
while (begin)
{
cout << begin->_data << "<->";
begin = begin->_next;
}
cout << "over" << endl;
}
void PushBack(const T& x)
{
if (_head == NULL)
{
_head = new Node<T>(x);
_tail = _head;
}
else
{
Node<T>*tmp = new Node<T>(x);
tmp->_pre = _tail;
_tail->_next = tmp;
_tail = tmp;
}
}
void PushFront(const T& x)
{
if (_head == NULL)
{
_head = new Node<T>(x);
_head->_next = NULL;
_head->_pre = NULL;
}
else
{
Node<T>* tmp = new Node<T>(x);
tmp->_next = _head;
_head->_pre = tmp;
_head = tmp;
_head->_pre = NULL;
}
}
void PopBack()
{
if (_head == _tail)
{
delete _head;
_head = _tail = NULL;
}
else
{
Node<T>* tailPre = _head;
while (tailPre->_next != _tail)
{
tailPre = tailPre->_next;
}
_tail->_pre = tailPre;
tailPre->_next = NULL;
delete _tail;
_tail = tailPre;
}
}
void PopFront()
{
if (_head == _tail)
{
delete _head;
_head = _tail = NULL;
}
else
{
Node<T>* headPre = _head;
_head = _head->_next;
delete headPre;
headPre = NULL;
_head->_pre = NULL;
}
}
Node<T>* Find(const T& x)
{
Node<T>* pHead = _head;
while (pHead !=NULL)
{
if (pHead->_data == x)
{
return pHead;
}
pHead = pHead->_next;
}
return NULL;
}
void Erase(Node<T>* pos)
{
if (pos == _head)
{
_head = _head->_next;
_head->_pre = NULL;
}
else if (pos == _tail)
{
_tail = _tail->_pre;
_tail->_next = NULL;
}
else
{
Node<T>* posNext = pos->_next;
Node<T>* posPre = pos->_pre;
posNext = posPre->_next;
posPre = posNext->_pre;
}
delete pos;
}
protected:
Node<T>* _head;
Node<T>* _tail;
};
int main()
{
List<int> l;
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
l.PushBack(5);
l.PushBack(6);
l.PushBack(7);
l.Print();
l.PopBack();
l.Print();
l.PushFront(5);
l.Print();
l.PopFront();
l.Print();
Node<int>* ret = l.Find(3);
l.Erase(ret);
getchar();
return 0;
}
3.用模板实现队列(先进先出) (在双链表的基础上)
(1)模板参数实现队列
template<typename T, class Container=List<T>> //有缺省参数
//template<typename T, class Container>
class Queue
{
public:
Queue()
{}
void PushBack(const T& d)
{
_con.PushBack(d);
}
void PopBack()
{
_con.PopFront();
}
private:
Container _con; //Container是模板形参,模板实参传的类型就是Container的类型
};
int main()
{
Queue<int,List<int>> q1;
Queue<int, int>q2;
q1.PushBack(1);
q1.PushBack(2);
q1.PushBack(3);
q1.PushBack(4);
q1.PopBack();
return 0;
}
(2)用模板的模板参数实现队列
template<typename T, template<typename>class Container = List> //有缺省参数
//template<typename T, template<typename>class Container>
class Queue
{
public:
Queue()
{}
void PushBack(const T& d)
{
_con.PushBack(d);
}
void PopBack()
{
_con.PopFront();
}
private:
Container<T> _con; //Container是一个模板类类型的模板形参
};
int main()
{
Queue<int,List> q1;
Queue<int>q2;
q1.PushBack(1);
q1.PushBack(2);
q1.PushBack(3);
q1.PushBack(4);
q1.PopBack();
return 0;
}
4.非类型的类模板参数
template <typename T, size_t MAX_SIZE = 10> //带缺省模板参数
class SeqList
{
public:
SeqList();
private:
T _array[MAX_SIZE];
int _size;
};
template <typename T, size_t MAX_SIZE>
SeqList <T, MAX_SIZE>::SeqList()
: _size(0)
{}
void Test()
{
SeqList<int> s1;
SeqList<int, 20> s2;
}
int main()
{
Test();
return 0;
}
注意:浮点数和类对象是不允许作为非类型模板参数的
template<class T, string name> //error
template <class T, double MaxSize> //error
5.类模板的特化
(1)全特化
template<typename T>
class SeqList
{
public:
SeqList();
~SeqList();
private:
int _size;
int _capacity;
T* _data;
};
template<typename T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{}
template<typename T>
SeqList<T>::~SeqList()
{
delete[]_data;
}
template<>
class SeqList<int>
{
public:
SeqList();
~SeqList();
private:
int _size;
int _capacity;
int* _data;
};
SeqList<int>::SeqList()
:_size(0)
, _capacity(10)
, _data(new int[_capacity])
{}
SeqList<int>::~SeqList()
{
delete[]_data;
}
int main()
{
SeqList<double> s1;
SeqList<int> s2;
return 0;
}
(2)偏特化(局部特化)偏特化并不仅仅是指特殊部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
<span style="font-size:12px;">#include<iostream>
using namespace std;
template <typename T1,typename T2>
class Data
{
public:
Data();
private:
T1 _d1;
T2 _d2;
};
template <typename T1,typename T2>
Data<T1, T2>::Data()
{
cout << "Data<T1,T2>" << endl;
}
template<typename T1>
class Data<T1, int>
{
public:
Data();
private:
T1 _d1;
int _d2;
};
template<typename T1>
Data<T1, int>::Data()
{
cout << "Data<T1,int>" << endl;
}
int main()
{
Data<int, int>d1;
return 0;
}</span>
偏特化指针:
#include<iostream>
using namespace std;
template<typename T1,typename T2>
class Data
{
public:
Data();
private:
T1 _d1;
T2 _d2;
};
template<typename T1,typename T2>
Data<T1, T2>::Data()
{
cout << "Data<T1,T2>" << endl;
}
template<typename T1,typename T2>
class Data<T1*, T2*>
{
public:
Data();
private:
T1 _d1;
T2 _d2;
T1* _d3;
T2* _d4;
};
template<typename T1,typename T2>
Data<T1*, T2*>::Data()
{
cout << "Data<T1*,T2*>" << endl;
}
int main()
{
Data<int*,int*>d1;
return 0;
}
偏特化引用:
#include<iostream>
using namespace std;
template<typename T1,typename T2>
class Data
{
public:
Data();
private:
T1 _d1;
T2 _d2;
};
template<typename T1,typename T2>
Data<T1, T2>::Data()
{
cout << "Data<T1,T2>" << endl;
}
template<typename T1,typename T2>
class Data<T1&, T2&>
{
public:
Data(const T1& d3,const T2& d4);
private:
T1 _d1;
T2 _d2;
const T1& _d3;
const T2& _d4;
};
template<typename T1,typename T2>
Data<T1&, T2&>::Data(const T1& d3, const T2& d4)
:_d3(d3)
, _d4(d4)
{
cout << "Data<T1&,T2&>" << endl;
}
int main()
{
Data<int&, int&>d1(1,2);
return 0;
}
6.模板的编译分离:
SeqList.h
class SeqList
{
public:
SeqList();
private:
int _size;
int _capacity;
T* _data;
};
SeqList.cpp
template <class T>
SeqList <T>::SeqList()
:_size(0)
, _capacity(0)
, _data(0)
{}
Test.cpp
#include"SeqList.h"
int main()
{
SeqList<int>s1;
return 0;
}
模板分离编译链接错误的原因:SeqList.h和SeqList.cpp经过编译链接生成SeqList.obj,Test.cpp经过编译链接后生成Test.obj,编译时SeqList<T>没有实例化出SeqList<int>实例,所以链接错误。
解决办法:
(1)在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。(不推荐)
(2)将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。
模板总结:
优点
(1)模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
(2) 增强了代码的灵活性。
缺点
(1)模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
(2)出现模板编译错误时,错误信息非常凌乱,不易定位错误。