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

再探模板与泛型编程

本章目标

0.typename的特殊使用场景
1.非类型模板参数
2.模板特化
3.模板分离编译

0.typename的特殊使用场景

在定义模板参数的时候,我们会可以用class定义,也可以用typename定义.
这两者在此时的使用并没有什么区别
而在去模板的内嵌类型的时候,我们必须加上typename来表示,它是模板的内嵌类型

template <class container>
void print(const container& con)
{typename container::const_iterator it = con.cbegin();while (it != con.cend()){cout << *it << " ";it++;}cout << endl;}

如果我们不加typename提前声明它是模板的内嵌类型的话,编译器并不知道它是什么,如果当前的这个const_iterator,它可能会被认为是静态成员变量,那么此时就是不符合语法的.

1.非类型模板参数

在前面我们了解到模板支持泛型编程,我们template来定义模板,用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;}

这样的好处是它很大程度上代替了宏,我们知道,因为宏本身带来的危险,在c++中是尽量去避开宏的使用,之前有介绍过用inline函数去代替宏定义的函数,而在这里同样的,因为宏本身的缺陷,我们并不考虑使用.
这样用非类型模板参数的好处是我可以随时调节N的大小

array<int,10> s1;
array<double ,100> s2;

我们在这里定义两个array类的对象,它们的容量分别是10和100.
如果我们使用宏的话,我们仅能定义一种大小的对象.

2.模板特化

在模板中,我们可以写出与类型无关的代码,来进行泛型编程.
但是在有些情况下,因为特殊的类型的原因,可能会出错

class Date
{
public:Date(int year = 1900, 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& _cout, const Date& d);
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
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来进行比较两个地址,那么它们的结果一定是随机的.
因为函数,我们写的是一个传值操作.比较两个地址的值.

2.1函数模板特化

我们可以可以模板特化一个date*类型的函数解决
以下是函数模板特化的步骤

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

它很像我们之前写过的函数模板的显示实例化.但跟其是不同的.显示实例化的它的底层实现是相同的.模板特化正是因为它的底层在当前类型没法使用了,我们需要在实现出一份

template<>
bool Less<Date*>( Date* p1,  Date*  p2)
{return *p1 < *p2;
}

但是对于函数模板特化要注意的情况是在参数列表中出现了const的情况

template <class T>
bool Less(const T& p1, const T& p2)
{return p1 < p2;
}
template<>
bool Less<Date*>( Date*const& p1,  Date* const& p2)
{return *p1 < *p2;
}

在正常模板中的const修饰的是p1和p2本身不能够修改.
而在特化模板中,我们不能把const放到*的左边那样是表示指针指向的对象不能够修改.
我们想要的是指针本身不能被修改.所以要放到指针的右边.
不过我们对于函数模板特化,我们并不建议使用,我们可以另写一个函数,让它与模板构成函数重载

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

2.2类模板特化

类模板特化是分为两种一种是全特化,另一种是偏特化.

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;} 

我们可以指定类的参数.来另外实现一个类.

2.2.1偏特化

偏特化分为两种,第一种是偏特化是我们只特化部分参数

 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;};

在后面的priority_queue的模拟实现,我们会进一步进行解释.

3.模板分离编译

在前面我们介绍模板一般情况下我们是不会进行模板分离编译的,
分离编译会出现链接错误.
为了解释是什么链接错误,让我们先回顾一下编译型语言生成可执行程序的过程.
1.预处理
展开头文件,宏替换,条件编译.去掉注释.
2.编译
检查语法,将代码转为汇编语言
3.汇编
将汇编语言转为二进制机器码
4.链接
将它们之前的链接在一起,生成可执行程序
在链接前,各个文件是不会有交互的,如果我们将模板进行声明定义分离,就会导致模板不会实例化对象
我们举个例子

//test.h
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<list>
using namespace std;
template <class container>
void print(const container& con);
//test.cpp
#include"test.h"
template <class container>
void print(const container& con)
{typename container::const_iterator it = con.cbegin();while (it != con.cend()){cout << *it << " ";it++;}cout << endl;
}
#include"test.h"
int main()
{/*Student s1;s1.Print();*/vector<int> v = { 1,2,34,5 };print(v);return 0;
}

在主函数中只有声明并且知道模板应该实例化成什么,但没有定义.
而在test.cpp的文件中,知道模板的定义,但没有实例化,因为不知道模板参数的类型
因为实例化一定实在链接之前的阶段完成的.在连接时,当主函数根据函数声明去其他文件的符号表中找函数定义,因为没有实例化,也就没有定义.
这样就会报链接错误.
当然也是有解决办法的就是显示实例化出一份模板

template
void print<vector<int>>(const vector<int>& con);

在test.cpp的文件中就解决了.
但我们仍然建议在头文件中定义模板,因为这样头文件展开之后,模板就知道实例化成什么了,因为在同一个文件当中.这样也就不会出现链接错误了.

相关文章:

  • sizeof和strlen区分,(好多例子)
  • 52单片机LED实验
  • An Improved Fusion Scheme for Multichannel Radar Forward-Looking Imaging论文阅读
  • DAY 50 leetcode 1047--栈和队列.删除字符串中的所有相邻重复项
  • 每日一道leetcode(补充版)
  • AI提效思考 - 第一期
  • 线程基础题
  • 【Elasticsearch入门到落地】11、RestClient初始化索引库
  • 【最后203篇系列】029 基于Machinations构造回测系统
  • SQL注入 01
  • 机器学习专栏(4):从数据饥荒到模型失控,破解AI训练的七大生死劫
  • 实现对象之间的序列化和反序列化
  • Kubernetes控制平面组件:调度器Scheduler(一)
  • Java 软件测试开发相关资源
  • DSA数据结构与算法 6
  • 快速从S32K358切换到328
  • 在阿里云和树莓派上编写一个守护进程程序
  • NLP 梳理03 — 停用词删除和规范化
  • Python 深度学习实战 第11章 自然语言处理(NLP)实例
  • 嵌入式芯片中的 SRAM 内容细讲
  • 阻燃材料点火就着引发一场火灾,河北一企业的产品被指不达标且涉嫌欺诈
  • “80后”师虎已任陕西旬邑县委书记
  • 日方炒作中国社会治安形势不佳,外交部:政治操弄意图明显
  • 郑州卫健委通报郑飞医院“血液净化”问题:拟撤销该院血液净化技术备案
  • 30小时已过,俄罗斯复活节停火不再延长
  • 普京宣布临时停火30小时