三、模版与泛型
Author: XFFer_
文章目录
01 模版概念,函数模版定义、调用
1 概述
2 函数模版的定义
3 函数模版的使用
4 非类型模版参数
概述
vector实际上就是一个模版,vector才是一个类
- 所谓泛型编程是以独立于任何类型的方式编写代码。使用泛型编程时,我们需要提供具体程序实例所操作的类习惯或者值
- 模版是泛型编程的基础,模版是创建类或者函数的蓝图或者公式。我们给这些蓝图或者公式提供足够的信息,让这些蓝图或者公式真正的转变成具体的类或者函数,发生在编译时
- 模版支持将类型作为参数的程序设计方式,从而实现了对泛型程序设计的直接支持
函数模版的定义
overloaded function(重载函数)实际上就是两个同名函数,但是形参列表有明显不同。
template<typename T>
T funcadd(T a, T b)
{
T add_result = a + b;
return add_result;
}
- 模版定义是用
template
关键开头的,后边跟<>
,里面叫模版参数列表(模版实参) <>
里必须有一个模版参数,模版参数前typename/class
,模版参数列表里表示函数定义中用到的“类型”或“值”- 多个模版参数时,需要用多个
template <typename A, typename B>
函数模版的使用
和正常调用函数相似,系统会实例化出推断出的形参类型对应的函数
模版函数可以是内联inline
(加在template的下一行)的,模版定义不会生成代码,只会在调用函数模版时,才会实例化出一个特定版本的函数。
非类型模版参数
用传统的类型名来指定非类型参数,当模版被实例化时,这些非类型模版参数的值必须是常量表达式,在编译时实例化。
template<int a, int b>
int func()
{
int result = a + b;
return result;
}
int answer = func<12, 13>(); //显式的指定模版参数--在尖括号中提供额外的信息
02 类模版概念、类模版定义、使用
1 概述
2 类模版定义
3 类模版的成员函数
4 模版类名字的使用
5 非类型模版参数
概述
用类模版实例化一个特定的类,需要在模版名后用
<>
提供额外的信息。实例化类模版需要包含全部信息,包括类成员函数的定义
类模版定义
template <typename 形参名1, typename 形参名2, ..., typename 形参名n>
class 类名
{
...
};
template<typename T>
class myvector
{
public:
typedef T* myiterator;
public:
myiterator mybegin(); //返回值是myiterator,其实就是指向容器内部对象的指针
myiterator myend();
void func();
myvector& operator=(const myvector&);
};
类模版的成员函数
- 类模版的成员函数可以写在类定义中,这种成员函数会被隐式声明为inline函数
- 类模版一旦被实例化之后,每个实例都会有自己版本的成员函数
- 类模版的成员函数具有和这个类模版相同的模版参数
- 定义在类模版之外的成员函数必须以
template<模版参数表>
开始,类名后要用<>
把模版参数列表里的参数名列出
template<typename T>
void myvector<T>::func
{
...
}
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector&)
{
return *this;
}
浮点型/类类型 不能做类模版的非类型模版参数
03 用typename场合、默认模版参数、趣味写法分析
1 typename的使用场合
2 函数指针做其他函数的参数
3 函数模版趣味用法举例
4 默认模版参数
typename的使用场合
类型成员
typedef T* iterator;
vector<int>::iterator iter;
这个typedef
出的就是类型成员。
#include <vector>
vector<int> contain;
for (vector<int>::iterator iter = contain.begin(); iter != contain.end(); iter++) {}
这里的contain是一个类对象;iterator是实例化vector得到的类中的类型成员;iter是一个指向int对象的指针,因为typedef *T iterator
,contain是一个储存整形数据的类对象,可以调用类成员函数begin()
、end()
,返回值也是指向整形变量的指针。
template<typename T>
typename myvector<T>::myiterator myvector<T>::begin() {}
模版实例出的类后跟作用域标识符::
默认被系统处理成成员,而myiterator在这里作为类型成员(返回值类型),必须使用typename声明。
函数模版趣味用法举例
template<typename T, typename F>
void func(const T& i, const t& j, F funcpoint)
{
funcpoint(i, j);
}
int mcfunc(int i, int j)
{
return i+j;
}
func(2, 4, mcfunc); //T->int,F->int (*) (int, int)的函数指针
04 成员函数模版,显式实例化、声明
1 普通类的成员函数模版
2 类模版的成员函数模版
3 模版显式实例化,模版声明
普通类的成员函数模版
成员函数都可以是函数模版,成为“成员函数模版”,虚函数。
类模版的成员函数模版
template <typename T>
class A {
public:
template <typename P>
void func(P& p);
};
template <typename T>
template <typename P>
void A<T>::func(P& p) {}
类模版的成员函数(普通成员函数/模版成员函数)只有为程序所用才会实例化。
模版显式实例化、模版声明
Q:为了防止在多个.cpp文件中都实例化相同的类模版
A:C++11提出“显式实例化”
//“显式实例化”手段中的“实例化定义”
template A<float>;
//“显式实例化”手段中的“实例化声明”
extern template A<float>; //不会再在本文件中生成这个实例化版本
//目的是告诉编译器,在其他文件中已经有了该模版的实例化版本
05 using定义模版别名,显式指定模版参数
1 using定义模版别名
2 显式指定模版参数
using定义模版别名(类型模版)
typedef std::map<std::string, int> map_s_i;
但是typedef
定义出的是固定的格式。
C++98中:
template <typedef M>
struct map_s
{
typedef map<string, M> map_c;
};
//很类似iterator定义了一个类型成员
map_s<int>::map_c mapl;
C++11中:
template <typename T>
using str_map = map<string, T>;
06 模版全特化、偏特化(局部特化)
1 类模版特化
类模版全特化
类模版偏特化2 函数模版特化
函数模版全特化
函数模版偏特化
类模版特化
类模版全特化
特化:对特殊的类型(类型模版参数)进行特殊的处理。
template <typename T, typename F>
struct test {
void functest() {
cout << "调用了泛型版本" << endl; }
};
//特化类模版
template <>
struct test<int, double> {
void functest() {
cout << "调用了特化版本" << endl; }
};
//特化成员函数
template <>
void test<double, float>::functest() {
cout << "调用了特化版本的成员函数" << endl;
}
类模版偏特化
//参数数量进行偏特化
template <typename T, typename F, typename L>
struct test {
... };
template <typename F>
struct test<double, F, int> {
... };
//模版参数范围上的特化版本
template <typename T>
struct test {
... };
template <typename T>
struct test<const T> {
...}; //const特化版本
函数模版全特化
template <typename T, typename P>
void func(T& tmprv, P& tmprc)
{
... }
template <>
void func(double& tmprv, int& tmprc)
{
... }
函数模版没有偏特化!
必须都有泛型模版,才可以定义特化模版。模版定义、实现都放在一个.h
文件中。
07 可变参模版
1 可变参函数模版
简单范例
参数包的展开2 可变参类模版
通过递归继承方式展开参数包
可变参函数模版
...
的位置很关键!
template <typename... T>
void myfunc(T... argc)
{
cout << sizeof...(T) << endl; //返回T...的类型数
cout << sizeof...(argc) << endl;
}
T中存放的是任意个不同类型,称作可变参类型; argc中存放着任意个形参,称作可变形参。
参数包的展开
一个参数typename T
和一包参数typename... F
,这种可变参函数模版更容易展开!
//参数包用迭代方式展开
void func() {} //终止迭代
template <typename T, typename... F>
void func(const T& src, const F&... stv)
{
cout << src << endl;
func(stv...);
}
可变参类模版
通过递归继承的方式展开参数包
template <typename... Args> class myclass { }; //为了保证可变参模版定义成功
template<> class myclass<> { //特化空模版
public:
myclass() {
cout << "myclass<>::myclass()执行" << endl;
}
};
template <typename First, typename... Others>
class myclass<First, Others...> : private myclass<Others...> //偏特化
{
public:
myclass() : m_i(0)
{
cout << "myclass::myclass执行了" << ' ' << "this: " << this << endl;
}
First m_i;
};
int main
{
myclass<int, float, double> cls();
}
(Debug结果:感觉挺难的)
- 首先声明构造函数的执行是从父类->子类的
- 实例化一个类对象,类
cls()
括号内的参数是根据构造函数中的参数确定的 - (每次取可变参)类对象
<int, float, double>
继承于<float, double>
;<float, double>
继承于<double>
;<double>
继承于< >
。模版参数同样也是特化版本 - 就这样
< >
是基类对象,控制台输出*“myclass<>::myclass()执行”;紧接着<double>
-><float, double>
-><int, float, double>
都会输出"myclass:myclass执行了"*
08 可变参模版续、模版模版参数
1 可变参类模版
通过递归组合方式展开参数包
通过tuple和递归调用展开参数包
总结2 模版模版参数
可变参类模版
通过递归组合方式展开参数包
组合关系(复合关系):类A中包含B对象(即在类A定义内部定义一个类B的对象)。
template <typename... Args> class myclass {}; //保证递归组合方式能够成功复合
template <>
class myclass<>
{
public:
myclass() {
cout << "myclass<>::myclass()执行" << endl; }
};
template <typename First, typename... Others>
class myclass<First, Others...>
{
public:
myclass(First prao, Others... pano) : m_i(0), m_t(pano)
First m_i;
myclass<Others...> m_t;
};
通过tuple和递归调用展开参数包
实现思路:计数器从0开始,每处理一个参数,计数器+1,一直到把所有参数处理完;最后搞一个模版偏特化,作为递归调用结束。
#include <tuple>
//mycount用于统计,从0开始,mymaxcount表示参数数量
template <int mycount, int maxmycount, typename... T>
class myclass {
public:
static void mysfunc(const tuple<T...>& t)
{
cout << "value= " << get<mycount>(t) << endl;
//get是tuple元组的用法get<整数(表示位置从0开始)>(tuple对象)
myclass<mycount + 1, maxmycount, T...>::mysfunc(t);
//递归调用,计数器+1,取下一个tuple位置中的值
}
};
//特化一个结束版本
template<int maxmycount, typename... T>
class myclass<maxmycount, maxmycount, T...> {
public:
static void mysfunc(const tuple<T...>& t)
{
}
};
template <typename... T>
void myfunc(const tuple<T...>& t)
{
myclass<0, sizeof...(T), T...>::mysfunc(t);
}
int main()
{
tuple<float, int ,double> mytuple(3.2f, 3, 5);
myfunc(mytuple);
}
(也挺难的能看懂就好)
- get< >( )是元组的一个方法,目的是取各索引下的值,0 ~ len - 1,< >中存索引,( )中存tuple对象名
- 这个例子中的可变模版参数T…,实际作用是用来代表tuple元组中保存的多种类型
- myclass是一个计数器,mycount是get的索引位,maxmycount是get方法的终止位,在函数myfunc中给mycount初始化为0,maxmycount初始化为传入元组内的元素个数
sizeof...(T)
- 计数器内每get到tuple的一个元素,就迭代调用mycount索引值+1的计数器版本,maxmycount不变
- 直到mycount==maxmycount时,也就是特化的版本,终止执行,因为索引值执行到len - 1之后就没有内容了
模版模版参数
用于在模版中还要使用类模版的情况下!
这是一个模版参数,前面都叫类型模版参数,这个模版参数本身,又是一个模版。
第种写法(第二种直接放里面了)
template <
typename T, //类型模版参数
template<class> class Container //模版 模版参数
//template<typename W> typename Container
>
class myclass
{
public:
T t_i;
Container<T> myc;
myclass()
{
for (i = 0; i <10; i++)
{
myc.push_back(i);
}
}
};
template<typename T>
using myvec = vector<T, allocator<T>>; //需要确定一个分配器,系统没能自己确定
myclass<int, myvec> mvectobj;
//这里是因为vector有第二个参数allocator分配器,所以需要自己用using手动定义一个
(理解)
- 模板模板参数
template<class/typename> class/typename 名字
- 这里的class不代表类定义,而是和typename可互换
- 下面使用
Container<T>
时,第一个typename系统确定为T,第二个确定为class(类)
using myvec = vector<T, allocator<T>>
是固定写法