【浅尝C++】使用模板实现泛型编程第二弹=>非类型模板参数/模板特化/模板分离编译详解

在这里插入图片描述

🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。
🎯每日格言:每日努力一点点,技术变化看得见。

文章目录

  • 非类型模板参数
  • 模板的特化
    • 概念
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板分离编译
    • 分离编译概念
    • 模板分离编译存在问题的原因
    • 解决方法
  • 模板总结


非类型模板参数

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

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

#include <iostream>

//大小为5的静态数组
template<class T, size_t N = 5>
class MyArray
{
public:
	MyArray(int n)
		:_arr(new T[N])
		, _size(n)
	{}
	T& operator[](const int& index)
	{
		return _arr[index];
	}
private:
	T* _arr;
	size_t _size;
};

int main()
{
	MyArray<int> arr(10);
	for (int i = 0; i < 5; i++)
	{
		arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述
非类型模板参数的数值默认数值必须是常量:如宏定义常量、字面常量、枚举常量。但不可以const修饰的常变量,如const int、const char等。

模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

比如下方代码:实现了一个专门用来进行大于比较的函数模板↓↓↓

#include <iostream>
using namespace std;

template<class T>
bool Greater(T left, T right)
{
	return left > right;
}

class A
{
public:
	A(int n)
		:_a(n)
	{}
	int _a;
};

int main()
{	
	cout << Greater(2, 1) << endl;
	A* a1 = new A(999);
	A* a2 = new A(9);
	cout << Greater(a1, a2) << endl;
	return 0;
}

在这里插入图片描述
上面代码中Greater(2,1)的比较结果是正确的,但Greater(a1,a2)的比较结果是错误(Greater对a1和a2指向的地址进行比较,而这里希望对A类内的_a进行比较)。

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化

函数模板特化

函数模板的特化步骤:

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

上面给出的示例无法正确比较自定义类型A,下面代码给出了特化示例↓↓↓

#include <iostream>
using namespace std;

class A
{
public:
	A(int n)
		:_a(n)
	{}
	int _a;
};

template<class T>
bool Greater(T left, T right)
{
	return left > right;
}

//特化版本
template<>
bool Greater<A*>(A* left, A* right)
{
	cout << "Special!" << endl;
	return left->_a > right->_a;
}

int main()
{	
	cout << Greater(2, 1) << endl;
	A* a1 = new A(999);
	A* a2 = new A(9);
	//调用特化版本,不调用自动生成的函数模板
	cout << Greater(a1, a2) << endl;
	return 0;
}

在这里插入图片描述
一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出,而不是使用模板特化。我们可以实现一个与模板函数名相同的普通函数,在普通函数能够匹配的情况下,将会优先使用普通函数

#include <iostream>
using namespace std;

class A
{
public:
	A(int n)
		:_a(n)
	{}
	int _a;
};

template<class T>
bool Greater(T left, T right)
{
	return left > right;
}

//普通函数
bool Greater(A* left, A* right)
{
	cout << "Normal Function is called" << endl;
	return left->_a > right->_a;
}

int main()
{	
	A* a1 = new A(999);
	A* a2 = new A(9);
	//优先匹配普通函数
	//如果匹配普通函数时,无法匹配或需要隐式类型转换,则会使用模板生成
	cout << Greater(a1, a2) << endl;
	return 0;
}

在这里插入图片描述
使用与函数模板同名的普通函数实现,简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

类模板特化

全特化

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

下面的类模板有2个参数,在类模板之后又实现了一个第一个参数类型为int,第二个参数类型为char的特化版本,由于特化版本将类模板的两个参数都确定化,故这种特化称为全特化。

#include <iostream>
using namespace std;

//类模板
template<class T1, class T2>
class Two
{
public:
	Two(T1 t1, T2 t2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};

//特化版本
template<>
class Two<int, char>
{
public:
	Two(T1 t1, T2 t2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << "I am Special " << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};

int main()
{
	Two<int, int>obj1(1, 2);
	Two<int ,char>obj2(1, 'C');
	obj1.Print();
	obj2.Print();
	return 0;
}

在这里插入图片描述

偏特化

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

template<class T1, class T2>
class Test
{
public:
	PP(T1 t1, T2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};

偏特化有以下两种表现方式:

  1. 部分特化
    将模板参数类表中的一部分参数特化。
template<class T1>
class Test<T1, char>//将T2特化为char类型
{
public:
	PP(T1 t1, T2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};
  1. 参数更进一步的限制
    偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//特化为指针类型
template<class T1, class T2>
class Test<T1*, T2*>
{
public:
	PP(T1 t1, T2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};
//特化为引用类型
template<class T1, class T2>
class Test<T1&, T2&>
{
public:
	PP(T1 t1, T2)
		:_t1(t1)
		,_t2(t2)
	{}
	void Print()
	{
		cout << _t1 << " " << _t2 << endl;
	}
private:
	T1 _t1;
	T2 _t2;
};

模板分离编译

分离编译概念

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

模板分离编译存在问题的原因

如果我将Add函数模板声明于test.h,实现于test.cpp,并在main.cpp中调用,则会出现链接错误。

//test.h
template<class T>
T Add(const T& left, const T& right);

//test.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

//main.cpp
#include "test.h"
int main()
{
	Add(1, 2);//链接错误
	return 0;
}

我们先来复习一下源文件如何编程可执行文件的↓↓↓
在这里插入图片描述
预处理阶段会执行的操作有:头文件展开、条件编译、宏替换、去除注释。
编译阶段会执行的操作有:词法分析、语法分析、语义分析(如果出现语法错误,将在编译阶段报错),在该阶段还会生成将函数名与函数所在地址映射到一起的符号表。
汇编阶段会执行的操作是:将汇编语言转化为二进制语言。
链接阶段:从符号表中获取信息,将函数声明与函数定义链接到一起。

由于各个文件是分开编译的,add.cpp无法确定Add的函数类型,在编译阶段并没有生成对应类型的Add函数。因而,在链接时,无法找到int参数类型的Add函数地址,导致链接错误。

解决方法

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。
//test.hpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

//main.cpp
#include "test.hpp"
int main()
{
	Add(1, 2);//链接错误
	return 0;
}

★ps:建议采用这种解决方案。将声明和定义写在同一个hpp或h文件后缀的文件中。

  1. 模板定义的位置显式实例化。
//test.h
template<class T>
T Add(const T& left, const T& right);

//test.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

template int Add<int>(const int&, const int&);

//main.cpp
#include "test.h"
int main()
{
	Add(1, 2);//链接错误
	return 0;
}

★ps:这种方式也可以实现分离编译,但相对繁琐,不推荐使用这种解决方案。

模板总结

【优点】

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

【缺陷】

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

🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/500597.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp开发微信小程序设置分包,简单易学

文章目录 前言一、在 manifest.json文件中的源码试图中配置二、配置pages.json 前言 我们使用uniapp开发微信小程序的时候&#xff0c;当我们的包体积过大的时候&#xff0c;无法真机模拟。 因为小程序单个包只支持2MB&#xff08;现已支持预览4MB&#xff09;&#xff0c;所以…

PyTorch深度学习

一、深度学习的概念 如上所示&#xff0c;人工智能包含了机器学习和深度学习&#xff0c;其中深度学习是机器学习的一种特殊的学习方法&#xff0c;人工智能的核心是深度学习 1、深度学习 深度学习需要用到大量的神经网络构建和运算模块&#xff0c;故出现了很多的深度学习框…

通讯录改进———动态版本

在上一篇博客中讲完了动态内存分配&#xff0c;这时候我们就可以改进之前写的通讯录了&#xff0c;可以将其升级为动态内存的版本&#xff0c;既不用担心联系人满了&#xff0c;也不用担心内存浪费太大。 要将其改为动态版本主要是两件事&#xff0c;首先初始化的时候我们要动…

PS从入门到精通视频各类教程整理全集,包含素材、作业等复发(2)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 初级教程素材 等文件 https://www.alipan.com/s/fC…

jsp用户登录界面

主界面 <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><meta charset"UTF-8"><title>登录界面</title> </head> <body bgcolor"#faebd7"> <form…

RabbitMQ 延时消息实现

1. 实现方式 1. 设置队列过期时间&#xff1a;延迟队列消息过期 死信队列&#xff0c;所有消息过期时间一致 2. 设置消息的过期时间&#xff1a;此种方式下有缺陷&#xff0c;MQ只会判断队列第一条消息是否过期&#xff0c;会导致消息的阻塞需要额外安装 rabbitmq_delayed_me…

下载及安装PHP,composer,phpstudy,thinkPHP6.0框架

文章目录 前言 thinkPHP是一款开源的PHP框架&#xff0c;它是基于MVC&#xff08;Model-View-Controller&#xff09;设计模式构建的。thinkPHP提供了丰富的功能和组件&#xff0c;使得开发人员可以快速、高效地构建和维护Web应用程序。 以下是thinkPHP框架的一些特点和功能&…

C语言最大公约数(辗转相除法)

输入两个整数&#xff0c;求他们的最大公约数&#xff1a; 如果我们不用辗转相除法的话&#xff0c;两个整数的最大公约数&#xff0c;我们就可以定义一个整数为两个整数中最小的那个数&#xff0c;然后两个整数一起除我们新定义的整数&#xff0c;如果都除尽了&#xff0c;这…

Vidmore Video Fix for Mac 视频修复工具

Vidmore Video Fix for Mac是一款功能强大且易于使用的视频修复工具&#xff0c;专为Mac用户设计。它凭借先进的视频修复技术&#xff0c;能够帮助用户解决各种视频问题&#xff0c;如视频文件损坏、无法播放、格式不支持等。 软件下载&#xff1a;Vidmore Video Fix for Mac v…

php将网页用wkhtmltoimage内容生成为图片

php架构ThinkPHP6 1. 安装 knp-snappy架构 composer require knplabs/knp-snappy use Knp\Snappy\Image; use Illuminate\Support\Facades\Storage;// 生成图片 /user/local/bin/wkhtmltoimage为你的wkhtmltoimage的位置。 $snappy new Image(/usr/local/bin/wkhtmltoimage…

【NoSQL】MongoDB

文章目录 概述NoSQL数据库四大家族mongodb和mysql存储数据形式有什么不同 概念适用场景环境搭建1、下载2、安装 基础入门高级查询聚合和管道索引备份和恢复来源 概述 MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案…

vscode调试Unity

文章目录 vscode调试UnityC#环境需求开始调试 Lua添加Debugger环境配置联系.txt文件配置Java环境 添加调试代码断点不生效的问题 vscode调试Unity C# 现在使用vscode调试Unity的C#代码很简单&#xff0c;直接在vscode的EXTENSIONS里面搜索“Unity”&#xff0c;第一个就是&am…

如何在 Mac 上打开、编辑、复制、移动或删除存储在 Windows NTFS 格式 USB 驱动器上的文件 Tuxera NTFS for Mac使用教程

当您获得一台新 Mac 时&#xff0c;它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac&#xff0c;您需要一个附加的 NTFS 驱动程序。Tuxera 他可以帮忙实现这一功能&#xff01; Tuxera可以轻松转换驱动器&#xff1a;无论使用Windows PC还是Mac&…

什么是PMP,对工作的作用大不大?

PMP最早是由PMI发起&#xff0c;已成为全球公认的项目管理专业资格认证之一&#xff0c;PMP认证在全球190多个国家和地区获得最广泛的专业认可。 随着PMP的认可度不断提高&#xff0c;持有PMP证书的项目经理在求职过程中更有优势&#xff0c;不少公司对项目经理是否持证上岗也制…

2.java openCV4.x 入门-hello OpenCV

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天 &#x1f9ed;文章导航&#x1f9ed; ⬆️ 1.环境搭建 ⬇️ 3.Mat之构造函数与数据类型 hell…

三步提升IEDA下载速度——修改IDEA中镜像地址

找到IDEA的本地安装地址 D:\tool\IntelliJ IDEA 2022.2.4\plugins\maven\lib\maven3\conf 搜索阿里云maven仓库 复制https://developer.aliyun.com/mvn/guide中红框部分代码 这里也是一样的&#xff1a; <mirror><id>aliyunmaven</id><mirrorOf>*&…

期货开户要找到适合自己的系统

物有一个生物圈&#xff0c;大鱼吃小鱼&#xff0c;小鱼吃虾。在期货市场这条生物圈里面&#xff0c;大部分人就是期货市场的虾子&#xff0c;是被吃的&#xff0c;所以必须成长起来&#xff0c;往更高一层走&#xff0c;到可以吃虾子的时候&#xff0c;就是挣钱的时候。学习不…

TCP/IP 网络模型有哪几层?(计算机网络)

应用层 为用户提供应用功能 传输层 负责为应用层提供网络支持 使用TCP和UDP 当传输层的数据包大小超过 MSS&#xff08;TCP 最大报文段长度&#xff09; &#xff0c;就要将数据包分块&#xff0c;这样即使中途有一个分块丢失或损坏了&#xff0c;只需要重新发送这一个分块…

【解決|三方工具】Obi Rope 编辑器运行即崩溃问题

开发平台&#xff1a;Unity 2021.3.7 三方工具&#xff1a;Unity资产工具 - Obi Rope   问题背景 使用Unity三方开发工具 - Obi Rope 模拟绳索效果。配置后运行 Unity 出现报错并崩溃。通过崩溃日志反馈得到如下图所示 这是一个序列化问题造成的崩溃&#xff0c;指向性为 Obi…

深度学习pytorch——卷积神经网络(持续更新)

计算机如何解析图片&#xff1f; 在计算机的眼中&#xff0c;一张灰度图片&#xff0c;就是许多个数字组成的二维矩阵&#xff0c;每个数字就是此点的像素值&#xff08;图-1&#xff09;。在存储时&#xff0c;像素值通常位于[0, 255]区间&#xff0c;在深度学习中&#xff0…