template——模板进阶(C++)

        在之前的文章中,介绍了模板初阶:Cpp_桀桀桀桀桀桀的博客-CSDN博客

        在本篇中将会对模板进一步的讲解。本篇中的主要内容为:非类型模板参数、函数模板的特化、类模板的特化(其中包含全特化和偏特化),最后讲解了模板的分离编译问题,以及出现链接错误的原因。

        目录如下:

目录

1. 非类型模板参数

1.1 模板的按需实例化

2. 模板的特化

2.1 特化的概念

2.2 函数模板特化

2.3 类模板特化

3. 模板的分离编译

3.1 模板的分离编译及其原理

1. 非类型模板参数

        模板参数分为:类类型形参非类型形参

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

        非类型形参:使用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

        如下所示的两份代码:

namespace MyArray1
{
// 使用宏定义
#define N 10// 定义一个模板类型的静态数组template<class T>class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}namespace MyArray2
{// 使用宏定义// 定义一个模板类型的静态数组template<class T, size_t N = 10>class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}

        如上所示,当我们想要写一个常量数组类,我们可以使用两种方法,一种是使用宏定义来确定数组的大小,另一种是使用非类型模板参数来充当数组的大小,我们在使用非类型模板参数的时候,也可以像使用函数参数一样,给一个缺省值。

        关于使用其他的非类型模板参数,我们的非类型模板参数只能使用整型做模板参数,对于其他非类型的模板参数,只有到 C++20 标准之后才可以使用。如下:

        关于类(函数)模板传参与函数传参的时刻:对于类模板传参而言,传参的时候是在编译阶段,因为模板属于一个半成品,在编译阶段需要使用传入的参数来生成确定的类(代码);而对于函数传参而言,函数传参是在运行时传参,将我们需要运行的参数传入函数中进行计算。

1.1 模板的按需实例化

        在模板实例化的时候,并不会检测语法错误,如下:

        我们在函数中调用 assert 函数和 size 函数都出现了语法错误,但是我们在生成解决方案的时候却可以通过,这是因为对于模板而言,这是一个半成品,在语法编译的的时候,因为并没有调用类函数,所以并不会检测语法。

        当我们实例化一个对象的时候,是否会检测出错误呢?如下:

        当我们实例化一个对象的时候,调用其中一个函数的时候,也还是不会检测出错误,这是因为按需实例化,不仅仅是类的按需实例化,类函数也是按需实例化,只有当调用的函数出现错误的时候,才会检测出来

        对于模板实例化的步骤为:根据模板实例化 --> 半成品模板 --> 实例化成具体的类/函数 --> 语法编译。

2. 模板的特化

2.1 特化的概念

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

        如上所示,当使用指针进行比较的时候,原本应该输出为0,但是却输出为1,这是因为在 Less 中并没有比较指针指向的内容重载函数,而是直接的比较指针的大小,所以输出的结果显示错误。

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

2.2 函数模板特化

        函数模板特化的步骤

        1. 必须要现有一个基础的函数模板;

        2. 关键字 template 后面接一个空的<>;

        3. 函数名后面跟一对尖括号,尖括号内指定需要特化的类型;

        4. 函数形参必须要和模板函数的基础参数类型完全相同,如果不同,编译器可能会薄一些奇怪的错误。

        如下:

template<class T>
bool Less(T x, T y) {return x < y;
}// 函数模板特化
template<>
bool Less<Date*>(Date* x, Date* y) {return *x < *y;
}// 函数重载
bool Less(Date* x, Date* y) {return *x < *y;
}

        如上所示的函数模板特化形式,就可以解决这样的问题,但是其实我们也可以写一个重载函数来解决这个问题。通常情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现功能,最好直接写一个重载函数,通常不建议写函数模板特化。

2.3 类模板特化

        关于类模板的特化其中包含全特化偏(半)特化

        其中全特化为将模板参数列表中所有的参数都确定化,如下:

template<class T1, class T2>
class A {
public:A() { cout << "A<T1, T2>" << endl; }
private:T1 _a1;T2 _a2;
};// 将参数列表中所有的参数都确定化
template<>
class A<int, char> {
public:A() { cout << "A<int, char>" << endl; }
private:int _a1;char _a2;
};void Test01() {A<int, int> aa1;A<int, char> aa2;
}

        关于偏特化,也分为两种类型,一种是将模板参数类表中的一部分参数特化,另一种是将参数进一步的限制,如下:

// 将模板参数表中的一部分参数特化
template<class T1>
class A<T1, int> {
public:A() { cout << "A<T1, int>" << endl; }
private:T1 _a1;int _a2;
};// 将模板参数表中的参数进一步的限制
template<class T1,class T2>
class A<T1*, T2*> {
public:A() { cout << "A<T1*, T2*>" << endl; }
private:T1 _pa1;T2 _pa2;
};

3. 模板的分离编译

        首先关于什么是分离编译,分离编译就是将一个类或者一个函数的声明与定义分别放在不同的文件之中,然后在生成目标文件的过程中,需要将所有目标文件(每一个源文件都会生成一个目标文件)链接起来,形成一个单一的可执行文件的过程叫做分离编译模式

3.1 模板的分离编译及其原理

        关于模板的编译与分离,就是将一个模板的声明放在一个 .h 文件中,然后将一个模板的定义放在另一个 .cpp 文件之中,如下:

        如上图所示,当我们将模板的声明与定义分隔开的时候,调用对应函数的时候就会导致报错(若我们不调用对应的函数的时候,就不会报错,这是因为对于模板而言,只是一个半成品,只有按需实例化的时候才会检测出错误),链接错误。

        出现这种错误的原因:

        当我们声明和定义分离的时候,我们将头文件包含在当前 main 函数所在的文件中,在预处理阶段,会将头文件在 main 函数所在的文件展开,然后在编译阶段,我们将我们的模板进行实例化,根据传入的模板参数进行实例化,有多少种就会实例化多少种代码,然后在链接的阶段,会去找我们调用的函数,但是在展开实例化的函数中,我们只有声明,然后就会去分离定义的文件中寻找,当在其他文件中找到定义的时候,这里的定义并没有在编译阶段跟着实例化,仍然还是带有模板参数的模板函数,所以链接的时候就找不对对应需要的实例化模板函数

        关于以上的解决方法,在分离定义中进行显示实例化,如下:

        如上所示,我们可以使用显示实例化来解决这个问题,但是这种问题的解决方法也仅仅只是治标不治本,当我们传入另一个模板实参的时候,还需要显示实例化一次,这并符合模板的特点,所以说,最好将模板的定义和声明放在同一个地方,或者分离定义在同一个文件中。

        另外,关于模板这一块的调用形式很奇怪,一不小心就会用错。所以对于模板的使用,建议将模板的声明和定义放入到同一个文件中(因为在同一个文件中时,既有声明也有定义,直接就实例化,编译的时候,有函数定义,就有函数地址,就不需要等到链接的时候在去寻找

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

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

相关文章

每日一题11:Pandas:数据重塑-透视

一、每日一题 解答&#xff1a; import pandas as pddef pivotTable(weather: pd.DataFrame) -> pd.DataFrame:df_pivot weather.pivot(indexmonth, columnscity, valuestemperature)return df_pivot 题源&#xff1a;力扣 二、总结 Pandas 是一个强大的 Python 数据分析…

三更草堂前后端分离个人博客项目的开发笔记

文章目录 项目实战-前后端分离博客系统1.课程介绍2.创建工程3.博客前台3.0 准备工作3.1 SpringBoot和MybatisPuls整合配置测试 3.1 热门文章列表3.1.0 文章表分析3.1.1 需求3.1.2 接口设计3.1.3 基础版本代码实现3.1.4 使用VO优化3.1.5 字面值处理 3.2 Bean拷贝工具类封装3.2 查…

深入理解C#中的IO操作:Path类的详解

文章目录 前言一、Path类的概述二、Path类的主要方法2.1 Path.GetFullPath(string relativePath)2.2 Path.GetDirectoryName(string path)2.3 Path.GetFileName(string path)2.4 Path.GetFileNameWithoutExtension(string path)2.5 Path.GetExtension(string path)2.6 Path.Com…

ubuntu下使用docker安装es和kibana以及ik分词器还有logstash

友情提醒&#xff1a;es和kibana的版本最好一致 0.准备工作 mkdir -p /home/elasticsearch/data/ mkdir -p /home/elasticsearch/config/ mkdir -p /home/elasticsearch/plugins/ chmod -R 777 /home/elasticsearch 编写配置文件 echo http.host: 0.0.0.0 http.cors.ena…

二叉树的前序、中序、后序遍历

二叉树的前序、中序、后序 1.二叉树的前序遍历 题目&#xff1a; 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff…

Typescript 哲学 - d.ts文件

The .d.ts syntax intentionally looks like ES Modules syntax. ES Modules was ratified by TC39 in 2015 as part of ES2015 (ES6), while it has been available via transpilers for a long time default export (esModuleInterop:true) / export 讲一个 d.ts export 的…

小程序的小组件

进度的组件 文字换行过滤 以及 排序 简单易懂 只为了记录工作 <template><div><ProgressBar :progress"progress" /><button click"increaseProgress">增加进度</button><view class"goods-name">12…

鸿蒙ArkUI开发:常用布局【主轴】

ArkUI中常用布局容器 线性布局&#xff08;Row/Column&#xff09; 线性布局的子元素在线性方向上&#xff08;水平方向和垂直方向&#xff09;依次排列线性布局容器包括[Row]和[Column]。Column容器内子元素按照垂直方向排列&#xff0c;Row容器内子元素按照水平方向排列开发…

一道dp错题

dis(a,b)就是两点之间的距离公式 那么这道题该怎么解呢,.先看数据范围x,y<1e4,so,18个点两点之间距离最大18*1e4*sqrt(2)<2^18,所以如果跳过的点大于18个点,那么显然一个区间内最多不会跳跃超过17个点 现在我们想知道前i个点跳跃几次在哪跳跃能够达到最小花费,不妨设跳…

whisper报错:hp, ht, pid, tid = _winapi.CreateProcess [WinError 2] 系统找不到指定的文件。

in _execute_child hp&#xff0c; ht&#xff0c; pid&#xff0c; tid _winapi.CreateProcess&#xff08;executable&#xff0c; args&#xff0c; FileNotFoundError&#xff1a; [WinError 2] 系统找不到指定的文件。 原因&#xff1a; 没装ffmpeg 或者 ffmpeg没添加到…

VUE2+ffmpeg处理非h264编码格式视频

1、安装npm install ffmpeg/ffmpeg0.10.0 ffmpeg/core0.9.8 video.js8.12.0 2、在vue.config.js中devServer配置 headers: {// 如果需要用到ffmpeg确保ShareArrayBuffer能够正常使用,可能会有安全隐患Cross-Origin-Embedder-Policy: require-corp,Cross-Origin-Opener-Policy:…

【408精华知识】提高外部排序速度的三种方式

文章目录 一、败者树二、置换-选择排序三、最佳归并树 一、败者树 还没写完… 二、置换-选择排序 三、最佳归并树 写在后面 这个专栏主要是我在学习408真题的过程中总结的一些笔记&#xff0c;因为我学的也很一般&#xff0c;如果有错误和不足之处&#xff0c;还望大家在评…