『C++ - 模板』之模板进阶

news2024/12/24 10:27:06

文章目录

  • 模板进阶
    • 非类型模板参数
      • 类型模板参数与非类型模板参数的不同
    • 模板的特化
      • 全特化
      • 偏特化
      • 全特化与偏特化的区别
    • 模板的分离编译
      • 解决办法
    • 总结

模板进阶

非类型模板参数

类型模板参数与非类型模板参数的不同

  • 类型模板参数
  • 非类型模板参数

类型模板参数一般用来设置模板的类型;

而非类型模板参数默认为整形常量;

同时作为模板参数它们都可以进行定义缺省值;(同时,由于是整型常量,所以只能作为模板参数而不能再其他地方再进行赋值,因为左值不能被修改,而N为整形常量为左值)

#include<iostream>
using namespace std;
template<class T = int,int N = 20>//T为类型模板参数,N为非类型模板参数
//一般来说 类型模板参数后面跟的都是类型,而一般非类型模板参数后面一般为整型常量
//(包括int/char/short/long/long long等)

    /* bool 类型也可以作为非类型模板参数的类型,bool也属于整型家族 */
class Array{
  T _a[N];
};

void test_1(){

  Array<int,10> _a1;
  Array<double,20> _a2;

}

int main()
{
	test_1();
  return 0;
}



模板的特化

模板的特化分为两种,分为类模板的特化以及函数模板的特化(函数模板暂不支持偏特化)

同时还可以分为全特化偏特化(部分特化);


全特化

特化一般至在原有泛型编程的基础上,对某些类型或者参数进行特殊的处理;

假设有一个仿函数的函数模板,该函数模板可以对同个类型进行较小比较;



template<class T>
struct Less{
  bool operator()(const T&a,const T&b){
    return a<b;
  }
};

void test_2(){
  int a = 10,b = 20;
  Less<int> lessfunc;
  cout<<lessfunc(a,b)<<endl;

}

运行test_2函数后所得的结果为1;

但是若是传的数据类型为指针类型,则讲出现不一样的答案;


void test_2(){
  int a = 10,b = 20;
  Less<int*> lessfunc1;
  cout<<lessfunc1(&a,&b)<<endl;
}

由于在在C++中指针也可以进行比较,所以在这里也进行了比较,但是打印出的结果为0;

原因是虽然进行了比较但是比较只是指针中的单纯比较,并不是我们需要的结果;

而在C++中却出现了模板特化的语法;

//原模版
template<class T>
struct Less{
  bool operator()(const T&a,const T&b){
    return a<b;
  }
};

//模板的特化
template<>//语法中模板特化不需要定义模板参数
struct Less<int*>{//而在此处时应该声明需要特化的类型
  bool operator()(const int *a,const int *b){//根据特定的类型对其特化需要的操作
    return *a<*b;
  }
};

void test_2(){
  int a = 10,b = 20;
  Less<int> lessfunc;
 //cout<<lessfunc(a,b)<<endl;
  Less<int*> lessfunc1;
  cout<<lessfunc1(&a,&b)<<endl;
}

运行上面这段程序将可以得出预期的结果1;

在模板的特化中需要注意特化的语法;

同时在模板的特化时需要注意:

  • 在进行模板特化时必须存在一个原有的模板;

    模板的特化是在原有的模板中进行一种类似重载的操作;

同时模板的特化和函数的重载有一定的区别,在一个类中,相应的类与其模板可以同时存在;

且相应的函数模板也可以与其相应的函数声明同时存在;

只不过一个属于模板,一个属于声明;(模板只有在调用的时候根据模板参数的类型去实例化相应的类/函数)

上段代码演示的为类模板特化;

template<class T>
  bool operator()(const T&a,const T&b){
    return a<b;
  }

template<>
  bool operator()(const int *a,const int *b){//根据特定的类型对其特化需要的操作
    return *a<*b;
  }
↑函数模板特化↑


在大多数情况下,函数模板特化的使用较少;

因为在大多情况下函数可以进行重载(模板与声明可以同时存在,构成重载);

同时应该注意,函数模板暂不支持偏特化;


偏特化

对于模板的特化除了全特化以外还有偏特化;

全特化类似于重载,但是对于偏特化而言,只是在原有的模板基础上增添(进一步进行)了类型的限制;

即可以对一定类型的模板参数进行特殊处理;

//原模板
template<class T>
struct Less{
  bool operator()(const T&a,const T&b){
    return a<b;
  }
};

//偏特化处理
template<class T>//对于全特化而言,<>内不需要模板参数,而偏特化时需要显示原模板参数
struct Less<T*>{
  bool operator()(const T*a,const T*b)const{
    return *a<*b;
  }
};

void test_3(){
  int a = 10;
  int b = 20;

  cout<<Less<int>()(a,b)<<endl;
  cout<<Less<int*>()(&a,&b)<<endl;

}

该处对test_3函数进行调用时得出的结果都未true;

与全特化处理而言,偏特化处理更适合处理某一类型的特殊类型,在这里演示了统一对指针进行处理;


全特化与偏特化的区别

全特化与偏特化的命名方式与特化的模板参数数量无关;

只与特化的模板参数的规则有关;

全特化与偏特化的区别;

这个规则是指是否完全限制所有模板参数以致达到全特化的效果;

如:


template<class T1,class T2>
class print{
	void Print(T1 a,T2 b){
  		cout<<"T1,T2"<<endl;
  		cout<<"原模板"<<endl;
	}
};

template<class T1>
class print<T1>{
	void Print(T1 a,int* b){
  		cout<<"T1,int*"<<endl;
  		cout<<"偏特化"<<endl;
	}
};

template<class T1,class T2>
class print<T1,T2>{
	void Print(T1 a,T2* b){
 		cout<<"T1,T2*"<<endl;
  		cout<<"偏特化"<<endl;
	}
}
template<class T1>
class print<T1>{
		void Print(T1* a,char* b){
  		cout<<"T1*,char*"<<endl;
        cout<<"偏特化"<<endl;
	}
};

如上段代码而言,这里的特化行为并没有指定所有的模板参数为某个单独的类型,而是限制了部分模板参数使其为某一类的类型或者说是某一种单独类型;

所以在这里可以将偏特化分为三种表现形式:

  1. 部分模板参数指定类型进行特化( 例: < T1 , T2 > 特化为 < T1 , int* > );
  2. 所有模板参数将其特化为该类型的限制( 例: < T1 , T2 > 特化为 < T1* , T2& > );
  3. 以上两种的集合( 例: < T1 , T2 > 特化为 < T1* , double* > )


模板的分离编译

对于模板来说是不支持分离编译的,即分文件进行声明与定义;

但是在同文件种是可以进行声明定义分离的;

为什么在多文件的情况下不支持声明与定义分离?

首先我们要了解c/C++程序的翻译过程;

一个程序的翻译过程一般包括四步:

  • 预处理

    在预处理阶段中,一般会进行以下操作:

    1. 头文件展开
    2. 宏替换
    3. 条件编译
    4. 去注释等

    同时在该阶段,原来的.c\.cpp被翻译过后将会生成一个.i后缀文件;

    同时在该次翻译过后,代码一样也为c/C++;

  • 编译

    该该阶段中,.i文件将会被翻译成.s后缀的汇编代码;

  • 汇编

    在该阶段中将会把.s后缀的文件翻译成.o的可重定向二进制目标文件;

  • 链接

    在该过程中,将会把多个.o可重定向二进制目标文件进行链接,最后生成.exe可执行程序;

假设有三个文件Func.h文件用于声明,Func.cpp用于定义,main.cpp用于测试;

三个文件内的代码分别为:

  • Func.h

    #pragma once
    
    template<class T>
    void Add(const T& a, const T& b);//函数模板声明
    
    void func(int a,int b);//函数声明
    
    
  • Func.cpp

    #include<iostream>
    
    #include"Func.h"
    
    using namespace std;
    
    template<class T>
    void Add(const T& a, const T& b){
        cout<<a+b<<endl;
    }
    
    void func(int a,int b){
        cout<<a+b<<endl;
    }
    
  • main.cpp

    #include<iostream>
    
    #include"Func.h"
    
    using namespace std;
    
    int main()
    {
        func(10,20);
        
        Add(10,20);
        Add(5.5,2,2);
        
        return 0;
    }
    

假设运行上面这段程序将会出现链接失败;

连接失败的原因即为模板进行了分离编译;

从该图可以看出程序翻译的过程;

其实在最初的预处理中的展开头文件就能了解到问题的所在;

首先模板是一个未经过实例化的存在;

在这个程序中,func()函数可以被直接进行调用的原因是因为即使声明定义分离,函数的定义也仍然存在主体;

但是对于模板来说是一个不存在实体即没有被实例化的存在;

在首先的预处理阶段,头文件展开,此时main.cpp文件内与Func.cpp文件内的内容大概如下;

/*
*main.cpp
*/

#include<iostream>//在该演示中不便于展开该头文件

#pragma once

template<class T>
void Add(const T& a, const T& b);//函数模板声明

void func(int a,int b);//函数声明

using namespace std;

int main()
{
    func(10,20);
    
    Add(10,20);
    Add(5.5,2,2);
    
    return 0;
}
/*
*Func.cpp
*/

#include<iostream>//在该演示中不便于展开该头文件

#pragma once

template<class T>
void Add(const T& a, const T& b);//函数模板声明

void func(int a,int b);//函数声明

using namespace std;

template<class T>
void Add(const T& a, const T& b){
    cout<<a+b<<endl;
}

void func(int a,int b){
    cout<<a+b<<endl;
}

从上面两个文件可以看出,为什么在编译过程中并没有报错而最后在链接的过程中才进行报错;

在编译过程中,main.i文件内由于头文件的展开,已经存在了模板;

意思当Add()函数调用时,找到了对应的声明;

同理func()函数也如此;

而函数真正的调用在链接阶段,链接阶段将会通过对应声明的函数的地址找到函数的定义从而进行调用;

但是在这里实际中Add()函数模板并未进行实例化,所以才导致的链接错误;

解决办法

那有什么办法可以解决对于模板的分离编译吗?

  • 对模板进行显式实例化

    当在模板定义的文件中进行模板的显式实例化即可解决一部分问题;

    当然只是一部分的问题;

    #include<iostream>
    
    #include"Func.h"
    
    using namespace std;
    
    template<class T>
    void Add(const T& a, const T& b){
        cout<<a+b<<endl;
    }
    
    template
    void Add(const int& a ,const int& b);
    
    template
    void Add<double>(const double& a ,const double& b);
    //以上两种皆可
    
    
    void func(int a,int b){
        cout<<a+b<<endl;
    }
    

    由于进行了显式实例化,在调用的过程中会根据变量的类型优先匹配该文件内的对应的声明
    template void Add<double>(const double& a ,const double& b);

    根据该声明实例化一份对应类型的函数;


  • 分离声明定义时不采用分离编译

    即不将声明与定义分离问两个文件;


总结

模板的优缺点

  • 优点

    1. 模板采用了泛型编程,可以进行代码的复用从而可以高效的进行开发;
    2. 模板增强了代码的灵活性;
  • 缺点

    1. 模板的使用将会造成代码膨胀;

      即在原来的编译过程中只需要进行语法的判断,而对于模板而言则多增加了一个步骤为模板的实例化;

      不同的类型实例化出的代码也不同从而导致代码膨胀,同时也导致了编译时间变长;

    2. 当使用模板进行开发时,若是出现模板的编译错误,则大量的错误信息使在开发过程中不能精确找到错误位置;

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

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

相关文章

Self-Supervised Learning(2021补)

文章目录 引子X mask inputNext Sentence PredictionDownstream TasksGLUEBERT的四个用法情感分析POS标注自然语言推断Natural Language Inferencee (NLI)问答&#xff08;抽取式&#xff09; BERT的衍生模型Multi-lingual BERTGPT的野望&#xff08;略&#xff09; 发现有这一…

sql语句数据库查询:如果当前元素已经使用过,下拉框不显示该元素该如何查询?

写宿舍管理系统&#xff0c;做到宿管和楼栋关系时&#xff0c;新增一个宿管&#xff0c;一个宿管管理一栋楼&#xff0c;如果当前楼栋已选择&#xff0c;那么就不能再选&#xff0c;如图所示&#xff1a; 最开始使用的是&#xff1a; SELECT DISTINCT b.building_num,b.TYPE,b…

Linux系统编程05

在代码中启动多个进程 使用system库函数启动多个进程 传统的进程调用就是我们在命令框里输入运行某个进程&#xff0c;而我们可以依靠代码&#xff0c;实现让一个进程取启动另一个进程 在进程运行过程我们使用命令ps -elf看到正在运行的有三个进程 system的调用过程 首先./…

Zoho Creator推出全新的Canvas布局设计器功能

自2021年Zoho CRM的UI设计工具——Canvas画布功能发布以来&#xff0c;受到了广泛好评&#xff0c;它的出现为CRM的页面布局形式提供了更多选择和可能&#xff0c;让CRM用户彻底告别了“单调、死板、机械”的交互页面。 8月1日&#xff0c;Zoho Creator也推出了全新的Canvas画…

聊一聊如何在Vue中使用事件总线( Event Bus)进行组件间通信

事件总线模式允许不同的组件之间进行通信。它要求一个中央枢纽&#xff0c;组件可以通过它发送和接收事件&#xff0c;从而使组件之间的数据交换和交互更加顺畅。本文探讨了它的使用方法&#xff0c;以便开发人员能够充分利用它在Vue开发中的潜力。 跨通信是应用程序中组件之间…

jmeter如何测试websocket接口?

jmeter做接口测试&#xff0c;很多人都是做http协议的接口&#xff0c;就有很多人问websocket的接口怎么测试啊&#xff1f; 首先&#xff0c;我们要明白&#xff0c;websocket接口是什么接口。 然后&#xff0c;我们怎么用jmeter测试&#xff1f; jmeter要测试websocket接口…

B/S医院手术麻醉临床系统:围术期的认识

手术是治疗很多疾病最有效而且绕不开的措施。而从医生和患者确定了要进行手术治疗的时候&#xff0c;医院相关人员就会开始围着患者团团转&#xff0c;在术前、术中和术后&#xff0c;医生会告诉患者很多事情&#xff0c;患者也会了解很多就诊相关知识。 一、围术期的认识 围术…

qml之动态元素类型

文章目录 动画例子 应用动画例子 缓动曲线例子 动画分组例子 嵌套动画代码 状态和转换代码 动画 QMlL使用插值的方式控制属性的更改。动画是在指定的时间内一些列属性的持续变化。 常用的动画类型元素动画&#xff1a;PropertyAnimation:属性值改变播放动画NumberAnimation:qr…

【快速解决】在vs2022中配置SFML图形库

目录 SFML 图形库的安装步骤如下&#xff1a; 1.下载 SFML 在 SFML 的官网&#xff08;下载对应操作系统版本的 SFML&#xff09;。​编辑 2.解压文件 将下载的压缩包解压至任意位置&#xff0c;得到类似如下的目录结构&#xff1a; 3.配置 VS 打开 Visual Studio&#xff…

Linux ————使用常用的Linux命令

&#xff08;一&#xff09;Linux命令的特点 在Linux系统的早期版本中&#xff0c;由于不支持图形用户界面&#xff0c;命令行成为了主要的操作手段。对于那些在文本模式和终端模式下需要查看系统状态和监控系统操作的用户&#xff0c;熟悉常用的Linux命令是至关重要的。以下是…

自然语言处理---Transformer机制详解之Self attention机制详解

1 Self-attention的特点 self-attention是一种通过自身和自身进行关联的attention机制, 从而得到更好的representation来表达自身. self-attention是attention机制的一种特殊情况&#xff0c;在self-attention中, QKV, 序列中的每个单词(token)都和该序列中的其他所有单词(to…

RDB.js:适用于 Node.js 和 Typescript 的终极对象关系映射器

RDB.js 是适用于 Node.js 和 Typescript 的终极对象关系映射器&#xff0c;可与 Postgres、MS SQL、MySQL、Sybase SAP 和 SQLite 等流行数据库无缝集成。无论您是使用 TypeScript 还是 JavaScript&#xff08;包括 CommonJS 和 ECMAScript&#xff09;构建应用程序&#xff0c…

高效MMdetection(3.1.0)环境安装和训练自己数据集教程(实现于Linux(ubuntu),可在windows尝试)

很久没用mmdetection了&#xff0c;作为目标检测常见的几个深度学习框架&#xff0c;mmdetection用的人还是很多的&#xff0c;其中比较吸引人的一点就是mmdetection集成了非常多的算法&#xff0c;对于想做实验对比和算法学习的人来说&#xff0c;基于这个框架可以事半功倍。因…

“暂停加息,股市低迷:242只股票创新低,比特币突破2.8万美元后看涨趋势不可挡!“

11 月1日 FOMC 会议 美联储主席杰罗姆鲍威尔周五在纽约发表讲话&#xff0c;毫不意外地&#xff0c;他采取了更加鸽派的立场&#xff0c;因为在不确定的世界中&#xff0c;美国政府的过度杠杆化和可能即将到来的经济衰退已成为共识。 根据鲍威尔对未来加息的最低限度讨论&…

Datawhale学习笔记AI +新能源:电动汽车充电站充电量预测2

在飞浆平台上成功运行出pandas-profiling啦~ 首先一键安装 pip install ydata_profiling然后演示&#xff0c;可以生成一个网页对数据有一个比较好的理解 import numpy as np import pandas as pd from ydata_profiling import ProfileReporttrain_power pd.read_csv(/home/…

【神印王座】半神级别的伊莱克斯,有着什么故事,他又为何会陨落?

【侵权联系删除】【文/郑尔巴金】 你知道手握日月摘星辰&#xff0c;世间无我这般人&#xff0c;如果我想我早已成神&#xff0c;这背后到底藏着一个什么样的故事吗&#xff1f;伊莱克斯一个配角&#xff0c;为何能让人如此痴迷&#xff1f;在最近的国漫场里&#xff0c;有两部…

Windows Server 2019 搭建FTP站点

目录 1.添加IIS及FTP服务角色 2.创建FTP账户&#xff08;用户名和密码&#xff09;和组 3.设置共享文件夹的权限 4.添加及设置FTP站点 5.配置FTP防火墙支持 6.配置安全组策略 7.客户端测试 踩过的坑说明&#xff1a; 1.添加IIS及FTP服务角色 a.选择【开始】→【服务器…

【刷题篇】反转链表

文章目录 一、206.反转链表二、92.反转链表 ||三、25. K 个一组翻转链表 一、206.反转链表 class Solution { public://使用头插//三个指针也可以ListNode* reverseList(ListNode* head) {if(headnullptr)return nullptr;ListNode* curhead;ListNode* newheadnew ListNode(0);L…

推荐一款简单好用的Bug管理软件

软件开发的速度和质量是企业成功的关键因素&#xff0c;然而随着软件项目的复杂性增加&#xff0c;Bug的管理变得越来越困难。为了解决这个问题&#xff0c;Zoho公司推出了一款强大的Bug管理软件&#xff0c;帮助企业在一个地方记录和跟踪Bug&#xff0c;提高修复效率。 这款名…

spring tx:advice事务配置—— tx:advice中不允许出现属性 ‘transaction-manager‘

今天在配置java事务管理时出现了一些问题。 提示&#xff1a;只有这几个属性 经过查询资料发现是bean的配置少了一些。 可以在xml文件顶部添加&#xff1a; xmlns:tx"http://www.springframework.org/schema/tx" 下面也提供一份bean文件配置的模板&#xff1a; &a…