一、普通类的成员函数模板
1、不管普通类还是类模板,它的成员函数可以是一个函数模板,成为”成员函数模板“, 不可以是虚函数,否则编译器报错;
class A
{
public:
template<typename T>
void MyFt(T tmpt)//成员函数模板
{
cout << "tmpt : " << tmpt << endl;
}
};
int main()
{
A a;
a.MyFt(3);//函数模板自己会类型推断,自己推断出int型
//编译器在遇到这条语句的时候,编译器就会实例化这个函数模板
return 0;
}
二、类模板的成员函数模板
1、类模板的模板参数必须用<>指定,成员函数模板(函数模板)的参数都可以编译器推断
template<typename C>
class A
{
public:
template<typename T1>
A(T1 v1, T1 v2);
template<typename T2>
void MyFt(T2 tmpt)
{
cout << "tmpt : " << tmpt << endl;
}
};
template<typename C>
template<typename T1>
A<C>::A(T1 v1, T1 v2)
{
cout << "有参构造函数执行" << endl;
}
int main()
{
A<float> a(1, 2);
A<float> a2(1.1, 1.2);//A<float>已经实例化了,编译器不会在实例化一个A<float>类
//型了
return 0;
}
2、类模板的成员函数(包括普通成员函数/成员函数模板) 只有为程序所用(代码中出现了对该函数或者该函数模板的调用时)才进行实例化。如果某函数从未使用,则不会实例化该成员函数,在整个代码段或者其他段中都没有编译进去;
三、模板显式实例化、模板声明
1、为了防止在.cpp文件中都实例化相同的类模板,所以c++11提出了解决方法,称为“显示实例化”,通过“显示实例化”来避免这种生成多个相同类模板实力的开销:
2、显示实例化实现:
//x.h
template<typename C>
class A
{
...
};
template <typename T>
void myFunc(T i1, T i2)
{
...
}
//project1.cpp
//“显式实例化”手段中的“实例化定义”,这种实例化定义只需要在一个.cpp文件写就可以了
template class A<float>;
template void myFunc(int v1, int v2);
//project2.cpp
//其他cpp文件使用A<float>
//“显式实例化”手段中的“实例化声明”
extern template class A<float>;
extern template void myFunc(int v1, int v2);
//extern作用:不会在本文件中生成一个extern后边所表示的模板实例化版本代码。
//extern目的:告诉编译器,在其他的源文件(.cpp文件)中已经有了一个该模板的实例化版本了。
四、using定义模板别名
1、typedef一般用来定义类型别名;
typedef usigned int uint_t;//相当于给unsigned int类型起了一个别名uint_t]
uint_t abc;
2、类模板使用typedef获取别名:
typedef std::map<std::string, int> map_s_i;
map_s_i myMap;
myMap.insert({"first", 1});
3、希望定义一个类型,如:std::map<std::string, 类型自己定>,以下是c++11实现。
template<typename T>
using str_map_t = std::map<std::string, T>;//str_map_t是类型别名
//using用来给一个“类型模板”起名字(别名)用的
int main()
{
str_map_t<int> map;
map.insert({"second", 2});
return 0;
}
using在用于定义类型或类型模板的时候,其包含了typedef的所有功能,如下;
typedef usigned int uint_t;//typedef定义类型的方法感觉像定义一个变量:类型名 变量名
using uint_t = unsigned int;//using定义类型的定义方法感觉像赋值一样
typedef std::map<std::string, int> map_s_i;
using map_s_i = std::map<std::string, int>;
template<typename T>
using myFunc_M = int(*)(T, T);//定义类型模板,是个函数指针模板
int myTestFunc(int i1, int i2)
{
cout << "myTestFunc" << endl;
return 0;
}
int main()
{
myFunc_M<int> mf = myTestFunc;
mf(1, 2);
return 0;
}
4、using中使用这种模板,既不是类模板也不是函数模板,我们可以看成是一种新的模板类型:类型模板(模板别名)
五、显式指定模板参数
template<typename T1, typename T2, typename T3>
T1 sum(T2 i1, T3 i2)
{
T1 result = i1 + i2;
return result;
}
int main()
{
//显示指定模板参数
auto result = sum<long, long, long>(2000000000, 2000000000);
cout << result << endl;
return 0;
}
六、类模板特化
1、泛化:模板,可以随便指定类型
2、特化:对特殊的类型(类型模板参数)进行特殊对待,给它开小灶,给它写合适它的专用代码;
3、类模板全特化:
template<typename T, typename U>
class TC //泛化的TC类模板
{
void functest()
{
cout << "泛化版本" << endl;
}
};
1)全特化:就是所有类型模板参数(如下T和U),都得用具体的类型代表;
template<typename T, typename U>//特化版本必须要有泛化版本
class TC //泛化的TC类模板
{
void functest()
{
cout << "泛化版本" << endl;
}
};
template<>//全特化:所有类型模板参数都用具体类型代表,所以这里的template后边的<>里就为空
class TC<int, int>//上边的T绑定到这里的第一个int,上边的U绑定到这里的第二个int
{
//在这里可以对该特化版本做单独处理
void functest()
{
cout << "int, int的特化版本" << endl;
}
};
//一个泛化版本,可以有无数个特化版本
template<>
class TC<double, int>
{
void functest()
{
cout << "double, int的特化版本" << endl;
}
};
int main()
{
TC<int, double, double> td;
td.functest();
return 0;
}
2)常规全特化:
a)注意:必须先有泛化版本,才能存在特化版本。只要涉及特化,一定先存在泛化;
b)当T和U这两个类型模板参数都为int类型时,我们希望做一个特化版本;
c)编译器会优先选择特化版本代码;
template<typename T, typename U>//特化版本必须要有泛化版本
class TC //泛化的TC类模板
{
void functest()
{
cout << "泛化版本" << endl;
}
};
template<>//全特化:所有类型模板参数都用具体类型代表,所以这里的template后边的<>里就为空
class TC<int, int>//上边的T绑定到这里的第一个int,上边的U绑定到这里的第二个int
{
//在这里可以对该特化版本做单独处理
void functest()
{
cout << "int, int的特化版本" << endl;
}
}
//一个泛化版本,可以有无数个特化版本
template<>
class TC<double, int>
{
void functest()
{
cout << "double, int的特化版本" << endl;
}
}
int main()
{
TC<char, int> tcCharInt;
tcCharInt.functest();//调用泛化版本
TC<int, int> tcIntInt;
tcIntInt.functest();//调用int, int的特化版本
TC<double, int> tcDoubleInt;
tcDoubleInt.functest();//调用double, int的特化版本
return 0;
}
3)特化成员函数,而不是模板:
template<typename T, typename U>//特化版本必须要有泛化版本
class TC //泛化的TC类模板
{
public:
void functest()
{
cout << "泛化版本" << endl;
}
};
template<>
void TC<double, double>::functest()
{
cout << "double,double的functest()特化版本" << endl;
}
int main()
{
TC<double, double> tdd;//泛化版本对象,调用的是泛化版本的构造函数
tdd.functest();//因为我们特化了double,double类型的functest函数,所以调用的是特化版本的
//functest函数
return 0;
}
4、类模板偏特化(局部特化):
1)偏特化从两方面说起:
a)从模板参数数量上:
template<typename T, typename U, typename W>
class TC
{
public:
void functest()
{
cout << "泛化版本" << endl;
}
};
//从参数数量上进行偏特化,我们现在绑定2个类型模板参数,留一个类型模板参数
template<typename U>//因为另外两个被绑定到具体类型,所以这里只剩下一个U类型模板参数了
class TC<int, U, double>//注意类型模板参数可以跳着来的
{
public:
void functest()
{
cout << "偏特化int, U, double版本" << endl;
}
};
b)从模板参数范围上:
template<typename T>//特化版本必须要有泛化版本
class TC //泛化的TC类模板
{
public:
TC()
{
cout << "泛化版本构造函数" << endl;
}
void functest()
{
cout << "泛化版本" << endl;
}
};
template<typename T>
class TC<const T>
{
public:
void functest()
{
cout << "const T 特化版本" << endl;
}
};
template<typename T>
class TC<T *>
{
public:
void functest()
{
cout << "T * 特化版本" << endl;
}
};
template<typename T>
class TC<T &>
{
public:
void functest()
{
cout << "T & 特化版本" << endl;
}
};
int main()
{
TC<int> td;
td.functest();//泛化版本
TC<const int> td;
td.functest();//const T 特化版本
TC<int *> td1;
td.functest();//T * 特化版本
TC<int &> td2;
td.functest();// T & 特化版本
return 0;
}
5、偏特化(局部特化)还是模板类型,全特化就不是模板了;
七、函数模板特化
1、函数模板全特化:
template<typename T, typename U>
void tfunc(T &v1, U &v2)//泛化版本
{
cout << "tfunc泛化版本" << " v1 : " << v1<< " v2 : " << v2 << endl;
}
template<>
void tfunc(int &v1, double &v2)
{
cout << "tfunc全特化版本" << " v1 : " << v1<< " v2 : " << v2 << endl;
}
int main()
{
// const char *p = "I Love China!";
// int i = 12;
// tfunc(p,i); //执行泛化版本
int i = 1;
double di = 2.2;
tfunc(i, di);//执行全特化版本
return 0;
}
a)全特化函数模板实际上等价于实例化一个函数模板,并不等价于一个函数重载;
b)如果既有特化版本又有重载函数,编译器首先会选择:普通函数>特化版本>泛化版本;
void tfunc<int, double>(int &, double &){};//全特化等价于实例化一个函数模板
void tfunc<int &v1, double &v2>{};//函数重载
2、函数模板偏特化:函数模板不能偏特化;
八、模板特化版本放置位置建议
1、模板定义、实现一般放在.h中;
2、模板的特化版本应该和模板的泛化版本都应该放在同一个.h文件中;
3、泛化版本放在各个特化版本之前;
九、可变参数模板
1、可变参模板(Variadic Template):允许模板中含有0个到任意个模板参数,在语法上也和传统模板不太一样,多了一个“...”;
template<typename ... T>
void functest(T... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
functest("abc");//输出1
functest(1, 2, 3 ,"a");//输出4
return 0;
}
1)我们一般把这个args成为“一包”或“一堆”参数,而且这些参数的类型可以各不相同;
2)我们理解T这种类型的时候,不能把它理解成一个类型,需要理解成零到多个不同的类型,自然对应的参数args也应该是多个不同类型的参数;
3)这一包参数中可以容纳0到多个模板参数,而且这些模板参数可以为任意的类型;
4)T后面带了“...”,所以我们称呼T为“可变参类型”。它看起来是一个类型名,实际上它是包含了零或者多个不同的类型(一包类型)
5)args:可变形参,既然T代表的是一包类型,那显然args代表的就是一包形参;
template<typename T, typename... U>
void myfunc1(const T &firstarg, const U&... otherargs)//(一个参数,一包参数)这种可变参函数模板写法最适合参数包的展开
{
cout << sizeof...(otherargs) << endl;
}
int main()
{
myfunc1(1);//输出0
myfunc1(10, "abc", 12.7);//输出2
return 0;
}
2、参数包展开:
1)展开套路比较固定,一般都是用递归函数的方式来展开参数。要求我们在代码编写中,有一个参数包展开函数和一个同名的递归终止函数;
//递归终止函数
void myfunc1()
{
cout << "参数包展开时执行了递归终止函数myfunc1" << endl;
}
template<typename T, typename... U>
void myfunc1(const T &firstarg, const U&... otherargs)
{
cout << sizeof...(otherargs) << endl;
cout << "收到的参数值为:" << firstarg << endl;
myfunc1(otherargs...);//递归调用,注意写法
}
int main()
{
myfunc1(10, "abc", 12.7);//输出2
//1:myfunc1(10, "abc", 12.7);
//2:myfunc1("abc", 12.7);
//3:myfunc1(12.7);
//4:myfunc1();终止函数
return 0;
}
十、可变参类模板
1、略!