为了解决强类型语言的严格性与灵活性的冲突,将类型抽象
语言类型
模板的定义形式
template <class/typename T>
模板的类型
template <模板参数表> //模板参数列表不能为空
返回类型 函数名(参数列表)
{ 函数体 }
template <class T>
T func(T t)
函数模板通过**模板参数推导(实例化)**为模板函数。
// 隐式实例化:编译器自动推导。
cout << add(i1, i2) << endl; // 由i1,i2都是int,自动生成函数 int add(int,int)
// 显式实例化
cout << add<double>(d1, d2) << endl;
函数模板与普通函数间的关系
模板的特化:统一的函数模板不能适用的类型时,模板在特定类型下的实现,。
#include
#include
using std::cout;
using std::endl;
template <class T>
T add(T x, T y)
{
cout << "T add(T,T) " << endl;
return x + y;
}
//模板的全特化,是函数模板。也可以函数重载,普通的函数
template <>
const char * add<const char *>(const char * px, const char * py)
{
char * ptmp = new char[strlen(px) + strlen(py) + 1]();
strcpy(ptmp, px);
strcat(ptmp, py);
return ptmp;
}
int main() {
const char * ps1 = "hello";
const char * ps2 = "world";
const char * pstr = add(ps1, ps2);
cout << pstr << endl;
delete pstr;
int i1 = 1, i2 = 2;
double d1 = 1.1, d2 = 2.2;
cout << add(i1, i2) << endl;
cout << add(d1, d2) << endl;
return 0
}
模板头文件与实现文件
C++头文件都是使用模板进行编写的,而模板的的特点是必须知道所有实现才能进行正常编译。。模板不能将声明与实现分开,模板的类型是 inline
函数。模板的运行机制是在编译时,通过实参传递时,进行参数推导。当头文件与实现文件分离时,实现文件中没有函数的调用,所以在编译时并不会进行参数推导,没有函数的产生。
函数声明与实现分离:
#ifndef __ADD_H__
#define __ADD_H__
template <class T>
T add(T x, T y);
#include "add.tcc" //实现函数声明与实现分离
#endif
模板的参数类型
#include
using std::cout;
using std::endl;
template <class T, int kBase=10> //double报错
T multiply(T x, T y)
{
return x * y * kBase;
}
void test0()
{
int i1 = 10, i2 = 11;
//常量的传递是在函数调用时完成的
cout << multiply<int, 10>(i1, i2) << endl;
cout << multiply<int, 20>(i1, i2) << endl;
cout << multiply(i1, i2) << endl; // 若没有给int给默认参数,则无法推导报错
}
int main(){
test0();
return 0;
}
成员函数模板
class Point
{
public:
template <typename T = int>
T func()
{
return (T)_dx;
}
private:
double _dx;
double _dy;
};
注意类模板的嵌套,一层一层写,成员函数类外定义必须加上模板参数列表。
#include
#include
using std::cout;
using std::endl;
using std::string;
template <class T, size_t kSize =10>
class Stack
{
public:
Stack()
: _top(-1)
, _pdata(new T[kSize]())
{}
~Stack();
T top() const;
bool empty() const;
bool full() const;
void push(const T & t);
void pop();
private:
int _top;
T * _pdata;
};
//如果是类模板,其成员函数在类外定义时,必须加上模板参数列表
template <class T, size_t kSize>
Stack<T,kSize>::~Stack()
{
if(_pdata) {
delete [] _pdata;
}
}
template <class T, size_t kSize>
T Stack<T, kSize>::top() const
{
return _pdata[_top];
}
template <class T, size_t kSize>
bool Stack<T, kSize>::empty() const
{
return _top == -1;
}
template <class T, size_t kSize>
bool Stack<T, kSize>::full() const
{
return (kSize - 1) == _top;
}
template <class T, size_t kSize>
void Stack<T, kSize>::push(const T & t)
{
if(full()) {
cout << "stack is full, cannnot push data any more!" << endl;
} else {
_pdata[++_top] = t;
}
}
template <class T, size_t kSize>
void Stack<T, kSize>::pop()
{
if(empty()) {
cout << "stack is empty, no data!" << endl;
} else {
--_top;
}
}
void test1()
{
Stack<string> stack;
cout << "此时栈是否为空? " << stack.empty() << endl;
stack.push(string(2, 'a'));
cout << "此时栈是否为空? " << stack.empty() << endl;
cout << stack.top() << endl;
for(int idx = 1; idx < 11; ++idx) {
stack.push(string(2, 'a' + idx));
}
cout << "此时栈是否已满? " << stack.full() << endl;
while(!stack.empty()) {
cout << stack.top() << endl;
stack.pop();
}
cout << "此时栈是否为空? " << stack.empty() << endl;
}
int main(void)
{
test1();
return 0;
}
C++ 11 新特性,对参数进行了高度的泛化,可表示0到任意个数、任意类型的参数。
模板参数包 Args:可以接受任意多个参数作为模板参数
template <typename... Args>
函数参数包 args:函数可以接受多个任意类型的参数。要求函数参数包必须唯一,且是函数的最后一个参数。
template<typename... T>
void f(T... args)
省略号的作用
T... args
args...
获取可变模板参数的个数
sizeof...(Args)
sizeof...(args)
参数包在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用递归终止函数终止递归过程。
#include
using namespace std;
//递归终止函数
void print() {
cout << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest) {
cout << head << " ";
// 调用过程,对参数包的展开过程
print(rest...);
}
//递归终止函数
template<typename T>
T sum(T t) {
return t;
}
//展开函数
template<typename T, typename ... Types>
T sum(T first, Types ... rest) {
return first + sum<T>(rest...);
}
int main() {
print(1, 2, 3, "hello", "world");
cout << sum(1, 2, 3, 4) << endl; //10
return 0;
}
逗号表达式:表达式1, 表达式2
,先执行表达式1
,再执行表达式2
,返回的结果只与表达式2
有关。利用初始化列表来初始化变长数组,在数组构造的过程中展开参数。
#include
using namespace std;
// 1、处理参数包中每一个参数的函数
template <class T>
void printarg(T t) {
cout << t << endl;
}
// 2、展开参数包
template <class ...Args>
void expand(Args... args) {
// 先执行printarg(args)展开参数包并打印参数,再执行逗号表达式得到结果0
// 1、int arr[] = {(printarg(1), 0), (printarg(2), 0), (printarg(3), 0)}
// 2、int arr[] = {0, 0 ,0}
int arr[] = {(printarg(args), 0)...};
}
int main() {
expand(1, "hello", 3);
return 0;
}
将函数作为参数,改写成 lambda
表达式
#include
using namespace std;
template<class F, class... Args>
void expand(const F& f, Args&&...args) {
//std::initializer_list 接收任意长度的初始化列表,这里用到了完美转发
initializer_list<int>{(f(std::forward< Args>(args)), 0)...};
}
int main() {
// 只能参数列表中类型的参数
expand([](int i) { cout << i << endl; }, 1, 2, 3);
return 0;
}