《模板的进阶》

news2025/3/1 3:44:02

本文主要介绍C++模板知识,包括模板的参数类型,模板的特化,模板的分离编译

文章目录

  • 思维导图
  • 一、非类型模板参数
  • 二、模板的特化
    • 2.1模板特化的概念
    • 2.2函数模板特化
    • 2.3类模板的特化
      • 2.3.1全特化
      • 2.3.2偏特化
    • 2.4非类型模板参数也是可以特化的
  • 三、模板的分离编译
    • 3.1分离编译的概念
    • 3.2模板的分离编译
    • 3.3解决办法
  • 总结模板


思维导图

在这里插入图片描述


一、非类型模板参数

模板参数分为:

  • 类型形参
  • 非类型形参
    我们现在想要实现一个静态的栈,我们之前可以控制它存储数据的类型,但是现在我们想要控制它的大小,我们就可以用非类型模板参数。直接传入我们想要的存储大小的栈即可。
//#define N 100
// 想要实现一个静态的栈
// 用非类型模板参数(是一个常量)替代宏
template<class T,size_t N=10>
class myStack
{
public:
	void push(const T& x);
private:
	T _a[N];
	size_t _top;
};
int main()
{
	myStack<int,100> st1;//大小为100的栈
	myStack<int,200> st2;//大小为200的栈
	return 0;
}

  • 类型形参:出现在模板参数列表中,跟在class或者typename关键字之后的参数类型名称
  • 非类型形参:用整型常量作为类(函数)模板的一个参数,在类(函数)模板中可以将该参数当做常量来使用
    注意:浮点数、类对象、字符串是不允许作为非类模板参数的
    非类模板参数必须在编译期间就能确认结果
    非类型模板参数是一个整型常量

二、模板的特化

2.1模板特化的概念

模板特化:不同于模板实例化,模板在某种特定情况下的具体实现
函数模板——可以理解为参数匹配
通常,使用模板可以实现一些与类型无关的代码,但是对于一些特殊类型的可能会得到一些错误的结果:

class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	bool operator<(const Date& d)
	{
		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;
};
template<class T>
bool ObjLess(const T& left, const T& right) 
{
	return left < right;
}
int main()
{
	cout << ObjLess(1, 2) << endl;

	Date* p1 = new Date(2022, 1, 1);
	Date* p2 = new Date(2022, 2, 1);
	cout << ObjLess(p1, p2) << endl;
	return 0;
}

p1和p2交换一下声明顺序,得出来的结果可能不一样,这是因为new的原因,new是去堆上随机申请一块空闲的空间,我们传值初始化时,T被实例化为Date*类型,比较的就是两个指针的大小,所以出问题了。

解决:
函数模板——参数匹配
我们手动写一个最匹配的函数参数的版本

// 函数模板——参数匹配
template<class T>
bool ObjLess(const T& left, const T& right) 
{
	return left < right;
}

bool ObjLess(Date*& left,  Date*& right)
{
	return *left < *right;
}
int main()
{
	cout << ObjLess(1, 2) << endl;  //->匹配第一个模板
	Date* p1 = new Date(2022, 1, 1);
	Date* p2 = new Date(2022, 2, 1);
	cout << ObjLess(p1, p2) << endl;//->匹配第二个显示实例化类型
	return 0;
}

2.2函数模板特化

函数模板特化步骤:

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

我们这里需要特化一下,即对于特殊的类型进行处理一下:
写法一:显示实例化(常用)

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

写法二:特化(专用化)-不常用

template<>// 空的尖括号
bool ObjLess<Date*&>( Date*& left,  Date*& right)
{
	return *left < *right;
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出,函数模板中,这种特化很鸡肋。

2.3类模板的特化

特化顾名思义就是特殊化,比如需要针对某些类型特殊化处理

特化步骤:

  1. 必须要先有一个基础的类模板
  2. 关键字template后面接一对空的尖括号<>
  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;
};
void Test()
{
	Data<int, int> d1;//调用第一个普通模板
	Data<int, char> d2;//调用第二个全特化模板
}

2.3.2偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

偏特化有以下两种表现方式:
第一种:部分特化

// 将第二个参数特化为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>
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(const T1& d1, const T2& d2): _d1(d1), _d2(d2)
 {
 cout<<"Data<T1&, T2&>" <<endl;
 }
private:
 const T1 & _d1;
 const T2 & _d2; 
 };
void test2 () 
{
 Data<double , int> d1; // 调用特化的int版本
 Data<int , double> d2; // 调用基础的模板 
 Data<int *, int*> d3; // 调用特化的指针版本
 Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}

2.4非类型模板参数也是可以特化的

template<size_t N>
class A
{
public:
	A() { cout << "A<N>" << endl; }
};
template<>
class A<10>
{
public:
	A() { cout << "A<10>" << endl; }
};
void test()
{
	A<20> aa1;
	A<10> aa2;
}

三、模板的分离编译

3.1分离编译的概念

.h:函数声明和结构定义
.cpp:函数定义
平时的项目中,应该做到声明和定义分离,不然难以维护项目
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
模板是不支持分离编译

3.2模板的分离编译


// func.h
#include<iostream>
using namespace std;

void func1(int x);

template<class T>
void func2(const T& x);
/
// func.cpp
#include"func.h"
void func1(int x)
{
	cout << "void func1(x)" << endl;
}

template<class T >
void func2(const T& x)
{
	cout << "void func2(const T& x)" << endl;
}
//
// test.cpp
#include"func.h"
int main()
{
	func1(10);
	func2(20);
	return 0;
}

在这里插入图片描述
这是什么原因呢?
func.h func.cpp test.cpp

  1. 预处理——头文件展开/条件编译/宏替换/去掉注释…
    生成:vector.i test.i
  2. 编译——检查语法问题,没有问题,就生成汇编代码
    生成:vector.s test.s
  3. 汇编——把汇编代码转成二进制机器码
    生成:vector.o test.o
  4. 链接——把所有的.o文件链接起来形成obj文件
    原因就是:模板T不知道具体类型,在编译时不处理,没有实例化,没有生成汇编代码,而正常的函数是可以找到汇编地址,并在链接时调用.

在这里插入图片描述

3.3解决办法

  1. 将声明和定义放到一个文件 xxx.hpp 或者xxx.h。推荐使用这种。
// func.h
#include<iostream>
using namespace std;

void func1(int x);

// 声明
template<class T>
void func2(const T& x);
// 定义
template<class T >
void func2(const T& x)
{
	cout << "void func2(const T& x)" << endl;
}

那么使用它的地方,头文件展开以后,直接就有模板定义和实例化,那么直接就可以填上函数调用地址。不需要链接去找。
这样普通函数是链接时去其他文件找,而模板函数是编译时就确定了地址。
由于声明和定义放在一起,维护性就会变得较差。

  1. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
// 显示实例化
// func.cpp
#include"func.h"
void func1(int x)
{
	cout << "void func1(x)" << endl;
}
template<class T >
void func2(const T& x)
{
	cout << "void func2(const T& x)" << endl;
}
// 解决办法显示实例化
template
void func2<int>(const int& x);

总结模板

优点

  • 模板复用了代码,节省资源,可以进行更快的迭代开发,C++STL因此而生成
  • 增加了代码的灵活性

缺点

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

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

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

相关文章

Android 调用系统隐藏的类和方法

1.Android系统隐藏的类和方法 阅读Android源码时&#xff0c;会发现很多被UnsupportedAppUsage注解的方法&#xff0c;这些方法不能被外部应用访问。 比如Android中的PackageParser类&#xff0c;这个类是在android.content.pm包下面&#xff1a; 可以看到这个类是隐藏的&…

有哪些自动化构建工具推荐? - 易智编译EaseEditing

以下是几个常用的自动化构建工具推荐&#xff1a; Jenkins&#xff1a; Jenkins 是一个开源的自动化构建工具&#xff0c;广泛用于持续集成和持续交付。 它支持各种编程语言和版本控制系统&#xff0c;并提供了丰富的插件生态系统&#xff0c;可实现灵活的构建流程和自动化部…

【Spring】— 动态SQL :<choose>、<when>和<otherwise>元素

<choose>、<when>和<otherwise>元素 在使用<if>元素时&#xff0c;只要test属性中的表达式为true&#xff0c;就会执行元素中的条件语句&#xff0c;但是在实际应用中&#xff0c;有时只需要从多个选项中选择一个执行。例如&#xff0c;若用户姓名不为…

九、Docker网络

Docker网络 一、docker网络介绍 Docker网络在Docker的基础知识中算比较重要的了&#xff0c;需要多多实验理解。 Docker服务安装启动后默认在host上创建了三个网络&#xff1a; [rootk8s-m1 ~]# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UN…

毛毛莫名的大一生活总结

毛毛莫名的大一生活总结 1. 关于高考我的看法1.1 初中1.2 高中 2. 大一开学前的暑假3. 大一开学3.1 军训3.2 学生会 学校社团 运动团体3.2.1 学生会3.2.1.1 院学生会3.2.1.2校学生会 3.2.2 社团3.2.3 运动团体 4. 大学生活 1. 关于高考我的看法 1.1 初中 初一初二对学习不用太…

一种简单的Android骨架屏实现方案----0侵入0成本

对骨架屏的理解 什么是骨架屏 所谓骨架屏&#xff0c;就是在页面进行耗时加载时&#xff0c;先展示的等待 UI, 以告知用户程序目前正在运行&#xff0c;稍等即可。 等待的UI大部分是 loading 转圈的弹窗&#xff0c;有的是自己风格的小动画。其实大同小异。而骨架屏无非也是一…

本地部署github上的stable diffuion,轻松玩转ai绘画(新手小白也能懂)

你也想自己生成上面这样好看的图片吗&#xff1f;废话不多说&#xff0c;跟着博主&#xff0c;按步骤来&#xff0c;做完你也就可以了&#xff0c;而且无任何限制&#xff0c;懂得都懂&#xff01;&#x1f60e; 目录 第一步&#xff1a;准备VPN 第二步&#xff1a;安装Pyth…

LabVIEWCompactRIO 开发指南第七章48

LabVIEWCompactRIO 开发指南第七章48 5.如果控制器上已经安装了LabVIEW实时和NI-RIO&#xff0c;请选择自定义软件安装并点击下一步。如果出现警告对话框&#xff0c;请单击“是”。单击NI-工业通信用于EtherCAT的框。将自动检查所需的依赖项。单击下一步继续在控制器上安装软…

一款开源的无线CMSIS DAP ARM芯片下载调试器详细说明

文章目录 概要1. 一般概念1.1 CMSIS—DAP的一般概念1.2 支持的芯片1.3 典型应用场景 2. 原理图与尺寸图2.1 Host端&#xff08;发送端&#xff09;原理图2.2 Target&#xff08;目标&#xff09;端原理图2.3 Host尺寸图2.4 Target尺寸图2.5 实物图 3. 使用方法3.1 连接方法3.1.…

群晖折腾记1—群晖NAS使用docker中的ddns-go,DDNS阿里云ali实现Ipv6访问

群晖折腾记1—群晖NAS使用docker中的ddns-go,DDNS阿里云ali实现Ipv6访问 前置条件科普时间具体步骤1. 域名购买2. 获取AccessKey ID和Secret3. 获取免费SSL证书4. 在docker中运行ddns-go6.设置反向代理 前置条件 1、移动宽带只有IPv6公网地址&#xff0c;若你的宽带为电信或联…

Eslint配置指南

ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。ESLint 是一个开源的 JavaScript 代码检查工具&#xff0c;它是用来进行代码的校验&#xff0c;检测代码中潜在的问题&#xff0c;比如某个变量定义了未使用、函数定义的参数重复、变量名没有按规范命名等等。 中…

logging 模块因权限问题写入日志失败

哈喽大家好&#xff0c;我是咸鱼 今天跟大家分享一个使用 Python 的 logging 模块写入日志文件时遇到的权限问题&#xff0c;不知道你们有没有遇到过 1.案例现象 今天上班的时候手机短信收到了 zabbix 告警&#xff0c;但是发现了不对劲的地方&#xff1a;微信没有收到告警信…

【C++】虚函数相关常见问题

【C】虚函数相关常见问题 文章目录 【C】虚函数相关常见问题1.说说为什么要虚析构&#xff1f;2. C默认的析构函数为什么不是虚函数?3. 构造函数能不能是虚函数4. 说说什么是虚继承&#xff0c;解决什么问题&#xff0c;如何实现&#xff1f;5. 说说什么是虚函数6.说说虚函数的…

VESC操作入门——PPM输入控制和ADC输入控制

目录 一、PPM输入控制1.1、硬件准备1.2、PPM信号1.3、校准电机1.4、输入设置 二、ADC输入控制2.1、硬件准备2.2、更改固件2.3、电压信号2.4、校准电机2.5、输入设置 三、电动车转把控制3.1、转把说明3.2、转把测试 ODrive、VESC和SimpleFOC 教程链接汇总&#xff1a;请点击 一、…

SpringBoot作日志切面记录

目录 1.WebLogAspect 2.配置log4j2.yml 3.效果 话不多说&#xff0c;直接上代码&#xff1a; 1.WebLogAspect import java.util.Arrays;import javax.servlet.http.HttpServletRequest;import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.Str…

第Y2周:训练自己的数据集

我的环境&#xff1a; 训练自己的数据集 一、准备自己的数据集1. 编写split_train_val.py文件2.生成训练文件索引文件 二、创建训练yaml文件三、开始训练 一、准备自己的数据集 数据集来源&#xff1a;kaggle水果检测 目录结构如下&#xff1a; 1. 编写split_train_val.py…

ClickHouse基本使用总结

查看系统配置 查看系统表 select * from system.clusters; 验证zookeeper #验证zookeeper是否与当前数据库clickhouse进行了正确的配置 SELECT * FROM system.zookeeper WHERE path /clickhouse; 建表 创建本地表 MergeTree&#xff0c;这个引擎本身不具备同步副本的功能&…

Kali Linux 2023.2 发布(Hyper-V 和 PipeWire)

Kali Linux 2023.2 发布&#xff08;Hyper-V 和 PipeWire&#xff09; 请访问原文链接&#xff1a;https://sysin.org/blog/kali-linux-2023/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 更新说明 2023 年 5 月 30 日&…

国产上新!芯驰D9多核Cortex-A55核心板,国产车规级平台

随着信息技术的快速发展&#xff0c;市场对芯片的需求越来越大&#xff0c;中国芯片行业自20世纪80年代开始起步&#xff0c;经过近40年的努力&#xff0c;也进入了一个新的时代&#xff0c;芯片国产化乃未来发展的大势所趋。米尔电子作为行业领先的嵌入式模组厂商&#xff0c;…

【Difussion Model】理解和编程

目录 理论框架text-to-imgaedecodergeneration modelclip的原理 FID指标&#xff1a;评估图像生成的好坏数学原理 理论 框架 不断的进行去噪&#xff0c;并且在这个过程中&#xff0c;step也作为“去噪模型&#xff08;其实就是扩散模型&#xff09;”的输入&#xff1a; de…