泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化:让编译器根据实参推演模板参数的实际类型
实现一个交换函数,在以前用c语言写的时候,针对不同的类型我们需要写多个交换函数,在C++中有了模板后并不需要,他会通过实际的参数来推断函数模板的参数类型,进而实例化出一份合适的函数供你使用
//函数模板
template <class T>
//模板参数T的类型并不是固定的,通过实际传递的参数来推导T的类型,实例化出一份函数
//template 这种写法也是可以的
void Swap(T &num1, T& num2)
{
T tmp = num1;
num1 = num2;
num2 = tmp;
}
void functest()
{
//交换整形
int a = 10, b = 20;
Swap(a,b);//隐士实例化
cout << "a :" << a << "b :" << b << endl;
//交换浮点型
float c = 10.55f, d = 3.14f;
Swap(c, d);//隐士实例化
cout << "c :" << c << "d :" << d << endl;
}
流程示例图:
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
编译器根据实参的类型推模板参数的类型,这种也叫隐士类型转换,而显示实例化又是怎么样的呢
显式实例化:在函数名后的<>中指定模板参数的实际类型
//显示指定模板参数类型为int
int a = 10, b = 20;
Swap<int>(a, b);//显示实例化
//显示指定模板参数类型为float
float c = 10.55f, d = 3.14f;
Swap<float>(c, d);//显示实例化
显示指定模板参数类型后,就不再需要编译器根据实参的类型去推断模板参数T的类型了,模板参数类型显示指定是啥就是啥
1、 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
2、 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
3、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
//可以声明多个模板参数
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
部分模拟实现vector类,后继在加以补充
//指定名命空间mzt
namespace mzt
{
template<class T>
class vector
{
public:
vector()
: _a(nullptr)
,_size(0)
,_capacity(0)
{ }
//往容器中插入数据
void push_back(const T& data)
{
//扩容
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
T* tmp = new T[newcapacity];
assert(tmp);
_a = tmp;
_capacity = newcapacity;
}
_a[_size++] = data;
}
//返回pos位置处的值
T& operator[](size_t pos)
{
assert(pos < _size);
return _a[pos];
}
size_t getsize()
{
return _size;
}
private:
T* _a;//
size_t _size;//元素个数
size_t _capacity;//空间
};
}
void func1()
{
//使用显示指定实例化类对象
mzt::vector<int> a;
//插入数据
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
//遍历
for (size_t i = 0; i < a.getsize(); i++)
{
cout << " " << a[i];
}
cout << endl;
}
模板参数分类:类型形参、非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
namespace mzt
{
//模板参数给了缺省值后在定义对象时可以不指定显示模板参数
template<class T = int, size_t N = 10>
//声明模板参数和非类型模板参数
class Array
{
public:
void f()
{
//N = 10; //N为常量不允许修改
}
private:
T* arr[N];
};
}
int main()
{
mzt::Array< > a; //不提供模板参数,使用缺省值
return 0;
}
注意:
1、 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2、 非类型的模板参数必须在编译期就能确认结果
通常情况下使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化
template<class T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}
int main()
{
char arr1[] = "hello";
char arr2[] = "hello";
bool ret = mzt::IsEqual(arr1, arr2);// 0
cout << ret << endl;
const char* p1 = "hello";
const char* p2 = "hello";
ret = mzt::IsEqual(p1, p2); // 1
cout << ret << endl;
}
同一字符串比较的结果确是不同,因为指针只会指向一块已经存在的空间,就是字符串的起始地址,而如果是数组的话他会创建两块不相同的空间,但是不符合预期的效果
解决办法模板特化
//方法一:
//这里针对字符串类型需要做特殊的处理
template< > //空括号
bool IsEqual<const char *>(const char* const& left,
const char* const& right)
{
return strcmp(left, right) == 0;
}
//方法二:
//也可以采用以下写法,如果IsEqual这个函数有,编译器就不会去通过
//函数模板来实例化生成一份函数了
bool IsEqual(const char* left, const char* right)
{
return strcmp(left, right) == 0;
}
void func()
{
char arr1[] = "hello";
char arr2[] = "hello";
bool ret = mzt::IsEqual(arr1, arr2);// 0
cout << ret << endl;
const char* p1 = "hello";
const char* p2 = "hello";
ret = mzt::IsEqual<const char*>(p1, p2);//1
// 调用特化版本的函数模板的实例化,
//当然这个模板参数类型也可以不显示指定,
//1、如果不指定的话调用的就是方法二的函数了
//2、指定参数类型后只会去调用特化版本的函数模板
cout << ret << endl;
}
函数模板特化总结:
1、必须要先有一个基础的函数模板
2、关键字template后面接一对空的尖括号<>
3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
4、函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
//这里针对字符串类型需要做特殊的处理
template<>
bool IsEqual(const char* const& left,
const char* const& right)
{
return strcmp(left, right) == 0;
}
即是将模板参数列表中所有的参数都确定化。
namespace mzt
{
template<class T1, class T2>
class Array
{
public:
Array() { cout << "" << endl; }
};
template<>
class Array<int,int>
{
public:
Array() { cout << "" << endl; }
private:
};
template<>
class Array<int, double>
{
public:
Array() { cout << "" << endl; }
private:
};
template<>
class Array<int*, int*>
{
public:
Array() { cout << "" << endl; }
private:
};
template<>
class Array<int&, int&>
{
public:
Array() { cout << "" << endl; }
private:
};
}
int main()
{
//都实例化了特化版本的类模板,调用构造函数创建对象
mzt::Array<int, int>a1;
mzt::Array<int, double>a2;
mzt::Array<int*, int*>a3;
mzt::Array<int&, int&>a4;
mzt::Array<double, int>a5;
return 0;
}
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类
部分特化
namespace mzt
{
template<class T1, class T2>
class Array
{
public:
Array() { cout << "" << endl; }
};
//部分特化,将第二个参数特化为int
template<class T1>
class Array<T1, int>
{
public:
Array() { cout << "" << endl; }
};
//部分特化,将第二个参数特化为double
template<class T1>
class Array<T1, double>
{
public:
Array() { cout << "" << endl; }
};
}
int main()
{
mzt::Array<int,int> a1;
mzt::Array<int, double> a2;
mzt::Array<char, char> a3;//调用原类模板
return 0;
}
参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
namespace mzt
{
template<class T1, class T2>
class Array
{
public:
Array() { cout << "" << endl; }
};
//指针类型的偏特化
template<class T1, class T2>
class Array<T1*, T2*>
{
public:
Array() { cout << "" << endl; }
};
//引用类型的偏特化
template<class T1, class T2>
class Array<T1&, T2&>
{
public:
Array() { cout << "" << endl; }
};
}
int main()
{
//根据显示指定的模板参数类型编译器自动去调用该类模板
mzt::Array<int,int> a1;
mzt::Array<int*, double*> a2;
mzt::Array<char&, char&> a3;
return 0;
}
func1.cpp
template<class T>
T Sub(T& left, T& right)
{
return left - right;
}
func1.h
template<class T>
T Sub(T& left, T& right);
test.c
int main()
{
int a = 10, b = 20;
int ret = Sub(a,b);
cout << ret << endl;
return 0;
}
模板分离编译后在链接的过程中会发现找不到Sub函数
解决办法显示实例化
template<class T>
T Sub(T& left, T& right)
{
return left - right;
}
//定义的时候显示实例化
template
int Sub<int>(int& left, int& right);
解决办法二:不需要显示实例化,函数模板的定义和声明都放到.h文件中
template<class T>
T Sub(T& left, T& right)
{
return left - right;
}
【优点】
【缺陷】