[C++随想录] 模版进阶(上)

简介: [C++随想录] 模版进阶(上)

模版中 class 与 typename

一般情况下, 我们定义一个模版, 模版中的 class/ typename 的意义是一样的.

但是, 有一种情况除外👇👇👇

template<class Container>
void Print(const Container& v)
{
  Continer::iterator it = v.begin();
  while (it != v.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
}

🗨️这是为什么呢?


首先, iterator 迭代器是属于类的一种类型, 我们要指定类域 ⇒ 即Continer应该是一种类型才对

其次, 编译器是从上到下编译的, 所以此时模版还没实例化 ⇒ 编译器不清楚Continer是一种类型还是一种对象?

编译器为什么会有这种疑惑呢?

因为 用 :: 调用内部成员有两种方式: 1. 类型 2.静态成员对象

此时我们这里需要的是 类型 ⇒ 所以, 我们需要在前面加上 typename, 从而告诉编译器虽然这里还没有实例化, 但是这里是一种类型👇👇👇

template<class Container>
void Print(const Container& v)
{
  typename Container::iterator it = v.begin();
  while (it != v.end())
  {
    cout << *it << " ";
    ++it;
  }
  cout << endl;
}

非类型模版参数

🗨️根据前面的学习, 我们知道了模版的参数可以是 类型, 容器适配器等, 它们都是一种变量, 是一种类型~~, 有没有一种模版参数是常量的, 不可改变的呢?


其实, 模板参数是分两类的:

类型模版参数 — — 在模版参数中跟在class/ typename的后面充当一种 类型

非类型模版参数 — — 用 常量 来充当模版的一个参数, 在函数/ 类中就当做一个常量使用

构造一个静态数组来练练手:

namespace muyu
{
  template<class T, size_t N = 10>
  class Array
  {
  public:
    Array()
    {
    }
    T& operator[](size_t pos)
    {
      return _arr[pos];
    }
    const T& operator[](size_t pos) const
    {
      return _arr[pos];
    }
    size_t size() const
    {
      return _size;
    }
  private:
    T _arr[N];
    size_t _size = N;
  };
}
void test_Array()
{
  muyu::Array<int, 10> arr;
  for (int i = 0; i < arr.size(); i++)
  {
    arr[i] = i;
    cout << arr[i] << " ";
  }
  cout << endl;
}
int main()
{
  test_Array();
  return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

总结:

  1. 非类型模版参数是常量, 不能修改
  2. 非类型模版参数必须是 整形家族



非类型模版参数的应用


array数组 是非常的鸡肋, 跟 普通的数组没有什么两样, 还是 C++11 更新的😥😥😥

模版的分离编译

🗨️什么是分离编译?


一个程序由多个源文件共同实现的, 每个源文件单独生成目标文件. 最后将所有的目标文件链接起来形成一个统一的可执行文件的过程.

接下来, 我们来看一下模版的分离编译的情况:

// stencil.h
template<class T>
// 声明
T& Add(const T& x, const T& y);
// implement.cpp
// 定义
template<class T>
T Add(const T& x, const T& y)
{
  return x + y;
}
// main.cpp
int main()
{
  Add(1, 2);
  return 0;
}

🗨️为什么模版的分离编译会出现 链接错误 ?


首先, 编译的四个阶段 :预处理, 编译, 汇编, 链接

编译阶段 — — 对代码进行 语法分析, 语义分析, 如果没有什么问题, 就生成汇编

链接阶段 — — 将多个 .o文件 链接形成一个 目标文件, 同时检查 地址问题

比如: 普通函数的声明与定义分离:

编译阶段 — — 如果 语法检查, 语义检查没什么问题, 虽然定义没有, 但可以做一个承诺 -- 它的定义是有的, 先让它过去, 等链接阶段在深层次检查

链接阶段 — — 进一步检查是否有定义(地址)

🗨️普通函数是可以的, 为啥模版的分离编译有问题?

先搞清楚, 普通函数的参数类型是已知的, 而模版参数是未知的 ⇐ 因为还没有模版实例化.

C++编译器在处理 函数模版 和 类模版的时候, 要进行实例化函数模版 和 类模版, 要求编译器在实例化模版时必须在上下文可以查看到其定义实体; 而反过来, 在看到实例化模版之前, 编译器对模版的定义是不做处理的. 原因很简单, 编译器怎么会预先知道 typename实参 是什么呢?

🗨️那怎么样才能实现模版的分离编译呢?


1. 在定义的地方 显示实例化

不推荐这种, 因为不同类型就要显示实例化多次 ⇒ 那么就失去了模版的意义~~

// stencil.h
template<class T>
// 声明
T& Add(const T& x, const T& y);
// implement.cpp
// 定义
template<class T>
T Add(const T& x, const T& y)
{
  // 显示实例化
  template
  class Add<int, int>
  return x + y;
}
// main.cpp
int main()
{
  Add(1, 2);
  return 0;
}
  1. 将模版的声明与定义写在同一个文件中, 文件可以命名为 .hpp 或 .h 都是可以的

模版的特化

先看下面的例子👇👇👇

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);
  }
private:
  int _year;
  int _month;
  int _day;
};
// 函数模板 -- 参数匹配
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;
}

运行结果:

1
1
0

由于我们传的是 地址, 属于 内置类型 && 我们不能改变 内置类型的比较规则 ⇒ 就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。

模板特化中分为函数模板特化与类模板特化。

函数模版的特化

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);
  }
private:
  int _year;
  int _month;
  int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
  return left < right;
}
// 函数模版的特化 -- Date*
template<>
bool Less<Date*>(Date* left, Date* 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;
}

运行结果:

1
1
1
  1. 函数模版的特化离不开原有模版
  2. 函数模版的特化的写法: template<> 函数名后面要跟上特化的类型, 然后 改变里面进行比较的类型
  3. 函数模版的特化, 还不如写一个特殊类型的同名函数和原函数模版构成 函数重载
  • 函数 模版的特化, 还不如写一个 函数重载👇👇👇
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);
  }
private:
  int _year;
  int _month;
  int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
  return left < right;
}
// 函数的重载
bool Less(Date* d1, Date* d2)
{
  return *d1 < *d2;
}
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;
}


相关文章
|
2月前
|
编译器 C++
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
33 1
|
2月前
|
安全 算法 C语言
【C++进阶】深入STL之string:掌握高效字符串处理的关键
【C++进阶】深入STL之string:掌握高效字符串处理的关键
32 1
【C++进阶】深入STL之string:掌握高效字符串处理的关键
|
2月前
|
编译器 C++
C++模板进阶
C++模板进阶
16 1
|
2月前
|
存储 算法 程序员
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
28 4
|
2月前
|
算法 安全 编译器
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
34 1
|
2月前
|
存储 算法 程序员
【C++进阶】深入STL之vector:构建高效C++程序的基石
【C++进阶】深入STL之vector:构建高效C++程序的基石
29 1
|
2月前
|
编译器 C++
【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
28 1
|
2月前
|
C++
C++中函数模版与类模版
C++中函数模版与类模版
41 4
|
2月前
|
存储 缓存 编译器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
19 0
|
2月前
|
C++ 容器
【C++进阶】深入STL之list:高效双向链表的使用技巧
【C++进阶】深入STL之list:高效双向链表的使用技巧
29 0