参考: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)
{
};
  • 其中 就是一个类型形参,名字由用户确定。

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

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(, 2.1) << endl; //输出5

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

return ;
}

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[];
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 = ;
A<b> m; //short到int提升 //4 整数转换
template<unsigned int a>
class A { };
A<> 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 == ;
} bool full() const //判断栈是否已满
{
return size == MAXSIZE;
} private:
T elems[MAXSIZE]; //使用数组存放栈元素,由于非类型形参MAXSIZE在类内是一个常量,所以可以用来声明数组大小
int size; //栈已使用空间
}; /**********模板类,声明结束,定义成员函数开始********/ template<class T, int MAXSIZE>
myStack<T, MAXSIZE>::myStack(): size() //构造函数,初始化为空
{
} 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 <= )
{
throw out_of_range("myStack::pop(): stack is empty");
}
--size;
} template<class T, int MAXSIZE>
T myStack<T, MAXSIZE>::top() const //返回栈顶元素
{
if(size <= )
{
throw out_of_range("myStack::top(): stack is empty");
}
return elems[size - ];
} /***********成员函数定义结束**********************/ int main(int argc, char *argv[])
{
try
{
myStack<int, > int20Stack; //显示模板实参
myStack<int, > int40Stack;
myStack<string, > stringStack; int20Stack.push();
cout << int20Stack.top() << endl; //输出7
int20Stack.pop(); for(int i = ; i < ; ++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 ;
}
catch(out_of_range const &ex)
{
cerr << "Exception: " << ex.what() << endl;
return EXIT_FAILURE;
}
}

3 默认形参

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

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 = > //第二个类型形参和非类型形参有默认值
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 ;
}

C++模板详解的更多相关文章

  1. 25.C++- 泛型编程之函数模板(详解)

    本章学习: 1)初探函数模板 2)深入理解函数模板 3)多参函数模板 4)重载函数和函数模板 当我们想写个Swap()交换函数时,通常这样写: void Swap(int& a, int&am ...

  2. 26.C++- 泛型编程之类模板(详解)

    在上章25.C++- 泛型编程之函数模板(详解) 学习了后,本章继续来学习类模板   类模板介绍 和函数模板一样,将泛型思想应用于类. 编译器对类模板处理方式和函数模板相同,都是进行2次编译 类模板通 ...

  3. c3p0-config.xml模板详解

    c3p0-config.xml模板详解 <c3p0-config> <default-config> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.De ...

  4. C++ 类模板详解(一):概念和基本使用方式

    与函数模板类似地(C++函数模板详解(一):概念和特性) ,类也可以被一种或多种类型参数化.例如,容器类就是一个具有这种特性的典型例子,它通常被用于管理某种特定类型的元素.只要使用类模板,我们就可以实 ...

  5. C++模板详解(三):参数化声明详解

    在前两节中(C++模板详解(一).C++模板详解(二)),我们了解了函数模板和类模板的基本概念和使用方法.在这篇博文里,我们主要来详细地阐述一下"模板的参数声明"这个话题,并且也谈 ...

  6. 【转】 C++模板详解

    C++模板 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数.返回值取得任意类型. 模板是一种对类型进行参数化的工具: 通常有 ...

  7. Percona监控MySQL模板详解

    InnoDB Adaptive Hash Index 显示了"自适应哈希索引"的使用情况,哈希索引只能用来搜索等值的查询. # Hash table size 17700827, ...

  8. 28.C++- 单例类模板(详解)

    单例类 描述 指在整个系统生命期中,一个类最多只能有一个实例(instance)存在,使得该实例的唯一性(实例是指一个对象指针)  , 比如:统计在线人数 在单例类里,又分为了懒汉式和饿汉式,它们的区 ...

  9. C++模板详解——使用篇

    假如我们需要取得两个变量中较大的变量,或许,我们可以通过重载的方式实现,如下. int max(int fir,int sec); float max(float fir,float sec); do ...

随机推荐

  1. Bootstrap学习笔记之整体架构

    之前有粗略地看过一下Bootstrap的内容,不过那只是走马观花式地看下是怎么用的,以及里面有什么控件,所以就没想着记笔记.现在由于要给部门做分享,所以不得不深入地去学习下,不然仅是简单地说下怎么用, ...

  2. 更改win7资源管理器启动位置

    打开资源管理器属性,在目标(T)后边加上: /e,::{20D04FE0-3AEA-1069-A2D8-08002B30309D} 俺滴笨笨原本目标(T)是: %windir%\explorer.ex ...

  3. ehcache 缓存技术

    一 ehcache API: 1: Using the CacheManager 1.1所有ehcache的使用, 都是从 CacheManager. 开始的.有多种方法创建CacheManager实 ...

  4. [分享] VIM 常用命令及游戏练级

    分享一个不错的文章,讲解了 VIM 的常用命令. http://coolshell.cn/articles/5426.html 另,介绍一个可以帮助熟悉VIM命令的练级游戏. 游戏地址:http:// ...

  5. [转]c/c++输入函数

    最全输入函数 c/c++ 一: c=getchar(); 功能:读入一个字符 说明:调用此函数时要求在程序的第一行有预编译命令:#include<stdio>,不过在做c++时 有#inc ...

  6. U3D NGUI改变GameObject Activity闪烁的问题

    不是关闭再激活GameObject会闪烁,而是再激活时,NGUI渲染步骤不一致导致的闪烁. 并且文字激活后渲染要慢一帧,如果延迟一帧处理,又会导致精灵图片快一帧,图片重叠.这个测试结果不一定准确,先记 ...

  7. Ubuntu 12.04 LTS(64bit) 环境下JDK、 Eclipse、 ADT、 快捷图标

    一.在FriendlyARM,Tiny4412,,安装包下可补充: (按照手册添加openjdk-6-jdk 后) 安装JDK (Java),选择需要的JDK,或者全部安装. a) OpenJDK-6 ...

  8. curl 查看网站连接情况

    curl -o /dev/null -s -w "nslookup_time :%{time_namelookup}\n time_connect: %{time_connect}\ntim ...

  9. [CF660C]Hard Process(尺取法)

    题目链接:http://codeforces.com/problemset/problem/660/C 尺取法,每次遇到0的时候补一个1,直到补完或者越界为止.之后每次从左向右回收一个0点.记录路径用 ...

  10. C++ STL之priority_queue

    STL中的priority_queue(优先队列)是一种会按照自定义的一种方式(数据的优先级)来对队列中的数据进行动态的排序的容器,不同优先级的情况下,top()上永远是最高优先级的数据,其底层采用的 ...