一、简介
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如vector <int>
或vector <string>
。
模板分为函数模板和类模板。
二、函数模板
基本语法:1
template < {typename|class} t1 [ , {typename|class} t2 ... ] >
ret_type func_name(/*list*/){/*body*/}
其中,template
是模板声明关键字,typename
(可用class
代替)2是形参声明关键字,t1...
是形参;第二行就是正常的函数定义。
【实例】:
template<class T>
void outp(T _o){cout<<_o;}
...
outp(12345);
outp("\nhello world!")
输出
12345
hello world!
不难看出,模板的作用类似于对所有数据类型的重载,第2行参数列表中T _o
就使用了上一行定义的形参类型T
,表示T
作为一个占位符名称,可以被任何确切数据类型替换。
请注意,模板形参(<>
中)不能为空。
【实例】使用模板函数返回二者最大值
#include<iostream>
using namespace std;
template<class T> T MX(T _a,T _b){return (_a>_b)?_a:_b;}
//模板声明和函数定义也可以写在一行。
int main(){
cout<<MX(-5,5)<<endl;
cout<<MX('a','z')<<endl;
cout<<MX("hello","world")<<endl;
cout<<MX("world","hello")<<endl;
cout<<MX("abc","abd")<<endl;
cout<<MX("abc","xyz")<<endl;
cout<<MX("abc","cba")<<endl;
return 0;
}
输出:
5
z
hello
hello
abc
xyz
cba
由此可见,如本例,在使用与函数内操作不符的形参类型时,虽然不会报错,但结果将会非常不可控。
三、模板类
基本语法:
template<{typename|class} t1 [,{typename|class} t2 ...]>
class class_name{/*class body*/};
类似于模板函数,t1...
是占位类型名称。
<>
中的形参可以使用class
关键字声明,但与下一行的类定义关键字class
意义不同。
【实例】定义模板类_stack
并使其具备STL库中stack<>
的性质(使用stack<>
书写方便学习)
template<typename t>
class _stack{
private:
stack<t> _st;//以数据类型t创建栈
public:
void push(t const&);
void pop();
t top() const;//栈顶元素,返回t类型
bool empty();
size_t size();
};
//请注意:类外定义函数也要加上模板声明,且_stack<t>的<>必写。
template<typename t>
void _stack<t>::push(t const& _p) {_st.push(_p);}
template<typename t>
void _stack<t>::pop() {_st.pop();}
template<typename t>
t _stack<t>::top() const{return _st.top();}
template<typename t>
bool _stack<t>::empty(){return _st.empty();}
template<typename t>
size_t _stack<t>::size(){return _st.size();}
...
_stack<int> si;
_stack<string> sstr;
_stack<bool> sbl;
三个要点:
- 即使在类外定义方法,仍需在定义前加上模板声明(就行模板函数一样)
- 在定义方法时域访问符
::
前的类名要加<...>
写对应的形参。 - 定义对象时在类名后要加上
<...>
与模板类的形参对应。如果有多个形参则用,
隔开。
四、模板函数重载
模板函数也可以重载,但要求参数个数必须不同(而不仅是参数列表类型不同也行)。
template<class t>
t MX(t a,t b){return (a>b)?a:b;}
template<class t>
t MX(t a,t b,t c){return (a<b)?b:((a>c)?a:c);}
五、函数模板重载
不同的是,这种重载指的是形参列表(<>
中)的形参,也要求个数不同(不然怎么区分呢)
template<class t>
void o(t a,t b){cout<<a<<b;}
template<class t,class T>
void o(t a,T b){cout<<a<<b;}
六、关于typename
typename
可以用来声明模板形参。
它还有一个作用——使用嵌套依赖类型(nested depended name)
【例】
//须知:类中typedef的类型在类外使用(仅当可访问时)要加【::】
class ex{
public:
typedef int T;
...
...
};
...
template<class t>
void func(t a){
#if 0
typedef t::T TT;//非法!error: need 'typename' before 't::T' because 't' is a dependent scope
#endif
typedef typename t::T TT;//合法。
}
这个时候 typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 t::T
是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
七、模板分离
如果模板类的声明、定义分别在tmp.h和tmp.cpp中:
|—Proj
|——tmp.h
|——tmp.cpp
|——main.cpp
则在包含时必须同时包含二者(而不是靠tmp.cpp中的包含嵌套):
//main.cpp
#include"tmp.h"
#include"tmp.cpp"
...
@HaohaoCppDebuger |寻兰
2021/12/2
THANK YOU !