一、函数模板
1. 概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2. 定义
(1)语法
template <typename T1, Typename T2, ..., Typename Tn>
以 template 开始,后跟模板参数列表,模板参数列表不能为空,用 <> 括起来,用逗号分隔,除了 typename 和 class,我们还可以用一个特定的类型名,它表示一个值,而非一个类型,注意关键字不能用 struct
(2) typename 和 class 的区别
typename 也可以写成 class,typename 和 class 一般通用,但 typename 可以使用嵌套依赖类型,当T是一个类,而这个类又有子类时,例如:typename T::TestType,在这种情况下,typename 会告诉编译器 TestType 是一个类型名称,而不是成员变量或者成员变量,前面没有typename,编译器就不能确定 T::TestType 是一个类型还是一个成员名称,
#include <iostream>
class Test1
{
public:
typedef int TestType;
};
template <class T>
void Test2(T test)
{
typedef typename T::TestType TestType;
TestType test_length = 1;
std::cout << test_length << std::endl;
}
int main()
{
Test1 t1;
Test2(t1);
return 0;
}
3. 函数模板实例化
template <typename T1, typename T2>
T1 Add(T1 left, T2 right)
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
return left + right;
}
void Test()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
cout << Add(1, 2.2) << endl;
cout << Add<int, int>(1, '1') << endl;
}
(1)隐式实例化:Add(1, '1')
没有明确指定模板参数,编译器会在编译阶段通过推演实参来确定模板参数列表中的类型参数所代表的类型,再结合函数模板来生成处理具体类型函数的代码,一般情况下不会进行隐式实例化。
(2)显式实例化:Add<int, int>(1, '1')
明确指定了模板参数列表中T的实际类型,不需要进行参数推演,但是可能会进行隐式类型转化,转化成功编译器生成代码,转化失败将会报错
extern int Add(int, char); 将一个实例化声明为 extern,表示在程序其他位置已经有了一个非 extern 的声明,编译器将不会生成实例化代码
extern int Add(int, int);
int Add(int a, int b)
{
return a + b;
}
(3)调用原理
在实例化之前,对函数模板进行简单的语法检测,如忘记分号或拼写错误
遇到模板时,检查实参数目是否正确,参数数目是否匹配
在实例化期间,通过对实参的类型进行推演来确定模板参数列表中类型参数,根据确定的类型参数的实际类型以及函数模板的实现来生成一份处理具体类型的代码,才可以发现类型的错误,也可能在链接时给出
二、类模板
1. 概念
与函数模板不同,编译器不能为类模板推导参数类型,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
2. 定义
template <class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
3. 类模板成员该函数
在类模板外定义成员函数,需要以 template 开头,后跟模板参数列表
template <class T>
size_t Seqlist<T>::Size()
{
return m_size;
}
4. 友元函数
(1)模板参数为友元
template <typename Type>
class Test
{
friend Type;
};
(1)模板为友元
template <typename T> class Pal;
// C 是一个普通类
class C
{
// 用类的 C 实例化的 Pal 是一个友元
friend class Pal<C>;
// Pal2 的所有实例化都是 C 的友元,无需前置声明
template <typename T> friend class Pal2;
};
template <class T>
// C2 是一个模板类
class C2
{
// C2 的每个实例都被 Pal 声明为友元
friend class Pal<C>;
// Pal2 的所有实例化都是 C2 的每个实例的友元,无需前置声明
template <class X> friend class Pal2;
// Pal3 是一个非模板类,它是所有 C2 的实例
friend class Pal3;
};
5. static 成员
类模板的每个实例独有一个 static 对象
template <typename T>
class Test
{
public:
static size_t count()
{
return m_ctr;
}
private:
static std::size_t m_ctr;
};
6. 注意
类的模板成员不能是虚函数
三、模板参数
(1)模板类型参数
template <class T>
T Add(T a, T b)
{
return a + b;
}
template <class T, U> (x)
(2)非类型模板参数
template <int N, in M>
T Cmp(const char(&p1)[N], const char(&p2)[M])
{
return strcmp(p1, p2);
}
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用,实参必须是一个常量表达式,绑定的指针或引用必须具有静态的生存期,不能用一个普通的局部变量或动态对象作为指针或引用类型模板的实参。
(3)参数列表的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例,如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
四、模板编译
当遇到一个模板时,编译器不会生成代码,当实例化一个特定的模板时,编译器才会生成代码,为了生成实例化版本,编译器需要知道函数模板或类模板的定义,模板的头文件通常需要包括定义与声明。
五、可变参数
template <class ... T>
void PrintSize(T ... args);
1. 求可变参数的个数
template <class... T>
void PrintSize(T... args)
{
cout << sizeof...(args) << endl;
}
2. 可变参数展开
(1)递归调用
template <typename T>
T Sum(T num)
{
return num;
}
template<typename T, typename ... Types>
T Sum(T first, Types ... nums)
{
return first + Sum<T>(nums...);
}
(2)逗号表达式展开
template <class T>
void print(T t)
{
cout << t << endl;
}
template <class ... Types>
void Print(Types ... nums)
{
int arr[] = { (print(nums), 0) ... };
}
六、特例化
1. 函数模板特例化
(1)步骤
必须要先有一个基础的函数模板
关键字template后面接一对空的尖括号<>
函数名后跟一对尖括号,尖括号中指定需要特化的类型
函数形参表: 必须要和模板函数的基础参数类型完全相同
(2)函数重载与模板特例化
函数模板特例化本质是实例化一个模板,它是一个实例,而非函数的重载,匹配时,会选择非函数模板
(3)示例
template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if (strcmp(left, right) > 0)
return true;
return false;
}
2. 类模板特例化
1. 全特化
将模板参数类表中所有的参数都确定化
template <class T1, class T2>
class Print
{
public:
void print(T1 t1, T2 t2)
{
cout << "T1 - T2" << endl;
}
private:
T t1;
T t2;
};
template <>
class Print<int, char>
{
public:
void print()
{
cout << "int - char" << endl;
}
private:
T t1;
T t2;
};
2. 偏特化
部分特例化:将模板参数类表中的一部分参数特化
限制参数:例如参数偏特化为指针类型