C++入门到精通 --第三章 模板与泛型

三、模版与泛型

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>>是固定写法

八、未归类知识点

01 函数调用运算符、function类模版

1 函数调用运算符
2 map类模版
3 function类模版

函数调用运算符

(),如果在类中重载了函数调用运算符(),那么就可以像使用函数一样使用该类的对象了,对象(实参)
system("pause");暂停程序运行,等待任意键继续执行。

class biggerthan
{
public:
	int operator()(int getvalue) const
	{
		if (getvalue < 0)
			return 0;
		return getvalue;
	}
};
biggerthan bgt;
int result = bgt(200);
//int result = bgt.operator()(200);

结论:只要对象所属的类重载了()“函数调用运算符”,这个类对象就变成可调用对象,并且可以调用多个版本的(),只要重载()参数列表有明显差异。

不同调用对象的相同调用形式

调用参数返回值类型相同,叫做“调用形式”相同。
一种调用形式对应一个函数类型(如int(int))。

map模版

map也是一个类模版/容器,但是不同于vector,map里每一项是两个数据,一个叫“”,一个叫“”。

int func (int value);
#include <map>
map<string, int (*) (int)> func_bag;	//map每项有两个数据,这里第一个数据是字符串,第二个数据是int(int)函数类型的指针
func_bag.insert({"src", func});
//map<string, function<int (int)> func_bag = {{"src", func}};

func_bag["src"](3);	//func_bag[键]就是map里面的值,也就是函数的入口地址

insert({ , })用于向map中插入对象。map也有迭代器(回忆:vector的insert功能是用迭代器(指针)向它指向的地址插入对象)

标准库function类型介绍

function也是类模版,要提供模版参数来表示该function类型能够表示的“对象的调用形式”。

#include <functional>
functional<int(int)> f1 = func;	//统一调用形式,可以用内部重载()的类对象赋值给f1
发布了5 篇原创文章 · 获赞 11 · 访问量 130

猜你喜欢

转载自blog.csdn.net/qq_44455588/article/details/104392150
今日推荐