当前位置: 首页 > news >正文

【C++】模板进阶

一、非类型模板参数

模板参数分:类型形参非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

下面size_t N中的N为非类型形参,class T中的T为类型参数。

template<class T, size_t N>
class Stack
{
public://void func()//{//	N++;//N是常量不能修改,但是在编译的时候不会出现错误//}
private:int _a[N];int _top;
};

 C++20之前,只允许整形做非类型模板参数
 C++20,可以支持double(x64)等内置类型 

//C++20
template<double X,int* ptr>
class A
{};

 注意:
1. 浮点数(float(x86,x64),double(x86))、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。 

 这里介绍一个非常鸡肋的头文件

#include<array>

先说这个好处,这个头文件的好处。

看下面图片,如果我们使用静态数组难免会出现越界访问,且编译器也默认没有错误。 

如果使用array则会暴力检查出错误 

 array中使用了非类型模板参数

为什么说array有些鸡肋呢? 

因为vector可以完全替代array

下面的代码注意: 

template<class T>
void PrintVector(const vector<T>& v)
{// 没有实例化的类模板没实例化时,不能去里面查细节东西。typename vector<T>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;
}
int main()
{vector<int> v1 = { 1, 2, 3 };PrintVector(v1);return 0;
}

这里需要添加typename来明确是类型,因为vector<T>没有实例化,不敢去查const_iterator所以编译器会发生报错

 二、模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。这就是模板的特化。

2.1函数模板特化 

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

template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; Date d1(2022, 6, 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<>
bool Less<Date*>( const Date* & left, const Date* & right)
{return *left < *right;
}

我们会发现上面的代码出现了语法不匹配,为什么?

首先,我们先看模板,模板的形参是const T& left,const修饰的是letf,而特化中const修饰的是* left出现了语法不匹配。所以我们要这样修改

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

 注意:一般函数特化会直接简化形成函数,给这个类型专门匹配一个函数。

2.2类模板特化

 类模板特化在实践当中是非常有用的

2.2.1全特化

//类模板
template<class T1, class T2>
class Data
{
public://实现1功能Data() { cout << "Data<T1, T2>" << endl; }
};
//特化
template<>
class Data<int, char>
{
public://实现2功能Data() { cout << "Data<int, char>" << endl; }
};
int main()
{Data<int, int> d1;Data<int, char> d2;return 0;
}

2.2.2偏特化与半特化

//偏特化/半特化
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>->偏特化" << endl; }};// 两个参数偏特化为指针类型
template <class T1, class T2>
class Data <T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>->偏特化" << endl;cout << sizeof(T1) << endl;cout << sizeof(T2) << endl;cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;}
};
// 限定引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data()	{	cout << "Data<T1&, T2&>" << endl;	}
};

我们不难发现在两个偏特化转化为指针类型,对象d4传的参数为double*,int*。偏特化T1为double,T2为int

三、模板分离编译 

首先我们要知道C/C++程序要运行,要经历预处理 ---> 编译 ----> 汇编 ----> 链接

编译:对程序按照语言特性进行词法、语法、语法分析,错误检查无误后生成汇编代码。

注意:头文件不参与编译,但其他.cpp包含的文件中展开。编译器对工程中的多个源文件是分离开单独编译的。

链接:将多个obj文件合并成一个,并处理没有解决的地址问题。

下图,我们不难发现,普通函数在.h文件进行函数声明且.cpp进行定义时不会在链接时找不到地址 

若是模板进行分离

 Func.cpp中的Add不会编译,生成指令,因为模板没有被实例化。当然也就,没有Add的地址放进符号表,所以链接也就找不到

main.cpp中调用的地方,知道模板实例化成什么,但是只有声明,没有定义。

那么如何进行模板分离?

在Func.cpp中我们进行了模板声明,我们不难发现,我们在声明的代码中把T替换成int与double,使其进行了实例化,但是当main函数中添加Add('x','y');我们又要在Func.cpp进行字符类型的实例化这样十分麻烦。

所以一般把模板定义在.h文件中,如果是模板类,就一般定义在.h中类的外面 


http://www.mrgr.cn/news/11184.html

相关文章:

  • 基于微信小程序的外卖订餐系统设计与实现
  • Linux网络编程——C/C++Web服务器(二):IO多路复用select/poll/epoll实现服务器监听多客户端事件
  • 剪画:刷到禁止下载的视频,学会它照样提取音频!
  • ssrf+redis未授权访问写入任务计划
  • 岩土工程中的有限单元法:渗流问题的理论探索与编程实践
  • springboot springmvc spring区别
  • 上线eleme项目
  • 【2024年Python股票量化分析】实测可用的免费股票数据接口集合:实时数据、历史数据、基本面数据、财务数据API文档
  • sql高并发如何解决
  • Nginx 反向代理实现 Tomcat 高可用性负载均衡详解
  • 基于单片机的电子指南针设计
  • Excel公式与图表自动化:在Python中操作Excel公式并自动化生成图表
  • redis的RDB快照配置详解
  • 浅说树及其基本性质(上)
  • Axure设计之动态条形图教程(中继器)
  • MySQL(面试篇)
  • Neo4j - CQL简介
  • LoadBalancer负载均衡
  • 前端实现两张图片合成,图片换背景,简单p图程序
  • 【电脑使用耳机录音注意事项】