C++之模版进阶篇

news2024/11/23 0:44:53

目录

前言

1.非类型模版参数

2.模版的特化

2.1概念

2.2函数模版特化

2.3 类模板特化

2.3.1 全特化和偏特化

2.3.2类模版特化应用实例

3.模版分离编译

3.1 什么是分离编译

3.2 模板的分离编译

3.3 解决方法

4. 模板总结

结束语


前言

在模版初阶我们学习了函数模版和类模版的相关知识,对模版有了一定的了解,接下来我们将对模版进行进一步的了解,话不多说,直接上货!!!

本节内容

1. 非类型模板参数
2. 类模板的特化
3. 模板的分离编译

1.非类型模版参数

模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
非类型形参,就是 用一个常量作为类(函数)模板的一个参数 ,在类 ( 函数 ) 模板中可将该参数当成常量来使用

比如我们简单写一个静态栈,可以定义不同大小的栈,这里我们没有传默认参数,传也可以。

template<size_t N>
class Stack {
private:
    int _a[N];
    int _top;
};
int main() {
    Stack<5>s1;
    Stack<10>s2;

    return 0;
}
其实非类型形参在大小定义方面用的比较多,把大小当做常量。对于传参都传的是整数。
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
2. 非类型的模板参数必须在编译期就能确认结果
在C++11版本的array用了 非类型的模板参数, 来代替静态数组。
#include <array>
#include <iostream>
using namespace std;
int main(){
 array<int, 10> a1;
 array<int, 100> a2;
int a3[10];
return 0;
}

那么用array定义数组的好处是什么呢,是数组初始化吗,当然不是。

相比较于int a3[10],其实array的好处是越界检查方面,array对于数组的读和写都会进行越界检查,而int a3[10],对于读不会检查,而写会检查,进行的是抽取,一般会在数组后面默认分配两个数据大小空间左右。

 array<int, 10> a1;
 array<int, 100> a2;
// a1[100] = 3;
 int a3[10];
 cout << a3[100] << endl;
 a3[10] = 1;
// a3[12] = 1;

array底层访问是调用了一个函数进行实现,都会调用assert语句来判断。

template<class T, size_t N = 10>
class array
{
public:
	T& operator[](size_t index) { 
		assert(index < N);
		return _array[index];
	}
private:
	T _array[N];
	size_t _size;
};

其实对于数组的定义,在C++中我们采用vector来定义,可以进行初始化

vector<int>v(10,1);

但是如果要频繁的开设数组,其实array的效率可能要高些,因为array是在栈上开空间的,栈是向下生长的,vector是在堆上。 

2.模版的特化

2.1概念

通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 ,需要特殊处理,比如:实现一个专门用来小于比较的函数模版
class Date {
public:
    Date(int year = 1, int month = 1, int day = 1)
        :_year(year),
        _month(month),
        _day(day)
    {}
    bool operator<(const Date& other) const {
        if (_year != other._year) return _year < other._year;
        if (_month != other._month) return _month < other._month;
        return _day < other._day;
    }
    /*
   friend ostream&operator<<(ostream& out, const Date& d) {
        out << d._year << "-" << d._month << "-" << d._day;
        return out;
    }*/
private:
    int _year;
    int _month;
    int _day;
};
template <class T>
bool Less(const T &left,const T& right) {
    return left < right;
}
int main() {
    cout << Less(1, 2) << endl; // 可以比较,结果正确
    
    Date d1(2024, 7, 9);
    Date d2(2024, 7, 8);
   cout << Less(d1, d2) << endl; // 可以比较,结果正确
   Date* p1 = &d1;
   Date* p2 = &d2;
   cout << Less(p1, p2) << endl; // 可以比较,结果错误
    return 0;
}

 

可以看到, Less 绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中, p1 指向的d1 显然大于 p2 指向的 d2 对象,但是 Less内部并没有比较p1p2指向的对象内容,而比较的是p1p2 指针的地址,这就无法达到预期而错误。
此时,就 需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特化中分为函数模板特化 类模板特化

2.2函数模版特化

函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template <class T>
bool Less(const T &left,const T& right) {
    return left < right;
}

template<>
bool Less<Date*>(Date* const& left, Date* const& right)
{
	return *left < *right;
}
/*
template<>
bool Less<const Date*>(const Date* const& left, const Date* const& right)
{
    return *left < *right;
}
*/

注意:const要放在*之后,修饰的才是引用变量本身,放在*之前是修饰指向内容

非const特化

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

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

所以对于下面这段代码

Date * p1 = & d1 ;
Date * p2 = & d2 ;
cout << Less ( p1 , p2 ) << endl ; // 调用特化之后的版本,而不走模板生成了
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给 出。
bool Less(Date* left, Date* right)
{
 return *left < *right;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

2.3 类模板特化

2.3.1 全特化和偏特化

全特化即是将模板参数列表中所有的参数都确定化。
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
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;
};
//偏特化
template<class T1>
class Data<T1, int> {
public:
    Data() { cout << "Data<T1, int>" << endl; }
private:
    T1 _d1;
    int _d2;
};

int main() {
    Data<int, int>d1;
    Data<int, char>d2;
    Data<int, int> d3;
    return 0;
}

偏特化有以下两种表现方式:

部分特化

将模板参数类表中的一部分参数特化。
 
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
 Data() {cout<<"Data<T1, int>" <<endl;}
private:
 T1 _d1;
 int _d2;
};
参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本
/两个参数偏特化指针类型
//template<class T1,class T2>
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:
    Data()  { cout << "Data<T1*, T2*>" << endl;  }
private:
    T1 _d1;
    T2 _d2;
};
//两个参数特化引用类型
    template<class T1, class T2>
    class Data<T1&, T2&>
    {
    public:
        Data()  {  cout << "Data<T1&, T2&>" << endl; }
    private:
        T1 _d1;
        T2 _d2;
        };
    //一个指针一个引用
    template<class T1, class T2>
    class Data<T1&, T2*>
    {
    public:
        Data() { cout << "Data<T1&, T2*>" << endl; }
    private:
        T1 _d1;
        T2 _d2;
    };

2.3.2类模版特化应用实例

实例1:

class Date {
public:
    Date(int year = 1, 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);
    }
    
   
    friend ostream&operator<<(ostream& out, const Date& d) {
        out << d._year << "-" << d._month << "-" << d._day;
        return out;
    }
private:
    int _year;
    int _month;
    int _day;
};
/*
template <class T>
bool Less(const T &left,const T& right) {
    return left < right;
}
*/
template<class T>
struct Less
{
    bool operator()(const T& x, const T& y) const
    {
        return x < y;
    }
};
int main() {
    vector<Date>d1;
    d1.push_back(Date(2024, 8, 25));
    d1.push_back(Date(2024, 8, 24));
    d1.push_back(Date(2024, 8, 27));
    sort(d1.begin(), d1.end(), Less<Date>());
    for (const auto& date : d1) {
       cout << date << endl;
    }

    return 0;
}

 实例2:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Date {
public:
    Date(int year = 1, 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);
    }
    
   
    friend ostream&operator<<(ostream& out, const Date& d) {
        out << d._year << "-" << d._month << "-" << d._day;
        return out;
    }
    
private:
    int _year;
    int _month;
    int _day;
};
template<class T>
class Less
{
public:
    bool operator()(const T& x, const T& y) const
    {
        return x < y;
    }
};
// 对Less类模板按照指针方式特化

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


int main()
{
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 6);
    Date d3(2022, 7, 8);
    vector<Date> v1;
    v1.push_back(d1);
  
    v1.push_back(d2);
    v1.push_back(d3);
    // 可以直接排序,结果是日期升序
    
    sort(v1.begin(), v1.end(), Less<Date>());
    for (const auto& date : v1) {
        cout << date << endl;
    }
    vector<Date*> v2;
    v2.push_back(&d1);
    v2.push_back(&d2);
    v2.push_back(&d3);

    // 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
    // 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
    // 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
    sort(v2.begin(), v2.end(), Less<Date*>());
    for (const auto& date : v2) {
        cout << *date << endl;
    }
    return 0;
}

通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort 最终按照 Less 模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题:
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
 bool operator()(Date* x, Date* y) const
 {
 return *x < *y;
 }
};

 

3.模版分离编译

3.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.2 模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义。
// 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;
}
// main.cpp
#include"a.h"
int main()
{
 Add(1, 2);
 Add(1.0, 2.0);
 
 return 0;
}

C/C++程序的运行过程通常包括以下几个步骤:
1. 预处理(Preprocessing):
   - 预处理器处理源代码文件中的预处理指令,如 `#include`、`#define`、`#if`、`#ifdef` 等。
   - 预处理器将包含的文件内容插入到源文件中,处理宏定义,条件编译指令等。
   - 结果是一个扩展的源代码文件,通常以 `.i` 文件(C语言)或 `.ii` 文件(C++语言)表示。
2. 编译(Compilation):
   - 编译器将预处理后的源代码翻译成汇编语言。
   - 在这个阶段,编译器进行词法分析、语法分析、语义分析、中间代码生成和优化等操作。
   - 生成的汇编语言文件通常以 `.s` 文件表示。
3. 汇编(Assembly):
   - 汇编器将汇编语言转换成机器语言指令,这些指令是二进制形式的,可以被计算机的CPU直接执行。
   - 生成的目标代码文件通常以 `.o`(Linux/Unix系统)或 `.obj`(Windows系统)表示。
4. 链接(Linking):
   - 链接器将一个或多个目标文件以及所需的库文件合并成一个可执行文件。
   - 在这个阶段,链接器解析外部引用和符号,合并相同的函数和数据,进行重定位等操作。
   - 最终生成的可执行文件通常以 `.exe`(Windows系统)或无扩展名(Linux/Unix系统)表示。
5. 加载(Loading):
   - 当程序运行时,操作系统负责将可执行文件加载到内存中。
   - 加载器读取可执行文件,并将程序代码和数据映射到内存的适当位置。
6. 执行(Execution):
   - CPU开始执行程序的主函数,程序正式开始运行。
   - 在执行过程中,程序可能会进行各种计算、输入输出操作、调用库函数等。
7. 终止(Termination):
   - 程序执行完成后,或者遇到无法处理的错误时,程序会终止。
   - 在终止前,程序可能需要执行一些清理工作,如关闭打开的文件、释放分配的内存等。
 

链接之前各文件没有交互,模版没有实例化没有生成指令,代码,链接就出现问题。

3.3 解决方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者 xxx.h 其实也是可以的 推荐
用的地方就直接有定义了。
2. 模板定义的位置显式实例化 。这种方法不实用,不推荐。

显示实例化:

template

int Add(const int&left,const int&right)

对于其他类型照样,所以比较复杂

4. 模板总结

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

 
 

结束语

本篇就到此结束啦,内容有点多,相信大家通过本篇博客,对模版的认识有了进一步的理解。

最后感谢各位友友的支持,支持小编的留个赞吧!!!

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

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

相关文章

单细胞组学大模型(6)--- LangCell,医学/细胞文本知识增强模型效果

–https://arxiv.org/abs/2405.06708 代码开源&#xff1a;https://github.com/PharMolix/OpenBioMed LangCell: Language-Cell Pre-training for Cell Identity Understanding 留意更多内容&#xff0c;欢迎关注微信公众号&#xff1a;组学之心 研究团队和研究单位 聂再清…

Python画笔案例-077 绘制 颜色饱和度测试

1、绘制 颜色饱和度测试 通过 python 的turtle 库绘制 颜色饱和度测试,如下图: 2、实现代码 绘制 颜色饱和度测试,以下为实现代码: """饱和度渐变示例,本程序需要coloradd模块支持,请在cmd窗口,即命令提示符下输入pip install coloradd进行安装。本程序演…

如何彻底掌握 JavaScript 设计模式 23 大核心模式助你提升编程水平

如何彻底掌握 JavaScript 设计模式 23 大核心模式助你提升编程水平 设计模式是解决特定问题的常用解决方案&#xff0c;它们可以帮助开发者编写更清晰、可维护、可扩展的代码。在 JavaScript 中&#xff0c;常见的设计模式可以分为三大类&#xff1a;创建型模式、结构型模式 和…

javaweb - 请求响应02

数组集合参数 数组参数&#xff1a;请求参数名与形参数组名称相同且请求参数为多个&#xff0c;定义数组类型形参即可接收参数。 RequestMapping("/arrayParam")public String arrayParam(String[] hobby) {for (String s : hobby) {System.out.println(s);}retu…

xtu oj 神经网络

回顾 Dedicated to you. AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串 题目 某神经网络模型是如下 1.一共有m层&#xff0c;每层都有几个神经元&#xff0c;从上到下编号为1到n。 2.第 i 层的神经元只有第 i …

基于Python的自然语言处理系列(26):Get to the Point Summarization

在本篇文章中,我们将实现经典的"Get to the Point"模型,该模型最初发表于 Get to the Point: Summarization with Pointer-Generator Networks。这是当时最著名的摘要生成模型之一,至今仍有很多人使用其Pointer-Generator架构作为他们模型的一部分。 1. 模型简介…

数据库管理-第248期 23ai:全球分布式数据库-分片数据分布方法(20241006)

数据库管理248期 2024-10-06 数据库管理-第248期 23ai&#xff1a;全球分布式数据库-分片数据分布方法&#xff08;20241006&#xff09;1 系统管理分片2 用户定义分片2.1 分片空间2.2 在用户定义分片配置中添加分片空间2.3 为用户定义分片创建表空间2.4 用户定义分片创建分片表…

AI大模型应用开发实战-AI时代应用开发破局!

后端应用级开发者该如何拥抱 AI GC&#xff1f;就是在这样的一个大的浪潮下&#xff0c;我们的传统的应用级开发者。我们该如何选择职业或者是如何去快速转型&#xff0c;跟上这样的一个行业的一个浪潮? 0 AI金字塔模型 越往上它的整个难度就是职业机会也好&#xff0c;或者说…

毒蘑菇检测数据集 9200张 14类毒蘑菇 带标注 voc yolo

毒蘑菇检测数据集 9200张 14类毒蘑菇 带标注 voc yolo 分类名: (图片张数,标注个数) Amanita citrina: (700, 816) Gyromitra infula: (842, 1102) Hygrophoropsis aurantiaca: (766, 1578) Imleria badia: (794, 1027) Lactarius turpis: (728, 891) Boletus reticulatus: (67…

微信小程序开发-配置文件详解

文章目录 一&#xff0c;小程序创建的配置文件介绍二&#xff0c;配置文件-全局配置-pages 配置作用&#xff1a;注意事项&#xff1a;示例&#xff1a; 三&#xff0c;配置文件-全局配置-window 配置示例&#xff1a; 四&#xff0c;配置文件-全局配置-tabbar 配置核心作用&am…

日期类(Date)的实现 (C++版)

​ &#x1f339;个人主页&#x1f339;&#xff1a;喜欢草莓熊的bear &#x1f339;专栏&#x1f339;&#xff1a;C入门 目录 前言 一、Date的头文件&#xff0c;包含函数声明 二、 Date.cpp 2.1 int GetMonthDay(int year, int month) 2.2 bool Check() 2.3 Date& …

基于YOLOv8-deepsort算法的智能车辆目标检测车辆跟踪和车辆计数

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

HTB:Funnel[WriteUP]

目录 连接至HTB服务器并启动靶机 1.How many TCP ports are open? 2.What is the name of the directory that is available on the FTP server? 3.What is the default account password that every new member on the "Funnel" team should change as soon a…

cudnn8编译caffe过程(保姆级图文全过程,涵盖各种报错及解决办法)

众所周知,caffe是个较老的框架,而且只支持到cudnn7,但是笔者在复现ds-slam过程中又必须编译caffe,我的cuda版本是11.4,最低只支持到8.2.4,故没办法,只能编译了 在此记录过程、报错及解决办法如下; 首先安装依赖: sudo apt-get install git sudo apt-get install lib…

李宏毅 X 苹果书 自注意力机制 学习笔记下

b1 &#xff0c;b2...不是依序产生&#xff0c;而是同时被计算好的 从矩阵乘法角度看待self-attention运作过程 矩阵运算表示每一个a都要产生 a k v的操作如下&#xff1a; 矩阵运算表示的计算如下&#xff1a; A‘是A的normalization &#xff0c;用softmax 矩阵运算表示b计…

Ubuntu有关redis的命令

防火墙&#xff1a; systemctl status firewalld systemctl stop firewalld systemctl disable firewalld.service ifconfig查看ip地址 redis.conf在/etc/redis下&#xff0c;但是得sudo -i进入root模式 进入/etc/redis下开启redis-server服务 查看6379端口是否可以访问 net…

vue3- antd design vue 引入iconfont

文章目录 前言一、新建iconfont项目 前言 vue3项目中&#xff0c;如何引入第三方的iconfont的图标 一、新建iconfont项目 搜索需要的图标&#xff0c;加入购物车&#xff0c;购物车中图片加入项目 下载项目文件&#xff0c;打开压缩包后&#xff0c;将iconfont.js 文件拷贝到…

基于vue框架的大学生心理健康服务平台mwavu(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,心理专家,心理咨询,健康文章,咨询回复,心理案例,监测预警,解压游戏,放松音乐 开题报告内容 基于Vue框架的大学生心理健康服务平台开题报告 一、研究背景与意义 随着社会的快速发展和教育竞争的日益激烈&#xff0c;大学生面临着…

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz 简介适用场景Quartz核心概念Quartz 存储方式Quartz 版本类型引入相关依赖方式一&#xff1a;内存方式(MEMORY)存储实现定时任务1. 定义任务类2. 定义任务描述及创建任务触发器3. Quartz的…

VirtualBox Ubuntu22.04 NOI linux2.0 Terminal无法打开 终端打不开 两步解决法儿

新安装的虚拟机无法打开Terminal&#xff0c;从应用列表中单击Terminal&#xff0c;左上角任务栏会出现Terminal&#xff0c;并且鼠标转圈&#xff0c;但是过一会左上角Terminal消失&#xff0c;就像一切都没有来过。 解决办法&#xff1a; CTRL ALT F3 进入命令行模式&…