[C++]: 模板进阶

标题:[C++]: 模板进阶

@水墨不写bug



目录

一、非类型模板参数

(1)、非类型模板参数简介 

 (2)、非类型模板参数实例

二、模板的特化

(1)函数模板特化

(2)类模板特化

三、模板的分离编译


正文开始:

一、非类型模板参数


(1)、非类型模板参数简介 

        在模板初阶中,我们讲解了一般我们使用模板的做法:

//函数模板
template<class T1,class T2>
void func(T1 t1,T2,t2)
{
    //......
}

//类模板
template<class T>
class A
{
public:
    //......

private:
    T t;
}

        模板在如下场景中的使用会让你感到更加方便:

        如果我们要实现一个栈,在通常情况下我们可能会选择实现一个静态的栈,它的大小是固定的,比如:

typedef N 100

template<class T>
class stack
{

private:
    T _data[N];
}

         但是我们发现这样实现的栈的局限性很大,因为一旦确定它的大小,就无法改变了。如果我们想要在实例化的时候能够自己手动确定它的大小,就需要用到非类型模板参数;

        模板参数的类型分为:类型模板形参与非类型模板形参。

类型模板形参:出现在模板的参数列表中,跟在class或者typename之后;

非类型模板形参:就是用一个常量作为类(函数)模板的一个参数,在实例化的时候确定,在模板内部可以将该参数作为常量来使用。

在使用非类型模板形参之后,我们可以这样定义模板:


template<class T, int N>
class stack
{
public:

private:
	T st[N];
};

int main()
{
	stack<int, 100> st1;
	stack<int, 10> st2;
	
	return 0;
}

        其中,第二个模板参数 是一个整形常量,这个常量值在类实例化的时候确定。这样一来,就可以在创建栈的时候定义它的大小。

注意: 

        1.浮点数、类对象以及字符串是不允许作为非类型模板参数的。

        2.非类型模板参数必须在编译时就能确定。

        但是,在C++20及以后,浮点数可以作为类的非类型模板参数 。


 (2)、非类型模板参数实例

        STL中有一种容器,array

        在C++11及以后,它就是一种使用非类型模板参数的容器:

         array就是数组,但是它是一种封装后的一种数组;对于一般的数组,越界检查是部分的抽查,是通过编译器内部对比数组边界外的小范围内是否被改变来检测实现的;

        如果我们只读,检测不出来: 


int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10};
    a[10];
    a[11];
    a[12];
	return 0;
}

         如果我们越界写入一个值,就会被检测出来;甚至编译都无法通过:

        但是当我们在数组外距离边界比较远的地方越界写入时,就可能不会被检测出来:

但是array解决了这个问题,因为array是一个类,它可以通过在类内部实现对  [ ]  的重载来进行严格的越界检查,也就是通过assert()来进行检查。

在使用容器array时越界访问,在越界读时会被检测出来: 

#include<array>
using namespace std;
int main()
{
	array<int, 10> arr = {1,2,3,4,5,6,7,8,9,10};
	arr[10];
	return 0;
}

         

越界写时也可以检测出来:

#include<array>
using namespace std;
int main()
{
	array<int, 10> arr = {1,2,3,4,5,6,7,8,9,10};
	arr[10] = 1;
	return 0;
}

 


二、模板的特化

        我们曾将实现了一个比较大小的函数模板:

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

这个模板在大多数情况下都可以正常使用,不会出错,比如对int(整形家族),Date(重载了比较运算符的自定义类型等)都可以正常使用:

class Date
{
public:

	Date(int year = 0, int month = 0, int day = 0);

	bool operator<(Date d) const;

private:
	int _year = -1;
	int _month = -1;
	int _day = -1;
};

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

int main()
{
	int a = 1;
	int b = 2;
	cout << ::Less<int>(a, b) << endl;
	cout << ::Less<double>(9.2, 8.2) << endl;
	Date d1(2022, 1, 1);
	Date d2(2023, 1, 1);

	cout << ::Less<Date>(d1, d2) << " ";

	return 0;
}

但是对于一种特殊情况,Less函数就会出现问题了:

 


int main()
{

	Date d1(2022, 1, 1);
	Date d2(2023, 1, 1);

	cout << ::Less<Date*>(&d1, &d2) << " ";

	return 0;
}

运行结果会随着d1和d2的实例化顺序而不同:

        究其原因,是函数模板实例化出的函数是根据d1和d2的地址大小比较的而不是d1和d2本身。

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


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

(1)函数模板特化

1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同!


        我们就以上面的less函数模板特化出Date类函数为例,进行模板特化:

template<class T>
bool Less(T left, T right)
{
	return left < right;
}
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	Date d2(2023, 1, 1);

	Date d1(2022, 1, 1);

	cout << ::Less<Date*>(&d1, &d2) << " ";

	return 0;
}

        这样我们就完成了对函数模板的特化,这时,函数就会根据d1和d2大小来比较,而不是根据d1和d2的地址来比较。这样函数的结果就不会因为d1和d2的实例化顺序不同而产生差异了。


           但是,我们在实现一个函数的时候,传参传自定义类型消耗太大,于是就需要传引用,既然传引用就要防止对象被改变,需要加上const,如下:

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

当你试图对这个函数模板进行特化的时候,就会发现意想不到的问题:刚开始学习特化,有一个误区:那就是将原模板的类型直接替换到特化后的函数内,比如对上面这个函数进行特化<Date*>,可能你会这样写:


template<>
bool Less<Date*>(const Date*& pleft,const Date* & pright)
{
	return *pleft < *pright;
}

        但是,这是错误的写法;你会发现编译都无法通过,这就是违背了“4. 函数形参表: 必须要和模板函数的基础参数类型完全相同!”的这一条规则。

        仅仅对于语法来说,对于less模板,const修饰的两个变量本身不能修改,特化的Date*版本,两个参数是指针类型,要与模板保持一致,就需要const修饰变量本身,即const修饰指针本身。

        并且const在*之前,修饰指针的内容;const在*之后,修饰指针本身。那么这样写才是正确的:


template<>
bool Less<Date*>(Date* const& pleft, Date* const& pright)
{
	return *pleft < *pright;
}

        这一点需要非常慎重,特别注意!


        同时你可能会发现:

        const修饰指针本身,但是我们还可以通过指针解引用改变其内容,这不是我们希望的,这也就要求:当你在使用函数模板的时候,需要对特化出来的函数一清二楚,并不能说你试着特化一下,看一下特化出来的东西是不是想要的,这个不是概率问题。

(2)类模板特化

        类模板的特化分为全特化和偏特化。

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


template<class T1,class T2>
class Data
{
private:
	T1 t1;
	T2 t2;
};
template<>
class Data<char, int>
{
private:
	char t1;
	int t2;
};


偏特化:任何针对模版参数进一步进行条件限制设计的特化:


对参数的进一步限制可以是对参数部分特化:

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{

private:
    T1 _d1;
    int _d2;
};

也可以是对参数类型的进一步限制:

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{

private:
    T1 _d1;
    T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{

private:
    const T1 & _d1;
    const T2 & _d2;
};

        对于上述两种偏特化类型,可能会有一个误区:

        我们直接避开这个误区不谈,直接将正确的思想;其实,上述两个类名后面的特化参数只是一个标记,编译器会根据这个标记来匹配特化的类,而不会由于特化参数的写法而改变原本传入的参数类型:

比如:

template<class T1,class T2>
class Data
{
public:
	Data()
	{
		cout << "原模板" << endl;
	}
	
private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*, T2*>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
//引用偏特化
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1 = 0, const T2& d2 = 0)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}
	
private:
	const T1& _d1;
	const T2& _d2;
};
int main()
{
	Data<int, int> d1;
	Data<int*, double*> d2;
	Data<int&, int&> d3;
	
	return 0;
}

        指针偏特化,传入<int*,double*>,参数T1,T2分别就是int*,double*,不会因为类名后面的<T1*, T2*>而将T1,T2改变为int,double。

        引用偏特化也是类似的。

三、模板的分离编译

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

        当我们在使用模板时,如果使用分离编译模式,会导致链接错误。

由于模板在编译时不会实例化出对应的类或者函数,自然没有相应的地址。所以在链接时,编译器在找这个类或者函数的地址时,会找不到,所以报错。

解决方法:

       1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
        2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

模板总结
【优点】
        1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
        2. 增强了代码的灵活性
【缺陷】
        1. 模板会导致代码膨胀问题,也会导致编译时间变长
        2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
 


完~

未经作者同意禁止转载 

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

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

相关文章

Python数据分析-天气类型预测分析

一、研究背景 近年来&#xff0c;随着全球气候变化的加剧&#xff0c;天气预报和气象预测变得越来越重要。准确的天气预测不仅能够帮助人们做好日常生活的安排&#xff0c;还能在农业生产、防灾减灾等方面起到关键作用。随着大数据技术和机器学习算法的快速发展&#xff0c;利…

SAP PS学习笔记02 - 网络,活动,PS文本,PS文书(凭证),里程碑

上一章讲了PS 的概要&#xff0c;以及创建Project&#xff0c;创建WBS。 SAP PS学习笔记01 - PS概述&#xff0c;创建Project和WBS-CSDN博客 本章继续讲PS的后续内容。包括下面的概念和基本操作&#xff0c;以及一些Customize&#xff1a; - 网络&#xff08;Network&#xf…

7.10号小项目部分说明

总体说明 糖锅小助手 我这次主要对上次糖锅小助手界面添加了一个侧边栏&#xff08;侧边输入框放置了三个按钮&#xff0c;可以跳转到其他ai聊天界面&#xff0c;还可以退出聊天界面回到登录界面&#xff09;和一个日期输入框&#xff08;日期输入框获取时间&#xff0c;根据时…

通过Umijs从0到1搭建一个React项目

有一阵时间没写react了&#xff0c;今天通过umi搭建一个demo项目复习一下react&#xff1b;umi是一个可扩展的企业级前端应用框架&#xff0c;在react市场中还是比较火的一个框架。 Umi官方文档&#xff1a;Umi 介绍 (umijs.org) 一、构建项目。 1、安装包管理工具。 官方推…

js原型和类---prototype,__proto__,new,class

原型和原型链 在js中&#xff0c;所有的变量都有原型&#xff0c;原型也可以有原型&#xff0c;原型最终都指向Object 什么是原型 在js中&#xff0c;一个变量被创建出来&#xff0c;它就会被绑定一个原型&#xff1b;比如说&#xff0c;任何一个变量都可以使用console.log打…

支持向量机 (support vector machine,SVM)

支持向量机 &#xff08;support vector machine&#xff0c;SVM&#xff09; flyfish 支持向量机是一种用于分类和回归的机器学习模型。在分类任务中&#xff0c;SVM试图找到一个最佳的分隔超平面&#xff0c;使得不同类别的数据点在空间中被尽可能宽的间隔分开。 超平面方…

数据采集:如何使用八爪鱼采集BOSS直聘职位数据

大家好&#xff0c;我是水哥&#xff01; 今天给大家分享的是数据采集实战&#xff1a;使用「八爪鱼」第三方工具来采集 BOSS 直聘上的数据分析职位数据。 接下来&#xff0c;我们详细看一看。 不重复造轮子 在工作中&#xff0c;我们一定要形成一个认知&#xff0c;能用第…

初次用bable遍历vue项目下的中文

利用 babel 找到 AST 中的中文 // vite-plugin-babel-transform.js const parser require(babel/parser) const traverse require(babel/traverse).default // const types require(babel/types) // const generate require(babel/generator).default const fs require(f…

.Net C#执行JavaScript脚本

文章目录 前言一、安装二、执行 JavaScript 脚本三、与脚本交互四、JS 调用 C# 方法五、多线程使用总结 前言 ClearScript 是一个 .NET 平台下的开源库&#xff0c;用于在 C# 和其他 .NET 语言中执行脚本代码。它提供了一种方便和安全的方法来将脚本与应用程序集成&#xff0c;…

使用Go编写的持续下行测速脚本,快速消耗流量且不伤硬盘

介绍 使用go语言编写的持续下行测速脚本,可用于任意平台使用,通过指定URL清单文本文件自动遍历测速,支持多线程,支持多平台 特性 轻量级,无依赖采用内存进行缓存数据,不占用磁盘(如果内存较小请使用gcd项目)&#xff0c;最大程度减少磁盘IO,保护硬盘寿命可自定义最大下载文件…

Hum Brain Mapp:青春期早期的灰质流失可以用白质生长来解释吗?

摘要 关于大脑发育的一个基本谜题是&#xff0c;为什么儿童进入青春期时&#xff0c;灰质(GM)体积明显减少&#xff0c;而白质(WM)体积明显增加。一种流行的理论认为&#xff0c;由于被修剪的突触太小而不足以影响脑灰质体积&#xff0c;因此大脑总体积保持稳定&#xff0c;而…

Puppeteer 是什么以及如何在网络抓取中使用它 | 2024 完整指南

网页抓取已经成为任何处理网页数据提取的人都必须掌握的一项重要技能。无论你是开发者、数据科学家还是希望从网站收集信息的爱好者&#xff0c;Puppeteer都是你可以使用的最强大工具之一。本完整指南将深入探讨什么是Puppeteer以及如何有效地在网页抓取中使用它。 Puppeteer简…

wifi模组Ai-M62-32S的IO映射和UDP透传测试

wifi模组Ai-M62-32S的IO映射和UDP透传测试 基本IO 映射配网示例开启UDP透传示例复位AT查询wifi是否在线配置DHCP静态IP连接wifi连接UDP开启透传 基本IO 映射 对于wifi模组Ai-62-32S来说其模组 IO 引脚&#xff08;从模组左上角逆时针排序&#xff0c;引脚序号从 1 开始&#x…

LeetCode 2 两数相加

题目 给你两个 非空 的链表&#xff0c;表示两个非负的整数 它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以…

小程序需要进行软件测试吗?小程序测试有哪些测试内容?

在如今移动互联网快速发展的时代&#xff0c;小程序已成为人们生活中不可或缺的一部分。然而&#xff0c;面对日益增长的小程序数量和用户需求&#xff0c;小程序的稳定性和质量问题日益突显。因此&#xff0c;对小程序进行软件测试显得尤为重要。 近期的一项调查显示&#xf…

鸿蒙语言基础类库:【@ohos.util (util工具函数)】

util工具函数 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 该模块…

3d已经做好的模型怎么改单位?---模大狮模型网

在展览3D模型设计行业中&#xff0c;经常会遇到需要将已完成的模型进行单位转换的需求。这可能涉及从一种度量单位转换为另一种&#xff0c;例如从英制单位转换为公制单位&#xff0c;或者根据特定的展览场地要求进行尺寸调整。本文将探讨如何有效地修改已完成的3D模型的单位&a…

js实现图片放大镜功能,简单明了

写购物项目的时候&#xff0c;需要放大图片&#xff0c;这里用js写了一个方法&#xff0c;鼠标悬浮的时候放大当前图片 这个是class写法 <!--* Descripttion: * Author: 苍狼一啸八荒惊* LastEditTime: 2024-07-10 09:41:34* LastEditors: 夜空苍狼啸 --><!DOCTYPE …

【CSS in Depth 2 精译】2.4 视口的相对单位

当前内容所在位置 第一章 层叠、优先级与继承第二章 相对单位 2.1 相对单位的威力2.2 em 与 rem2.3 告别像素思维2.4 视口的相对单位 ✔️2.5 无单位的数值与行高2.6 自定义属性2.7 本章小结 2.4 视口的相对单位 前面介绍过的 em 和 rem 是相对于 font-size 定义的&#xff0…

【Pikachu靶场】安装部署通关详解超详细!!!(持续更新)

安装部署 Pikachu靶场&#xff0c;是一个带有漏洞的Web应用系统&#xff0c;在这里包含了常见的web安全漏洞。使用世界上最好的语言PHP进行开发-_-&#xff0c;数据库使用的是mysql&#xff0c;因此运行Pikachu你需要提前安装好"PHPMYSQL中间件&#xff08;如apache,ngin…