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


相关文章
|
25天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
41 2
|
28天前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
43 4
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
81 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
88 4
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
31 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
26 1
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
80 6