C++——反向迭代器|反向迭代器的实现|非类型模板参数|函数模板特化 |类模板特化|全特化偏特化(半特化)|模板分离编译模板总结

简介: 笔记

反向迭代器的实现


这里以链表为例:

正向迭代器的end就是反向迭代器的rbegin,正向迭代器的begin是反向迭代器的rend


普通思维:拷贝一份正向迭代器,修改一下,使之成为反向迭代器


优化思维:既要考虑list的反向迭代器,也要考虑vector的反向迭代器


这里用复用的方法,使vector和list都能用这个反向迭代器

1.png

对于反向迭代器operator ++走到前一个位置


namespace myspace
{
  //复用,迭代器适配器
  template <class Iterator,class Ref,class Ptr>
  struct  __reverse_iterator_
  {
  Iterator cur;//通过正向迭代器实现反向迭代器
  typedef __reverse_iterator_<Iterator,Ref,Ptr> RIterator;
  __reverse_iterator_(Iterator it)//it是正向迭代器
    :cur(it)
  {}
  operator++()
  {
    --_cur;
    return *this;
  }
  operator--()
  {
    ++_cur;
    return *this;
  }
  Ref operator *()
  {
    Iterator tmp = _cur;
    --tmp;
    return *tmp;
  }
  Ptr operator ->()
  {
  return &(operator*())
  }
  bool operator !=(const RIterator& it)
  {
  return_cur != it._cur;
  }
  };
}


在list.h添加这个迭代器

2.png

传参过程

3.png

并在list.h设置rend和rbegin


4.png5.png



用这个反向迭代器适配vector

6.png

7.png

这是迭代器适配器,可适配出反向迭代器


8.png


如果类能++和--,就能通过迭代器适配器进行反向迭代


迭代器功能分类:单向迭代器,双向迭代器,随机迭代器

9.png10.png11.png

随机迭代器是一种特殊的双向迭代器


适配器真正的特点就是复用


非类型模板参数


12.png

我们可用模板创建一个数组,但是我们想让int数组开辟100个空间,double开辟1000个空间


,除过修改N之外,还能这样做,再加一个非类型模板参数,传的参数不是类型,是常量

13.png



注意:

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2. 非类型的模板参数必须在编译期就能确认结果。


库里面的array(静态数组),只支持[],不支持插入……

14.png

直接使用array越界,会被查到array不管越界读还是越界写都会被查到,因为array[]是函数调用,函数里面会进行检查是否越界

15.png

C语言中越界读检查不到,越界写能检查出来,越界写是抽查

16.png

此时又不会报错

27.png



函数模板特化 |类模板特化


通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:写一个日期类,进行日期比较


特化不能单独存在,必须依赖于原函数模板或类模板

28.png

这一组比较的结果应该是0,但打印出来了1,这种结果不是我们想要的

29.png



我们进行模板特化


特化:对某些类型进行特殊化处理


函数模板的特化步骤:

1. 必须要先有一个基础的函数模板

2. 关键字template后面接一对空的尖括号<>

3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

30.png

如果传过来的数据是Date*类型,就走下面这个特化函数


对类模板也能特化

31.png

32.png



使用优先级队列和模板特化进行排序


// 类模板
namespace bit
{
  template<class T>
  struct less
  {
  bool operator()(const T& x1, const T& x2) const
  {
    return x1 < x2;
  }
  };
  //特化
  template<>
  struct less<Date*>
  {
  bool operator()(Date* x1, Date* x2) const
  {
    return *x1 < *x2;
  }
  };
}
int main()
{
  std::priority_queue<Date, vector<Date>, bit::less<Date>> dq1;
  dq1.push(Date(2022, 9, 27));
  dq1.push(Date(2022, 9, 25));
  dq1.push(Date(2022, 9, 28));
  dq1.push(Date(2022, 9, 29));
  while (!dq1.empty())
  {
  const Date& top = dq1.top();
  cout << top._year << "/" << top._month << "/" << top._day << endl;
  dq1.pop();
  }
  cout << endl;
  std::priority_queue<Date*, vector<Date*>, bit::less<Date*>> dq2;
  dq2.push(new Date(2022, 9, 27));
  dq2.push(new Date(2022, 9, 25));
  dq2.push(new Date(2022, 9, 28));
  dq2.push(new Date(2022, 9, 29));
  while (!dq2.empty())
  {
  Date* top = dq2.top();
  cout << top->_year << "/" << top->_month << "/" << top->_day << endl;
  dq2.pop();
  }
  return 0;
}

33.png


特化不能单独存在


全特化

全特化即是将模板参数列表中所有的参数都确定化。


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

34.png


有特化就走特化,没特化就走其它的


偏特化(半特化)

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

35.png

还能这样写,只要是指针就执行以下程序


//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
  Data() { cout << "Data<T1*, T2*>" << endl; }
private:
  T1 _d1;
  T2 _d2;
};

36.png


两个参数偏特化为引用类型


//两个参数偏特化为引用类型
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;
};
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;
};

也可以这样写,只要满足某些特化条件就会进入满足条件的特化



模板分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式


模板不支持分离编译


我们用之前写过的vector,把push_back和insert进行分离编译

37.png

在vector.cpp里面实现这俩个函数


这样写会报错


加上typename这些错误消失

38.png



为了简短点,我们也可以把这俩个函数的实现放到和.h相同的域名中


39.png

加typename原因:模板参数里即可用typename也可用class,而这里的typename是告诉编译器这里是一个类型


 Typename关键字 告诉编译把一个特殊的名字解释成一个类型,在下列情况下必须对一个name使用typename关键字:


  一个唯一的name(可以作为类型理解),嵌套在另一个类型中;

  依赖于一个模板参数,就是说模板参数在某种程度上包含这个name,当模板参数是编译器在指认一个类型时便会产生误解

   为了保险起见,应该在所有编译可能错把一个type当成一个变量的地方使用typename,如果你的类型在模板参数中是有限制的,那就必须使用typename

这种情况也要加typename

40.png41.png

之后去调用push_back,报了一个链接错误

42.png43.png

这是因为push_back的声明和定义分离了


因为这里有声明,但找不到定义,调用函数都会call一个地址,但是没有找到push_bakc的地址,因为push_back没有被实例化,调用函数,调的不是模板,调的是对应的实例化,.h文件里面的函数不会去链接,因为.h里面有这些函数的定义,vector<int> v会让这些函数实例化,直接就有了定义,编译阶段会确定这些函数的地址了,而push_back和insert不一样,这俩个函数.h只有这俩个的声明,没有定义,地址就只能在链接阶段去确认。报错说明:链接阶段没有找到地址,没找到的原因:T没办法确定,所以没有实例化,push_back和insert就没有进符号表,所以没实例化

44.png

解决办法:

1.模板声明和定义不要分离到.h和.cpp


2.显示实例化(不太推荐)


在实现模板的地方显示实例化,如果要传double类型,又要写一个template vector<double>进行显示实例化,这种方法比较麻烦!

45.png

模板总结


【优点】

1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生

2. 增强了代码的灵活性

【缺陷】

1. 模板会导致代码膨胀问题,也会导致编译时间变长

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误


相关文章
|
3月前
|
存储 编译器 程序员
C++类型参数化
【10月更文挑战第1天】在 C++ 中,模板是实现类型参数化的主要工具,用于编写能处理多种数据类型的代码。模板分为函数模板和类模板。函数模板以 `template` 关键字定义,允许使用任意类型参数 `T`,并在调用时自动推导具体类型。类模板则定义泛型类,如动态数组,可在实例化时指定具体类型。模板还支持特化,为特定类型提供定制实现。模板在编译时实例化,需放置在头文件中以确保编译器可见。
41 11
|
3月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
131 0
|
4月前
|
安全 程序员 C语言
C++(四)类型强转
本文详细介绍了C++中的四种类型强制转换:`static_cast`、`reinterpret_cast`、`const_cast`和`dynamic_cast`。每种转换都有其特定用途和适用场景,如`static_cast`用于相关类型间的显式转换,`reinterpret_cast`用于低层内存布局操作,`const_cast`用于添加或移除`const`限定符,而`dynamic_cast`则用于运行时的类型检查和转换。通过具体示例展示了如何正确使用这四种转换操作符,帮助开发者更好地理解和掌握C++中的类型转换机制。
|
5月前
|
C++
使用 QML 类型系统注册 C++ 类型
使用 QML 类型系统注册 C++ 类型
122 0
|
5月前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
58 0
|
5月前
|
设计模式 安全 IDE
C++从静态类型到单例模式
C++从静态类型到单例模式
46 0
|
2天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
33 18
|
2天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
30 13
|
2天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
20 5
|
2天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
18 5