模板与泛型

模板定义

函数重载的例子

#include <iostream>
using namespace std;

int Max(int a, int b)
{
	return a>b ? a:b;
}

char Max(char a, char b)
{
	return a>b ? a:b;
}

float Max(float a, float b)
{
	return a>b ? a:b;
}

void main()
{
	//char a = 'c';
	
	int  x = 1;
	int	 y = 2;
	cout<<"max(1, 2) = "<<Max(x, y)<<endl; 

	float a = 2.0;
	float b = 3.0;
  
	cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;

	system("pause");
	return ;
}
#include <iostream>
using namespace std;
/*
int Max(int a, int b)
{
	return a>b ? a:b;
}

char Max(char a, char b)
{
	return a>b ? a:b;
}

float Max(float a, float b)
{
	return a>b ? a:b;
}
*/

//template 关键字告诉C++编译器 我要开始泛型编程了,请你不要随意报错
//T - 参数化数据类型
template <typename T>
T Max(T a, T b){
	return a>b ? a:b;
}

/*如果T 使用int 类型调用,相当于调用下面这个函数
int Max(int a, int b)
{
	return a>b ? a:b;
}
*/


void main()
{
	//char a = 'c';
	
	int  x = 1;
	int	 y = 2;
	cout<<"max(1, 2) = "<<Max(x, y)<<endl; //实现参数类型的自动推导
	cout<<"max(1, 2) = "<<Max<int>(x,y)<<endl;//显示类型调用

	float a = 2.0;
	float b = 3.0;
  
	cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;

	system("pause");
	return ;
}

函数模板定义形式

由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用

  template < 类型形式参数表 >
  类型  函数名 (形式参数表)
{
    //语句序列
}

模板说明

template < 类型形式参数表 >
类型形式参数的形式:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
(注:typename 和 class 的效果完全等同)

函数定义

类型 函数名 (形式参数表)
{
}
注意:模板说明的类属参数必须在函数定义中出现一次函数参数表中可以使用类属类型参数,也可以使用一般类型参数

函数模板调用

max(a, b); //显式类型调用
max(a, b); //自动数据类型推导
在这里插入图片描述
特例
模板参数类型不知道应该推断为int类型还是float类型

cout << "max(2.0, 3.0) = " << Max(a, y) << endl;

在这里插入图片描述

非类型模板参数

template<typename T>

这里的T,因为前面使用typename来修饰, 所以T代表一个类型,是类型参数,这个模板参数列表里,还可以定义非类型参数.
类型参数表示一个类型,而非类型参数表示的是一个值, 既然非类型参数表示的是一个值,那这里就不能使用typename或者class类修饰. 而是要用传统类型名来指定非类型参数.(int,char…)
当模板被实例化之后,这种非类型模板参数的值可以由用户提供,或者编译器推断都可以.这些值必须都得是常量表达式,因为实例化这些模板是编译器在编译的时候来实例化的

template<int a, int b>
int funcaddv2()
{
	int addhe = a + b;
	return addhe;
}

上述例子中,没有类型模板参数,只有非类型模板参数,他的调用方式如下

	int result = funcaddv2<12, 13>();//要通过"<>"来传递参数
//显示指定模板参数,在尖括号中提供额外信息
	cout << result << endl;
//只做演示,这种代码并无意义

换种写法

	int a = 12;
	//int result = funcaddv2<a, 13>();
	//这种写法报错,非类型模板参数必须是常量表达式,值必须实在编译的时候就能确定,\
	//因为实例化模板是在编译的时候做的事

类模板的使用

为什么需要类模板

类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下面语句声明了一个类模板:

template <typename T>
class A
{
public:
	A(T t)
	{
		this->t = t;
	}

	T &getT()
	{
		return t;
	}

public:
	T t;
};

1.类模板用于实现类所需数据的类型参数化
2.类模板在表示支持多种数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响
示例

#include <iostream>
#include <string.h>
using namespace std;

template <typename T>
class A
{
public:
	A(T t = 0)
	{
		m_t = t;
	}

	T& getT()
	{
		return m_t;
	}
	
private:
	T m_t;
};

int main(void)
{
	A<int> a(666);
	cout << a.getT() << endl;

	A<float> b(6.6f);   //"<>"这里必须带类型
	cout << b.getT() << endl;

	//A c(2);  //报错,如下图
	return 0;
}

在这里插入图片描述

类模板作为函数参数

类外定义函数

void printA(A<int> &a){
	cout<<a.getT()<<endl;
}

main中

	A<int>  a(666);
	printA(a);	//模板类做为函数参数

类模板函数的三种表达描述方式

所有的类模板函数写在类的内部

—上述即为写在类内部—
意思就是,函数体也写在了类的内部{};中

所有的类模板函数写在类的外部,在一个cpp中

#include <iostream>
using namespace std;

template <typename T>
class A
{
public:
	A(T t = 0);

	T& getT();

	A operator +(const A& other);

	void print();

private:
	T t;
};


template <typename T>
A<T>::A(T t)
{
	this->t = t;
}

template <typename T>
T& A<T>::getT()
{
	return t;
}

template <typename T>
A<T> A<T>::operator+(const A<T>& other) {
	A<T> tmp; //类的内部类型可以显示声明也可以不显示
	tmp.t = this->t + other.t;
	return tmp;
}

template <typename T>
void A<T>::print() {
	cout << this->t << endl;
}

int main(void) {

	A<int>  a(666), b(888);
	//cout<<a.getT()<<endl;

	A<int> tmp = a + b;

	tmp.print();

	return 0;
}

总结:
在同一个cpp 文件中把模板类的成员函数放到类的外部,需要注意以下几点

1.函数前声明 template <类型形式参数表>
2.类的成员函数前的类限定域说明必须要带上虚拟参数列表
3.返回的变量是模板类的对象时必须带上虚拟参数列表
4.成员函数参数中出现模板类的对象时必须带上虚拟参数列表
5.成员函数内部没有限定

所有的类模板函数写在类的外部,在不同的.h和.cpp中

demo.h

#pragma once

template <typename T>
class A
{
public:
	A(T t=0);

	T &getT();

	A operator +(const A &other);

	void print();

private:
	T t;
};

demo

#include "demo.h"
#include <iostream>

using namespace std;

template <typename T>
A<T>::A(T t)
{
		this->t = t;
}

template <typename T>
T &A<T>::getT()
	{
		return t;
	}

template <typename T>
A<T> A<T>::operator+(const A<T> &other){
		A<T> tmp; //类的内部类型可以显示声明也可以不显示
		tmp.t =this->t + other.t;
		return tmp;
	}

template <typename T>
void A<T>::print(){
	cout<<this->t<<endl;
}

int main(void){
	
	A<int>  a(666), b(888);
	//cout<<a.getT()<<endl;

	A<int> tmp = a + b;

	tmp.print();

	system("pause");
	return 0;
}

注意: 将类模板的声明(.h文件)和实现(.cpp 或.hpp文件)完全分离即可,包含头文件.其他写法跟跟类型二相同

typename的使用场合

表明其后面跟的是一个类型

(1)在模版定义里,表明其后面的模版参数是类型参数,这个前面已经有介绍

函数模板

template<typename T, int a, int b>
int funcaddv2(tc){...}

类模板

template<typename T>
class myvector{..}

(2)用typename表明这是一个类型(类型成员)
"::"是作用域运算符,当访问类中的静态成员变量时需要用到, 即类名::静态成员变量名

#pragma once

template<typename T>
class myvector
{
public:
	typedef T* myiterator;

public:
	myvector();
	myvector& operator = (const myvector&);

public:
	myiterator mybegin();
	myiterator myend();  //未定义
};

template<typename T>
inline myvector<T>::myvector()
{
}

template<typename T>
myvector<T> & myvector<T>::operator=(const myvector&)
{

	return;
}

template<typename T>
typename myvector<T>::myiterator myvector<T>::mybegin()
{
	//注意这里的typename
}

成员函数mybegin()在定义时用到了typename,为什么用这个typename呢,首先普通类用不到,类模板才会用到,可以看到类模板中有个模板参数T, 因为这个T可能是任意一种类型,编译器在遇到这种T::后面跟着一些内容的时候或者说只要T和"::"一起出现的时候,导致编译器无法区分,因为这有可能是一个静态成员变量;所以这里用typename来修饰告诉编译器.myiterator是一个类型,所以使用typename .

普通类成员函数模板

#include <iostream>

using namespace std;

class A
{
public:

	template<typename T>
	void myfunc(T tmpt)
	{
		cout << tmpt << endl;
	}
};

int main(void)
{
	A a;
	a.myfunc(3);  //3
	//成员函数模板的模板参数可以推断.
	return 0;
}

成员函数本身可以是一个函数模板,如上,这种称为"成员函数模板",但是这种成员函数模板不可以是虚函数.

类模板的成员函数模板

根据上面代码加,如下

#include <iostream>

using namespace std;

template <typename C>
class A
{
public:

	template<typename T>
	void myfunc(T tmpt)
	{
		cout << tmpt << endl;
	}

	template<typename T2>
	A(T2 v1, T2 v2);//构造引入一个自己的模板参数,和整个类的函数模板没有关系

public:
	C m_ic;
};

int main(void)
{
	A<int> a(1, 2);
	A<float> a2(1.1, 2.2);
	A<float> a3(1.1f, 2.2f);

	a.myfunc(3);

	return 0;
}

template <typename C> //先写类模板的模板参数列表,否则报错
template<typename T2>
 A<C>::A(T2 v1,T2 v2)
{
	 cout << v1 << "\t" << v2 << endl;
}

指的注意的点:
如上,在构造函数模板的实现中,要先写 类模板的模板参数列表,在写构造函数模板自己的函数列表

其他的,类成员函数用法没有区别,两个函数模板没有关联.互不打扰

模板全特化与偏特化

全特化

常规全特化

所有模板参数类型都未定义,等到使用时才知道,通用的模板
以往实现的模板都是属于泛化的类模板,先看一个泛化

//泛化版本
template <typename T, typename U>
struct TC
{
	TC()
	{
		cout << "TC泛化版本构造函数" << endl;
	}
	void func0()
	{
		cout << "TC泛化版本" << endl;
	}
};

这里定义了一个TC类模板,这就属于一个泛化的类模板
涉及特化,一定先存在泛化,这里的模板参数T和U都可以用int类型代表,既然T和U用int类型来代表了,T和U就不存在了(被绑定了一个具体类型).
针对T和U都作为int类型的特版本要这样写:
:下面的代码一定要放在TC类模板泛化版本的下面.因为所有的类型模板参数都用具体类型代表,因此下面版本是一个全特化版本.

//全特化版本
template<>
struct TC<int, int>//全特化的意思就是(这里是两个模板参数)两个参数都指定成int(也可以是别的类型)了,\
//所以这里<>是空的,后面的偏特化可以区分
{
	TC()
	{
		cout << "TC<int, int>特化版本构造函数" << endl;
	}
	void func0()
	{
		cout << "TC<int, int>特化版本func0()" << endl;
		//...
		//这里可以对特化版本做特殊处理
	}
};

现在,抛开这个int,int的特化版本,其他任何TC类末班的对象调用func0()函数,都应该执行泛化版本的func0()函数,如果调用时指定了"int,int"类型,编译器就会执行这些特化版本的代码.

main主函数中加入如下代码:

//main()
	TC<int, int> tcint;
	tcint.func0();

	TC<double, char> tcOther;
	tcOther.func0();

结果很显然:
在这里插入图片描述

特化类模板的成员函数

继续上面代码添加,为了方便观察,这里贴全代码,添加的部分在代码中有注释

#include <iostream>

using namespace std;


//泛化版本
template <typename T, typename U>
struct TC
{
	TC()
	{
		cout << "TC泛化版本构造函数" << endl;
	}
	void func0()
	{
		cout << "TC泛化版本" << endl;
	}
};

//全特化版本
template<>
struct TC<int, int>
{
	TC()
	{
		cout << "TC<int, int>特化版本构造函数" << endl;
	}
	void func0()
	{
		cout << "TC<int, int>特化版本func0()" << endl;
		//...
		//这里可以对特化版本做特殊处理
	}
};
/*******************************添加的代码*/
//特化类模板的成员函数
template <>
void TC<double, double> ::func0()
{
	cout << "TC<double, double>特化版本func0()" << endl;
}
/****************************添加的代码*/
int main(void)
{
/****************************添加的代码*/
	TC<double, double> tcdouble; //注意,这里并没有定义double,double的特化
	tcdouble.func0();
/****************************添加的代码*/
	return 0;
}

注意上面的结果,这里并没为double,double特化,只有int,int,所以,构造tcdouble对象调用的是泛化版本的TC类模板构造,但是调用func0()时调用的依然是<double,double>的func0()特化版本.
在这里插入图片描述

类模板偏特化

偏特化也叫局部特化,一个是模板参数数量上的偏特化,一个是模板参数范围上的偏特化.

模板参数数量上的偏特化

#include <iostream>

using namespace std;

template<typename T, typename U, typename W>
class TC2
{
public:
	TC2()
	{
		cout << "TC2泛化版本构造函数" << endl;
	}

	void func1()
	{
		cout << "泛化版本func1()函数" << endl;
	}
};

template<typename U>
class TC2<int, U, double>
{
public:
	TC2()
	{
		cout << "TC2<int, U, double>偏特化版本构造函数" << endl;
	}

	void func1()
	{
		cout << "<int, U, double>偏特化版本func1()函数" << endl;
	}
};

int main(void)
{
	TC2<double, int, int> tc2dii;//泛化版本
	tc2dii.func1();

	TC2<int, int, double> tc2iid;//偏特化化版本
	tc2iid.func1();

	return 0;
}

模板参数范围上的偏特化

首先理解一下参数范围的概念,例如原来是int类型,如果变成const int类型,这个范围就变小了,其他比如T,变成T*,T&,T&&,对于T来说,类型范围都是缩小了.

#include <iostream>

using namespace std;

template<typename T>
class TC3
{
public:
	TC3()
	{
		cout << "TC3()泛化版本构造函数" << endl;
	}
	void func2()
	{
		cout << "TC3()泛化版本func2()函数" << endl;
	}

};

template<typename T>
class TC3<const T>
{
public:
	TC3()
	{
		cout << "TC3()范围特化版本构造函数" << endl;
	}
	void func2()
	{
		cout << "TC3()范围特化版本func2()函数" << endl;
	}
};

int main(void)
{
	TC3<int> tc3int;
	tc3int.func2();
	TC3<const int> tc3const;
	tc3const.func2();

	return 0;
}

在这里插入图片描述

函数模板特化

普通函数全特化

#include <iostream>

using namespace std;

template<typename T, typename U>
void funct(T& tmp1,U& tmp2)
{
	cout << "泛化版本funct()函数" << endl;
	cout << tmp1 << endl;
	cout << tmp2 << endl;
}
template<>
void funct(int& tmp1, double& tmp2)
{
	cout << "全特化版本<int,double>funct()函数" << endl;
	cout << tmp1 << endl;
	cout << tmp2 << endl;
}

int main(void)
{
	int a = 1;
	int b = 2;
	double c = 1.1;

	funct(a, b);  //int ,int 
	funct(a, c);  //int ,double

	return 0;
}

用法跟类模板相似.

全特化实际上等价于实例化一个函数模板,但并不等价于一个函数重载,在系统中,如果一个函数调用,选择函数模板合适,选择普通函数也合适的时候,编译器会优先考虑普通函数,再考虑特化版本,最后看泛化.

普通函数 > 函数模板的特化版本 > 函数模板的泛化版本

普通函数偏特化

函数模板不能偏特化,只有类模板可以.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值