C++ 进阶之路:非类型模板参数、模板特化与分离编译详解

目录

非类型模版参数

类型模板参数

非类型模板参数

非类型模板参数的使用

模板的特化

函数模板的特化

类模板的特化

全特化与偏特化

偏特化的其它情况

模板的分离编译

什么是分离编译

为什么要分离编译

为什么模板不能分离编译

普通的类和函数都是可以分离编译的。

同样是分离编译,普通函数/类可以,函数模板、类模板为什么不行??? 

编译链接有哪些过程?

链接的时候到底做了什么??

解决模板不能分离编译的方法

显示实例化

不要分离编译


非类型模版参数

在 C++ 中,非类型模板参数是一种在模板定义中使用的参数类型,它不是一个数据类型,而是一个具体的值或对象引用。

类型模板参数

//类型模板参数
template<class T>
class A
{};

非类型模板参数

 那么什么是非类型模板参数呢?首先,我们来观察以下代码

假设我想要一个数组的大小一个是100,一个是1000,只能把这个 N 要么给 100 要么给1000,或者再定义一个类出来一个N 100一个 N 1000,如果这样改的话代码过,代码处理不同类型不够灵活,所以我们就有了非类型模板参数

#define N 100
template<class T>
class Array
{
private:T _a[N];
};
int main()
{Array<int> a1;  // 100Array<int> a2;  // 1000return 0;
}

非类型模板参数的使用

 非类型模板参数的使用和类型模板参数的使用类似,也是一样的传参。

#include <iostream>
using namespace std;
//#define N 100
//#define N 1000//类型模板参数,非类型模板参数
template<class T, int N>
class Array
{
private:T _a[N];
};
int main()
{Array<int, 100> a1;  // 100Array<int, 1000> a2;  // 1000cout << sizeof(a1) << endl;cout << sizeof(a2) << endl;return 0;
}

  这里的N是常量,不能修改的,因为数组的大小是常量。

template<class T, int N>
class Array 
{
public:Array() {N = 10; //不能修改}
private:T _a[N];
};int main()
{Array<int, 100> a1;  // 100Array<int, 1000> a2;  // 1000cout << sizeof(a1) << endl;cout << sizeof(a2) << endl;return 0;
}

 注意点:

记住一点:模板参数不一定全部传类型,也有可能传整型来固定大小啊。非类型模板参数是一个参数,不是什么类型都能做非类型模板参数的。

double 和 自定义类型等不能作为非类型模板参数。

#include <iostream>
using namespace std;template<class T, char ch> 
//char short可以做非类型模板参数,还有long long / long / int / short / char 整型家族//以下类型不能作为非类型模板参数
template<class T, double D> 
template<class T, string s>
class B
{};

模板的特化

模板的特化:针对某些类型的特殊化处理

函数模板的特化

针对某一种模板的具体类型,要根原来的模板做不一样的处理,就写一个特化,以下是函数模板的写法,针对const char* 类型进行特殊处理。

#include <iostream>
#include <string>
using namespace std;
//原模板
template<class T>
bool IsEqual(T& left, T& right)
{return left == right;
}
//模板的特化:针对某些类型的特殊化处理
template<>
bool IsEqual<const char*>(const char*& left, const char*& right)
{return strcmp(left, right) == 0;
}
int main()
{int a = 0, b = 1;cout << IsEqual(a, b) << endl;//这里是指针在比较,而是想比较ASCII码的值,这种情况下就需要使用到特化const char* p1 = "hello";const char* p2 = "world";cout << IsEqual(p1, p2) << endl;return 0;
}

类模板的特化

#include <iostream>
using namespace std;
//原类模板
template<class T1, class T2>
class Date
{
public:Date(){cout << "Date<T1, T2>" << endl;}
private:T1 _d1;T2 _d2;
};//特化的类模板
template<>
class Date<int, char>
{
public:Date(){cout << "特化的:Date<int, char>" << endl;}
private:int _d1;char _d2;
};
int main()
{Date<int, int> d1;Date<int, char> d2;return 0;
}

全特化与偏特化

全特化:就是针对特定的一组完整的模板参数,进行完全特殊的处理,为其提供专门的实现,不再使用通用模板的实现方式。

 

偏特化:就是对部分模板参数进行特殊处理,比如只针对其中一个或几个参数进行特化,或者对参数满足特定条件时进行特殊处理,而其他未特化的参数仍然保持一定的通用性。

注意:第一个参数匹配上了,优先使用偏特化,再看第二个参数。

#include <iostream>
using namespace std;
//原模板
template<class T1, class T2>
class Date
{
public:Date(){cout << "原模板:Date<T1, T2>" << endl;}
};
//全特化
template<>
class Date<int, char>
{
public:Date(){cout << "全特化:Date<int, char>" << endl;}
};
//偏特化(半特化)
template<class T2>
class Date<int, T2>
{
public:Date(){cout << "偏特化:Date<int, T2>" << endl;}
};int main()
{Date<int, int> d1;  //偏特化Date<int, char> d2; // 全特化Date<int, double> d3; //偏特化Date<char, double> d4; //原模板return 0;
}

偏特化的其它情况

还有一种情况的特化,对指针,或者引用的特化,也是一种特化。

#include <iostream>
using namespace std;
//原模板
template<class T1, class T2>
class Date
{
public:Date(){cout << "原模板: Date(T1, T2)" << endl;}
private:T1 _d1;T2 _d2;
};
//特化模板,两个指针的特化,不管什么类型,只要是指针就调用该模板
template<class T1, class T2>
class Date<T1*, T2*>
{
public:Date(){cout << "特化都是指针:Date(T1*, T2*)" << endl;}
};
//特化的都是引用
template<class T1, class T2>
class Date<T1&, T2&>
{
public:Date(){cout << "特化都是引用:Date(T1&, T2&)" << endl;}
};
int main()
{Date<int, int> d1;Date <int*, char*> d2;Date <char*, double*> d3;Date<int&, int&> d4;Date<double&, char&> d5;return 0;
}

模板的分离编译

什么是分离编译

  • 项目工程中一般将函数或者类的声明放到.h,将函数或者类的定义放到.cpp

为什么要分离编译

在一个项目工程中,代码不仅仅是我们平时写的几百行代码,而是几万甚至十几万行代码,分离编译的好处就是方便查看和维护。

为什么模板不能分离编译

普通的类和函数都是可以分离编译的。

这就是我们在实现前面所学的容器的时候,类模板和函数模板都写在一个.h文件中的,因为不能分离编译

同样是分离编译,普通函数/类可以,函数模板、类模板为什么不行??? 

和编译链接有关系,实际项目中有很多文件,Func.h  Func.cpp  Test.cpp 以三个文件为例。.

编译链接有哪些过程?

Linux环境下为例,生成的后缀都是Linux中的文件后缀

1.预处理:展开头文件,宏替换,条件编译,去掉注释

        生成 Func.i  Test.i

2.编译:检查语法,生成汇编代码

        生成Func.s  Test.s

3.汇编:将汇编代码转成二进制的机器码

        生成Func.o  Test.o

4.链接:将两个obj目标文件链接起来

        生成 a.out,也可以指定名字,没有指定生成的就是a.out

链接的时候到底做了什么??

汇编代码和二进制的机器码几乎是一一对应的,只不过我们看不懂二进制的机器码,所以我这里就看汇编代码,来进行理解。这里 call,机器码会用一段指令一一对应,只不过我们这里用汇编方便理解。

call之前填不上地址,为什么填不上地址

因为只包含了声明,没有具体实现,只有当定义(cpp文件)的时候才会有地址。

 编译的过程从i  -> s  -> o,从头到尾只有声明,都没有它的地址,参数都是能匹配上的,所以这里填问号。

一、函数调用的一般过程

在 C++ 中,函数的调用通常经历预处理、编译、汇编和链接等阶段。

对于普通函数,如 F1,在预处理阶段,头文件被展开。在编译阶段,当看到对F1的调用时,编译器会在符号表中记录这个未解析的符号,同时根据函数声明确定函数的参数和返回值类型等信息。在汇编阶段,会生成相应的汇编代码,但此时函数的具体地址还未确定。在链接阶段,链接器会在所有的目标文件和库文件中查找F1的实现,如果找到了,就会将其地址填入符号表中的相应位置,完成链接。

二、函数模板的特殊情况

对于函数模板F2,情况则有所不同。

  1. 头文件中的声明:在头文件(如 Func.h)中,只包含了函数模板的声明:

    在头文件中,只是包含了模板函数的声明,不会把具体调用时的参数值(比如这里的 10)传递给头文件。

    头文件只是提供了一个模板的框架和接口声明,当在某个源文件(如 test.cpp)中进行实际调用时,编译器根据调用时传入的具体参数来确定模板参数的类型,但这个具体的参数值并不会传递到头文件中去。

      template<class T>void F2(const T& x);
    这里只是提供了一个模板的框架,并不针对特定的类型进行实例化,也不知道具体的类型参数是什么。
  2. test.cpp中的调用:当在test.cpp中看到对F2(10)的调用时,在预处理阶段,头文件被展开,引入函数模板的声明。在编译阶段,编译器知道这里调用了一个模板函数,并记录下这个调用以及传入的参数类型(这里是int),但此时它并不能确定最终的函数实现。因为模板的本质是一种代码生成机制,只有在确定了具体的类型参数后,才能生成真正的函数代码。在汇编阶段,由于不知道具体类型,所以无法确定函数的具体地址,只能在符号表中记录一个未解析的符号,标记这个地方需要在链接阶段找到具体的函数地址。

  3. Func.cpp中的情况:在编译Func.cpp时,由于不知道会有哪些具体的类型参数传入模板函数,所以无法针对特定的类型进行实例化生成具体的函数代码。

  4. 链接阶段的问题:在链接阶段,链接器会查找所有的目标文件和库文件,试图解析未定义的符号。对于普通函数,只要在某个编译单元中有其实现,就可以找到并填入地址。但对于函数模板,由于在编译阶段没有针对特定类型进行实例化,链接器也无法确定具体的函数地址。即使在test.cpp中调用了F2(10),链接器也不能从这个调用中推断出在Func.cpp中应该实例化出针对int类型的函数模板版本。

三、总结

为什么 F1 可以找到 call 所需的地址,而F2 找不到呢???

对于普通函数 F1,在链接阶段,链接器可以在各个编译单元中找到其实现,因为普通函数的实现是确定的,不依赖于特定的类型参数,所以可以顺利地在符号表中填入正确的地址完成链接。

而对于函数模板F2,在声明的头文件中只是提供了模板的框架,不知道具体的类型参数。在test.cpp中调用F2(10)时虽然实例化成了int类型,但这个信息并不能自动传递到Func.cpp中去进行实例化。在编译Func.cpp时,由于不知道具体的类型参数,无法实例化出具体的函数,所以在链接阶段,链接器找不到针对特定类型实例化后的函数地址,最终在符号表中没有 F2 的有效地址,从而出现链接错误。

所以,虽然在test.cpp中调用F2(10)传入了int类型,但这个信息在编译和链接的过程中并不会自动传递到头文件(如Func.h)或Func.cpp中。这是由于编译和链接过程的独立性以及头文件和链接器的局限性所决定的。函数模板通常不能像普通函数那样简单地进行分离编译,需要在包含模板定义的编译单元中进行实例化,或者通过显式实例化等特殊手段来确保在链接阶段能够找到正确的函数地址。

解决模板不能分离编译的方法

显示实例化

似于特化,int类型可以,但是double又不行了,用一个就得实例化,这种方法不常用。

double 类型的实例化

Template

Void F2<double>(const double& x);

不要分离编译

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

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

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

相关文章

【学习笔记】数据结构(六 ①)

树和二叉树 &#xff08;一&#xff09; 文章目录 树和二叉树 &#xff08;一&#xff09;6.1 树(Tree)的定义和基本术语6.2 二叉树6.2.1 二叉树的定义1、斜树2、满二叉树3、完全二叉树4、二叉排序树5、平衡二叉树&#xff08;AVL树&#xff09;6、红黑树 6.2.2 二叉树的性质6.…

通用大模型 vs 垂直大模型:谁将赢得AI战场?

引言 在人工智能领域&#xff0c;大模型的快速发展引发了广泛的关注和讨论。大模型&#xff0c;尤其是基于深度学习和海量数据训练的模型&#xff0c;已经在多个领域展现出强大的能力。从自然语言处理、图像识别到自动驾驶和医疗诊断&#xff0c;AI大模型正深刻改变着我们的生…

基于二自由度汽车模型的汽车质心侧偏角估计

一、质心侧偏角介绍 在车辆坐标系中&#xff0c;质心侧偏角通常定义为质心速度方向与车辆前进方向的夹角。如下图所示&#xff0c;u为车辆前进方向&#xff0c;v为质心速度方向&#xff0c;u和v之间的夹角便是质心侧偏角。 质心侧偏角的作用有如下三点&#xff1a; 1、稳定性…

SVN笔记-SVN安装

SVN笔记-SVN安装 1、在windows下安装 SVN 1、准备svn的安装文件 下载地址&#xff1a;https://sourceforge.net/projects/win32svn/ 2、下载完成后&#xff0c;在相应的盘符中会有一个Setup-Subversion-1.8.17.msi的文件&#xff0c;目前最新的版本是1.8.17&#xff0c; 这里…

opencv4.5.5 GPU版本编译

一、安装环境 1、opencv4.5.5 下载地址&#xff1a;https://github.com/opencv/opencv/archive/refs/tags/4.5.5.ziphttps://gitee.com/mirrors/opencv/tree/4.5.0 2、opencv-contrib4.5.5 下载地址&#xff1a;https://github.com/opencv/opencv_contrib/archive/refs/tags/4…

Python中的数据可视化:从基础图表到高级可视化

数据可视化是数据分析和科学计算中不可或缺的一部分。它通过图形化的方式呈现数据&#xff0c;使复杂的统计信息变得直观易懂。Python提供了多种强大的库来支持数据可视化&#xff0c;如Matplotlib、Seaborn、Plotly等。本文将从基础图表入手&#xff0c;逐步介绍如何使用这些库…

【第十一章:Sentosa_DSML社区版-机器学习之分类】

目录 11.1 逻辑回归分类 11.2 决策树分类 11.3 梯度提升决策树分类 11.4 XGBoost分类 11.5 随机森林分类 11.6 朴素贝叶斯分类 11.7 支持向量机分类 11.8 多层感知机分类 11.9 LightGBM分类 11.10 因子分解机分类 11.11 AdaBoost分类 11.12 KNN分类 【第十一章&…

Java语言程序设计基础篇_编程练习题***18.33 (游戏:骑士旅途的动画)

目录 ***18.33 (游戏:骑士旅途的动画) 习题思路 代码示例 动画演示 ***18.33 (游戏:骑士旅途的动画) 为骑士旅途的问题编写一个程序&#xff0c;该程序应该允许用户将骑士放到任何一个起始正方形&#xff0c;并单击Solve按钮&#xff0c;用动画展示骑士沿着路径的移动&…

深度学习之表示学习 - 贪心逐层无监督预训练篇

引言 在人工智能的浩瀚星空中&#xff0c;深度学习以其强大的数据处理与模式识别能力&#xff0c;成为了一颗璀璨的明星。而表示学习&#xff0c;作为深度学习的核心基石之一&#xff0c;正引领着这一领域不断突破边界。表示学习旨在将原始数据转换为更加抽象、更有意义的特征…

leetcode第二十六题:删去有序数组的重复项

给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &#xff0c;你…

Rasa对话模型——做一个语言助手

1、Rasa模型 1.1 模型介绍 Rasa是一个用于构建对话 AI 的开源框架&#xff0c;主要用于开发聊天机器人和语音助手。Rasa 提供了自然语言理解&#xff08;NLU&#xff09;和对话管理&#xff08;DM&#xff09;功能&#xff0c;使开发者能够创建智能、交互式的对话系统。 1.2…

Apache Iceberg 数据类型参考表

Apache Iceberg 概述-链接 Apache Iceberg 数据类型参考表 数据类型描述实例方法注意事项BOOLEAN布尔类型&#xff0c;表示真或假true, false用于条件判断&#xff0c;例如 WHERE is_active true。确保逻辑条件的正确性。INTEGER32位有符号整数42, -7可用于计算、聚合&#xf…

【系统架构设计师】专题:中间件技术

更多内容请见: 备考系统架构设计师-核心总结目录 文章目录 一、中间件概述二、中间件特点三、中间件的分类四、中间件产品介绍一、中间件概述 中间件(middleware) 是基础软件的一大类,属于可复用软件的范畴。顾名思义,中间件处在操作系统、网络和数据库之上,应用软件的下层…

着色器ShaderMask

说明 实现一个渐变进度条&#xff0c;要求&#xff1a; 颜色渐变的过程是循序渐进的&#xff0c;而不是看起来像是将渐变条逐渐拉长了。 效果 源码 // 渐变进度条Stack(children: [// 背景色板Container(width: 300,height: 8,decoration: BoxDecoration(borderRadius: Bord…

ollama 部署教程(window、linux)

目录 一、官网 二、安装方式一&#xff1a;window10版本下载 三、安装方式二&#xff1a;linux版本docker 四、 模型库 五、运行模型 六、API服务 七、python调用 ollama库调用 langchain调用 requests调用 aiohttp调用 八、模型添加方式 1.线上pull 2.导入 GGU…

Parallels Desktop 20 for Mac 推出:完美兼容 macOS Sequoia 与 Win11 24H2

Parallels Desktop 20 for Mac 近日正式发布&#xff0c;这一新版本不仅全面支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;还在企业版中引入了一个全新的管理门户。新版本针对 Windows、macOS 和 Linux 虚拟机进行了多项改进&#xff0c;其中最引人注目的当属 Parallels …

C++编程语言:基础设施:源文件和程序(Bjarne Stroustrup)

第15章 源文件和程序 (Source Files and Programs) 目录 15.1 单独编译(Separate Compilation) 15.2 链接(Linkage) 15.2.1 文件局部名(File-Local Names) 15.2.2 头文件(Header Files) 15.2.3 一次定义原则(The One-Definition Rule) 15.2.4 标准库头文件 1…

基于YOLOv8+LSTM的商超扶梯场景下行人安全行为姿态检测识别

基于YOLOv8LSTM的商超扶梯场景下行人安全行为姿态检测识别 手扶电梯 行为识别 可检测有人正常行走&#xff0c;有人 跌倒&#xff0c;有人逆行三种行为 跌倒检测 电梯跌倒 扶梯跌倒 人体行为检测 YOLOv8LSTM。 基于YOLOv8LSTM的商超扶梯场景下行人安全行为姿态检测识别&#xf…

STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)

在研究声音、电力或任何形式的波形时&#xff0c;我们常常需要穿过表面看本质。FFT&#xff08;快速傅里叶变换&#xff09;就是这样一种强大的工具&#xff0c;它能够揭示隐藏在复杂信号背后的频率成分。本文将带你走进FFT的世界&#xff0c;了解它是如何将时域信号转化为频域…

如何将Excel表格嵌入Web网页在线预览、编辑并保存到自己服务器上?

猿大师办公助手作为一款专业级的网页编辑Office方案&#xff0c;不仅可以把微软Office、金山WPS和永中Office的Word文档内嵌到浏览器网页中实现在线预览、编辑保存等操作&#xff0c;还可以把微软Office、金山WPS和永中Office的Excel表格实现网页中在线预览、编辑并保存到服务器…