C++模版(高阶)

本文介绍了C++中的非类型模版参数,其用于确定数组大小,以及模板特化,包括类模板全特化(如针对int类型)和偏特化(如针对T和int类型)。还讨论了函数模板的全特化和偏特化,以及模板分离编译的两种常见处理方式。
摘要由CSDN通过智能技术生成

目录

非类型模版参数

模板特化

类模板全特化

类模板偏特化

函数模板全特化与偏特化

模板分离编译


非类型模版参数

前面的模版中,使用的都是针对对象类型设计的模版参数,从而便于编译器针对不同类型推演出不同类型的函数或者类

但是有一种模版参数比较特殊,即非类型模版参数,有以下特点:

  1. 只可以定义为整型类型的常量
  2. 非类型的模板参数必须在编译期就能确认结果

示例代码:

template<class T, size_t N = 10>
class A
{

private:
    T arr[N];
};

在上面的代码中,T即为类型模板参数,而N即为非类型模板参数,并且因为size_t代表无符号整型,所以属于整型系列,编译通过

非类型模板参数主要使用在为数组开辟空间,当需要使用该类中的数组时,可以使用默认的10作为数组大小,也可以自定义N的大小从而确定数组的大小

在C++11中,新增了一个容器名为array,底层就是对数组进行了一个封装,目的是方便处理数组的相关问题,比如越界访问

array容器的定义:

template < class T, size_t N > class array;

原来的数组是C类型的数组,该数组对越界访问的控制并不严格,甚至有时并不能发现是越界访问,所以针对这个问题,C++11添加了array容器,从而更好地处理越界访问等问题

模板特化

在C++中,除了可以对任意类型使用模板以外,还可以使用模板特化来针对某一种类或者函数提供特殊的模板

模板特化分为全特化和偏特化,而对于类和函数来说,也分为类模板全特化和偏特化以及函数模板全特化和偏特化

函数模板的特化步骤:

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

类模板全特化

所谓全特化,即特化的模板参数类型全部用指定的类型替换普通模板中的类型

例如下面的代码:

#include <iostream>
using namespace std;

//普通模板
template<class T1, class T2>
class A
{
public:
    A(T1 val1, T2 val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T1 num1;
    T2 num2;
};

//全特化为int类型
template<>
class A<int, int>
{
public:
    A(int val1, int val2)
        :num1(val1)
        ,num2(val2)
    {}

private:
    int num1;
    int num2;
};

int main()
{
    A<double, int> a(1.9, 2);//调用普通模板
    A<int, int> a1(1, 2);//调用全特化模板
    return 0;
}

在上面的代码中,针对int类型使用了全特化的类,此时如果使用两个int类型的值创建对象,那么编译器会直接调用全特化的类进行构造

此时考虑下面的仿函数

//仿函数
template<class T>
class less
{
public:
    bool operator()(T val1, T val2)
    {
        return val1 < val2;
    }
};

对于int类型,double类型这种普通的数值类型来说,直接比较并不会有什么问题(此处不考虑浮点数精度问题),但是如果为指针类型,那么比较方式会有不同,因为比较指针除了比较二者地址以外,还有比较指针指向的内容,而对于上面的比较大小的仿函数,如果直接将指针类型作为模板参数,那么比较的就是指针本身存的地址,如果此时想比较指针指向的内容时就需要用到全特化,参考下面的代码:

//仿函数
//Date为自定义类型,并且已经重载*和<
template<>
class less<Date*>
{
public:
    bool operator()(Date* val1, Date* val2)
    {
        return *val1 < *val2;
    }
};

类模板偏特化

对比全特化,偏特化就是只有一部分是指定的类型,其余的部分还是普通的模板参数类型,例如下面的代码:

//偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
    A(T val1, int val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T num1;
    int num2;
};

在上面代码中,只要第二个模板参数类型时int类型时,就会走偏特化构造函数

现在有了下面三种模板:

//普通模板
template<class T1, class T2>
class A
{
public:
    A(T1 val1, T2 val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T1 num1;
    T2 num2;
};

//全特化为int类型
template<>
class A<int, int>
{
public:
    A(int val1, int val2)
        :num1(val1)
        ,num2(val2)
    {}

private:
    int num1;
    int num2;
};

//偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
    A(T val1, int val2)
        :num1(val1)
        , num2(val2)
    {}

private:
    T num1;
    int num2;
};

下面有三个对象:

A<double, double> a1(1.2, 1.2);//调用普通模板
A<int, int> a2(1, 2);//调用全特化模板
A<double, int> a3(1.9, 2);//调用偏特化

因为doubledouble类型没有偏特化和全特化,所以走普通模板,而intint类型有全特化,所以走全特化模板,对于doubleint类型,因为有偏特化,所以走偏特化模板

所以在普通模板、偏特化模板和全特化模板中,匹配顺序依次是:

  1. 全特化
  2. 偏特化
  3. 普通模板

函数模板全特化与偏特化

函数模板的全特化与偏特化方式参考下面的代码:

//普通函数模板
template<class T1, class T2>
T1 add(T1 val1, T2 val2)
{
    return val1 + val2;
}

//全特化函数模板
template<>
int add<int, int>(int val1, int val2)
{
    return val1 + val2;
}

//偏特化函数模板
template<class T>
T add<int, T>(int val1, int val2)
{
    return val1 + val2;
}

但是对于函数模板来说,一般不需要用到特化,只需要用函数重载+最匹配原则即可

模板分离编译

在C++中本身是不支持模板的声明和定义分别放在两个文件中,所以一般的处理方式有以下两种:

  1. 不写声明直接定义放在.h文件中
  2. 将声明写在定义之前,一般放在.hpp文件中

📌

一般的.hpp文件即为声明和定义在一起,表示该文件中既有类和函数模板的声明,也有对应的定义

例如下面的.hpp文件

//函数模板声明
template<class T>
T Add(const T& left, const T& right);

//普通函数声明
void func();

//类模板声明
template<class T>
class Stack 
{
public:
    //成员函数声明
    void Push(const T& x);
    void Pop();
private:
    T* _a = nullptr;
    int _top = 0;
    int _capacity = 0;
};

//函数模板定义
template<class T>
T Add(const T& left, const T& right)
{
    cout << "T Add(const T& left, const T& right)" << endl;
    return left + right;
}

//普通函数定义
void func()
{
    cout << "void func()" << endl;
}

//成员函数定义
template<class T>
void Stack<T>::Push(const T& x)
{
    cout << "void Stack<T>::Push(const T& x)" << endl;
}

//成员函数定义
template<class T>
void Stack<T>::Pop()
{
    cout << "void Pop()" << endl;
}

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怡晗★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值