C++模板详解
参考: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(, 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 默认形参
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 = > //第二个类型形参和非类型形参有默认值
- 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++模板详解的更多相关文章
- 25.C++- 泛型编程之函数模板(详解)
本章学习: 1)初探函数模板 2)深入理解函数模板 3)多参函数模板 4)重载函数和函数模板 当我们想写个Swap()交换函数时,通常这样写: void Swap(int& a, int&am ...
- 26.C++- 泛型编程之类模板(详解)
在上章25.C++- 泛型编程之函数模板(详解) 学习了后,本章继续来学习类模板 类模板介绍 和函数模板一样,将泛型思想应用于类. 编译器对类模板处理方式和函数模板相同,都是进行2次编译 类模板通 ...
- c3p0-config.xml模板详解
c3p0-config.xml模板详解 <c3p0-config> <default-config> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.De ...
- C++ 类模板详解(一):概念和基本使用方式
与函数模板类似地(C++函数模板详解(一):概念和特性) ,类也可以被一种或多种类型参数化.例如,容器类就是一个具有这种特性的典型例子,它通常被用于管理某种特定类型的元素.只要使用类模板,我们就可以实 ...
- C++模板详解(三):参数化声明详解
在前两节中(C++模板详解(一).C++模板详解(二)),我们了解了函数模板和类模板的基本概念和使用方法.在这篇博文里,我们主要来详细地阐述一下"模板的参数声明"这个话题,并且也谈 ...
- 【转】 C++模板详解
C++模板 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数.返回值取得任意类型. 模板是一种对类型进行参数化的工具: 通常有 ...
- Percona监控MySQL模板详解
InnoDB Adaptive Hash Index 显示了"自适应哈希索引"的使用情况,哈希索引只能用来搜索等值的查询. # Hash table size 17700827, ...
- 28.C++- 单例类模板(详解)
单例类 描述 指在整个系统生命期中,一个类最多只能有一个实例(instance)存在,使得该实例的唯一性(实例是指一个对象指针) , 比如:统计在线人数 在单例类里,又分为了懒汉式和饿汉式,它们的区 ...
- C++模板详解——使用篇
假如我们需要取得两个变量中较大的变量,或许,我们可以通过重载的方式实现,如下. int max(int fir,int sec); float max(float fir,float sec); do ...
随机推荐
- Spring Loaded is a JVM agent for reloading class file changes
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot ...
- javascript ajax的语法
ajax参数: 详细参数转到如下地址: http://www.w3school.com.cn/jquery/ajax_ajax.asp $.ajax语法: jQuery.ajax([settings] ...
- SQL经典问题 找出连续日期及连续的天数
转自:http://bbs.csdn.net/topics/360019248 如何取到每段连续日期的起始终止日期以及持续天数及起始日期距上一期终止日期的天数,能否用一句sql实现?备注:数据库环境是 ...
- Android之网络请求库
自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection ...
- PowerDesigner建立与数据库的连接,以便生成数据库和从数据库生成到PD中。
第一步,打开PD15,找到[Database],如图所示: 第二步,点击新建按钮,创建一个新的ODBC连接. 第三步,这里选择系统数据源,并选择Oracle 第四步,这里需要注意,服务器需要写的是Or ...
- Ubuntu下eclipse的Extjs提示插件安装
使用eclipse编写extjs时,一定会用到spket这个插件,spket可以单独当作ide使用,也可以当作eclipse插件使用,我这里是当作eclipse的插件使用的,下面来一步步图解说明如何配 ...
- ACdream 1735 输油管道 (排序)
http://acdream.info/problem?pid=1735 官方题解:http://acdream.info/topic?tid=4246 因为主干线是平行于x轴的直线,那么跟x坐标其实 ...
- [51NOD1087]1 10 100 1000(规律,二分)
题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1087 用高中的数列知识就可以推出公式,不难发现f(n)=f(n ...
- TCSRM5961000
一直没想到怎么去重 看了眼别人的代码...so easy啊 同余啊 唉..脑子被僵尸吃掉了 难得1000出个简单的 #include <iostream> #include<cstd ...
- 一些数论概念与算法——从SGU261谈起
话说好久没来博客上面写过东西了,之前集训过于辛苦了,但有很大的收获,我觉得有必要把它们拿出来总结分享.之前一直是个数论渣(小学初中没好好念过竞赛的缘故吧),经过一道题目对一些基础算法有了比较深刻的理解 ...