C++ - 模版进阶 - array

 简介

 之前对模版的进行了初步了解和使用,可查看博客:C++ 初始模板_c++模板初始化_chihiro1122的博客-CSDN博客

 其实模版除了是一类算法,或者自定义类型的 套用,还有其他功能,和其他的更高阶的使用方法。

之前在实现 各种 C++ 当中的 STL 的容器的时候用就多次用到了类,比如:套用正向迭代器模版实现的 反向迭代器的适配器;还有 queue 和 stack 容器适配器;还有仿函数的实现,都是使用了 模版来实现的:

C++ - 优先级队列(priority_queue)的介绍和模拟实现 - 反向迭代器的适配器实现_chihiro1122的博客-CSDN博客

C++ - stack 和 queue 模拟实现 -认识 deque 容器 容器适配器_chihiro1122的博客-CSDN博客  


模版进阶

typename 前缀修饰问题的解决 

在简介当中都是类模版,我们在这里回顾一下函数模版,函数模版不想类模版一样需要 显示实例化,直接传入参数就可以按照模版来自动进行替换:

//template<typename Container>
template<class Container>
void Print(const Container& v)
{typename Container::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it; // 因为只重载了 前置的++}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto ch : v){cout << ch << " ";}cout << endl;Print(v);return 0;
}

上述 Print()函数不仅仅可以打印 vector 的多种参数类型,其他容器也可以打印。

但是需要注意的是,上述在取出迭代器的时候,使用的方式有些奇怪,如下所示:
 

typename Container::const_iterator it = v.begin();

如果前面不加 typename  前缀的话,就会报错

 我们在定义模版参数的时候,参数之前的前缀可以是  class  可以是 typename,如下所示:

template<typename Container>
template<class Container>

但是,不管是哪一种定义方式,如果是 const 迭代器的话,在迭代器类型之前,必须用 typename前缀修饰

原因很简单:

上述如果不加 typename 的代码: 

Container::const_iterator it = v.begin();

 程序运行,首先进行编译实例化。

如果要寻找 const_iterator ,编译器就会去 Container 当中去寻找,但是这里寻找就会有问题,如果我们不使用函数模版,那么就不会出现问题,因为这里的 Container 直接写成 vector<int> ,直接实例化了,编译器直接在这个实例化的对象当中寻找就行了。

但是,如果使用模版,这里就有三种情况,一种是在 Container 当中寻找 const_iterator 这个动态成员变量;另一种是寻找 const_iterator 这个内部类(对象);那么到底这个 const_iterator 是一个成员变量,还是一个对象,还是一个类型,编译器搞不明白。而此时的 Container 不知道是什么。

如果  Container::const_iterator 这个表示的是一个类型,那么此处的语法是正确的;如果表示的是一个 对象,就不符合语法了。

 所以,在此处,加一个 typename 表示此处的 Container::const_iterator 就是一个 类型。等实例化再去实例化的对象当中去寻找。

 其实这里还有更好的方式来解决,用一个 auto就可以自动推导类型 ,就不用再使用之前一大长串 的类型名了。这里我们就可以体会到了 auto 的强大。

因为 auto 一定是类型,所以编译器就不会再去往 对象那一方面去想了。


 但是不是所有使用 typename 的地方都可以用 auto,有些地方还是需要用到 typename 的,比如在 优先级对象当中就是用了 typename。如下图所示:

 

 问题:有些编译器会 按需实例化,比如:当类当中的 某一个成员函数当中有编译错误,如果这个函数没有调用,那么编译器会略过这个错误;但是 按需实例化也是看编译器的,不同的编译器实例化程度不同。

 非类型模版参数

 模版当中不仅仅有需要类型模版参数的情况,可能还需要传入一些数值,比如下面这个例子,定义一个静态栈:

#define N 10template<class T>
class stack
{
public:private:T _a[N];size_t _size;
};int main()
{stack<int> st1;  // 10 个stack<int> st2;  // 100 个
}

此时我想定义两个静态栈,但是因为是静态的,宏 N 的大小不能改变 ,那么上述代码我们只能满足 一种情况,这就和 C 当中的 宏 一样的。

所以,C++ 当中的模版参数,还可以传入值:

 

template<class T, size_t N>
class stack
{
public:private:T _a[N];size_t _size;
};int main()
{stack<int, 10> st1;  // 10 个stack<int, 100> st2;  // 100 个
}
template<class T, size_t N>

上述模版直接用 类型来当做是模版参数的类型,这个N 就是一个非类型的模版参数,解决了上述静态栈的问题。

关于非类型模版参数,需要注意的点:

  •  非类型的模板参数相等于是一个常量,他不像函数参数一样,可以进行修改;非类型的模版参数是不能进行修改的。(也就是说;非类型的模板参数必须在编译期就能确认结果。)
  • 非类型模板参数的类型必须是整形

模版的特化 

 函数模版的特化

 模版可以实现无关类型的代码,但是一些特殊类型的代码可能会出现问题

template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确int d1 = 1;int d2 = 2;cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

 最后一组,传入的是指针类型,那么 模版类型 T 就是指针,指针的比较大小是比较地址的高低,但是肯定有上述的情况下,我们想要传入指针但是不想按照指针去比较,按照传入指针 解引用的值来进行比较,但是我们不可能直接修改模版函数,如果改成解引用的话,之前我们想要实现的功能就不能实现了。

所以,上述的例子就要用到模版的特化


 上述的less ()函数,特化之后如下所示:

// Less 函数的模版
template<class T>
bool Less(T left, T right)
{return left < right;
}// Less 模版函数的特化
template<>
bool Less<int*>(int* left, int* right)
{return *left < *right;
}

 如上下面一个 Less<int*> 就是Less 模版函数的函数特化。

注意

  • 模版函数的特化一定要有模版为前提,然后再去对这个函数模版进行 模版特化。
  • 函数的特化不是函数的重载。

 上述的特化还可以写成下述函数重载的样式(和函数模版实例化出的函数进行 函数重载)

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

 上述的 模版特化 函数重载,两种方式虽然都可以达到我们想要目的但是,上述两种情况都只是实现了 int 类型的指针问题,不能解决多种指针的问题

 所以,聪明的你一下发现了,那么我们在实现一个模版不就行了?是这样的,看如下代码:

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

 当,传入的T 是一个指针的时候,虽然 第一种形式的模版 和 上述这个模版都可以匹配,但是,上面这个模版更加的符合,所以,如果传入的是 某类型的指针的话,就会调用上面这个模版

而且,如果你实现上述两个模版的同时,在想上述一样实现了某一个类型的重载函数,或者像第一次那样的 实现 模版函数的 特化那么这两个都是现成的编译器优先调用现成的函数。 

 类模板的特化

 类模板的特化和函数模版的特化是类似的 ,函数模版的特化是相当于是重新写了一个函数,类模板的特化也相当于是多写了一个类:

// 类模板
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};// 类模板的全特化
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}

 输出:

Data<T1, T2>

Data<int, char>

 当传入的模版参数是 int  和 char 的时候,调用的就是下面定义的 特化的 模版类,然后进行特殊处理,在这个当中的特殊处理,不影响 之前定义的类模板。

 运用场景,在使用仿函数的时候就可以使用,当我们想对仿函数的 类模板 当中某一个类型的实例化进行特殊操作的时候,就可以使用上述类模板的特化,来对某一种类型进行特化。

像之前在 介绍 优先级队列,当中对 less 这个仿函数的介绍,当传入的是日期类指针(Date*)的结果就不对,这时候,就可以使用 特化,给 less 类模板 特化出一个 特殊处理的类。

 全特化和偏特化

 向上述的 :

template<>
class Data<int, char>和 template<>
bool Less<int*>(int* left, int* right)

 都属于是全特化。全特化就是把所以的模版参数都特化

偏特化,也叫做半特化,就是没有把全部的模版参数都特化。

 如下代码所示:

// 类模板的全特化
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};// 类模板的偏特化(特化部分参数)
template<class T1>
class Data<T1, char>
{
public:Data() { cout << "Data<T1, char>" << endl; }
private:T1_d1;char _d2;
};

 向上述的偏特化,是对某一些模版参数进行特化;其实偏特化有两种方式:

  • 一种就是向上述一样的 特化部分参数。
  • 另一种就是对某一些模版参数类型的进一步限制。

 

// 类模板的偏特化(对一些参数进行一些限制)
template<class T1, class T2>
class Data<T1*, T2* >
{
public:Data() { cout << "Data<T1, char>" << endl; }
private:
};

 向上述不对某些参数进行特化,只是判断传入的两个模版参数是不是指针,是就调用这个 特化的类

 特化在库当中也是有运用的,库当中对为了在某一模版当中找到其中调用的模版的模版参数,即用了萃取,而萃取本质上其实就是特化实现的,只不过库当中的萃取实现很麻烦。

array 静态数组

 array数组和 c语言  当中的 数组,在功能上和 效率上没有任何区别,就连不能用变量来初始化个数这个特性都是一样的。而且,如果使用 array 的无参数的构造函数,里面的元素也不会进行初始化,这里和 C 当中的 数组也是一样的;

array 相比于 C当中的数组,唯一的好处就是,array 可以检查越界,而且检查非常的快,他是用assert ()断言来实现的。如果是 C语言的 数组,只是读的访问,越界是检查不出来的,写可能会检查出来;而 array 容器无论是 写还是读,都会检查出来。利用的就是 operator[] 当中的 对 下标越界的检查。

除此之外,array 容器 ,相比于数组是没有任何优点的。

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

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

相关文章

集成学习算法是什么?如何理解集成学习?

什么是集成学习&#xff1f; 集成学习通过建立几个模型来解决单一预测问题。它的工作原理是生成多个分类器/模型&#xff0c;各自独立地学习和作出预测。这些预测最后结合成组合预测&#xff0c;因此优于任何一个单分类的做出预测。 机器学习的两个核心任务 任务一&#xff1…

嵌入式:C高级 Day3

一、整理思维导图 二、判断家目录下&#xff0c;普通文件的个数和目录文件的个数 三、输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可…

ARM 常见汇编指令学习 9 - 缓存管理指令 DC 与 IC

文章目录 ARM64 DC 与 IC 指令 上篇文章&#xff1a;ARM 常见汇编指令学习 8 - dsb sy 指令及 dsb 参数介绍 ARM64 DC 与 IC 指令 AArch64指令集中有两条关于缓存维护&#xff08;cache maintenance&#xff09;的指令&#xff0c;分别是IC和DC。 IC 是用于指令缓存操作&…

4.msf辅助模块

目录 1 在虚拟机中设置与外部相同的网段 2 当前内网中的可用IP arp_sweep 3 搜索指定IP的TCP端口信息 portscan/tcp 4 扫描http服务的路由 http/dir_scanner 5 SSH密码爆破 ssh/ssh_login 1 在虚拟机中设置与外部相同的网段 我真实机的地址的网段是192.168.0 我虚拟…

前端:地图篇(一)

1、前言 在很多的出行程序中&#xff0c;都会使用到地图这一个功能&#xff0c;在实际的开发中我们也不会去开发一个自己的地图模型。如果自己开发一个地图模型&#xff0c;那么需要投入的成本、人力都是非常巨大的。所以我们很多网站和APP中使用的都是第三方的接口和JS&#…

MPLS(下)

LDP --- 标签分发协议 --- 主要应用在MPLS的控制层面 MPLS控制层面需要完成的工作主要就是分配标签和传递标签。分配标签的前提是本地路由表中得先存在标签&#xff0c;传递标签的前提也是得先具备路由基础。所以&#xff0c;LDP想要正常工作&#xff0c;则需要IGP作为基础。 …

Ubuntu 虚拟机和主机无法互相复制文字和文件

1.在虚拟机列表中&#xff0c;右键查看是否有安装VMware Tools&#xff0c;如果没有安装点击安装&#xff0c;如果已经安装了&#xff0c;上面显示重现安装VMware Tools&#xff0c;并且为灰色&#xff0c;如图&#xff1a; 2.如果没有安装点击安装&#xff0c;如果已经安装&am…

深度学习论文分享(六)Simple Baselines for Image Restoration

深度学习论文分享&#xff08;六&#xff09;Simple Baselines for Image Restoration 前言Abstract1 Introduction2 Related Works2.1 Image Restoration2.2 Gated Linear Units 3 Build A Simple Baseline3.1 Architecture3.2 A Plain Block3.3 Normalization3.4 Activation3…

wordpress发表文章时报错: rest_cannot_create,抱歉,您不能为此用户创建文章(已解决)

使用wordpress 的rest api发布文章&#xff0c;首先使用wp-json/jwt-auth/v1/token接口获取token&#xff0c;然后再使用/wp-json/wp/v2/posts 接口发表文章&#xff0c;但是使用axios请求时&#xff0c;却报错&#xff1a; 但是&#xff0c;我在postman上却是可以的&#xff0…

Java 监听Mysql binlog

使用 mysql-binlog-connector-java 1. mysql-binlog-connector-java 官网 2. Java代码中&#xff0c;如何监控Mysql的binlog&#xff1f; 前置条件 1. mysql服务器表结构 CREATE TABLE student (id int NOT NULL AUTO_INCREMENT,name varchar(255) CHARACTER SET utf8mb4 C…

IDEA的实用快捷键大全

目录 1.常规快捷键 1.1通用类 1.2注释类 1.3操作类 1.4展开与关闭 2.智能补全类快捷键 3.程序结构类快捷键 4.统一操作快捷键 1.常规快捷键 1.1通用类 像 Ctrl C 复制&#xff0c; Ctrl V 粘贴&#xff0c; Ctrl S保存文件&#xff0c; Ctrl X剪切&#xff0c;这种…

idea 2023 新版ui中git的相关操作

前两个月换了新电脑&#xff0c;下了最新版的idea发现可以切换一套新的ui了 切换新ui肯定不太习惯&#xff0c;很多操作都得重新摸索一下 在这里记录几个git相关的操作 忽略我下面截图中当前项目是js的后端项目…… 切换ui 首先说一下怎么切换新旧版ui&#xff0c;我这里就…

LeetCode每日一题Day5——21. 合并两个有序链表

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;算法修炼之练气篇&#xff08;C\C版&#xff09; &#x1f353;专栏&#xff1a;算法修炼之筑基篇&#xff08;C\C版&#xff09; &#x1f433;专栏&#xff1a;算法修炼之练气篇&#xff08;Python版&#xff09; …

3ds Max如何进行合成的反射光泽通道渲染

推荐&#xff1a; NSDT场景编辑器 助你快速搭建可二次开发的3D应用场景 1. 准备场景 步骤 1 打开 3ds Max。smart_phone.max打开已 随教程提供。 打开 3ds Max 步骤 2 按 M 打开材质编辑器。选择空材料 槽。单击漫射通道。它将打开材质/贴图浏览器窗口。选择位图&#xff0…

ffmpeg安装

简介 FFmpeg是一个开源的音视频处理库&#xff0c;它提供了一系列的工具和API&#xff0c;可以用于处理音视频文件。你可以使用FFmpeg的命令行工具来执行各种音视频处理操作&#xff0c;比如转码、剪辑、合并等。FFmpeg的命令格式通常是&#xff1a;ffmpeg [全局选项] {[输入文…

Hum Brain Mapp:用于功能连接体指纹识别和认知状态解码的高精度机器学习技术

摘要 人脑是一个复杂的网络&#xff0c;由功能和解剖上相互连接的脑区组成。越来越多的研究表明&#xff0c;对脑网络的实证估计可能有助于发现疾病和认知状态的生物标志物。然而&#xff0c;实现这一目标的先决条件是脑网络还必须是个体的可靠标记。在这里&#xff0c;本研究…

性能优化-react路由懒加载和组件懒加载

背景 随着项目越来越大&#xff0c;打包后的包体积也越来越大&#xff0c;严重影响了首屏加载速度&#xff0c;需要对路由和组件做懒加载处理 主要用到了react中的lazy和Suspense。 废话不多说&#xff0c;直接上干货 路由懒加载 核心代码 import React, { lazy, Suspens…

GPS/北斗RTK差分定位系统的原理以及应用领域

导语&#xff1a;现代定位技术在国内外的发展与应用越来越广泛&#xff0c;其中GPS和北斗是两大被广泛使用的全球卫星定位系统。本文将介绍GPS/北斗RTK差分定位系统的原理以及其在各个领域的应用。 一、GPS/北斗RTK差分定位系统的原理 GPS/北斗RTK差分定位系统&#xff0c;即全…

【TypeScript】安装的坑!

TypeScript安装 安装TypeScript安装时候可能报错这样开头的数据&#xff08;无法枚举容器中的对象&#xff09;——原因&#xff1a;没权限先解决没权限的问题如果发现无法修改-高级-修改继续安装想使用tsc-发现&#xff0c;tsc不能用解决方法&#xff1a;配置环境变量最后的最…

便捷省心的手机直播影视工具,畅享轻松电视娱乐时光

便捷省心的手机直播影视工具&#xff0c;畅享轻松电视娱乐时光 在快节奏的现代生活中&#xff0c;我们常常渴望能够以简单、省心的方式消遣自己&#xff0c;享受高品质的电视娱乐。幸运的是&#xff0c;随着技术的进步&#xff0c;便捷省心的手机直播影视工具应运而生。这些工…