【C++】模板

server/2024/10/18 6:11:19/

Ⅰ、非类型模板参数

  • 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数返回值取得任意类型。

  • 模板是一种对类型进行参数化的工具;

  • 可以编写与类型无关的代码;

说明

  1. template和class是关键字,typename可以代替class。暂时class与typename没有区别。
  2. 尖括号 <> 中的参数叫模板形参,模板形参和函数形参类似,但不能为空。
  3. 一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,在该函数中使用内置类型的地方都可以使用模板形参名
  4. 模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了一个实例。

注意模板的声明和定义只能在全局,命名空间或类中进行。不能再局部或者函数内进行。比如main函数。

模板参数分类:类型形参非类型形参
  • 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
  • 非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;
};
  • 上面代码中的N就是非类型的模板形参。 
注意
  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的。只能是整型,指针和引用。
  • 调用非类型模板形参的实参必须是一个常量表达式,非类型的模板参数必须在编译期就能确认结果
  • 任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
  • 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
  • 另外,sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。

注意:这里只能是用size_t类型作为非类型模板参数,不能用浮点数,类对象等,因为在C++20之前,这些还没有设计出来,只有size_t可以用;

非类型模板形参的形参和实参间所允许的转换

允许从数组到指针,从函数到指针的转换。即数组到指针的转换。如:

template < int *a> 
class A{}; 
int b[1]; 
A< b> m;

修饰符的转换。即从int 到const int 的转换。如:

template< const int * a > 
class A{}; 
int b; 
A<&b> m;


提升转换。即从short到int 的提升转换。如:

template class A{}; 
const short b=2; 
A m(b);


整值转换。即从int 到unsigned int的转换。如:

template class A{};A<3> m; 

模板按需实例化

  • 当我们实例化一个类时,我们只需要实例化我们用到的那些函数就可以,没有用到的不会实例化 

Ⅱ、模板的特化

  • 通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结,需要特殊处理。
    • 比如:实现了一个专门用来进行小于比较的函数模板
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 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指针的地址,这就无法达到预期而错误。
  • 此时,就需要对模板进行特化
    • 即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化
函数模板的特化步骤:(函数模板一般重载,不特化。实现简单明了,代码的可读性高,容易书写)
  •  必须要先有一个基础的函数模板
  •  关键字template后面接一对空的尖括号<>
  •  函数名后跟一对尖括号,尖括号中指定需要特化的类型
  •  函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了return 0;
}
注意 :一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给 出。
类模板特化:
全特化
  • 全特化即是将模板参数列表中所有的参数都确定化。
例如:
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 TestVector()
{Data<int, int> d1;Data<int, char> d2;
}
偏特化
偏特化有以下两种表现方式:
部分特化
  • 将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};
参数更进一步的限制
  • 偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}private:
T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename 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; };

Ⅲ、模板分离编译

什么是分离编译
  • 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
模板的分离编译
// 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;
}
  • 这种做法最后在链接时会出现错误

原因

  • 我们处理文件都是先经过预处理,编译,汇编,最后一起链接起来。
  • 每个文件是分开编译的,所以,当编译.cpp文件时,模板不知道T是什么类型,就没办法实例化。当链接的时候,main.cpp中找不到生成模板实例化的地址,就会报错,原因就在于模板没有实例化,
  • 而当我们把模板的声明和定义写在一起,模板编译时就会生成对应的实例化模板,这样就可以避免这个问题了,

当然还有一个办法,就是在模板定义的位置显式实例化,但是这种方法麻烦,又不实用,所以不推荐

解决方法
  • 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
  • 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
模板总结

http://www.ppmy.cn/server/131342.html

相关文章

微软发布Windows 11 2024更新,新型Copilot+ AI PC功能亮相

前言 微软在Windows 11的2024更新中加强了对人工智能的应用&#xff0c;推出了新功能Copilot。 此次更新的版本号为26100.1742&#xff0c;Copilot将首先在Windows Insider中推出&#xff0c;计划于11月向特定设备和市场推广&#xff0c;用户需开启“尽快获取最新更新”选项以…

【layui】多文件上传组件实现

插件预览效果&#xff1a; 需要引入layui的脚本文件layui.js和样式文件layui.css html代码&#xff1a; <div class"layui-input-block"><div class"layui-upload-list"><table class"layui-table"><colgroup><col…

开源商城系统crmeb phpstudy安装配置

BOSS让我最快时间部署一套开源商场系统&#xff0c;今天就以crmeb为例。 快速部署在linux中我会首选docker&#xff0c;因为我要在windows中部署&#xff0c;本文就选用phpstudy集成环境做了。 什么是crmeb 我从官网摘点&#xff1a; CRMEB产品与服务 CRMEB通过将CRM&#x…

ROS2 通信三大件之动作 -- Action

通信最后一个&#xff0c;也是不太容易理解的方式action&#xff0c;复杂且重要 1、创建action数据结构 创建工作空间和模块就不多说了 在模块 src/action_moudle/action/Counter.action 下创建文件 Counter.action int32 target # Goal: 目标 --- int32 current_value…

Python知识点:基于Python工具,如何使用Seq2Seq进行机器翻译

开篇&#xff0c;先说一个好消息&#xff0c;截止到2025年1月1日前&#xff0c;翻到文末找到我&#xff0c;赠送定制版的开题报告和任务书&#xff0c;先到先得&#xff01;过期不候&#xff01; 如何使用Python工具进行Seq2Seq机器翻译 概述 Seq2Seq&#xff08;Sequence-to…

无心剑七绝《泊院雕楼》

七绝泊院雕楼 清歌咏尽桂花香 泊院雕楼醉夕阳 逸兴无端飞万里 幽情宛转忆潇湘 2024年10月13日 平水韵七阳平韵 这首七绝《泊院雕楼》以清新脱俗的语言&#xff0c;描绘了一幅宁静致远的画面。 首句“清歌咏尽桂花香”&#xff0c;以“清歌”起兴&#xff0c;形象地描绘了桂花香…

利用Spring Boot构建高效B2B医疗病历平台

第1章绪论 计算机已经从科研院所&#xff0c;大中型企业&#xff0c;走进了平常百姓家&#xff0c;Internet遍及世界各地&#xff0c;在网上能够用计算机进行文字草拟、修改、打印清样、文件登陆、检索、综合统计、分类、数据库管理等&#xff0c;用科学的方法将无序的信息进行…

linux 中mysql my.cnf 配置模版

前置准备 sudo systemctl stop mysqld 注意&#xff1a; 原本配置重命名做备份 备份数据 删文件 直接新建 my.cnf 把配置 11要粘进去的内容 直接粘进去 注意&#xff1a;尽管log-bin 和 log_bin 都可以启用二进制日志&#xff0c;但为了保持与现代MySQL版本的兼容性和一…