• C++【STL】【模板进阶】


    目录

    一、非类型模板参数

    1.给模板参数设置缺省值

    2.Array 

    二、模板的特化

    1.函数模板的特化

    2.类模板的特化

    3.类模板的全特化

    4.类模板的偏特化(半特化)

    模板参数可以是匹配指针

    模板参数可以是匹配引用

    模板参数可以是匹配指针和引用

    三、模板的分离编译

    typename和class的区别

     声明和定义分离就会报错

    为什么声明和定义分离就链接错误了?

    如何解决?

    1.在同一个文件中分离声明和定义

    2.显式实例化(不推荐)

    模板总结


    一、非类型模板参数

    模板参数分类类型形参与非类型形参
    类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
    非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

    之前我们使用模板都是这样使用的

    1. #include
    2. #define N 100
    3. template<class T>
    4. class array{
    5. private:
    6. T _a[N];
    7. };
    8. int main()
    9. {
    10. array<int> a1;
    11. array<double> a2;
    12. return 0;
    13. }

    但是假设我们想要让a1的大小变成100,a2的大小变成1000,怎么办呢?

    这里我们就可以再添加一个模板参数,但是这个模板参数并不是某一种数据类型,而是一个常量

    1. #include
    2. //非类型模板参数 --常量
    3. template<class T,size_t N>
    4. class array{
    5. private:
    6. T _a[N];
    7. };
    8. int main()
    9. {
    10. array<int,100> a1;
    11. array<double,1000> a2;
    12. return 0;
    13. }

    1.给模板参数设置缺省值

     模板参数和函数参数是非常像的,我们还可以给我们的模板参数一个缺省值

    1. #include
    2. //非类型模板参数 --常量
    3. template<class T,size_t N=10>
    4. class array{
    5. private:
    6. T _a[N];
    7. };
    8. int main()
    9. {
    10. array<int,100> a1;
    11. array<double> a2;
    12. return 0;
    13. }

    注意:
    1. 浮点数、类对象以及字符串不允许作为非类型模板参数的(char是算整型家族的)。
    2. 非类型的模板参数必须在编译期就能确认结果

    2.Array 

    这里我们从cpluscplus网站中看到我们的的array定义 

    fixed-size sequence containers,固定大小的顺序容器,也就是一个静态数组

     这个标准库的array是在2011年的c++11才增加的

    这个array和我们c语言中的数组的区别是什么呢?

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. array<int,100> a1;//c++
    7. int a2[10];//c
    8. cout<<sizeof (a1)<
    9. cout<<sizeof (a2)<
    10. return 0;
    11. }

     除了array是支持使用迭代器去访问的,其两者的区别还在于其迭代器的的越界检查。

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. array<int,10> a1;
    7. int a2[10];
    8. //越界写和越界读都能被查到
    9. a1[10];
    10. //a2的越界读是检查不到的,越界写是可以检查到的
    11. // a2[10];
    12. cout<<sizeof (a1)<
    13. cout<<sizeof (a2)<
    14. return 0;
    15. }

    array中是一个函数调用,这个函数调用就会检查这个函数的下标有没有超出参数N,所以它对于越界的检查会更加好

     而我们的c语言中的数组仅仅是将数组的首地址加上N,再解引用来得到我们的结果。指针解引用只是抽查是否越界,只针对越界写,越界读不检查。

    二、模板的特化

    有些情况下我们希望我们能够对模板进行特殊化处理, 这里我们就要用到模板特化

    1.函数模板的特化

    我们首先定义一个日期类

    1. struct Date
    2. {
    3. Date(int year, int month, int day)
    4. :_year(year)
    5. , _month(month)
    6. , _day(day)
    7. {}
    8. bool operator>(const Date& d) const
    9. {
    10. if ((_year > d._year)
    11. || (_year == d._year && _month > d._month)
    12. || (_year == d._year && _month == d._month && _day > d._day))
    13. {
    14. return true;
    15. }
    16. else
    17. {
    18. return false;
    19. }
    20. }
    21. bool operator<(const Date& d) const
    22. {
    23. if ((_year < d._year)
    24. || (_year == d._year && _month < d._month)
    25. || (_year == d._year && _month == d._month && _day < d._day))
    26. {
    27. return true;
    28. }
    29. else
    30. {
    31. return false;
    32. }
    33. }
    34. int _year;
    35. int _month;
    36. int _day;
    37. };
    1. #include
    2. using namespace std;
    3. // 函数模板 -- 参数匹配
    4. template<class T>
    5. bool Less(T left, T right)
    6. {
    7. return left < right;
    8. }
    9. // 函数模板 -- 参数匹配
    10. template<class T>
    11. bool Greater(T left, T right)
    12. {
    13. return left > right;
    14. }
    15. int main()
    16. {
    17. cout << Greater(1, 2) << endl; // 可以比较,结果正确
    18. Date d1(2022, 7, 7);
    19. Date d2(2022, 7, 8);
    20. cout << Greater(d1, d2) << endl; // 可以比较,结果正确
    21. Date* p1 = &d1;
    22. Date* p2 = &d2;
    23. //指针比较指针,但是我们并不想要的是指针大小的比较结果,我们只是想知道这个指针指向的日期类的比较
    24. cout << Greater(p1, p2) << endl; // 可以比较,结果错误
    25. return 0;
    26. }

     C++中比较的都是变量和对象,类型不能够比较!!

    比方说我们设置T==Date*类型,进行单独比较,C++是不支持的!!!

    1. // 函数模板 -- 参数匹配
    2. template<class T>
    3. bool Less(T left, T right)
    4. {
    5. return left < right;
    6. }
    7. 函数模板 -- 参数匹配
    8. template<class T>
    9. bool Greater(T left, T right)
    10. {
    11. return left > right;
    12. }
    13. // 特化--针对某些类型进行特殊化处理
    14. //如果我们这个T是Date*类型,就会走下面这个函数
    15. //注意,下面这个函数是对上面这个函数的特化,上面那个函数必须存在的!!!
    16. template<>
    17. bool Greater(Date* left, Date* right)
    18. {
    19. return *left > *right;
    20. }
    21. int main()
    22. {
    23. cout << Greater(1, 2) << endl; // 可以比较,结果正确
    24. Date d1(2022, 7, 7);
    25. Date d2(2022, 7, 8);
    26. cout << Greater(d1, d2) << endl; // 可以比较,结果正确
    27. Date* p1 = &d1;
    28. Date* p2 = &d2;
    29. //指针比较指针,但是我们并不想要的是指针大小的比较结果,我们只是想知道这个指针指向的日期类的比较
    30. cout << Greater(p1, p2) << endl; // 可以比较,结果错误
    31. return 0;
    32. }

    2.类模板的特化

    下面我们写了一个less仿函数

    1. namespace zhuyuan
    2. {
    3. template<class T>
    4. struct less
    5. {
    6. bool operator()(const T& x1, const T& x2) const
    7. {
    8. return x1 < x2;
    9. }
    10. };
    11. }
    12. int main()
    13. {
    14. Date d1(2022, 7, 7);
    15. Date d2(2022, 7, 8);
    16. Date* p1 = &d1;
    17. Date* p2 = &d2;
    18. zhuyuan::less lessFunc1;
    19. cout << lessFunc1(d1, d2) << endl;
    20. //我们想要比较的并不是指针的大小,而是指针指向的对象的大小
    21. zhuyuan::less lessFunc2;
    22. cout << lessFunc2(p1, p2) << endl;
    23. return 0;

    针对类模板也可以特化

    1. namespace zhuyuan
    2. {
    3. template<class T>
    4. struct less
    5. {
    6. bool operator()(const T& x1, const T& x2) const
    7. {
    8. return x1 < x2;
    9. }
    10. };
    11. // 特化
    12. template<>
    13. struct less
    14. {
    15. bool operator()(Date* x1, Date* x2) const
    16. {
    17. return *x1 < *x2;
    18. }
    19. };
    20. }
    21. int main()
    22. {
    23. Date d1(2022, 7, 7);
    24. Date d2(2022, 7, 8);
    25. Date* p1 = &d1;
    26. Date* p2 = &d2;
    27. zhuyuan::less lessFunc1;
    28. cout << lessFunc1(d1, d2) << endl;
    29. zhuyuan::less lessFunc2;
    30. cout << lessFunc2(p1, p2) << endl;
    31. return 0;
    32. }

     不特化就会按指针的大小比较,特化就会按指针指向的对象比

    1. namespace zhuyuan
    2. {
    3. template<class T>
    4. struct less
    5. {
    6. bool operator()(const T& x1, const T& x2) const
    7. {
    8. return x1 < x2;
    9. }
    10. };
    11. // 特化
    12. template<>
    13. struct less
    14. {
    15. bool operator()(Date* x1, Date* x2) const
    16. {
    17. return *x1 < *x2;
    18. }
    19. };
    20. }
    21. int main()
    22. {
    23. std::priority_queue, zhuyuan::less> dq1;
    24. //这个如果不特化,就会出现问题
    25. std::priority_queue, zhuyuan::less> dq2;
    26. dq2.push(new Date(2022, 9, 27));
    27. dq2.push(new Date(2022, 9, 25));
    28. dq2.push(new Date(2022, 9, 28));
    29. dq2.push(new Date(2022, 9, 29));
    30. return 0;
    31. }

    这是有特化的结果,也就是按照从大到小排

    如果我们将上面的特化的代码处理掉,我们观察到我们的地址是按照从大到小排的

    注意:

    1.特化不能单独存在,原来的函数模板必须存在! 

    3.类模板的全特化

     这个与我们的缺省参数非常像,缺省可以分为全缺省和半缺省,特化也可以分为全特化和半特化,偏特化。

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

    1. template<class T1, class T2>
    2. class Data
    3. {
    4. public:
    5. Data() { cout << "Data" << endl; }
    6. private:
    7. T1 _d1;
    8. T2 _d2;
    9. };
    10. // 全特化
    11. template<>
    12. //全部的模板参数全部都写死,只能匹配类型
    13. class Data<int, char>
    14. {
    15. public:
    16. Data() { cout << "Data" << endl; }
    17. private:
    18. /*int _d1;
    19. char _d2;*/
    20. };
    21. int main()
    22. {
    23. Data<int, int> d0;
    24. Data<double, int> d1;
    25. Data<int, char> d2;
    26. return 0;
    27. }

    4.类模板的偏特化(半特化)

    部分参数特化

    1. //原模板
    2. template<class T1, class T2>
    3. class Data
    4. {
    5. public:
    6. Data() { cout << "Data" << endl; }
    7. private:
    8. T1 _d1;
    9. T2 _d2;
    10. };
    11. // 偏特化
    12. template <class T1>
    13. class Dataint>
    14. {
    15. public:
    16. Data() { cout << "Data" << endl; }
    17. private:
    18. /*T1 _d1;
    19. int _d2;*/
    20. };
    21. int main()
    22. {
    23. //下面两个都是可以匹配到偏特化的版本的
    24. Data<int, int> d0;
    25. Data<double, int> d1;
    26. //
    27. Data<int, char> d2;
    28. return 0;
    29. }

    模板参数可以是匹配指针

    1. template<class T1, class T2>
    2. class Data
    3. {
    4. public:
    5. Data() { cout << "Data" << endl; }
    6. private:
    7. T1 _d1;
    8. T2 _d2;
    9. };
    10. //偏特化,只要这两个模板参数是指针,就能够被匹配到
    11. template<class T1, class T2>
    12. class Data
    13. {
    14. public:
    15. Data() { cout << "Data" << endl; }
    16. };
    17. int main()
    18. {
    19. Data<double, double> d3;
    20. Data<double*, double*> d4;
    21. Data<int*, char*> d5;
    22. //只有一个指针,匹配不到两个都是指针的模板的,只会匹配到最初原来的模板
    23. Data<int*, char> d6;
    24. return 0;
    25. }

    模板参数可以是匹配引用

    1. template<class T1, class T2>
    2. class Data
    3. {
    4. public:
    5. Data() { cout << "Data" << endl; }
    6. private:
    7. T1 _d1;
    8. T2 _d2;
    9. };
    10. template<class T1, class T2>
    11. class Data
    12. {
    13. public:
    14. Data() { cout << "Data" << endl; }
    15. };
    16. int main()
    17. {
    18. //可以用于匹配迭代器
    19. Data<int&, char&> d7;
    20. Data<int&, double&> d8;
    21. return 0;
    22. }

    模板参数可以是匹配指针和引用

    1. template<class T1, class T2>
    2. class Data
    3. {
    4. public:
    5. Data() { cout << "Data" << endl; }
    6. private:
    7. T1 _d1;
    8. T2 _d2;
    9. };
    10. template<class T1, class T2>
    11. class Data
    12. {
    13. public:
    14. Data() { cout << "Data" << endl; }
    15. };
    16. int main()
    17. {
    18. Data<int&, double*> d9;
    19. return 0;
    20. }

     特化匹配的原则就是有匹配的就匹配的,没有匹配的,就凑合一下,走相对匹配的

    1. const_iterator begin() const
    2. {
    3. return const_iterator(_head->_next);
    4. }
    5. const_iterator end() const
    6. {
    7. return const_iterator(_head);
    8. }
    9. iterator begin()
    10. {
    11. return iterator(_head->_next);
    12. }
    13. iterator end()
    14. {
    15. return iterator(_head);
    16. }

     比方说我们上面之前写的list中的迭代器,有const版本的和普通版本的,那如果只有const版本,那普通对象能调用吗?

    能调用!因为普通对象变成const对象,是权限的缩小,是允许的。

    那如果普通和const版本的迭代器都存在的时候,普通对象一定会去匹配普通版本的const。

    当我们如果想要让const对象和普通对象分开处理的时候,我们必须要分开去写。比方说我们想要对const不进行操作,但是对普通对象进行操作,我们就需要将const版本和普通版本的分开写出来。

    三、模板的分离编译

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

    typename和class的区别

    1. template<class T>
    2. //平时typename和class没有区别,但是在这里有区别
    3. //这个typename存在于这里是为了告诉编译器vector::iterator vector是一整个类型
    4. //就是你制定一个还没有实例化的模板,然后你要去取里面的内嵌类型
    5. // (在这个类里面定义的内部类,或者在这个类里面typedef的,这里我们的iterator就是一个内嵌类型)
    6. //因为编译器区分不清楚你的iterator到底是一个类型还是一个变量
    7. //因为静态的变量指定类域就可以访问,那这个iterator就变成一个变量了,所以编译器区分不清楚这是一个类型还是一个变量
    8. //所以加一个typename来告诉编译器这是一个类型
    9. typename vector::iterator vector::insert(typename vector::iterator pos, const T& x)
    10. {
    11. assert(pos >= _start);
    12. assert(pos <= _finish);
    13. if (_finish == _end_of_storage)
    14. {
    15. size_t len = pos - _start;
    16. reserve(capacity() == 0 ? 4 : capacity() * 2);
    17. pos = _start + len;
    18. }
    19. //
    20. iterator end = _finish - 1;
    21. while (end >= pos)
    22. {
    23. *(end + 1) = *end;
    24. --end;
    25. }
    26. *pos = x;
    27. ++_finish;
    28. return pos;
    29. }

    进一步看下面的例子

    1. template<class T>
    2. void PrintContainer(const list& lt)
    3. {
    4. //这里编译器就区分不清楚这个const_iterator是变量还是类型
    5. list::const_iterator it = lt.begin();
    6. // ...
    7. }
    8. int main()
    9. {
    10. list<int> lt;
    11. PrintContainer(lt);
    12. return 0;
    13. }

    1. template<class T>
    2. void PrintContainer(const list& lt)
    3. {
    4. //typename告诉编译器,这个list::const_iterator是一个类型,不是静态变量
    5. typename list::const_iterator it = lt.begin();
    6. // ...
    7. }
    8. int main()
    9. {
    10. list<int> lt;
    11. PrintContainer(lt);
    12. return 0;
    13. }

     

     声明和定义分离就会报错

    1. //这是测试程序
    2. int main()
    3. {
    4. zhuyuan::vector<int> v;
    5. v.push_back(1);
    6. v.push_back(2);
    7. v.push_back(3);
    8. for (size_t i = 0; i < v.size(); ++i)
    9. {
    10. cout << v[i] << " ";
    11. }
    12. cout << endl;
    13. return 0;
    14. }

    这是push_back的声明,放在vector.h文件中

    1. void push_back(const T& x);
    2. iterator insert(iterator pos, const T& x);

    这是push_back的定义,放在vector.cpp中

    1. template<class T>
    2. void vector::push_back(const T& x)
    3. {
    4. insert(_finish, x);
    5. }

    运行一下,直接报错,push_back链接错误

    链接错误都是有声明,找不到定义

    为什么声明和定义分离就链接错误了?

    这些函数调用都是call一个地址。

    构造函数,size函数,operator[]函数不会去链接,在vector.h中有定义。

    vector实例化时,这些成员函数也实例化,直接就有定义,编译阶段就确定地址了

    但是push_back和insert这两个函数与上面不同,因为在vector.h中只有声明,没有定义,那么地址就只能在链接阶段去确认了。

    这里的报错说明,链接阶段找不到!

    1. template<class T>
    2. void vector::push_back(const T& x)
    3. {
    4. insert(_finish, x);
    5. }

    因为上面的模板的定义中的T在编译链接阶段是没办法确定的,那么就没办法实例化(根本不知道应该实例化成什么类型),所以我们的push_back就没有进符号表,所以也就没办法确定链接地址。

    如何解决?

    1.在同一个文件中分离声明和定义

    模板的声明和定义不要分离到.h和.cpp,但在同一个文件中可以分离。

    下面我们就是将相对较长的函数push_back,insert,和reserve函数封装在类的外面。

    1. #pragma once
    2. #include
    3. // 声明
    4. namespace zhuyuan
    5. {
    6. template<class T>
    7. class vector
    8. {
    9. public:
    10. typedef T* iterator;
    11. vector()
    12. :_start(nullptr)
    13. , _finish(nullptr)
    14. , _end_of_storage(nullptr)
    15. {}
    16. ~vector()
    17. {
    18. delete[] _start;
    19. _start = _finish = _end_of_storage = nullptr;
    20. }
    21. size_t capacity() const
    22. {
    23. return _end_of_storage - _start;
    24. }
    25. const T& operator[](size_t pos) const
    26. {
    27. assert(pos < size());
    28. return _start[pos];
    29. }
    30. T& operator[](size_t pos)
    31. {
    32. assert(pos < size());
    33. return _start[pos];
    34. }
    35. size_t size() const
    36. {
    37. return _finish - _start;
    38. }
    39. void push_back(const T& x);
    40. iterator insert(iterator pos, const T& x);
    41. void reserve(size_t n);
    42. friend void reserve(size_t n);
    43. private:
    44. iterator _start;
    45. iterator _finish;
    46. iterator _end_of_storage;
    47. };
    48. //以下是上面的函数的定义
    49. //是放置在类的外部的
    50. //长的函数可以单独拎出来,让短的函数形成内联
    51. template<class T>
    52. void vector::push_back(const T& x)
    53. {
    54. insert(_finish, x);
    55. }
    56. template<class T>
    57. typename vector::iterator vector::insert(typename vector::iterator pos, const T& x)
    58. {
    59. assert(pos >= _start);
    60. assert(pos <= _finish);
    61. if (_finish == _end_of_storage)
    62. {
    63. size_t len = pos - _start;
    64. reserve(capacity() == 0 ? 4 : capacity() * 2);
    65. pos = _start + len;
    66. }
    67. //
    68. iterator end = _finish - 1;
    69. while (end >= pos)
    70. {
    71. *(end + 1) = *end;
    72. --end;
    73. }
    74. *pos = x;
    75. ++_finish;
    76. return pos;
    77. }
    78. template<class T>
    79. void vector::reserve(size_t n)
    80. {
    81. if (n > capacity())
    82. {
    83. size_t sz = size();
    84. T* tmp = new T[n];
    85. if (_start)
    86. {
    87. //memcpy(tmp, _start, sizeof(T)*sz);
    88. for (size_t i = 0; i < sz; ++i)
    89. {
    90. tmp[i] = _start[i]; // T对象是自定义类型时,调用T对象operator=
    91. }
    92. delete[] _start;
    93. }
    94. _start = tmp;
    95. _finish = _start + sz;
    96. _end_of_storage = _start + n;
    97. }
    98. }
    99. }

    测试代码

    1. int main()
    2. {
    3. zhuyuan::vector<int> v;
    4. v.push_back(1);
    5. v.push_back(2);
    6. v.push_back(3);
    7. for (size_t i = 0; i < v.size(); ++i)
    8. {
    9. cout << v[i] << " ";
    10. }
    11. cout << endl;
    12. return 0;
    13. }

    2.显式实例化(不推荐)

    这里我们显式实例化这一个类,其中的全部方法都会被实例化

    1. //显式实例化
    2. template class vector<int> ;

    但是显式实例化的话就将这个实例化给写死了,不能灵活地变化,换一个类型就要实例化一次,太麻烦了。

    模板总结

    【优点】
    1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
    2. 增强了代码的灵活性
    【缺陷】
    1. 模板会导致代码膨胀问题,也会导致编译时间变长
    2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

  • 相关阅读:
    举个栗子~Minitab 技巧(5):掌握常用快捷键,提高统计分析效率
    动态RDLC报表(四)
    【Unity】Inspector排版扩展学习初探
    盘一盘高性能设计的哪些点(二)
    ssm+微信小程序网易云音乐设计与实现毕业设计源码261620
    计算机网络-性能指标
    跨线程访问控件的操作
    博流BL602开发一 编译与实例
    Qt通过正则表达式筛选出字符串中的手机号
    【优化调度】基于与学算法实现库存优化控制问题附matlab代码
  • 原文地址:https://blog.csdn.net/weixin_62684026/article/details/127097641