初识C++ · 模板进阶

目录

前言:

1 非类型模板参数

2 按需实例化

3 模板特化

4 模板的分离编译


前言:

前面模板我们会了简单的使用,这里带来模板的进阶,当然,也就那么几个知识点,并不太难。


1 非类型模板参数

先来看这样一段代码:

#define N 100
template<class T>
class Arr
{
public:private:T _arr[N];
};

如果我们想要创建一个整型数组,可以使用这个类来创建,但是我们面临一个问题就是该数组的大小是固定的,我们想要简单控制这个数组的大小,可以使用宏,但是还是不够简便,因为宏不方便调试不说,实际上也是指定了大小,那么我们想要使用一个类,来创建不同大小的数组该怎么办?
这里使用到的就是非类型模板参数,如下:

template<class T,size_t N = 100>
class Arr
{
public:private:T _arr[N];
};
int main()
{Arr<int,10> a1;Arr<int,1000> a2;Arr<int> a3;return 0;
}

这里就得到了我们想要的不同大小的数组,那么,来个问题,编译器一共实现了几个类?

答:编译器这里一共实现了3个类,编译器根据模板参数的不同,就实现了不同的类。这里的非类型模板参数,我们可以理解为常量,如这里的N,但是在C++11只支持整型,连浮点数都不可以,只支持整型,比如int size_t char一类的,在C++ 20之后才可以支持其他类型。

这里涉及到了数组,那么引入一个小的知识点就是对于越界来说,array 数组 vector有着不同的反应:

int main()
{int arr[10];arr[10];arr[15] = 1;return 0;
}

对于普通数组来说,普通的越界只读来说,比如arr[10]是检查不来错误的,越界写来说,也是很多抽查不出来的,比如这段代码在vs2019上就不会报错。

那么对于array来说:

int main()
{std::array<int,10> array;array[10];return 0;
}

任何读写越界都会报错,但是呢,这是c++委员会后面加的,但是挺鸡肋的,因为我们有vector。

int main()
{std::vector<int> v;v.reserve(1000);return 0;
}

vector对越界的读写都会报错,这是一方面,其次是array是静态的数组,也就是大小定了,并且,它所属的空间是栈,栈的空间相对堆来说就会小很多,所以面临开大空间的时候,array就不吃香了,vector没事,因为可以动态开辟。


2 按需实例化

先看这样一段代码:

template<class T,size_t N = 100>
class Arr
{
public:T& operator[](size_t i){size(1);return _arr[i];}size_t size(){return _size;}bool empty(){return _size == 0;}
private:T _arr[N];size_t _size = 0;
};
int main()
{Arr<int> a1;a1.empty();return 0;
}

从语法层面来说,size()函数没有参数,那么我们传参数的话就会导致报错对吧?可是,实际上:

代码是没有报错,也就是说size()传参数是对的吗?

不,这是因为按需实例化

在主函数里面,我们实例化了a1,并且调用了empty函数,但是我们没有调用operator[]函数,那么编译器就不会实例化operator函数,因为我们没有调用,既然没有实例化size函数,那么传什么都不会报错,这就是按需实例化

再细节一点来说,编译器会根据模板实例化->实例化一个半成品模板->再实例化为一个具体的类或者函数->最后才是语法编译,所以没有语法报错。


3 模板特化

特化我们可以理解为特殊化处理,比如我们在栈和队列的时候实现的日期类的比较,就可以不用仿函数来实现比较,可以用特化来处理。

日期类:

class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:size_t _year;	size_t _month;size_t _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
template<class T>
bool less(const T& a,const T& b)
{return a < b;
}
int main()
{Date d1(2024, 5, 20);Date d2(2024, 5, 21);cout << (d1 < d2) << endl;return 0;
}

比如,使用函数模板,对于重载了比较符号的比较是没问题的,如果使用指针就会报错,因为默认是按照指针比较的,这里就可以使用特化:

int main()
{Date* p1 = new Date(2020, 1, 1);Date* p2 = new Date(2020, 1, 2);cout << (p1 < p2) << endl;return 0;
}

这段代码是有问题的是不用多说的,下面是解决方案:

template<class T>
bool Less(T left, T right)
{cout << "bool Less(T left, T right)" << endl;return left < right;
}template<>
bool Less<Date*>(Date* p1,Date* p2)
{return *p1 < *p2;
}int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 8);Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;return 0;
}

这里的语法就比较怪了,现在使用的是函数模板,有点像函数重载的感觉,当然,我们也可以直接重载一个出来:

bool Less(Date* left, Date* right)
{return *left < *right;
}

那么,调用是怎么调用的呢?

这里就和半成品,成品是一个道理,重载的函数就相当于成品,特化的函数快成品了,模板就是个半成品,调用的顺序也就说的通了。

以上是函数模板的特化,看起来就像是函数重载,接着是类中的模板特化:


//普通
template<class T1,class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }};//全特化
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
};//偏特化
template<class T1>
class Data<T1, char>
{
public:Data() { cout << "Data<T1, char>" << endl; }
};

使用方式和函数模板其实是差不多的,在特化这里分为全特化和偏特化,特化也不是什么特别的东西,其实就是对参数的进一步限制而已。


4 模板的分离编译

使用模板的时候,定义和声明最好放在一个文件,.h和.c文件分离会报错的,这里简单举个例子:

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

在调用这个函数的时候就会报错,只需要想清楚一个简单的问题就可以了,两个T是不是一样的T,能否用.h文件里面的T去平替.cpp里面的T,当然是不可以的,所以这里,就会报错,报的是链接错误,.h文件编译成功后,.cpp里面的文件是没有编译好的,因为T不知道是什么类型,调用的时候就会报错。

在链接阶段,编译器按照修饰好之后的函数名在符号表里面寻找函数,不知道类型就没有生成修饰好的函数名,那么就会报如下类似的错:


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式移植jpeglib--Linux交叉编译ARM平台

一 、交叉编译jpeg库 1.下载源码tar.gz 2. 源码目录下执行 jpeglib配置文件 ./configure CCarm-none-linux-gnueabihf-gcc LDarm-none-linux-gnueabihf-ld --prefix/work/jpeg_arm_lib --exec-prefix/work/jpeg_arm_lib --enable-shared --enable-static --hostarm-none-linu…

经典文献阅读之--MGS-SLAM(单目稀疏跟踪和高斯映射与深度平滑正则化)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

CiteScore 2023发布,AI Open斩获45分,位列全球计算机领域前1%

与影响因子&#xff08;IF&#xff09;一样&#xff0c;引用分数&#xff08;CiteScore&#xff09;同样是衡量学术期刊影响力的重要指标之一&#xff0c;且大有赶超前者的势头。 6 月 6 日&#xff0c;CiteScore 2023 正式发布&#xff0c;人工智能领域可自由访问的期刊平台 …

Java 8 中的 Stream API,用于处理集合数据

Java 8 引入了 Stream API&#xff0c;使得处理集合数据变得更加简洁和高效。Stream API 允许开发者以声明式编程风格操作数据集合&#xff0c;而不是使用传统的迭代和条件语句。 一、基本概念 1.1 什么是 Stream Stream 是 Java 8 中的一个新抽象&#xff0c;它允许对集合数…

CSRF 令牌的生成过程和检查过程

在 Django 中,CSRF 令牌的生成和检查过程是通过 Django 的 CSRF 中间件 (CsrfViewMiddleware) 和模板标签 ({% csrf_token %}) 自动处理的。以下是详细的生成和检查过程: CSRF 令牌的生成过程 用户访问页面: 当用户第一次访问页面时,Django 会为用户创建一个会话。如果用户…

人工智能、深度学习和机器学习的前世今生

人工智能、深度学习和机器学习的前世今生 引言 在当今科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;和深度学习&#xff08;DL&#xff09;已经成为引领第四次工业革命的重要力量。这些技术不仅在学术界和工业界掀…

C++ 数据共享与保护学习记录【代码】

一.项目一 1.头文件.h //A.h #pragma once //防止头文件被重复包含&#xff08;重复包含会被重复编译&#xff0c;也就是该类会被重复定义&#xff09; #ifndef HEAD_H //等价于&#xff08; #if !defined(HEAD_H) ) //defined是一个预处理操作符&#xff0c;相当于一个表达式…

整理好了!2024年最常见 20 道分布式、微服务面试题(二)

上一篇地址&#xff1a;整理好了&#xff01;2024年最常见 20 道分布式、微服务面试题&#xff08;一&#xff09;-CSDN博客 三、请解释CAP定理及其含义。 CAP定理是分布式计算领域的一个基本概念&#xff0c;由计算机科学家Eric Brewer在2000年提出&#xff0c;并由科学家Se…

力扣76.最小覆盖子串

力扣76.最小覆盖子串 用哈希表记录每个字母出现次数 枚举右端点 判断是否能全覆盖如果可以 并且更短 就更新 j 缩小区间再判断 class Solution {bool is_covered(int cnt_s[], int cnt_t[]) {for (int i A; i < Z; i) {if (cnt_s[i] < cnt_t[i]) {return false;}}fo…

上网操作的必要条件

一、 网卡 1、 为什么需要网卡 计算机为了实现网络通信&#xff0c;必须都要有网卡这个东西&#xff0c;网卡是计算机众多外部设备之一&#xff08;其它还有硬盘、键盘等&#xff09;&#xff0c;计算机将数据发给网卡&#xff0c;网卡负责将数据往外发送&#xff0c;通过IP定…

技术团队的冲突管理: 谷歌亚里士多德项目的启示

有效的冲突管理对于技术团队保持高效和创新的工作环境至关重要。谷歌的亚里士多德项目是一项内部研究&#xff0c;旨在了解成功团队的因素&#xff0c;强调了心理安全和开放沟通在促进团队成员之间的合作和解决分歧方面的重要性。本文将探讨受谷歌的亚里士多德项目和其他数据点…

工厂生产计划难以执行的真正原因及对策

在制造业中&#xff0c;生产计划的执行对于企业的运营至关重要。然而&#xff0c;许多工厂在生产计划执行过程中面临着诸多挑战&#xff0c;尤其是物料齐套率低的问题。本文将探讨工厂生产计划难以执行的真正原因&#xff0c;并提出相应的解决对策。 一、生产计划难以执行的真…

mysql optimizer_switch : 查询优化器优化策略深入解析

码到三十五 &#xff1a; 个人主页 在 MySQL 数据库中&#xff0c;查询优化器是一个至关重要的组件&#xff0c;它负责确定执行 SQL 查询的最有效方法。为了提供DBA和开发者更多的灵活性和控制权&#xff0c;MySQL 引入了 optimizer_switch 系统变量。这个强大的工具允许用户开…

nginx配置WebSocket参数wss连接

目录 一、原文连接 二、 配置参数 三、实践 四、重启nginx 五、连接websocket 一、原文连接 nginx配置websocket支持wss-腾讯云开发者社区-腾讯云 二、 配置参数 map $http_upgrade $connection_upgrade { default upgrade; close; } upstream websocket { se…

聚类的外部指标(Purity, ARI, NMI, ACC) 和内部指标(NCC,Entropy,Compactness,Silhouette Index)

在聚类分析中,外部指标和内部指标用于评估聚类结果的质量。外部指标需要知道真实的类别标签,而内部指标则仅基于聚类结果本身进行评估。 外部指标 Purity (纯度): 计算聚类结果中每个簇中最多数目的样本所属的类别,并计算所有簇的该类别样本数之和占所有样本数的比例。 Pyt…

【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)

目录 一、 进程1.1 PID(进程标识符)1.2 内存指针1.3 文件描述符表1.4 状态1.5 优先级1.6 记账信息1.7 上下文 二、线程三、总结&#xff1a;进程和线程之间的区别&#xff08;非常非常非常重要&#xff0c;面试必考题&#xff09; 一、 进程 简单来介绍一下什么是进程&#xf…

win 系统 cmd 命令从私库上传,下载jar包

1. 确保maven环境变量或者maven安装无误&#xff1b; 2.私库下载 命令 mvn dependency:get -DgroupId<your_group_id> -DartifactId<your_artifact_id> -Dversion<your_version> -Dpackagingjar -Dfile<path_to_your_jar_file> -Durl<your_privat…

写入文件内容

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在实例01中&#xff0c;虽然创建并打开一个文件&#xff0c;但是该文件中并没有任何内容&#xff0c;它的大小是0KB。Python的文件对象提供了write()…

【电路笔记】-分贝

分贝 分贝是以 10 为底的对数比,用于表示电路中功率、电压或电流的增加或减少。 1、概述 一般来说,分贝是响度的度量。 在设计或使用放大器和滤波器电路时,计算中使用的一些数字可能非常大或非常小。 例如,如果我们将两个放大器级级联在一起,功率或电压增益分别为 20 和…

os和os.path模块

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 目录也称文件夹&#xff0c;用于分层保存文件。通过目录可以分门别类地存放文件。我们也可以通过目录快速找到想要的文件。在Python中&#xff0c;并…