在实际编程中,实现一个交换函数或者有加减乘除功能的函数,往往只是涉及到单个类型的交换或运算,当然使用函数重载固然可以实现,但是存在以下几个不好的地方:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
typename是用来定义模板参数的关键字,也可以使用class(切记不能使用struct代替class)。
//单个参数
template<class T>
template<typename T>
//多个参数
template<class T1,class T2,………class Tn>
template<typename T1,typenameT2…………typename Tn>
template<>class T1,typename T2>
代码示例:
//与类型无关的加法函数
template<typename T>
T Add(T left,T right)
{
cout << typeid(T).name() << endl;//打印T的类型
return left+right;
}
int main()
{
Add(1,2);
Add(2.0,6.5);
Add('a','c');
return 0;
}
//运行结果
int
3
double
8.5
char
函数模板本身并不是一个函数,是编译器用使用方式产生特定具体类型函数的摸具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
如图所示,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型、整型也是如此。
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
代码示例:
template<typename T>
T Add(T left,T right)
{
cout << typeid(T).name() << endl;//打印T的类型
return left+right;
}
int main()
{
//参数类型一致
Add(1,2);
Add(2.0,6.5);
Add('a','c');
//参数类型不一致
cout << Add(4, 4.5) << end;
/*
此时编译器会报错,在编译期间,当编译器看到该实例化时,需要推演其实参类型。
通过实参 4 推演为int类型,通过实参 4.5 推演为double类型。
由于模板参数列表只有一个T,所以无法确定将T确定为int还是double
*/
//此时有两种处理方式 1.用户自己强制转换 2.使用显示实例化
//强制转化类型
cout << Add((double)4, 4.5) << end;
cout << Add(4, (int)4.5) << end;
return 0;
}
在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。
代码示例:
int main()
{
//显示实例化
//明确指定模板函数参数列表的实际类型,如果发现类型不一样,则进行隐式转换
//转换成功则编译通过,失败则报错
Add<int>(4,5.6);
Add<double>(4.5,5);
return 0;
}
//运行结果:
int
9
double
9.5
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
return 0;
}
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
//这里注意返回值
T1 Add(T1 left, T2 right)
{
cout << typeid(T).name() << endl;//打印T的类型
return left + right;
}
int main()
{
Add(1, 2); //与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); //模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
return 0;
}
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
以动态顺序表为例,代码示例:
template <class T>
class SeqList
{
private:
T* _array;
size_t _capacity;//容量
size_t _size;//有效元素个数
public:
SeqList(size_t capacity = 10)
:_array(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
//涉及到动态内存管理,必须显示定义析构函数
//定义在类内
~SeqList()
{
if (_array)
{
delete[] _array;
_size = _capacity = 0;
}
}
T& operator[](size_t index)
{
assert(index < _capacity);
return _array[index];
}
//使用尾插函数演示:类内声明,类外定义
void Push_back(const T& data);
//判空
bool empty()const
{
return 0 == _size;
}
//尾删
void PopBack()
{
if (empty())
{
return;
}
_size--;
}
//获取有效元素个数
size_t Size()
{
return _size;
}
//获取当前容量
size_t capacity()
{
return _capacity;
}
//打印顺序表
void display()
{
if (empty())
{
return;
}
for (int i = 0; i < _size; i++)
{
cout << _array[i]<<" ";
}
cout << endl;
}
};
//类外定义成员函数,需要加上模板参数列表
template<class T>
void SeqList<T>::Push_back(const T& data)
{
_array[_size++] = data;
}
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
代码示例:
int main()
{
//用上面的类模板实例化
//SeqList是类名,SeqList才是类型
SeqList<int> list1;
list1.Push_back(1);
list1.Push_back(2);
list1.Push_back(3);
list1.Push_back(4);
list1.display();
cout << list1.Size() << endl;
cout << list1.capacity() << endl;
list1.PopBack();
list1.PopBack();
cout << list1.Size() << endl;
cout << list1.capacity() << endl;
SeqList<double> list2;
list2.Push_back(1.5);
list2.Push_back(2.4);
list2.Push_back(3.2);
list2.Push_back(4.6);
list2.display();
SeqList<char> list3;
list3.Push_back('H');
list3.Push_back('e');
list3.Push_back('l');
list3.Push_back('l');
list3.Push_back('o');
list3.display();
return 0;
}
运行结果:
1 2 3 4 //list1
4
10
2
10
1.5 2.4 3.2 4.6 //list2
H e l l o //list3