一、非类型模板
之前学到的模板是类型模板,也就是模板参数都是代表的类型,但是非类型模板参数可以是具体的值,并且非类型模板参数的值是在编译阶段就要确认;
非类型模板参数值得注意的几点:
- 非类型模板参数的不能是类类型、字符串、浮点型(浮点型只有C++20版本之后支持)
- 非类型模板参数可以给缺省值;
- 当模板参数有缺省值,可以不传参,并且也确实没有传参时,要在实例化对象后面加上<>,这样适应各种版本的写法(不加<>只有C++20版本之后支持)
###代码示例:
1、类型一方面:
#include<iostream>
using namespace std;
template<size_t N>//N是非类型模板参数
class Arr
{
private:
int _arr[N];
};
int main()
{
Arr<10> a1;
return 0;
}
非类型模板参数不能是浮点型,除非是C++20版本的;

非类型模板参数不能是类类型;

2、缺省值一方面:
template<size_t N=10>//N是非类型模板参数
class Arr
{
private:
int _arr[N];
};
int main()
{
Arr<100> a1;//对于非类型模板参数显示传参
Arr<> a2;//不传参使用缺省值,要在后面类类型后面加上<>
return 0;
}
不加<>会报错,只有是C++20版本才支持这样写;

介绍一个使用到非类型模板参数的容器,array,这是一个相当于静态数组的容器;
给出几个接口观察非类型模板参数的作用并且看它与常规的数组得到区别:
//静态数组
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index)
{
assert(index < N);
return _arr[index];
}
const T& operator[](size_t index)const
{
assert(index < N);
return _arr[index];
}
bool empty()const
{
return _size == 0;
}
size_t size()const
{
return _size;
}
private:
T _arr[N];
size_t _size=0;//记录对象中实际数据的个数
};
int main()
{
::array<int, 10> arr1;//::表示代表在全局域找,那么就和库里面的容器array分开了
std::array<int ,10> arr2;//库里面的
return 0;
}
库里面的array也是没有初始化的;

再结合我们对array的实现对比平常的数组:
平常的数组可以越界读,也就是就算下标超过了 只去读也不会出错,但是这个容器array却不是,越界了去读调用[]的重载,再用assert断言,也会报错,更别说越界写。
二、模板的特化
1、函数的特化(引入概念)
模板的特化就是对于已经存在的一个模板,特例化一个差不多的模板出来,这个模板就是特例之后的模板;那么为什么要这样,这是由于这个存在的模板中的行为对于有的类型不适用,需要另一种写法才适合,所以此时需要用这个已存在的模板去特化;
- 函数模板的特化必须存在一个基础的函数模板;
- 函数模板的特化的template后面必须接上空的<>;
- 特化函数模板函数名后面接上<>,尖括号里面写上需要特殊处理的类型;
- 除了特殊处理的类型不同,其他部分要和原模版的基础参数类型相同。
###代码示例:
template<class T>
bool Less(T& x, T& y)
{
return x < y;
}
int main()
{
int a1 = 10;
int a2 = 20;
cout << Less(a1, a2) << endl;
double b1 = 10.99;
double b2 = 9.99;
cout << Less(b1, b2) << endl;
return 0;
}

这是一个 平常的函数模板的示例;
但是当T是指针时就不一定能如愿了:
template<class T>
bool Less(T& x, T& y)
{
return x < y;
}
int main()
{
int* p2 = new int(20);
int* p1 = new int(10);
cout << Less(p1, p2) << endl;
return 0;
}
![]()
我们想要的是比较10和20的大小,观察代码应该返回1,但是返回了0,这样不是10比20大了吗?其实并不是如此,此时传过去的是指针,而非int值,而指针的值是随机的,所以可能返回1也可能返回0,但是我们想要的是比较指针指向的值,所以此时不能用这个模板,此时就用到特化了;
template<class T>
bool Less(T& x, T& y)
{
return x < y;
}
template<>
bool Less<int*>(int*& x, int*& y)
{
return *x < *y;
}
int main()
{
int* p2 = new int(20);
int* p1 = new int(10);
cout << Less(p1, p2) << endl;
return 0;
}

虽然特化模板可以处理,但是我们直接给出函数,这样更为简单:
bool Less(int*& x, int*& y)
{
return *x < *y;
}
2、类的特化
类的特化的目的和函数特化的目的相同;除了特殊处理的部分,其他部分要与原模版的基础参数类型相同;
a、全特化
全特化就是将类的模板参数全部特化成具体的类型或者具体的值;
template<class T1, class T2>
class Less
{
public:
Less()
{
cout << "Less<T1,T2>" << endl;
}
private:
T1 d1;
T2 d2;
};
template<>
class Less<int ,char>
{
public:
Less()
{
cout << "Less<int,char>" << endl;
}
private:
int d1;
char d2;
};
void test1()
{
Less<int, int> l1;
Less<int, char> l2;
}
对于两个int的就调用原类模板,但是一旦有特化出的int和char的类模板,当显示实例化时,给出得到参数恰好是int char 那么就会调用特化的类模板;
b、偏特化(半特化)
###第一种偏特化就是只特化某些类模板参数;
class Less
{
public:
Less()
{
cout << "Less<T1,T2>" << endl;
}
private:
T1 d1;
T2 d2;
};
//偏特化
template<class T1>
class Less<T1, int>
{
public:
Less()
{
cout << "Less<T1,int>" << endl;
}
private:
T1 d1;
int d2;
};
void test2()
{
Less<int, int>l1;
}
当存在特化类时,就会调用特化的类模板,而不是原模版;
###第二种偏特化是根据传参的条件限制来给出的,适用于某一种情况;
例如指针和引用的情况
template<class T1,class T2>
class Less<T1*, T2*>//专门针对于指针的
{
public:
Less()
{
cout << "Less<T1*,T2*>" << endl;
}
private:
T1* d1;
T2* d2;
};
template<class T1, class T2>
class Less<T1&, T2&>专门针对于引用的
{
public:
Less()
{
cout << "Less<T1&,T2&>" << endl;
}
private:
int a = 1;
char c = 'o';
T1& d1=a;//引用必须在声明的时候就初始化,这里给两个变量就是为了去初始化
T2& d2=c;
};
void test3()
{
Less<int*, char*>l1;
Less<int&, char&>l2;
}

l1的T1就是int,T2就是char;l2的T1就是int,l2的T2就是char,这样规定保证了也可以使用T1、T2;
三、模板分离编译
C++中不支持模板的声明个定义分离;
解决方法:
- 显示实例化;
- 将模板的定义和声明写在一起;
一般都是将模板的定义和声明写在一起。
1726

被折叠的 条评论
为什么被折叠?



