C/C++【函数模板&类模板】
一、函数模板
概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
格式:
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
int main()
{
double d1=0,d2=1;
Swap(d1,d2);
}
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此
1.1 函数模板实例化
1.1.1隐式实例化
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Swap(a1, a2);
Swap(d1, d2);
/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
Add(a1, (int)d1);
return 0;
}
1.1.2显式实例化
int main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
Swap<int>(a, b);
return 0;
}
1.2模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
二 、类模板
2.1格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
2.2函数模板与类模板的不同
- 类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
- 类模板一般没有推演时机;函数模板是通过实参传递形参,推演模板参数
- 类模板需要显示实例化
- 他们是同一个类模板实例化出来的,但是模板参数不同,他们就是不同类型:类型可能是int类型或者double类型,大小不一样
Stack是类名,Stack<double>是类型
Stack<double> st1; // double
st1.Push(1.1);
Stack<int> st2; // int
st2.Push(1);
2.3理解类模板和模板类
1.类模板是一种在编译时实例化类,而模板类是一种在编译时创建一个特定类型的框架,可以用来作为其他类的基类。
2.类模板可以接受类型参数,而模板类不能接受类型参数。
3.总的来说,类模板和模板类都是使用模板来支持通用性,但是类模板是一种通用的类定义,可以用于生成不同的具体类,而模板类是一种具体的类定义,可以存储任意类型的数据
类模板和模板类是两个不同的概念。类模板是定义了一个通用的类的模板,可以根据具体的类型参数来实例化出不同的类。例如:
template<typename T>
class Stack {
private:
T *data;
int top;
public:
Stack() {
data = new T[100];
top = -1;
}
void push(T x) {
data[++top] = x;
}
T pop() {
return data[top--];
}
};
在上面的代码中,"Stack" 是一个类模板,"T" 是类型参数,可以是任意类型。通过实例化 "Stack<int>"、"Stack<double>" 等类型,可以得到不同的类。
而模板类是指已经被实例化的类,它是一个具体的类,而不是一个模板。例如:
Stack<int> s;
在上面的代码中,"Stack<int>" 是一个模板类,它是由 "Stack" 这个类模板实例化出来的一个具体的类。
因此,类模板和模板类是两个不同的概念,类模板是定义通用类的模板,模板类是由类模板实例化出来的具体类。
三、非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template<class T, size_t N = 10>
class 类模板名
{
// 类内成员定义
private:
T _array[N];
size_t _size;
};
四、模板的特化
4.1函数模板的特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
// 原函数模板
template <typename T>
void foo(T t) {
// do something
}
// 全特化,函数模板没有偏特化
template <>
void foo<int>(int t) {
// do something else
}
4.2类模板的特化
- 全特化即是将模板参数列表中所有的参数都确定化
- 偏特化/半特化:任何针对模版参数进一步进行条件限制设计的特化版本
//类模板的特化行为
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 全特化
template<>
class Data<double, char>
{
public:
Data() { cout << "Data<double, char>" << endl; }
};
// 半特化、偏特化
template<class T1>
class Data<T1, char>
{
public:
Data() { cout << "Data<T1, char>" << endl; }
};
// 参数类型进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
};
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
Data() { cout << "Data<T1&, T2&>" << endl; }
};
//int main()
//{
// Data<int, int> d1;
// Data<double, double> d2;
// Data<double, char> d3;
// Data<char, char> d4;
//
// Data<char*, char*> d5;
// Data<char, int*> d6;
// Data<double*, int*> d7;
//
// Data<double&, int&> d8;
//
// return 0;
//}
在《Effective C++》一书中条款25提到,C++只允许对class templates(类模板)偏特化,在function template(std::swap)(函数模板)上偏特化是行不通的。有兴趣的可以去看看
template <typename T>
void swap<widget<T>>(widget<T>& a,widget<T>& b) {
{
a.swap(b);
}
错误!!!
}
五、模板的分离编译
模板不可以分文件编写, 一但分文件编写, 就会报出链接错误
为什么会有链接错误?
因为对于模板而言, 只是一种定义规范, 并没有真正的定义, 而是在实例化的时候, 才会去真正的定义, 所以如果分文件编写, 在包含了头文件之后, 就只有声明而并不能找到实现, 因为模板是在实例化的时候才去定义, 所以模板定义的函数不会写入符号表, 也就不会被声明找到, 从而引发链接错误
模板是一种在编译时实例化的代码,它们需要在编译时被解析和生成。由于模板的实例化通常与特定的类型参数相关,因此需要在编译时对每个具体的类型参数进行实例化。因此,模板的编译过程必须在编译期间完成,无法在运行时动态生成。因此,模板不可以分离编译。
如果必须分离,可以在.cpp文件中对指定类型进行显式实例化,但是这样太过麻烦,不推荐
//vector的push_back, pop_back...的定义
//......
//......
//显式实例化
//用到哪个实例化模板类就显式写哪个
//比如我要用到vector<int>和vector<double>
//以下我就需要写
template
vector<int>
template
vector<double>