当前位置: 首页 > news >正文

C++模板进阶

C++教学总目录

C++模板进阶

  • 1、模板初阶的补充
  • 2、非类型模板参数
  • 3、模板的特化
    • 3.1、函数模板特化
    • 3.2、类模板特化
      • 3.2.1、全特化
      • 3.2.2、偏特化
      • 3.2.3、类模板特化的应用
  • 4、模板的分离编译

1、模板初阶的补充

现在假设我们有一个vector对象,我们要遍历输出vector对象中的所有数据,代码如下:

void Print(const vector<int>& v)
{vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}

但是接下来我还有一个list容器,我也要遍历输出list容器中的所有数据。并且之后可能还会有其他容器要遍历输出容器内的所有数据。基于这样的原因:我们可以直接把Print函数写成模板函数。

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

但是编译之后发现报错了:
在这里插入图片描述
这是因为Container::const_iterator可能是类型,也可能是一个对象,如果是类型就没有问题,但是如果是一个对象就存在语法错误。
说白了这里就是因为类中可能有静态成员变量,如果是静态成员变量就是一个对象,那么就不符合语法,如果是类型就没有问题。

基于上面的原因,我们需要在Container前面加上typename告诉编译器这是一个类型,等到对象实例化的时候到类中去找const_iterator这个类型。
如下:对于vector对象和list对象都可以调用

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

在这里插入图片描述
在优先级队列这里less的数据类型传的是Container::value_type,由于无法判断是对象还是类型,所以也需要在前面加上typename, 明确告诉编译器这里是类型。


2、非类型模板参数

在过去,我们实现一个栈的方式如下:

#define N 100
// 静态栈
template<class T>
class Stack
{
private:T _a[N];int _top;
};

有了非类型模板参数,我们可以这么写:

template<class T, size_t N>
class Stack
{
private:T _a[N];int _top;
};

有了非类型模板参数N,需要多少容量我们可以自己传递。
但是需要注意两个点:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。


库里实现的有bitset(位图)后面我们会学习。这里讲一个array:
在这里插入图片描述
array是C++11的产物,包含于头文件<array>,类似数组,是一个固定长度的容器。需要穿类型和容器的容量N。
实际上array和数组差不多,只不过对于越界的访问更加严格。
array重载operator[]中对pos位置进行断言检查,而普通数组越界编译器可能检查不出来。

用法如下:

array<int, 10> a;
a[0] = 0;
for (auto e : a)
{cout << e << " ";
}
cout << endl;

不过基本上没啥用,很鸡肋。


3、模板的特化

3.1、函数模板特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。

实现一个模板函数,如果x1 < x2返回true。

template<class T>
bool Less(T x, T y)
{return x < y;
}int main()
{int a = 2, b = 1;int* p1 = &a;int* p2 = &b;cout << Less(a, b) << endl;cout << Less(p1, p2) << endl;return 0;
}

上面比较两个整形是没有问题的。但是如果比较的是两个指针呢?这样就变成了比较两个指针地址的大小了,是不符合我们需求的。这时候我们就需要使用模板的特化。

函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

template<class T>
bool Less(T x, T y)
{return x < y;
}template<>
bool Less<int*>(int* x, int* y)
{return *x < *y;
}

我们自己写了一个int*的特化Less函数,对于指针的比较就会走这个函数,不会再去走模板生成了。


但是写模板的特化,我不如直接实现函数重载,如下:

bool Less(int* x, int* y)
{return *x < *y;
}

或者直接实现指针模板的Less函数:

template<class T>
bool Less(T* x, T* y)
{return *x < *y;
}

3.2、类模板特化

3.2.1、全特化

全特化就是将类模板中的所有参数都确定化,如下:


template<class T1, class T2>
class Test
{
public:Test() { cout << "Test<T1, T2>" << endl; }
private:T1 _a1;T2 _a2;
};template<>
class Test<int, double>
{
public:Test() { cout << "Test<int, double>" << endl; }
};int main()
{Test<int, char> t1;Test<int, double> t2;return 0;
}

在这里插入图片描述
可以看到,t2走的是我们全特化的Test类。


3.2.2、偏特化

偏特化就是特化一部分参数,如下:

template<class T1>
class Test<T1, int>
{
public:Test() { cout << "Test<T1, int>" << endl; }
};

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
例如限制Test类的两个参数为指针和引用类型:

template<class T1, class T2>
class Test<T1*, T2*>
{
public:Test() { cout << "Test<T1*, T2*>" << endl; }
};template<class T1, class T2>
class Test<T1&, T2&>
{
public:Test() { cout << "Test<T1&, T2&>" << endl; }
};

可以看下面main函数实例化出不同类型调用上面所写特化的不同类的构造函数。
在这里插入图片描述


3.2.3、类模板特化的应用

在这里插入图片描述


4、模板的分离编译

之前我们实现vector和list的时候,都是声明和定义写在一起。因为如果声明和定义分离在两个不同的文件中会出问题。
下面先给出代码:

// stack.h
#pragma once
#include <queue>namespace zzy
{template<class T, class Container = std::deque<T>>class stack{public:void push(const T& x);void pop();T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};}
// stack.cpp
#include "stack.h"namespace zzy
{template<class T, class Container>void stack<T, Container>::push(const T& x){_con.push_back(x);}template<class T, class Container>void stack<T, Container>::pop(){_con.pop_back();}
}

在main函数中调用push/pop函数,就会出现如下错误:
在这里插入图片描述
这是因为,找不到push/pop函数的地址。因为stack.h和stack.cpp经过预处理、编译、汇编、链接然后形成可执行程序文件,但是在链接的时候,在符号表中找不到push/pop函数的地址,因为这里是模板函数,没有实例化出具体的类,所以也就没有具体函数代码 ,所以没有地址。

解决方案有两个:
1、显示实例化模板类

在这里插入图片描述
但是这种方式只能针对某种类型,当我换成stack<double>还是会报错,这时候就需要继续添加stack<double>的显示实例化。因此不推荐这种方法。

2、将声明和定义写在一起或写在同一个文件中
在这里插入图片描述
可以像size和empty一样将声明和定义直接写在一起。

在这里插入图片描述

也可以声明定义分离,但是写在同一个文件中。
推荐使用第二种方式,可以发现前面的vector、list声明和定义都是写在一个文件中的。


http://www.mrgr.cn/news/70486.html

相关文章:

  • Unity 设计模式-单例模式(Singleton)详解
  • Vue3中使用:deep修改element-plus的样式无效怎么办?
  • 【MySQL数据库】C#实现MySQL数据库最简单的查询和执行函数
  • 恋爱通信史之完整性
  • 如何使用GPT API 自定义 自己的 RAG
  • SciPy库spatial.transform模块Rotation类的from_rotvec 函数介绍
  • 数据重塑:长宽数据转换【基于tidyr】
  • Scala的List
  • 科普|分享10个你不知道的公司数据安全防泄密措施,让企业数据安全牢不可破!
  • AI Weekly5:过去一周重要的AI资讯汇总(1104-1110)
  • Playwright——快速入门(初章)
  • 甘肃油糕,舌尖上的滚烫美味
  • 关于有机聚合物铝电容的使用(2)
  • RFID被装信息化监控:物联网解决方案深入分析
  • jvisualvm的使用
  • LeetCode:215. 数组中的第K个最大元素
  • springboot yml配置信息书写与获取
  • linux startup.sh shutdown.sh (kkFileView)
  • TypeScript:现代 JavaScript 的超级集
  • Linux——gcc编译过程详解与ACM时间和进度条的制作
  • 【SpringMVC】基础入门(1)
  • HTTP TCP三次握手深入解析
  • 排序算法(2)
  • 【Linux】网络编程2
  • mysql中数据不存在却查询到记录?
  • 数学与统计计算:Python math 与 statistics库基础教程