云计算百科
云计算领域专业知识百科平台

C++模板进阶

如果没有了解过模板的朋友,就去看一下我的模板初阶文章:C++之模板初阶-CSDN博客

通过模板我们可以尝试去实现泛型编程,模板分为函数模板和类模板

那么下面我会跟大家介绍一下,模板进阶的知识

非类型模板参数

模板参数被分为:类型形参和非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

我们先看一个例子,我们写个静态栈结构:

#define N 10
template<class T>//类型模板参数
class Stack
{
private:
T _a[N];
size_t _top;
};
int main()
{
Stack<int> st1;//大小为10
Stack<int> st2;

}

我们想要去改变栈的大小就修改我们的宏就好了,如果我们有两个栈呢?一个栈想要10,另一个想要1000的大小,这样子我们就不能满足多个栈的需求了,除非我们在定义一个类模板,但是这样子会减少我们的效率,所以,在C++当中,模板有一个非类型的模板参数概念:

template<class T,size_t N>
//T是类型模板参数,N是非类型模板参数,N是一个常量
class Stack
{
private:
T _a[N];
size_t _top;
};
int main()
{
Stack<int,100> st1;//100
Stack<int,20000> st2;//20000
}

我们这样子就可以通过传参来完成我们每个栈想要的大小需求了,在template<class T,size_t N>当中,T是类型模板参数,这里的N是非类型模板参数,这里的N是一个常量

那我们可以这样去实现传参嘛?

int main()
{
static int n;
cin>>n;
Stack<int,n> st;//error,非类型模板参数不能是变量
return 0;
}

这样子在语法上不允许的,因为非类型传参不能是变量

在STL中的容器当中,C++11新增了一个array容器,array这个容器就是类似这样的结构,它使用了非类型的模板参数:

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

array是一个大小固定的容器

但是array这个容器我们一般不建议去使用

因为函数调用会建议栈帧,数组过大,可能会造成栈溢出,用vector了话,空间不够就增容,比较灵活,增容是在堆区中开辟空间,而堆区时进行动态开辟的地方,它的空间比较大,知道需要的数据大小直接使用vector中的resize就好了,没必要使用array这个容器,所以我们可以知道C++11增加的array这个容器基本没有什么用,它的缺点大于他的优点

非类型模板参数缺省值

模板参数都可以给缺省值,模板参数给缺省值和函数参数给缺省值是完全相似的,可以全缺省,也可以半缺省(必须从右往左连续缺省)

比如:

//模板参数都可以给缺省值
//模板参数给缺省值和函数参数给缺省值是完全类似的
//可以全缺省
//也可以半缺省 — 必须从右往左连续缺省
template<class T,size_t N = 10>
class Array
{
private:
T _a[N];
}
int main()
{
Array<int> a1;
Array<int,20> a2;
return 0;
}

注意:如果全都是缺省值的时候不能创建这样的对象:

Array a1;

全部都是缺省值,我们可以不传参数,但是我们知道Array是个模板,模板也是有类型的,我们需要这样:

Array<> a1;

注意:

1、浮点数、类对象以及字符串是不允许我们作为非类型模板参数的

template<class T,string s1>
template<class T,double s1>

2、非类型的模板参数必须在编译期就能确认结果

模板的特化

概念

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

template<class T>
bool IsEqual(const T& left,const T& right)
{
return left==right;
}
int main()
{
cout<<IsEqual(1,2)<<endl;

char p1[] = "hello";
char p2[] = "hello";
cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量

return 0;
}

这个模板可以用来比较整形,但是比较地址就会出现问题

此时可以使用模板的特化,可以针对某些类型进行特殊化去处理,我们可以这样写

bool IsEqual(const char*& left,const char*& right)
{
return strcmp(left,right)==0;
}
int main()
{
cout<<IsEqual(1,2)<<endl;

char p1[] = "hello";
char p2[] = "hello";
cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量

return 0;
}

我们调式过后,发现这里不会进这个函数,因为数组名是指针常量,则这里的const修饰的是*left,是left指向的内容不能修改,而不是left不能修改,这里属于权限放大了

需要这样改,这样就可以进去了:

//模板的特化,针对某些类型进行特殊化处理
bool IsEqual(const char*& const left,const char*& const right)
{
return strcmp(left,right)==0;
}

bool IsEqual(const char* left,const char* right)
{
return strcmp(left,right)==0;
}

函数模板的特化

函数模板的特化步骤:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
  • template<class T>
    void Swap(T& a,T& b)
    {
    //vector代价太大
    T tmp = a;
    a = b;
    b = tmp;
    }
    int main()
    {
    int x = 1;
    int y = 2;
    Swap(x,y);
    vector<int> v1 = {1,2,3,4};
    vector<int> v2 = {10,20,30,40};
    Swap(v1,v2);
    return 0;
    }

    我们交换的类型是vector时,此时用模板函数进行交换代价太大了,一次拷贝构造+两次赋值重载,所以我们可以优化一下:

    //函数模板的特化
    template<>
    void Swap<vector<int>>(vector<int>& a,vector<int>& b)
    {
    a.swap(b);
    }

    当然也可以这样,利用模板的匹配原则,进行特殊化处理:

    //模板的匹配原则,进行特殊化处理
    void Swap(vector<int>& a,vector<int>& b)
    {
    a.swap(b);
    }

    类模板的特化

    全特化

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

    template<class T1,class T2>
    class Data
    {
    public:
    Data() { cout << "Data<T1,T2>"<<endl; }
    private:
    T1 _d1;
    T2 _d2;
    };
    //全特化
    template<>
    class Data<double, double>
    {
    public:
    Data() { cout << "Data<double,double>" << endl; }
    private:
    T1 _d1;
    T2 _d2;
    };

    int main()
    {
    Data<int,int> d1;
    Data<double,double> d2;
    return 0;
    }

    偏特化

    偏特化:任何针对模板参数进一步进行条件限制设计的特化版本

    偏特化有两种:

    1、部分特化,将模板参数类表中的一部分参数特化

    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,double>
    {
    public:
    Data() { cout << "Data<T1,double>" << endl; }
    };

    int main()
    {
    Data<int,double> d2;
    Data<char,double> d1;

    return 0;
    }

    2、参数更进一步的限制,偏特化并不是仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

    //必须要存在原模板,在原模板的基础上去特化
    template<class T1,class T2>
    class Date
    {
    public:
    Date() {
    cout << "Date<T1,T2>" << endl;
    }
    private:
    T1 _d1;
    T2 _d2;
    };

    //偏特化,传的类型是指针
    template<typename T1,typename T2>
    class Date<T1*, T2*>
    {
    public:
    Date()
    {
    cout << "Date<T1*,T2*>" << endl;
    }
    };

    //传的是引用
    template<typename T1, typename T2>
    class Date<T1&, T2&>
    {
    public:
    Date()
    {
    cout << "Date<T1&,T2&>" << endl;
    }
    };

    //也可以半指针,半引用
    template<typename T1, typename T2>
    class Date<T1&, T2*>
    {
    public:
    Date()
    {
    cout << "Date<T1&,T2*>" << endl;
    }
    };

    int main()
    {
    Date<char*, char*> d5;
    Date<int*, double*>d6;
    Date<int&, double&>d7;
    Date<int&, double*>d8;

    return 0;
    }

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C++模板进阶
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!