C++入门泛型编程介绍

2023-11-11

在这里插入图片描述

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

函数模板的实例化

隐式实例化:让编译器根据实参推演模板参数的实际类型

实现一个交换函数,在以前用c语言写的时候,针对不同的类型我们需要写多个交换函数,在C++中有了模板后并不需要,他会通过实际的参数来推断函数模板的参数类型,进而实例化出一份合适的函数供你使用

//函数模板
template <class T> 
//模板参数T的类型并不是固定的,通过实际传递的参数来推导T的类型,实例化出一份函数
//template <typename T>   这种写法也是可以的
void Swap(T &num1, T& num2)
{
	T tmp = num1;
	num1 = num2;
	num2 = tmp;
}

void functest() 
{
	//交换整形
	int a = 10, b = 20;
	Swap(a,b);//隐士实例化
	cout << "a :" << a << "b :" << b << endl;

	//交换浮点型
	float c = 10.55f,  d = 3.14f;
	Swap(c, d);//隐士实例化
	cout << "c :" << c << "d :" << d << endl;
}

流程示例图:
在这里插入图片描述
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

编译器根据实参的类型推模板参数的类型,这种也叫隐士类型转换,而显示实例化又是怎么样的呢

显式实例化:在函数名后的<>中指定模板参数的实际类型

//显示指定模板参数类型为int
int a = 10, b = 20;
Swap<int>(a, b);//显示实例化

//显示指定模板参数类型为float
float c = 10.55f,  d = 3.14f;
Swap<float>(c, d);//显示实例化

显示指定模板参数类型后,就不再需要编译器根据实参的类型去推断模板参数T的类型了,模板参数类型显示指定是啥就是啥

补充:

1、 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

2、 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

3、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

类模板

类模板的定义格式

//可以声明多个模板参数
template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
};

类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

部分模拟实现vector类,后继在加以补充

//指定名命空间mzt
namespace mzt 
{
	template<class T>
	class vector 
	{
	public:
		vector() 
			: _a(nullptr)
			,_size(0)
			,_capacity(0)
		{ }
		//往容器中插入数据
		void push_back(const T& data)
		{
			//扩容
			if (_capacity == _size) 
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				T* tmp = new T[newcapacity];
				assert(tmp);
				_a = tmp;
				_capacity = newcapacity;
			}

			_a[_size++] = data;
		}
		//返回pos位置处的值
		T& operator[](size_t pos) 
		{
			assert(pos < _size);
			return _a[pos];
		}

		size_t getsize() 
		{
			return _size;
		}
	private:
		T*  _a;//
		size_t _size;//元素个数
		size_t _capacity;//空间
	};
}

void func1() 
{
	//使用显示指定实例化类对象
	mzt::vector<int> a;
	//插入数据
	a.push_back(1);
	a.push_back(2);
	a.push_back(3);
	a.push_back(4);
	//遍历
	for (size_t i = 0; i < a.getsize(); i++) 
	{
		cout << " " << a[i];
	}
	cout << endl;
}

非类型模板参数

模板参数分类:类型形参、非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

namespace mzt 
{
	//模板参数给了缺省值后在定义对象时可以不指定显示模板参数
	template<class T = int, size_t N = 10> 
	//声明模板参数和非类型模板参数
	class Array 
	{
	public:
		void f() 
		{
			//N = 10;  //N为常量不允许修改
		}
	private:
		T* arr[N];
	};
	
}
int main()
{
	mzt::Array< > a; //不提供模板参数,使用缺省值  
	
	return 0;
}

注意:
1、 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2、 非类型的模板参数必须在编译期就能确认结果

模板特化

通常情况下使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化

函数模板特化

template<class T>
bool IsEqual(const T& left, const T& right)
{
	return left == right;
}

int main()
{
	char arr1[] = "hello";
	char arr2[] = "hello";
	bool ret = mzt::IsEqual(arr1, arr2);// 0
	cout << ret << endl;

	const char* p1 = "hello";
	const char* p2 = "hello";
	ret = mzt::IsEqual(p1, p2); // 1
	cout << ret << endl;
}

在这里插入图片描述
同一字符串比较的结果确是不同,因为指针只会指向一块已经存在的空间,就是字符串的起始地址,而如果是数组的话他会创建两块不相同的空间,但是不符合预期的效果

解决办法模板特化


//方法一:
//这里针对字符串类型需要做特殊的处理
template< >  //空括号
bool IsEqual<const char *>(const char* const& left, 
			const char* const& right) 
{
	return strcmp(left, right) == 0;
}

//方法二:
//也可以采用以下写法,如果IsEqual这个函数有,编译器就不会去通过
//函数模板来实例化生成一份函数了
bool IsEqual(const char* left, const char* right)
{
	return strcmp(left, right) == 0;
}

void func()
{
	char arr1[] = "hello";
	char arr2[] = "hello";
	bool ret = mzt::IsEqual(arr1, arr2);// 0
	cout << ret << endl;

	const char* p1 = "hello";
	const char* p2 = "hello";
	ret = mzt::IsEqual<const char*>(p1, p2);//1
	// 调用特化版本的函数模板的实例化,
	//当然这个模板参数类型也可以不显示指定,
	//1、如果不指定的话调用的就是方法二的函数了
	//2、指定参数类型后只会去调用特化版本的函数模板
	cout << ret << endl;

}

函数模板特化总结:

1、必须要先有一个基础的函数模板
2、关键字template后面接一对空的尖括号<>
3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
4、函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

//这里针对字符串类型需要做特殊的处理
template<>
bool IsEqual(const char* const& left, 
			const char* const& right) 
{
	return strcmp(left, right) == 0;
}

类模板特化

全特化

即是将模板参数列表中所有的参数都确定化。

namespace mzt 
{
	template<class T1, class T2> 
	class Array 
	{
	public:
		Array() { cout << "<class T1, class T2>" << endl; }
	};

	template<>
	class Array<int,int>
	{
	public:
		Array() { cout << "<int,int>" << endl; }
	private:
		
	};

	template<>
	class Array<int, double>
	{
	public:
		Array() { cout << "<int,double>" << endl; }
	private:

	};

	template<>
	class Array<int*, int*>
	{
	public:
		Array() { cout << "<int*, int*>" << endl; }
	private:

	};

	template<>
	class Array<int&, int&>
	{
	public:
		Array() { cout << "<int& ,int&>" << endl; }
	private:

	};
	
}

int main() 
{	
	//都实例化了特化版本的类模板,调用构造函数创建对象
	mzt::Array<int, int>a1;
	mzt::Array<int, double>a2;
	mzt::Array<int*, int*>a3;
	mzt::Array<int&, int&>a4;
	mzt::Array<double, int>a5;
	return 0;
}

在这里插入图片描述

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类

部分特化

namespace mzt 
{
	template<class T1, class T2> 
	class Array 
	{
	public:
		Array() { cout << "<class T1, class T2>" << endl; }
	};

	//部分特化,将第二个参数特化为int
	template<class T1>
	class Array<T1, int>
	{
	public:
		Array() { cout << "<class T1, int>" << endl; }
	};

	//部分特化,将第二个参数特化为double
	template<class T1>
	class Array<T1, double>
	{
	public:
		Array() { cout << "<class T1, double>" << endl; }
	};
	
}


int main() 
{	
	
	mzt::Array<int,int> a1; 
	mzt::Array<int, double> a2;
	mzt::Array<char, char> a3;//调用原类模板

	return 0;
}

在这里插入图片描述

参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

namespace mzt 
{
	template<class T1, class T2> 
	class Array 
	{
	public:
		Array() { cout << "<class T1, class T2>" << endl; }
	};

	//指针类型的偏特化
	template<class T1, class T2>
	class Array<T1*, T2*>
	{
	public:
		Array() { cout << "<T1*, T2*>" << endl; }
	};
	//引用类型的偏特化
	template<class T1, class T2>
	class Array<T1&, T2&>
	{
	public:
		Array() { cout << "<T1&, T2&>" << endl; }
	};	
}

int main() 
{	
	//根据显示指定的模板参数类型编译器自动去调用该类模板
	mzt::Array<int,int> a1;
	mzt::Array<int*, double*> a2;
	mzt::Array<char&, char&> a3;
	return 0;
}

在这里插入图片描述

模板分离编译

func1.cpp

template<class T>
T Sub(T& left, T& right)
{
	return left - right;
}

func1.h

template<class T>
T Sub(T& left, T& right);

test.c

int main() 
{	
	int a = 10, b = 20;
	int ret = Sub(a,b);
	cout << ret << endl;
	return 0;
}

模板分离编译后在链接的过程中会发现找不到Sub函数
在这里插入图片描述
解决办法显示实例化

template<class T>
T Sub(T& left, T& right)
{
	return left - right;
}
//定义的时候显示实例化
template
int Sub<int>(int& left, int& right);

解决办法二:不需要显示实例化,函数模板的定义和声明都放到.h文件中

template<class T>
T Sub(T& left, T& right) 
{
	return left - right;
}

模板总结

【优点】

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
    3.模板不支持分离编译

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++入门泛型编程介绍 的相关文章

随机推荐

  • 微前端的出现的背景和意义

    目录 微前端是什么 大规模 Web 应用的困局 传统 Web 应用的利与弊 背景和意义总结 微前端是什么 微前端是一种类似于微服务的架构 是一种由独立交付的多个前端应用组成整体的架构风格 将前端应用分解成一些更小 更简单的能够独立开发 测试
  • mysql删除一行_MySql删除表中一行的实操方法

    MySql删除表中一行的实操方法 首先你要确定能够唯一确定你那一行数据的字段或字段组合是哪些 DELETE FROM 表名 WHERE 字段1 and 字段2 and 字段1 为能够唯一确定某一行数据的字段组合 中填写你要 删除的字段具体值
  • Unity-ScrollRect-循环播放图片(确实没有转载是真的)

    都在代码和代码注释里了 发表在这里我还是有私心想要问问题的 是因为自己在使用时发现 Unity会调用两次通过继承来的PictureScrollView的Start函数两次 我并不能想明白是怎么回事 还请有了解的指点一下 先谢谢能给我指点的各
  • 【好文分享】亲试可行!简单快捷!如何在Ubuntu上编译Linux0.11

    2023年9月10日 周日上午 昨天晚上按照博客园的这篇文章试了一下 很快就成功在Ubuntu上编译运行了Linux0 11 https www cnblogs com chaoguo1234 p 16883932 html
  • linux redhat6.5 64位 login登陆无限循环

    场景 公司VC上虚机要迁移 通过拷贝vmfs文件方式迁移 开机后 发现无法登陆 一开始怀疑密码有问题 后来排除 然后网上搜索要修改 etc pam d login里边的参数 64位系统将 lib security pam limits so
  • Eclipse 报错: “Workspace in use or cannot be created, chose a different one.”

    打开eclipse报错 Workspace in use or cannot be created chose a different one 意识是 正在使用或无法创建工作区 选择另一个 解决办法 找到你eclipse得工作区 打开 me
  • C++ typeid运算符:获取类型信息

    typeid 运算符用来获取一个表达式的类型信息 类型信息对于编程语言非常重要 它描述了数据的各种属性 对于基本类型 int float 等C 内置类型 的数据 类型信息所包含的内容比较简单 主要是指数据的类型 对于类类型的数据 也就是对象
  • vue2和vue3 父子组件传参及区别

    vue3 1 父组件传子组件 在父组件的子组件标签中定义一个属性 在子组件中用defineProps接收父组件传来的值 父组件
  • python读取excle表中的数据

    没什么可介绍的 直接看代码 import pandas as pd from pandas import DataFrame if name main 读取excle表中的数据 file path r D ex Concrete Data
  • 从零搭建一个vue项目

    为了几个姐妹的需求 本文详细图解怎么样从零搭建一个vue项目 供参考 第一步 了解工具 首先我们需要一些工具 比如npm nodejs vue cli 和一个编译器vscode 也可以用别的 这里用vscode作为开发工具演示 第二步 检查
  • VMware虚拟机首次安装centos7.15后配置网络和关闭防火墙

    听了朋友的意见打算学下linux 学习当然是从安装开始了 网上找了个最新版的边看教程边装 网上看了好几个不同的教程 安装的时候还不难 都比较详细 但配置网络时都说的不太清楚 毕竟我没什么基础 如果不说清楚的话 很多地方我也不太懂 看了很多个
  • 编译内核的相关知识

    1 在PC端搭建环境 ubantu 2 树莓派等芯片带操作系统的启动过程 C51 STM32 裸机 用C直接操控底层寄存器实现相关业务 业务流程型的裸机代码 3 带有操作系统的 X86 intel windows 启动过程 电源 gt bi
  • openGL之API学习(三十)深度缓冲区深度值为负值

    通过 glReadPixels 0 0 WINDOW WIDTH WINDOW HEIGHT GL DEPTH COMPONENT GL UNSIGNED BYTE tmpPixelsBuffer 从帧缓冲区中读取深度信息 深度值竟然是负值
  • 插入排序 Insertion Sort

    插入排序 Insertion Sort 基本概念 插入排序的实现 时间复杂度和空间复杂度 稳定性 基本概念 从 index 1开始 不断将元素插入右边已经排好序的数组 适用于少量元素 Example 9 2 1 4 3 Step 1 9 2
  • 理解分组卷积与深度可分离卷积

    这两种卷积分别是在ResNext论文与MobileNet系列中体现的 貌似Xception中也有深度可分离卷积的体现 作用都很简单 为了降参 目录 1 分组卷积 group convolution 2 深度可分离卷积 depthwise s
  • 测试用例设计--等价类的几个例子

    等价类的设计思路 根据输入条件 确定等价类 包括有效等价类和无效等价类 建立等价类列表 为每个等价类规定一个唯一的编号 设计一个测试用例 使其尽可能多地覆盖尚未被覆盖的有效等价类 重复这一步 直到所有的有效等价类被覆盖完为止 设计一个测试用
  • 如何查看chrome浏览器插件位置 查看chrome浏览器插件位置的方法

    Chrome浏览器插件种类目繁多 插件支持功能也比较强大 Chrome浏览器内的下载的插件往往也可以被其他浏览器支持 那么如何查看chrome浏览器插件位置 想知道谷歌浏览器插件的存放位置小伙伴快来阅读下文教程 谷歌浏览器最新版下载2020
  • 面试大盘表 - 时间维护功能

    需求描述 由于本人主要负责干部和招聘板块 招聘板块接到了要做一个面试大盘表的需求 需要展示面试官的面试情况 还需要面试官维护自己的可面试时间 之后人资进行面试安排 然后面试者到手机端进行自主选择面试官和面试场次 最终展示 功能描述 移出维护
  • 联想全系列 Lenovo ThinkPad ThinkBook Thinkcenter ThinkStation 原厂恢复系统

    原厂恢复系统镜像 出厂预装正版系统 自带预装Office 自带一键恢复 联网自动激活系统 根据电脑型号或者MTM下载对应的系统恢复镜像 2022 4 30更新 型号后面括号里是MTM号 可对应个人电脑自行对应查找 如没有其对应机型可以Wei
  • C++入门泛型编程介绍

    目录 函数模板 函数模板格式 函数模板的实例化 补充 类模板 类模板的定义格式 类模板的实例化 非类型模板参数 模板特化 函数模板特化 类模板特化 全特化 偏特化 模板分离编译 模板总结 泛型编程 编写与类型无关的通用代码 是代码复用的一种