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. 出现模板编译错误时,错误信息非常凌乱,不易定位错误


相关文章
|
1月前
|
存储 编译器 程序员
C++类型参数化
【10月更文挑战第1天】在 C++ 中,模板是实现类型参数化的主要工具,用于编写能处理多种数据类型的代码。模板分为函数模板和类模板。函数模板以 `template` 关键字定义,允许使用任意类型参数 `T`,并在调用时自动推导具体类型。类模板则定义泛型类,如动态数组,可在实例化时指定具体类型。模板还支持特化,为特定类型提供定制实现。模板在编译时实例化,需放置在头文件中以确保编译器可见。
32 11
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
79 2
|
1月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
|
2月前
|
程序员 C++ 容器
C++编程基础:命名空间、输入输出与默认参数
命名空间、输入输出和函数默认参数是C++编程中的基础概念。合理地使用这些特性能够使代码更加清晰、模块化和易于管理。理解并掌握这些基础知识,对于每一个C++程序员来说都是非常重要的。通过上述介绍和示例,希望能够帮助你更好地理解和运用这些C++的基础特性。
41 0
|
2月前
|
安全 程序员 C语言
C++(四)类型强转
本文详细介绍了C++中的四种类型强制转换:`static_cast`、`reinterpret_cast`、`const_cast`和`dynamic_cast`。每种转换都有其特定用途和适用场景,如`static_cast`用于相关类型间的显式转换,`reinterpret_cast`用于低层内存布局操作,`const_cast`用于添加或移除`const`限定符,而`dynamic_cast`则用于运行时的类型检查和转换。通过具体示例展示了如何正确使用这四种转换操作符,帮助开发者更好地理解和掌握C++中的类型转换机制。
|
3月前
|
C++
使用 QML 类型系统注册 C++ 类型
使用 QML 类型系统注册 C++ 类型
56 0
|
4月前
|
编译器 C++ 运维
开发与运维函数问题之函数的返回类型如何解决
开发与运维函数问题之函数的返回类型如何解决
38 6
|
4月前
|
算法 编译器 程序员
C++为什么有参数依赖查找(ADL)?
为什么在限定名称查找和非限定名称查找之外,C++还要提供参数依赖查找这样的机制呢?它其实是在规范的查找框架下,提供了一种灵活性的补充
|
3月前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
42 0
|
4月前
|
编译器 C++ 容器
C++一分钟之-可变模板参数与模板模板参数
【7月更文挑战第21天】C++的模板实现泛型编程,C++11引入可变模板参数和模板模板参数增强其功能。可变模板参数(如`print`函数)用于处理任意数量的参数,需注意展开参数包和递归调用时的处理。模板模板参数(如`printContainer`函数)允许将模板作为参数,需确保模板参数匹配和默认值兼容。这些特性增加灵活性,但正确使用是关键。
57 4