模板是泛型编程的一种方式,主要目的就是节约代码量防止大量的重写代码。
函数模板
建立函数模板
建立一个函数模板,class可换为typename,但class更为通用
#include <bits/stdc++.h>
using namespace std;
template <class T1, class T2>
void show(T1 a, T2 b) {
cout << a + b ;
}
int main() {
int n = 100;
show(n, n);
return 0;
}
输出为200,调用show(int,int)
模板重载
多个对不同类型使用同一种算法的函数,也可使用模板,比如当想输出两个数组相加的和时,就得重载上述的函数,调用函数时会调用最匹配的函数
#include <bits/stdc++.h>
using namespace std;
template <class T1, class T2>
void show(T1 a, T2 b) {
cout << a + b ;
}
template <class T1, class T2>
void show(T1 *a, T2 *b, int c) {
int sum = 0;
for (int i = 0; i < c; i++)
sum += a[i] + b[i];
cout << sum;
}
int main() {
int a[5] = {1, 2, 3, 4, 5};
int b[5] = {5, 4, 3, 2, 1};
show(a, b, 5);
return 0;
}
这里show函数有两个版本,程序调用第二个输出两个数组和为30
可变模板参数
C++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数,可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”
#include <bits/stdc++.h>
using namespace std;
template<typename T>
void show(T a) {
cout << a << " end" << '\n';
}//只有一个参数时退出递归循环,这个退出函数必须写在前面!
template<typename T, typename ... Args>
void show(T a, Args ... args) {
cout << a << " rest=" << sizeof...(args) << '\n';
show(args...);//递归调用
}
int main() {
show(1, 6, 8, 's', "6fa", 5);
return 0;
}
输出结果
1 rest=5
6 rest=4
8 rest=3
s rest=2
6fa rest=1
5 end
显式具体化,显式实例化,隐式实例化(重点)
如果函数的操作不能直接实现(比如结构体相加)或者想对特定的模板进行操作,就得使用显式具体化,这拓宽了函数模板的操作,使其更为灵活,如果没找到匹配的具体化的模板函数,那么就是隐式实例化
#include <bits/stdc++.h>
using namespace std;
template <class T1, class T2>
void show(T1 a, T2 b) {
cout << a + b << " use implicit instantiation\n";
}
template <> void show<int, int> (int a, int b ) {
cout << a *a + b *b << " use explicit instantiation\n";
}
int main() {
int a = 5;
int b = 6;
show(a, b);
return 0;
}
a和b是int类型调用第二个show函数并输出 61 use explicit instantiation表明调用了显式具体化的模板函数
直接命令编译器创建特定的实例是显式实例化,模版的显式实例化其实只是一个声明,比如
template void show<int, int> (int, int);
注意template后面没有<>,这是和显式具体化的区别
根据在同一文件中使用同类型的显示实例化和显示具体化会报错的特点,先声明显示实例化,则后面的显示具体化如果存在,会报错,从而防止具体化该型模版!
c++11中,可以先具体化,再实例化声明,不会报错
二义性与调用优先级
如果原型完全匹配就会出现二义性
例如
void show(const int a) ;
void show(int a) ;
这就出现了二义性,但引用可以,指向非const数据的指针和引用优先与非const数据的指针和引用参数匹配,下面的不会出现二义性
void show(const int &a)
void show(int &a)
但如果有模板函数又不一样,普通函数优先于模板函数(即使两个函数完全匹配),如果匹配的都是模板函数,而较为具体的模板函数优先
例如
template <class T1, class T2>
void show(T1 a, T2 b) {
cout << a + b << " use the first function";
}
template<class T1> void show(T1 a, int b) {
cout << a << " use the second function";
}
这样调用如果是传入的第二个参数是int会调用第二个函数
最具体的也表示编译器推断使用哪种类型时执行的转换最少
例如
void show(T a) {
cout << a << " use the first function";
}
template<class T> void show(T *a) {
cout << a << " use the second function";
}
如果调用show(&a) 那么会调用第二个函数,因为第二个函数已经显式指出函数参数是指向T的指针,而第一个函数必须将T转换为指针类型,多了一层转换,所以第二个函数更具体。
总结:调用优先级:普通函数>显式具体化>显式实例化>普通模版
类模板
建立类模板
xx为类名
template<typename T>
class xx{
};
类模板和函数模板的区别(重点)
1、类模板没有隐式实例化,函数模板有
2、类模板可以偏特化(部分具体化),函数模板不可以
注:函数模板和类模板都可以有默认参数
#include <bits/stdc++.h>
using namespace std;
template<typename T1, typename T2 = int>
class Test {
public:
T1 a;
T2 b;
Test(T1 a, T2 b) {
this->a = a;
this->b = b;
cout << "the first\n";
}
};
template<typename T>
class Test <T, int> {
public:
T a;
int b;
Test(T a, int b) {
this->a = a;
this->b = b;
cout << "the second\n";
}
};
template<typename T1, typename T2=int>
void show(T1 a, T2 b) {
cout << a << b << '\n';
}
/*template<typename T1>
void show<T1, int>(T1 a, int b) {
cout << a << b << '\n';
}*///不可以声明,因为不可以有函数模板的偏特化
int main() {
//Test tmp("abcde", 50);不可以,因为没有指定具体的模板类型
Test <string>tmp("abcde", 50); //正确的声明,且类模板有默认参数,由于偏特化,匹配了第二个类
return 0;
}
类模板成员函数的生成
如果成员函数中需要调用模板成员的方法,只有在调用此函数的时候才会生成该成员函数
#include <bits/stdc++.h>
using namespace std;
template<typename T>
class Test {
public:
T a;
Test(T a) {
this->a = a;
}
void show() {
a.skr();
}
};
int main() {
Test <string>tmp("abcde");//成功,因为还没调用show方法
tmp.show();//失败,string类没有skr方法
return 0;
}
类模板的继承
继承时必须指定父类模板的类型,可以用子类的模板来指定
template<typename T>
class Test {
};
class Test_son1: public Test<string> {
};
template<typename T>
class Test_son2: public Test<T> { //用子类的模板指定
};
类模板与指针栈
如果想要定义一个类模板里存放指针栈,第一要为这个栈开辟空间,创建一个指针数组,注意:栈的任务是管理指针而不是创建新的指针。第二就是每次往栈中压入指针时应该压入指向存放数据的不同内存的不同指针,避免出现用一个指针读入数据再压入栈中,这会导致最后压入的都是一个指针的地址,这里不做代码演示
类模板和友元
模板的友元分为三种
1.非模板友元
2.约束模板友元
3.非约束模板友元
非模板友元
友元函数不是函数模板,也就是说这个友元函数是所有模板实例化的友元(这个是重点),这个友元函数只是把类模板当作参数,并且需要重载不同版本参数的友元函数
#include <bits/stdc++.h>
using namespace std;
template<typename T>
class Test {
private:
static int count;
public:
friend void show();
friend void multi_edition(Test<T>);
Test() {
count++;
}
~Test() {
count--;
}
};
template<typename T>
int Test<T>::count = 0;
void show() {
cout << "number of Test<int>=" << Test<int>::count << '\n';
cout << "number of Test<double>=" << Test<double>::count << '\n';
}
void multi_edition(Test<int> s) {
cout << "Test<int> edition\n";
}//Test<int>的友元普通函数
void multi_edition(Test<double> s) {
cout << "Test<double> edition\n";
}//Test<double>的友元普通函数
template <typename T>
void multi_edition(T s) {
cout << typeid(s).name() << " edition\n";
}//重载的函数模板
int main() {
Test<int> t1, t2;
Test<double> t3;
int p = 100;
show();
multi_edition(t1);
multi_edition(t3);
multi_edition(p);
return 0;
}
输出为
number of Test<int>=2
number of Test<double>=1
Test<int> edition
Test<double> edition
i edition
约束模板友元
既然友元函数可以是非模板的,那么也可以是模板的,并且可以将其约束到类的每个具体化中
与友元函数的具体化相匹配,每个具体化的模板都有自己的具体化的友元函数(这是和前面的非模板友元最大的区别!),这里的关键是友元函数的具体化,可以回顾前面的函数模板的具体化的写法,将<>置于函数名后面
下面必须先声明函数模板show,原因是下面要使用show的具体化版本
#include <bits/stdc++.h>
using namespace std;
template<typename T>
void show();
template<typename T>
class Test {
private:
static int count;
public:
friend void show<T>();//友元模板的具体化
Test() {
count++;
}
~Test() {
count--;
}
};
template<typename T>
int Test<T>::count = 0;
template<typename T>
void show() {
cout << "number of Test<T>=" << Test<T>::count << '\n';
}//避免了具体化每一个友元函数
int main() {
Test<int> t1, t2;
Test<double> t3;
show<int>();
show<double>();
return 0;
}
非约束模板友元函数
前面是在类外面声明的模板的具体化,不同类获得不同函数的具体化。当然也可以在类的内部声明模板,这样每个函数的具体化都是每个类的友元函数(这是关键!从一对一变为一多对多)
template<typename T>
class Test {
public:
template<typename T1,typename T2> friend void show(T1 a,T2 b);
};
|