没了解过模板的读者,先学习模板初阶: C++之模板初阶
通过模板我们可以实现泛型编程,模板分为函数模板和类模板,下面我们就说点模板进阶的一些东西。
模板参数分类类型形参与非类型形参。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
举个例子,比如我们写个静态栈结构:
#define N 10
template<class T>//类型模板参数
class Stack
{
private:
T _a[N];
size_t _top;
};
int main()
{
Stack<int> st1;//大小为10
Stack<int> st2;
}
我们想要改栈的大小就改宏就可以了,但是我们有两个栈呢?一个栈的大小想要10,另一个栈想要1000的大小,这样就不能满足多个栈的需求了,除非再定义一个类模板,但是这样代价太大,在C++模板当中有一个非类型的模板参数概念:
template<class T,size_t N>
//T是类型模板参数,N是非类型模板参数,N是一个常量
class Stack
{
private:
T _a[N];
size_t _top;
};
int main()
{
Stack<int,100> st1;//100
Stack<int,20000> st2;//20000
}
我们呢可以这样传参吗?
int main()
{
static int n;
cin>>n;
Stack<int,n> st;//error,非类型模板参数不能是变量
return 0;
}
在STL中的容器当中,C++11新增了array这个容器,array这个容器就是类似这样的结构,它使用了非类型的模板参数:
template<class T,size_t N>
class Array
{
private:
T _a[N];
}
但是array这个容器不建议使用,为什么呢?
C缺点之一:后期C11等等标准增加了不少鸡肋的语法,让语言变得臃肿,学习成本增加,一些刚需的东西,姗姗来迟,甚至还没来(网络库)。
比如:
//模板参数都可以给缺省值
//模板参数给缺省值和函数参数给缺省值是完全类似的
//可以全缺省
//也可以半缺省 -- 必须从右往左连续缺省
template<class T,size_t N = 10>
class Array
{
private:
T _a[N];
}
int main()
{
Array<int> a1;
Array<int,20> a2;
return 0;
}
需要注意的是,如果全都是缺省值时不能这样创建对象:
Array a1;
全部都是缺省值,我们可以不传参数,但是我们知道Array是个模板,模板也是有类型的,我们需要这样:
Array<> a1;
注意:
template<class T,string s1>
template<class T,double s1>
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如:
template<class T>
bool IsEqual(const T& left,const T& right)
{
return left==right;
}
int main()
{
cout<<IsEqual(1,2)<<endl;
char p1[] = "hello";
char p2[] = "hello";
cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量
return 0;
}
我们想一想可能可以这样解决:
template<class T>
bool IsEqual(const T& left,const T& right)
{
if(T == const char*)
{
return strcmp(left,right)==0;
//可是语法不支持
}
else
{
return left==right;
}
}
模板的特化,针对某些类型进行特殊化处理,我们可以这样写:
bool IsEqual(const char*& left,const char*& right)
{
return strcmp(left,right)==0;
}
int main()
{
cout<<IsEqual(1,2)<<endl;
char p1[] = "hello";
char p2[] = "hello";
cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量
return 0;
}
需要这样改,这样就可以进去了:
//模板的特化,针对某些类型进行特殊化处理
bool IsEqual(const char*& const left,const char*& const right)
{
return strcmp(left,right)==0;
}
也可以这样改,将引用去掉:
bool IsEqual(const char* left,const char* right)
{
return strcmp(left,right)==0;
}
template<class T>
void Swap(T& a,T& b)
{
//vector代价太大
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 1;
int y = 2;
Swap(x,y);
vector<int> v1 = {1,2,3,4};
vector<int> v2 = {10,20,30,40};
Swap(v1,v2);
return 0;
}
当我们交换的类型为vector时,此时用模板函数进行交换代价太大了,一次拷贝构造+两次赋值重载,所以我们可以这样写:
//函数模板的特化
template<>
void Swap<vector<int>>(vector<int>& a,vector<int>& b)
{
a.swap(b);
}
当然也可以这样,利用模板的匹配原则,进行特殊化处理:
//模板的匹配原则,进行特殊化处理
void Swap(vector<int>& a,vector<int>& b)
{
a.swap(b);
}
template<class T1,class T2>
class Data
{
public:
Data() { cout << "Data<T1,T2>"<<endl; }
private:
T1 _d1;
T2 _d2;
};
//全特化
template<>
class Data<double, double>
{
public:
Data() { cout << "Data<double,double>" << endl; }
private:
T1 _d1;
T2 _d2;
};
int main()
{
Data<int,int> d1;
Data<double,double> d2;
return 0;
}
偏特化有两种表现方式:
template<class T1,class T2>
class Data
{
public:
Data() { cout << "Data<T1,T2>"<<endl; }
private:
T1 _d1;
T2 _d2;
};
//偏特化或者半特化
template<class T1>
class Data<T1,char>
{
public:
Data() { cout << "Data<T1,double>" << endl; }
};
int main()
{
Data<double,char> d2;
Data<int,char> d1;
return 0;
}
template<class T1,class T2>
class Data
{
public:
Data() { cout << "Data<T1,T2>"<<endl; }
private:
T1 _d1;
T2 _d2;
};
//偏特化或者半特化:不一定是特化部分参数,有可能是对参数的限制
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*,char*> d5;
Data<int*,int*> d6;
Data<int&,char&> d7;
Data<int&,int&> d8;
return 0;
}
首先上结论:模板不支持分离编译
我们正常写模板是需要声明和定义放在一起的,是因为模板不支持分离编译:
//.h文件
template<class T>
void F(const T& x)
{
cout<<"void F(const T& x)"<<endl;
}
下面我们来验证不支持分离编译的原因是什么:
首先在.h文件中写模板的声明:
template<class T>
void F(const T& x);//声明
在.cpp中写模板的定义:
#include"Func.h"
template<class T>
void F(const T& x)//定义
{
cout << "void F(const T& x)" << endl;
}
在test.cpp中测试:
#include"Func.h"
int main()
{
F(1);
}
此时出现了链接错误,为什么我们平时使用的普通函数不会报错,而模板函数会报链接错误的呢?
首先我们有这三个文件:
Func.h Func.cpp test.cpp
程序生成可执行程序的过程是编译和链接,编译阶段又分为预处理、编译、汇编三个阶段:
1、预处理
预处理之后生成的文件是Func.i、test.i,Func.cpp和test.cpp分别变成了:
Func.i
template<class T>
void F(const T& x);
void F(const T& x)
{
cout<<"void F(const T& x)"<<endl;
}
Test.i
template<class T>
void F(const T& x);
int main()
{
F(1);
}
2、编译
对应生成的文件是Func.s和test.s
3、汇编
对应生成的文件是Func.o和test.o
4、链接
模板的实例化是在编译阶段要做的事情,在编译阶段,Func.i生成Func.s时并不知道T是什么类型,实例化的指令test.i文件里才知道,所以并没有实例化,而在链接之前它们不进行交汇,各自干各自的事情,Func.i生成Func.s没有实例化生成,所以在链接时候不会找到F函数模板生成的实例化函数,就发生了链接错误。
解决方案一:
在Func.cpp文件中显式指定实例化
template
void F(const int& x);
缺陷:用一个类型就得显式实例化一个,非常麻烦
解决方案二:
不分离编译。声明和定义或者直接定义在.h中
对于类也是一样的:
Func.h
#include<iostream>
using namespace std;
template<class T>
void F(const T& x)//定义
{
cout << "void F(const T& x)" << endl;
}
template<class T>
class Stack
{
public:
Stack();
~Stack();
private:
T* _a;
int _top;
int _capacity;
};
Func.cpp
#include"Func.h"
template<class T>
Stack<T>::Stack()
{
_a = new T[10];
_top = 0;
_capacity = 10;
}
template<class T>
Stack<T>::~Stack()
{
delete[] _a;
_a = nullptr;
}
test.cpp
#include"Func.h"
int main()
{
Stack<int> st;
}
此时运行程序也会发生链接错误:
我们的解决方法和函数模板是完全类似的:
对于类模板,还有一个概念:按需实例化
比如我们还另外写了push函数
#include<iostream>
using namespace std;
template<class T>
void F(const T& x)//定义
{
cout << "void F(const T& x)" << endl;
}
template<class T>
class Stack
{
public:
Stack()
{
_a = new T[10];
_top = 0;
_capacity = 10;
}
template<class T>
~Stack()
{
delete[] _a;
_a = nullptr;
}
void push(const T& x)
{
_a[_top] = x;
_top++;
}
private:
T* _a;
int _top;
int _capacity;
};
但是我们在test.cpp当中不使用栈的push操作:
#include"Func.h"
int main()
{
Stack<int> st;
return 0;
}
此时我们故意将push函数弄出个语法错误,比如去掉分号:
void push(const T& x)
{
_a[_top] = x
_top++;
}
原因:
当我们使用push成员函数时:
此时就会报错了。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/attemptendeavor/article/details/121863281
内容来源于网络,如有侵权,请联系作者删除!