返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++【模板进阶】
  • 860
分享到

C++【模板进阶】

c++开发语言java 2023-08-19 21:08:15 860人浏览 独家记忆
摘要

✨个人主页: 北 海 🎉所属专栏: C++修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 文章目录 🌇前言🏙️正文1、非

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2019 版本 16.11.17

成就一亿技术人



🌇前言

模板是搭建 STL 的基本工具,同时也是泛型编程思想的代表,模板用好了可以提高程序的灵活性,以便进行更高效的迭代开发,模板除了最基本的类型替换功能外,还有更多高阶操作:非类型模板参数、全特化、偏特化等,以及关于模板声明与定义不能分离(在两个不同的文件中)的问题,都将在本文中进行介绍

活字印刷


3D9;️正文

1、非类型模板参数

之前所使用的模板参数都是用来匹配不同的类型,如 intdoubleDate 等,模板参数除了可以匹配类型外,还可以匹配常量(非类型),完成如数组、位图等结构的大小确定

1.1、使用方法

在定义模板参数时,不再使用 classtypename,而是直接使用具体的类型,如 size_t,此时称为 非类型模板参数

注:非类型模板参数必须为常量,即在编译阶段确定值

利用 非类型模板参数 定义一个大小可以自由调整的 整型数组

template<size_t N>class arr{public:int& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];//创建大小为 N 的整型数组};

结果

再加入一个模板参数:类型,此时就可以得到一个 泛型、大小可自定义 的数组

template<class T, size_t N>class arr{public:T& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:T _arr[N];//创建大小为 N 的整型数组};

结果

非类型模板参数支持缺省,因此写成这样也是合法的

template<class T, size_t N = 10>//缺省大小为10

1.2、类型要求

非类型模板参数要求类型为 整型家族,其他类型是不行的

比如下面这些 非类型模板参数 都是标准之内的

//整型家族(部分)template<class T, int N>class arr1 {  };template<class T, long N>class arr2 {  };template<class T, char N>class arr3 {  };

而一旦使用其他家族类型作为 非类型模板参数,就会引发报错

//浮点型,非标准template<class T, double N>class arr4 {  };

结果

因此可以总结出,非类型模板参数 的使用要求为

  • 只能将 整型家族 类型作为非类型模板参数,其他类型不在标准之内
  • 非类型模板参数必须为常量(不可被修改),且需要在编译阶段确定结果

整型家族:charshortboolintlonglong long

1.3、实际例子:array

c++11 标准中,引入了一个新容器 array,它就使用了 非类型模板参数,为一个真正意义上的 泛型数组,这个数组是用来对标传统数组的

注意: 部分老编译器可能不支持使用此容器

array

array 的第二个模板参数就是 非类型模板参数

#include #include #include using namespace std;int main(){int arrOld[10] = { 0 };//传统数组array<int, 10> arrNew;//新标准中的数组//与传统数组一样,新数组并没有进行初始化//新数组对于越界读、写检查更为严格arrOld[15];//老数组越界读,未报错arrNew[15];//新数组则会报错arrOld[12] = 0;//老数组越界写,不报错,出现严重的内存问题arrNew[12] = 10;//新数组严格检查return 0;}

array 是泛型编程思想中的产物,支持了许多 STL 容器的功能,比如 迭代器运算符重载 等实用功能,最主要的改进是 严格检查越界行为

实际开发中,很少使用 array,因为它对标传统数组,连初始化都没有,vector 在功能和实用性上可以全面碾压,并且 array 使用的是 栈区 上的空间,存在栈溢出问题,可以说 array 是一个鸡肋的容器

array 如何做到严格的全面检查?

  • 这个很简单,得益于类的封装,在进行下标相关操作前,先将传入的下标 pos 进行合法性检验即可,如 assert(pos >= 0 && pos < N)

2、模板特化

模板除了可以根据传入的类型进行实例化外,还可以指定实例化,这就好比普通汽车只能在公路上行驶,但我们也可以将车进行特殊改装,让其能在山川泥潭中驰骋;模板特化的用意就在于此,通过对 泛型思想的特殊化处理 ,更好的符合我们的使用需求

普拉多

2.1、概念

通常情况下,模板可以帮我们实现一些与类型无关的代码,但在某些场景中,【泛型】无法满足调用方的精准需求,此时会引发错误,比如使用 日期类对象指针 构建优先级队列后,若不编写对应的仿函数,则比较结果会变为未定义

结果
详见 《C++ STL学习之【优先级队列】》

原因:泛型思想无法满足特殊场景

解决方案:利用模板的特化制定更加精准的比较逻辑

综上所述,所谓模板的特化,就是在原模板的基础之上,对原模板进行特殊化处理,创造出另一个 “特殊” 的模板,完成需求

2.2、函数模板特化

函数也可以使用模板,因此支持 模板的特化

比如在下面这个比较函数中,假若不进行特化,则会出现错误的结果

template<class T>bool isEqual(T x, T y){return x == y;}int main(){int x = 10;int y = 20;cout << "x == y: " << isEqual(x, y) << endl;char str1[] = "Haha";char str2[] = "Haha";//此时泛型比的是地址,实际内容是相等的!cout << "str1 == str2: " << isEqual(str1, str2) << endl;return 0;}

结果
原因:字符串比较时,比较的是地址,而非内容

解决方案:利用模板的特化,为字符串的比较构建一个特殊模板

//函数模板特殊,专为 char* 服务template<>bool isEqual<char*>(char* x, char* y){return strcmp(x, y) == 0;}

结果
此时比较的结果正常,成功解决了问题

不过对于函数模板特化来说,存在一个更加方便的东西:函数重载,同样也能解决特殊需求,同时写法没这么怪,不过既然存在 函数模板特化 这个语法,那么我们还是得学习下的

结果

2.3、类模板特化

模板特化主要用在类模板中,它可以在泛型思想之上解决大部分特殊问题,并且类模板特化还可以分为:全特化和偏特化,适用于不同场景

后续举例时需要用到 Date 日期类,这里先把代码放出来

class Date{public:Date(int year = 1970, 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:int _year;int _month;int _day;};

2.3.1、全特化

全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类

//原模板template<class T1, class T2>class Test{public:Test(const T1& t1, const T2& t2):_t1(t1),_t2(t2){cout << "template" << endl;}private:T1 _t1;T2 _t2;};//全特化后的模板template<>class Test<int, char>{public:Test(const int& t1, const char& t2):_t1(t1), _t2(t2){cout << "template<>" << endl;}private:int _t1;char _t2;};int main(){Test<int, int> T1(1, 2);Test<int, char> T2(20, 'c');return 0;}

结果

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板,这就好比虽然你家冰箱里有菜,但你还是想点外卖,因为外卖对于你来说更加合适

可以使用全特化,解决之前优先级队列中,类型为 日期类指针 Date* 的比较问题

注:这里只是举例说明,完整代码参考优先级队列相关文章

//对比较的仿函数进行全特化处理template<>struct less<Date*>{//比较 是否小于bool operator()(Date* x, Date* y){return *x < *y;}};template<>struct greater<Date*>{//比较 是否大于bool operator()(Date* x, Date* y){return *x > *y;}};

结果

注意:

  • 在进行全特化前,需要存在最基本的泛型模板
  • 全特化模板中的模板参数可以不用写
  • 需要在类名之后,指明具体的参数类型,否则无法实例化出对象

2.3.2、偏特化

偏特化,指 将泛型范围进一步限制,可以限制为某种类型的指针,也可以限制为具体类型

//原模板---两个模板参数template<class T1, class T2>class Test{public:Test(){cout << "class Test" << endl;}};//偏特化之一:限制为某种类型template<class T>class Test<T, int>{public:Test(){cout << "class Test" << endl;}};//偏特化之二:限制为不同的具体类型template<class T>class Test<T*, T*>{public:Test(){cout << "class Test" << endl;}};int main(){Test<double, double> t1;Test<char, int> t2;Test<Date*, Date*> t3;return 0;}

结果

偏特化(尤其是限制为某种类型)在 泛型思想特殊情况 之间做了折中处理,使得 限制范围式的偏特化 也可以实现 泛型

  • 比如偏特化为 T*,那么传 int*char*Date* 都是可行的

借助偏特化解决指针无法正常比较问题(也是可以偏特化为引用类型的)

//原来的比较模板template<class T>class Less{public:bool operator()(T x, T y) const{return x < y;}};//偏特化后的比较模板template<class T>class Less<T*>{public:bool operator()(T* x, T* y) const{return *x < *y;}};int main(){Date d1 = { 2018, 4, 10 };Date d2 = { 2023, 5, 10 };cout << "d1 < d2: " << Less<Date>()(d1, d2) << endl;cout << "&d1 < &d2: " << Less<Date*>()(&d1, &d2) << endl;int a = 1;int b = 2;cout << "&a < &b: " << Less<int*>()(&a, &b) << endl;return 0;}

结果

当然也可以使用 偏特化 解决 Date* 的比较问题,这里就不再演示

注意:

  • 在进行偏特化前,需要存在最基本的泛型模板
  • 偏特化与全特化很像,注意区分

3、模板的分离编译问题

早在 模板初阶 中,我们就已经知道了 模板不能进行分离编译,会引发链接问题

结果

下面就来谈谈为什么会出现这个问题

3.1、失败原因

声明与定义分离后,在进行链接时,无法在符号表中找到目标地址进行跳转,因此链接错误

下面是 模板声明与定义写在同一个文件中时,具体的汇编代码执行步骤

Test.h

#pragma once//声明template<class T>T add(const T x, const T y);//定义template<class T>T add(const T x, const T y){return x + y;}

main.cpp

#include #include "Test.h"using namespace std;int main(){add(1, 2);return 0;}

结果

声明与定义在同一个文件中时,可以直接找到函数的地址

代码从文本变为可执行程序所需要的步骤:

  1. 预处理:头文件展开、宏替换、条件编译、删除注释,生成纯净的C代码
  2. 编译:语法 / 词法 / 语义 分析、符号汇总,生成汇编代码
  3. 汇编:生成符号表,生成二进制指令
  4. 链接:合并段表,将符号表进行合并和重定位,生成可执行程序

当模板的 声明定义 分离时,因为是 【泛型】,所以编译器无法确定函数原型,即 无法生成函数,也就无法获得函数地址,在符号表中进行函数链接时,必然失败

错误

图解

简单举个例子:抛开模板这个东西,在头文件中声明函数,但不定义,调用函数时,报的就是链接错误

Test.h

#pragma once//只声明,不定义void sub(int x, int y);

main.cpp

#include #include "Test.h"using namespace std;int main(){//add(1, 2);sub(2, 1);return 0;}

结果

3.2、解决方法

解决方法有两种:

  1. 在函数定义时进行模板特化,编译时生成地址以进行链接
  2. 模板的声明和定义不要分离,直接写在同一个文件中
//定义//解决方法一:模板特化(不推荐,如果类型多的话,需要特化很多份)template<>int add(const int x, const int y){return x + y;}
//定义//解决方法二:声明和定义写在同一个文件中template<class T>T add(const T x, const T y){return x + y;}

这也就解释了为什么涉及 模板 的类,其中的函数声明和定义会写在同一个文件中 (.h),著名的 STL 库中的代码的声明和定义都是在一个 .h 文件中

结果

为了让别人一眼就看出来头文件中包含了 声明定义,可以将头文件后缀改为 .hpp,著名的 Boost 库中就有这样的命名方式

boost库


4、模板小结

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大

模板的优点

  • 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  • 增强了代码的灵活性

模板的缺点

  • 模板会导致代码膨胀问题,也会导致编译时间变长
  • 出现模板编译错误时,错误信息非常凌乱,不易定位错误

总之,模板 是一把双刃剑,既有优点,也有缺点,只有把它用好了,才能使代码 更灵活、更优雅


🌆总结

以上就是有关 C++【模板进阶】的全部内容了,在本文中,我们学习了非类型模板参数,认识了 C++11 中的新容器 array;然后学习了模板的特化,见识了模板特化的各种场景;最后明白了模板声明与定义不能分离的根本原因,总之,模板很强,但想要用好还得多练

C++ 初阶系列文章到此就正式结束了,后续将会继续更新 C++ 进阶内容,比如 继承多态高阶二叉树 等等高能知识点,敬请期待吧


星辰大海

相关文章推荐

STL 之 适配器

C++ STL学习之【优先级队列】

C++ STL学习之【反向迭代器】

C++ STL学习之【容器适配器】
===============

STL 之 list

C++ STL学习之【list的模拟实现】

C++ STL学习之【list的使用】
===============

STL 之 vector

C++ STL学习之【vector的模拟实现】

C++ STL学习之【vector的使用】

来源地址:https://blog.csdn.net/weixin_61437787/article/details/129823562

--结束END--

本文标题: C++【模板进阶】

本文链接: https://lsjlt.com/news/375940.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++【模板进阶】
    ✨个人主页: 北 海 🎉所属专栏: C++修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 文章目录 🌇前言🏙️正文1、非...
    99+
    2023-08-19
    c++ 开发语言 java
  • C++初阶学习之模板进阶
    目录一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1)全特化2)偏特化三、模板分离编译四、模板总结总结一、非模板类型参数 分类: 模板参数分类类型形参与非类型形参 概念...
    99+
    2024-04-02
  • C++数据结构模板进阶实例分析
    本文小编为大家详细介绍“C++数据结构模板进阶实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++数据结构模板进阶实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。非类型模板参数模板参数分类类型形参...
    99+
    2023-06-29
  • C++数据结构模板进阶的多方面分析
    目录非类型模板参数模板的特化函数模板的特化类模板的特化模板的分离编译总结⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class...
    99+
    2024-04-02
  • C++模板编程的高阶精妙
    c++++ 模板编程的高阶技术包括类型推理(自动推断类型)、类型约束(限制模板参数)和元编程(编译时操作)。这些技术通过实战案例展示,如可塑容器,允许开发者创建通用、高效且类型安全的代码...
    99+
    2024-05-22
    c++ 模板编程
  • 【C++】详细介绍模版初阶—函数模版、类模板
    文章目录 一、泛型编程二、函数模版2.1 函数模版概念2.2 函数模版格式2.3 函数模版的原理2.4 函数模版的实例化2.5 函数模版的匹配原则 三、类模版3.1 类模版定义3.2 类模...
    99+
    2023-09-02
    c++ 前端
  • VUE 模板语法实战宝典:解锁进阶技能
    VUE 模板语法概述 VUE 模板语法是一种基于 HTML 的语法,用于定义组件视图的结构和行为。它允许开发者使用 VUE 特定的语法元素将数据与 DOM 元素绑定,从而创建动态且响应式的前端界面。 基本语法 VUE 模板语法使用两个主...
    99+
    2024-03-04
    VUE 模板语法、指令、事件处理、插槽、组件
  • C语言进阶:指针的进阶(1)
    目录指针进阶字符指针字符指针的作用字符指针的特点指针数组指针数组的定义指针数组的使用总结指针进阶 我们在初阶时就已经接触过指针,了解了指针的相关内容,有: 指针定义:指针变量,用于...
    99+
    2024-04-02
  • C语言进阶:指针的进阶(2)
    目录数组指针数组指针的定义&数组名和数组名数组指针的使用反面用例正面用例Example类型辨别方法总结数组指针 由前面的例子,不难得出,数组指针是指向数组的指针,是指针而非...
    99+
    2024-04-02
  • C语言进阶:指针的进阶(3)
    目录数组传参和指针传参一维数组传参二维数组传参一级指针传参二级指针传参总结数组传参和指针传参 实践之中不免会碰到数组和指针作函数参数而如何设计形参的问题。 一维数组传参 一维数...
    99+
    2024-04-02
  • C语言进阶:指针的进阶(4)
    目录函数指针函数指针的定义函数指针的类型函数指针的使用Example总结函数指针 函数指针的定义 整型指针存放整型的地址;数组指针存放数组的地址;那么类比可得,函数指针存放函数的地...
    99+
    2024-04-02
  • C语言进阶:指针的进阶(5)
    目录函数指针数组函数指针数组的定义函数指针数组的使用转移表回调函数指向函数指针数组的指针总结函数指针数组 //整型数组 - 存放整型变量 int arr[10]; //字符数组 ...
    99+
    2024-04-02
  • [C++] 模板template
      目录 1、函数模板 1.1 函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 1.4.1 隐式实例化 1.4.2 显式实例化 1.5 模板参数的匹配原则 2、类模板 2.1 类模板的定义格式 2.2...
    99+
    2023-09-02
    c++ 开发语言
  • Node.js 模板引擎的进阶指南:解锁高级用法
    ...
    99+
    2024-04-02
  • 【C进阶】指针(二)
    六、函数指针数组 数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组 eg: int *arr[10]       //整形指针数组-数组-存放的是整形指针 char *arr[5]      //字符指针数组-数组-存放的...
    99+
    2023-09-12
    c语言 算法 数据结构
  • VUE 模板语法进阶技巧:提升你的开发水平
    Vue.js 的模板语法提供了强大的功能,使你能够快速简洁地构建动态的用户界面。掌握其进阶技巧可以显著提升你的开发效率和代码可维护性。 1. 指令缩写 Vue.js 提供了许多指令,可以通过缩写来简化代码: <button @cli...
    99+
    2024-03-04
  • C++ 函数模板和类模板详情
    目录1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式化 2.3 函数模板原理 2.4 函数模板实例化 2.5 模板参数的匹配原理 3. 类模板 3.1 类模...
    99+
    2024-04-02
  • C++函数模板和类模板分析
    本篇内容介绍了“C++函数模板和类模板分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.函数模板函数模板定义了参数化的非成员函数,这使得...
    99+
    2023-06-17
  • 浅析C++函数模板和类模板
    目录一、函数模板1、函数模板的定义和使用2、函数模板的编译原理3、函数模板的声明二、类模板1、类模板的定义和使用2、类模板的编译原理3、类模板的继承和派生C++语言全盘继承了C语言的...
    99+
    2024-04-02
  • VUE 模板语法进阶指南,带你解锁更多可能性
    一、Vue 模板语法的基本语法 Vue 模板语法是一种用于定义 DOM 结构并将其与数据模型绑定的语法。它基于 HTML,并提供了许多内置指令和过滤器来扩展 HTML 的功能,使其能够更加动态地响应数据。 <div id="app"...
    99+
    2024-02-10
    Vue.js, Javascript, 模板引擎, 数据绑定, 指令
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作