目录
模板简要
template<typename T>
template<typename T = int> //默认值
- 类型
T
必须支持模板中的各种操作 - 模板在程序编译期间被实例化,
T
的值必须是编译期常量表达式 - 模板不会产生代码,只有在调用模板时才会产生特定类型的代码
- 模板参数可以有默认值
函数模板
template<typename T>
const T& max(const T& a, const T& b)
{
return a > b ? a : b;
}
int main()
{
max(12,34);
//编译器根据函数实参推断出T是int类型,int类型支持 > 运算符
max<double>(12.34,56);
//必须显式指定模板参数,编译器推断不出来
max<A>(A(),A());
//类A必须支持 > 运算
return 0;
}
- 类型
T
必须支持函数模板里的各种操作,比如上面这个max
函数模板,类型T必须支持>
比较运算符。 - 函数模板本身不会产生代码,只有调用函数模板时,才会在
编译期间
生成一个特定的函数。 - 如果不调用函数模板,就不会产生任何代码。
//普通类型作为模板类型参数
template<typename T,int Q>
const T& max(const T& a, const T& b)
{
return a > b ? a : b;
}
int main()
{
max<int,5>(12,34);
//此时的5也就是模板类型int Q没什么用,仅做测试
int a = 5;
max<double,a>(12.34,56);
//编译失败,模板参数的值必须在编译期间就能确定,只能使用常量,const 或者 constexpr
return 0;
}
- 普通类型也可以作为模板参数,例如
int
,const int
- 类类型和浮点类型不能作为模板参数
类模板
- 必须显式指定模板类型参数,编译器推断不出来
- 类模板的函数声明和实现必须放在同一个头文件中
- 类模板的成员函数(包括普通函数)只有为程序所用才进行实例化。如果某函数从未使用,则不会实例化该成员函数
- 类模板参数也可以是普通类型
template<typename T>
class Myclass
{
public:
Myclass();
~Myclass()
{
//析构函数,类内定义
}
Myclass<T>& operator=(const Myclass& myclass);
Myclass& operator=(const Myclass& myclass);
//在类内,上面两种方式均可,<T>可以不加,但在类外定义一定要加<T>
//因为Myclass<T>才是完整类,Myclass只是一个模板不是类
};
template<typename T>
Myclass<T>::Myclass()
{
//构造函数,类外定义,注意::之前是带<T>的完整类名
}
template <typename T>
Myclass<T>& Myclass<T>::operator=(const Myclass& myclass)
{
//类外定义
}
typename
- typename用于模板参数列表关键字,此时可以与class关键字互换
- typename用于声明其后是一个类型,此时不可以与class关键字互换
template<class T> //此时typename可与class互换
class Myclass
{
public:
using point = T*; //定义一个类内类型
point myfunc(); //返回类型是类内类型
};
template<typename T>
typename Myclass<T>::point Myclass<T>::myfunc()
{
//类外实现时,必须要加typename声明其后的Myclass<T>::point是一个类型
}
成员函数模板
- 成员函数模板不能是虚函数
- 成员函数模板本身是函数模板,能够推断模板类型
- 不调用成员函数模板就不会实例化函数
//普通类的成员函数模板
class Myclass
{
public:
template<typename T>
T func(T temp);
};
template<typename T>
T Myclass::func(T temp)
{
//...
}
//模板类的成员函数模板
template<typename T>
class Myclass
{
public:
template<typename F>
F func(F temp);
};
template<typename T> //类的模板参数列表
template<typename F> //函数的模板参数列表
F Myclass<T>::func(F temp)
{
//...
}
模板显式实例化
有这样一个问题
如果一个头文件中定义了一个模板,在两个或多个源文件中都使用了这个模板
因为每个文件都是独立编译,最后再链接为可执行程序
那么在每个源文件中都会生成一个模板的实例
如果模板类型还相同,就会生成相同的实例,也就是重复代码。
解决方法
在一个源文件中显式实例化
template Myclass<int>; //显式实例化类模板
template int& add(int&, int&); //显式实例化函数模板
在其他使用到相同类型的源文件中显式声明
extern template Myclass<int>; //显式声明类模板
extern template int& add(int&, int&); //显式声明函数模板
缺点
- 有的编译器不会实现这种效果,还是会在每个源文件中都实例化
- 这种显式实例化会实例化类模板里的所有函数,而不是调用的时候再实例化
using
//函数指针
template<typename T>
using func = T(*)(T, T);
int sum(int a, int b)
{
return a + b;
}
int main()
{
func<int> f = sum;
std::cout << f(12, 34);
return 0;
}
模板特化
- 特化与泛化相反,是对特殊的类型进行特殊的处理
- 模板特化依赖于泛化,没有泛化的模板,也就没有特化的模板
全特化
//泛化的类模板
template<typename T,typename F>
class Myclass
{
public:
Myclass()
{
cout << "泛化版本" << endl;
}
void func()
{
cout << "泛化版本func()函数" << endl;
}
};
//<int,int>全特化的类模板
template<>
class Myclass<int, int>
{
public:
Myclass()
{
cout << "<int,int>特化版本" << endl;
}
//这样特化之后,<int,int>实例出的类中就不存在func()函数
};
//特化<int,double>的成员函数
template<>
void Myclass<int, double>::func()
{
cout << "特化<int,double>::func()函数" << endl;
}
偏特化
模板参数数量上偏特化
template<typename T,typename F,typename U>
class Myclass
{
public:
Myclass()
{
cout << "泛化版本" << endl;
}
};
template<typename T>
class Myclass<int, T, int>
{
public:
Myclass()
{
cout << "<int,T,int>偏特化版本" << endl;
}
};
模板参数范围上偏特化
template<typename T>
class Myclass
{
public:
Myclass()
{
cout << "泛化版本" << endl;
}
};
template<typename T>
class Myclass<const T>
{
public:
Myclass()
{
cout << "<const T>偏特化版本" << endl;
}
};
template<typename T>
class Myclass<T*>
{
public:
Myclass()
{
cout << "<T*>偏特化版本" << endl;
}
};
template<typename T>
class Myclass<T&>
{
public:
Myclass()
{
cout << "<T&>偏特化版本" << endl;
}
};
template<typename T>
class Myclass<T&&>
{
public:
Myclass()
{
cout << "<T&&>偏特化版本" << endl;
}
};
函数模板特化
函数模板只能全特化,不能偏特化
template<typename T, typename U>
void add(T a, U b)
{
cout << "泛化" << endl;
}
template<>
void add<int, int>(int a,int b)
{
cout << "特化" << endl;
}
可变参数模板
- C++ 2.0新特性,模板可以接受任意个数的参数。
- 三个点号
...
有了特殊意义。 - 参数可以是任意个数,并且任意类型,类型可以不相同。
可变参数函数模板
//终止函数
void print()
{
}
//参数包展开
template <typename T, typename... Types>
void print(const T& firstArg,const Types&... args)
{
cout << firstArg << endl;
cout << sizeof...(args) << endl;
print(args...);
}
template <typename... Types>
void print(const Types&... args)
{
cout << sizeof...(args) << endl;
}
int main(int argc, char* argv[])
{
//可以省略print<>里的内容,函数模板自动推断
print(7.5, "hello", bitset<16>(377), 42);
return 0;
}
- main中的print调用第二个print(第二个print比第三个print更特化),此print接受一个参数和一个包(不定参数)。因此至少需要一个参数(包可以为空)。
- 第一个print用来处理第二个print的最后一次调用(最后没有参数了)
可变参数类模板
- 递归继承方式 与 递归组合方式 展开参数包
递归继承方式展开参数包template<typename... Types> class Myclass { public: Myclass() { cout << "主模板 && 终止模板" << endl; } };
递归组合(复合)方式展开参数包template<typename T,typename... Types> class Myclass<T, Types...> :public Myclass<Types...> { public: Myclass(T t, Types... types) :Myclass<Types...>(types...) { cout << typeid(T).name() << ":" << t << endl; } };
main函数调用template<typename T,typename... Types> class Myclass<T, Types...> { public: Myclass(T t, Types... types) :m_class(types...) { cout << typeid(T).name() << ":" << t << endl; } Myclass<Types...> m_class; };
程序运行结果:int main(int argc, char* argv[]) { //必须显式指定<>中的模板参数 Myclass<int, double, char> myc(1, 12.3, 'q'); return 0; }
主模板 && 终止模板
char:q
double:12.3
int:1 - tuple和递归调用 展开参数包
程序运行结果:template<int count,int maxCount,typename... T> class Myclass { public: static void func(const tuple<T...>& t) { cout << get<count>(t) << endl; Myclass<count + 1, maxCount, T...>::func(t); } }; //特化版本,用于结束递归调用 template<int maxCount, typename... T> class Myclass<maxCount, maxCount, T...> { public: static void func(const tuple<T...>& t) { //空 } }; template<typename... T> void func(const tuple<T...>& t) { Myclass<0, sizeof...(T), T...>::func(t); } int main(int argc, char* argv[]) { tuple<float, int, char> t(12.3f, 100, 'y'); func(t); return 0; }
12.3
100
y