您的位置:首页 > 编程语言 > C语言/C++

C++模板详解

2014-09-09 01:48 239 查看
参考:C++ 模板详解(一)

模板:对类型进行参数化的工具;通常有两种形式:

函数模板:仅参数类型不同;

类模板: 仅数据成员成员函数类型不同。

目的:让程序员编写与类型无关的代码。

注意:模板的声明或定义只能在全局、命名空间、类范围内进行。即不能在局部范围、函数内进行,比如不能在main函数中声明或定义一个模板。

一 函数模板

1 函数模板的格式

template <class 类型名1,class 类型名2,......>
返回类型 函数名(参数列表)
{
函数体
}


template、class是关键字,class可以用关键字typename代替,在这里typename和class没区别

<>中的参数叫模板形参不能为空。模板形参用传入的实参类型来初始化。

模板形参可以在 返回类型、参数列表、函数体内使用。一旦编译器确定了模板实参类型,就称他实例化了函数模板的一个实例。

例1:swap函数的模板:

template <class T>
void swap(T& a, T& b)   //参数列表使用模板形参
{
T tmp = a;          //函数体内使用模板形参
a = b;
b = tmp;
}


当调用这个函数模板时,类型T就会被被调用时的类型所代替

比如swap(a,b),其中a和b是int 型,模板函数就变为swap(int &a, int &b)。

而当swap(c,d),其中c和d是double型时,模板函数会被替换为swap(double &a, double &b)。

例2:max函数的模板

#include<iostream>

template<typename T>
const T& myMax(const T &a, const T &b)
{
  return a > b ? a : b;
}

int main()
{
  cout << myMax(2.1, 2.2) << endl;         //输出2.2  模板实参被隐式推演成double
  cout << myMax<double>(2.1, 2.2) << endl; //输出2.2  显示指定模板参数。
  cout << myMax<int>(2.1, 2.2) << endl;    //输出2    显示指定的模板参数,会将参数转换为int。
14 
15   return 0;
16 }


2、注意:

不存在swap(int, int)这样的调用! 即不能用类型初始化,只能用实参推演来进行。即根据2来推出int型。

即只能进行swap(2, 3); 或者 int a, b; swap(a,b); 这样的调用。

二 类模板

1 类模板的格式为:

template<class 形参名1, class 形参名2, ...>
class 类名
{
...
};


与函数模板一样,以template开始,后接模板形参列表模板形参不能为空

类的数据成员和函数成员可以使用模板形参。

:一个类模板的例子:

template<class T>
class A
{
public:
T a;               //类成员使用模板形参
T b;
T func(T c, T &d);
};


2 类模板对象的创建:

方法:A<int> m; 类A中用到模板形参的地方都会被int 所代替。

两个模板形参:A<int, double> m; 类型之间用逗号隔开。

3 类模板形参必须指定类型而不是实参:

必须这样指定 A<int> m; 明确指定类型。

不能这样使用A<2> m; 类模板形参不存在实参推演的问题。

4 在类模板外部定义成员函数的方法为:

template<模板形参列表>
函数返回类型 类名<模板形参名>::函数名(参数列表)
{
函数体
}


:比如模板类A,有两个模板形参T1,T2,有一个成员函数 void func(),则外部定义该函数的语法为:

template<class T1, class T2>   //与类一致
void A<T1, T2>::func()      //类名也要加上模板参数列表
{
}


注意:当在类外面定义类的成员时,template后面的模板形参应与所定义的类的模板形参一致。

三 模板的形参

包括 类型形参、非类型形参、默认形参。

1 类型形参

类型形参由关键字class或typename后接说明符构成,如

template<class T>
void func(T a)
{
};


其中 T 就是一个类型形参,名字由用户确定。

函数模板,同一个类型形参,必须用相同类型的实参来初始化,比如

template<class T>
void func(T a, T b)
{
}


调用 func(2, 3.2); 将编译出错,因为模板形参T同时被指定为int 和 double,不一致,会出错。

类模板,其内部成员函数,则没有上述的限制,比如

template<class T>
class A
{
public:
T func(T a, T b);   //或者T func(const T &a, const T &b);  普通引用会编译报错
};


声明 A<int> a; 调用 a.func(2, 3.2); 在编译时不会出错

第二个实参3.2类型为double,在运行时,会强制类型转换为3。

:模板类的对象调用成员函数:

#include <iostream>
using namespace std;

template<class T>
class A
{
public:
A();
T func(T a, T b);
};

template<class T>     //类外定义构造函数
A<T>::A()
{
}

template<class T>     //类外定义成员函数
T A<T>::func(T a, T b)
{
return a + b;
}

int main(int argc, char *argv[])
{
A<int> ia;                        //模板实参为int类型的对象
cout << ia.func(3, 2.1) << endl;  //输出5

A<double> da;
cout << da.func(3, 2.1) << endl;  //输出5.1

return 0;
}


2 非类型形参

也就是内置类型形参,如

template<class T, int a>   //int a 就是非类型形参
class B
{
};


非类型形参有几点要注意的:

在模板定义的内部是常量值,也就是说,上面的a在类B内部是一个常量。

形参只能是整型、指针、引用,像double、string、string **是不允许的,但是double &、double *、对象的引用或指针是正确的。

实参必须是一个常量表达式,即必须能在编译时计算出结果。注意:局部对象/变量和其地址,全局指针/变量/对象,都不是常量表达式;全局变量/对象地址或引用const类型变量sizeof的结果,都是常量表达式。

形参是整型时,实参也必须是整型的,且在编译期间是常量,比如

template <class T, int a>
class A
{
};


如果有int b; 这时 A<int, b> m; 将出错,因为b不是常量;如果有const int b; 这时 A<int, b> m; 正确,因为这时b是常量。

非类型形参一般不应用于函数模板中,比如有函数模板

template<class T, int a>
void func(T b)
{
}


若用func(2)调用,会出现无法为非类型形参a推演出参数的错误;可以用显示模板实参来解决,如用func<int, 3>(2); 把非类型形参a设置为整数3。

形参实参间所允许的转换

//1 数组到指针,函数到指针的转换
template<int *a>
class A { };
int b[10];
A<b> m;      //数组转换成指针

//2 const修饰符的转换
template<const int *a>
class A { };
int b;
A<&b> m;     //从int*转换成const int *

//3 提升转换
template<int a>
class A { };
const short b = 2;
A<b> m;       //short到int提升

//4 整数转换
template<unsigned int a>
class A { };
A<3> m;    //int到unsigned int转换

//5 常规转换


例:由用户指定栈的大小,并实现栈的相关操作。

#include <iostream>
#include <string>
#include <stdexcept>  //std::out_of_range
#include <cstdlib>    //EXIT_FAILURE
using namespace std;

/*********模板类,声明开始,一般都是放在头文件的*********/

template<class T, int MAXSIZE>
class myStack
{
public:
myStack();
void push(T const &);  //入栈
void pop();            //出栈
T    top() const;      //返回栈顶

bool empty() const     //判断是否为空
{
return size == 0;
}

bool full() const      //判断栈是否已满
{
return size == MAXSIZE;
}

private:
T   elems[MAXSIZE];    //使用数组存放栈元素,由于非类型形参MAXSIZE在类内是一个常量,所以可以用来声明数组大小
int size;              //栈已使用空间
};

/**********模板类,声明结束,定义成员函数开始********/

template<class T, int MAXSIZE>
myStack<T, MAXSIZE>::myStack(): size(0)      //构造函数,初始化为空
{
}

template<class T, int MAXSIZE>
void myStack<T, MAXSIZE>::push(T const &new_elem)     //入栈
{
if(size == MAXSIZE)
{
throw out_of_range("myStack::push(): stack is full");
}
elems[size++] = new_elem;
}

template<class T, int MAXSIZE>
void myStack<T, MAXSIZE>::pop()       //栈顶出栈
{
if(size <= 0)
{
throw out_of_range("myStack::pop(): stack is empty");
}
--size;
}

template<class T, int MAXSIZE>
T myStack<T, MAXSIZE>::top() const    //返回栈顶元素
{
if(size <= 0)
{
throw out_of_range("myStack::top(): stack is empty");
}
return elems[size - 1];
}

/***********成员函数定义结束**********************/

int main(int argc, char *argv[])
{
try
{
myStack<int,    20>  int20Stack;    //显示模板实参
myStack<int,    40>  int40Stack;
myStack<string, 40>  stringStack;

int20Stack.push(7);
cout << int20Stack.top() << endl;   //输出7
         int20Stack.pop();

for(int i = 0; i < 40; ++i)
int40Stack.push(i);
cout << int40Stack.top() << endl;   //输出39
//int40Stack.push(41);              //继续添加元素,会抛出异常,输出Exception: myStack::push(): stack is full

stringStack.push("hello");
cout << stringStack.top() << endl;  //输出hello
         stringStack.pop();
//stringStack.pop();                //继续出栈,会抛出异常,输出Exception: myStack::push(): stack is empty

return 0;
}
catch(out_of_range const &ex)
{
cerr << "Exception: " << ex.what() << endl;
return EXIT_FAILURE;
}
}


3 默认形参

1 类模板可以有默认值,函数模板不能有默认值。

2 类模板,类型形参,默认值形式为:

template<class T1, class T2 = int>   //为第二个模板类型形参提供int型的默认值
class A
{
};


3 类模板,类型形参,默认值和普通函数的默认参数一样,如果有多个类型形参,则从第一个设定了默认值的形参之后,所有模板形参都要设定默认值

template<class T1 = int, class T2>  //错误!如果T1有默认值,T2也必须有
class A
{
};


4 类模板,类型形参,外部定义类的成员函数。template 后的形参列表应省略掉默认值。

template<class  T1, class T2 = int>
class A
{
public:
void func();
};

template<class T1,class T2>   //定义方法,省略掉默认值
void A<T1,T2>::func()
{
}


:有默认值的类模板

#include <iostream>
using namespace std;

template<typename T1, typename T2 = double, int abc = 5>  //第二个类型形参和非类型形参有默认值
class A
{
public:
void print(T1 a, T2 b);
};

template<typename T1, typename T2, int abc>   //类外定义时不能有默认值,毕竟类的声明是作为接口给别人看的
void A<T1, T2, abc>::print(T1 a, T2 b)
{
cout << a << ' ' << b << endl;
cout << abc << endl; }

int main(int argc, char *argv[])
{
A<int> a;
a.print(2.2, 2.1);   //输出 2 2.1 5, 输出2是因为指定为int型,进行了类型转换,输出5是默认值

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: