目录
模板是一种泛型编程,编译器可以根据情况将使用模板的部分实例化为具体的一些内容,方便程序员使用,这样一来,程序员只需要写一份代码,其底层可以根据情况需要,生成多份代码来进行使用。
1、关于前缀class/typename
在使用模板的过程中,我们往往可以使用template<class T>的形式来进行实现,其中class也可以和typename相互替代使用,二者区别不大,但是有些地方还是需要特别地去进行使用
template<class container>
void Print(const container& v)
{
container::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
class A
{
public:
int begin()
{
return 0;
}
static int const_iterator;
private:
int _a;
};
int A::const_iterator = 1;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
Print(v);
A(a);
A::const_iterator = a.begin();//使用静态成员的代码与使用迭代器的代码很相似
}
这段代码无法正常运行,问题出在Print函数的迭代器,这里在定义迭代器时必须加上typename作为前缀,当我们使用静态成员时的代码与这里的代码很像,所以如果不加,那么就会产生歧义,编译器并不明确container::const_iterator是类型还是对象,无法编译通过。不过,编译器虽然提示必须加上typename的前缀,但经过尝试后,发现如果加上class作为前缀也没有什么问题。
2、非类型模板参数
模板参数分为类型形参与非类型形参
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
非类型形参:用一个常量作为类模板或者函数模板的一个参数,在类模板或者函数模板中可将该参数当成常量来使用
template<class T,size_t N>
class Stack
{
private:
T _a[N];
int top;
};
int main()
{
Stack<int, 10> st1;
Stack<int, 100> st2;
}
注意:非类型模板参数被认为是常量(不能被修改),且必须为整型
3、模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但是对于一些特殊类型可能会得到一些错误的结果,需要去进行特殊处理,比如在使用仿函数实现大小比较时,如果需要对两个指针指向的内容进行比较,那么使用仿函数传参时,只会传入两个指针指向的地址,并不能对指针内容进行解引用实现我们需要的比较,因此我们需要对模板进行特化,也就是在原模板类的基础上,针对特殊类型所进行特殊化的实现方式
注意:特化的模板前面必须要有一个基础的模板
1)函数模板的特化
template<class T>
bool Less(T left, T right)
{
return left < right;
}
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;
}
bool Less(int* left, int* right)
{
return *left < *right;
}
template<class T>
bool Less(T* left, T* right)
{
return *left < *right;
}
2)类模板的特化
全特化:将模板参数列表中的所有参数都确定化
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
private:
int d1;
char d2;
};
template<>
class Date<int, char>
{
public:
Date()
{
cout << "Date<int,char>" << endl;
}
private:
int d1;
char d2;
};
偏特化:将模板参数类表中的一部分参数特化,或者是对模板参数更进一步的条件限制设计出一个特化版本
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
private:
int d1;
char d2;
};
template<class T1>
class Date<T1,char>
{
public:
Date()
{
cout << "Date<T1,char>" << endl;
}
private:
int d1;
char d2;
};
template<class T1, class T2>
class Date<T1*,T2*>
{
public:
Date()
{
cout << "Date<T1*,T2*>" << endl;
}
private:
int d1;
char d2;
};
template<class T1, class T2>
class Date<T1&,T2&>
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
private:
int d1;
char d2;
};
3)应用
可以使用模板特化来对之前的日期类进行比较大小并排序
注意:模板特化前需要先有基础模板、对日期类的定义必须放在前面
class Date
{
friend ostream& operator<<(ostream& out, Date& d);
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)
{
if (_year < d._year)
return true;
if (_year == d._year && _month < d._month)
return true;
if (_year == d._year && _month == d._month &&_day< d._day)
return true;
return false;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out,Date& d)
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
template<class T>
struct LessPDate
{
bool operator()(T* left, T* right)
{
return *left < *right;
}
};
template<>
struct LessPDate<Date*> // Date类需要再前面定义
{
bool operator()(Date* left, Date* right)
{
return *left < *right;
//return (*left).operator<(*right);
}
};
int main()
{
Date d1(2025, 8, 19);
Date d2(2025, 7, 19);
Date d3(2025, 9, 19);
vector<Date> v;
v.push_back(d1);
v.push_back(d2);
v.push_back(d3);
vector<Date*> v1;
v1.push_back(&d1);
v1.push_back(&d2);
v1.push_back(&d3);
sort(v1.begin(), v1.end(), LessPDate<Date*>());
//for (auto& e : v)
//{
// cout << e << endl;
//}
cout << **v1.begin() << endl;
}
4、模板分离编译
将模板函数的声明和定义分离,就像之前日期类的函数声明和定义分离一样,将模板声明放在.h文件中,定义放在.cpp文件中,在.cpp文件中编译器无法看到模板函数的实例化,也就是说,由于与模板声明分离,在定义的部分就不明确模板具体代表什么内容,因此不会生成具体的函数。
那么可以使用将声明和定义放在一个文件内的办法来进行解决,或者在定义的.cpp文件中显式实例化模板
5、总结
模板的优点在于其复用了代码,节省资源能够实现更快的迭代开发,C++的标准模板库(STL)因此而产生,同时也增强了代码的灵活性
缺点在于模板会导致代码膨胀问题,也会导致编译的时间变长,并且,在出现模板编译错误时,错误信息非常凌乱,不容易定位错误