前言
这一篇博客讲一下模版的其他内容,进阶内容,后面就会讲继承了,难度上升
1. 非类型模版参数
所谓非类型模版参数,就是在模版参数列表中,即可以存在类型参数,也可以存在非类型参数
template<class T, size_t N>
class Array
{
private:
T arr[N];
};
int main()
{
Array<int, 100> a1;
//这个就表示建立一个100个整型的数组
Array<int, 10> a1;
return 0;
}
这个也是根据不同的模版参数,编译器生成不同的类
template<class T, size_t N>
class Array
{
public:
func()
{
N--;
}
private:
T arr[N];
};
int main()
{
Array<int, 100> a1;
a1.func();
//这个就表示建立一个100个整型的数组
Array<int, 10> a1;
return 0;
}
还有要值得注意的是,非类型的模版参数是个常量,不可修改的
还有就是模版是不会进行编译的,或者说,只会对模版的外壳进行编译,不会对模版的内部进行编译,所以说模版内部有错误,不管是什么错误都是不会检查出来的,只有在调用那个函数的时候才会检查出来错误
还有值得说的就是,在C++20之前是只支持整型的非类型模板参数的,C++20就支持了内置类型的非类型模板参数
编译器默认是C++14,但是是可以修改的
template<class T, double N>
class Array
{
public:
int func()
{
N--;
}
private:
//T arr[N];
};
Array<int, 12.2> a;
只支持内置类型,不支持自定义类型
2. array
接下来,我们来认识一下array,array其实就是一个数组的类和模版,和数组没什么区别
array<int,10> a;
这就表示建立了一个10个整型的数组,但是这个和C语言的数组有什么区别吗,区别只有一个,那就是有关越界访问的
int arr[10],是这样的,会在数组的后面加上一两个位置,这两个位置设置为一些固定值,程序结束时看这些值有没有被改变,如果被改变了的话,说明就越界了,注意,这个报错是在程序结束时才报错
int arr[10] = { 0 };
arr[11] = 10;
比如这个更改了后面一个的值就报错说越界了
但是array是只要越界了就会马上报错,虽然比我们直接定义数组有点好处,但是这个array并没有什么用,为什么呢,因为还有vector啊,vector不比array好用多了?
我们再来讲一个其他东西
void PrintVector(const vector<int>& v)
{
vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
这是一个vector的打印函数,但是它只能打印int的,不能打印其它的,于是我们就可以实现一个打印函数的模板了
template<class T>
void PrintVector(const vector<T>& v)
{
vector<T>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
vector<double> a = { 1.1,2.3,3.4,5.3 };
PrintVector(a);
但是我们这样写却不能正常运行
template<class T>
void PrintVector(const vector<T>& v)
{
typename vector<T>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
但是我们在前面加上typename就可以正常使用了,为什么呢
第一是因为vector::const_iterator能这样使用的只有静态变量,typedef内容,内部类,到底是哪个呢,因为有T嘛,还没有初始化,所以不知道是什么,加上typename就指定了是那种typedef或者变量
第二就是没有实例化,所以不能随便访问,万一有错呢,所以加上那个东西
反正怎么说呢,记住这个东西就可以了,目前只有这种情况要特殊加上typename
3. 模板的特化
先看个例子
template<class T>
class myless
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 < t2;
}
};
myless<Date> a;
cout<<a(Date(23, 2, 3), Date(22, 3, 4));
这个可以正常比较
myless<Date*> a;
cout<<a(new Date(23, 2, 3),new Date(22, 3, 4));
但这个就无法正常比较了,原因就是底层比较的是指针,开辟空间的指针间的大小是无法确定的
解决办法就是重新建立一个仿函数
template<class T>
class myless1
{
public:
bool operator()(const T& t1, const T& t2)
{
return *t1 < *t2;
}
};
myless1<Date*> a;
cout<<a(new Date(23, 2, 3),new Date(22, 3, 4));
这是一种方法,再来个模板,但是我们还有其他方法就是模板特化
3.1 函数模板特化
template<class T>
bool Less(T left, T right)
{
return left < right;
}
还是这个函数,还是不能比较指针类的东西,于是就可以特化了
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;
}
函数模板的特化就是这个样子,这个就可以比较int类型的指针了
cout << Less(new int(1), new int(2));
需要什么就特化什么
但是函数模板特化有个很坑的点就是参数有const有&还有的时候容易出错,比如上面这个就不行,为什么呢,因为模板的意思是left不能修改,但是特化的意思是left不能修改,所以不一样,所以不行
把const移到*后面就可以了,还有就是const要在&的前面,因为模板都在前面
3.2 类模板特化
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "template<class T1,class T2>" << endl;
}
private:
};
template<>
class Date<int,double>
{
public:
Date()
{
cout << "class Date<int,double>" << endl;
}
private:
};
Date<int, double> a;
像这种全部模板参数都特化,叫做全特化
下面介绍几个偏特化
template<class T>
class Date<T ,double>
{
public:
Date()
{
cout << "class Date<int,double>" << endl;
}
private:
};
这个表示第二个模板参数是double就调用这个
template<class T1,class T2>
class Date<T1*, T2*>
{
public:
Date()
{
cout << "class Date<T1*, T2*>" << endl;
cout << typeid(T1).name() << endl;
}
private:
};
Date<int*,int*> a;
这个特化就表示只要是指针就调用这个模板,其中T1和T2不一定是指针,这样是为了以后用T1和T2定义非指针变量
这个的作用就是我们一开始提的,如果传的是Date的话,那么就可以用这个来比较了,T1就是Date,专门针对Date来特化一个
template<class T1, class T2>
class Date<T1&, T2&>
{
public:
Date()
{
cout << "class Date<T1&, T2&>" << endl;
cout << typeid(T1).name() << endl;
}
private:
};
Date<int&,int&> a;
这个就表示,只要是引用就调用这个模板,而且T1和T2的类型原理和指针是类似的
4. 模板分离编译
接下来我们讲一下模板分离编译,也就是解释为什么模板类的函数声明与定义不能分开的原因
为什么呢
如果说模板类的函数声明与定义分离,声明在.h中,定义在.cpp中,我们又说过,模板是不会进行编译的,意思是.cpp里面的函数就不会进行连接,也就相当于.cpp里面的内容你都白写了,根本没有用,那么这样的话,你要是使用模板的时候,就会去包含的头文件里面找,结果只有声明没有定义,那么就会出错。
所以最好的解决办法就是不要声明与定义分离
总结
下一篇博客讲继承