个人主页:点我进入主页
专栏分类:C语言初阶 C语言进阶 数据结构初阶 Linux C++初阶 算法
欢迎大家点赞,评论,收藏。
一起努力,一起奔赴大厂
目录
一.非类型模板参数
二.模板的特化
2.1引入
2.2全特化
2.3偏特化
三.模板的分离编译
3.1链接错误
3.2链接错误是如何产生的
3.3如何正确分离编译
3.4按需实例化
一.非类型模板参数
非类型模板参数就是模板的参数有一个常量,我们可以参考下面的代码:
template<class T, size_t N = 10>
class array
{
public :
T& operator[](size_t index)
{
return _arr[index];
}
int size()const
{
return _size;
}
private :
T _arr[N];
int _size=0;
};
在C++98中我们只支持整形的做为非类型模板参数,在后面的一些版中开始支持其他的类型作为非类型模板参数。在库中我们就有一个类似的设计array这个,我们需要知道这个非常的不好用,因为它实现的功能使用vector就可以实现,而且array在栈上开辟的空间 ,非常容易造成栈溢出,所以可以说这个基本没有任何用处。
二.模板的特化
2.1引入
class Date
{
public:
friend ostream& operator<<(ostream& _cout, const Date& d);
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
template<class T>
bool Less(T left, T right)
{
return left < right;
}
void test1()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
}
在日期类中我们比较当传入的是值的时候可以比较正确,但是当我i们传入指针时,我们就不能保证这个正确了,这个主要就是比较的是地址,而不是值,因此我们需要特化。
2.2全特化
template<class T>
bool Less(T left, T right)
{
cout << "Less(T left, T right)" << endl;
return left < right;
}
template <>
bool Less(Date* left, Date* right)
{
cout << "Less(Date * left, Date * right)" << endl;
return *left < *right;
}
我们需要在已经有的模板上再次写这个模板对它进行特化,但是template后面跟一个尖括号,尖括号里什么也不写,然后将类型写上,再写具体的代码。我们利用上面的代码运行可以看到:
在这里我们比较指针时会走我们的全特化,
2.3偏特化
偏特化和全特化类似,它只是特化模板的一部分,它就是对参数进一步进行限制,我们可以看下面的代码:
template <class T1,class T2>
class A
{
public :
A()
{
cout<<"A< T1,T2>" << endl;
}
private:
T1 _al;
T2 _a2;
};
template <class T1>
class A<T1,int>
{
public:
A()
{
cout << "A< T1,int>" << endl;
}
private:
T1 _al;
int _a2;
};
template <class T1,class T2>
class A<T1*, T2*>
{
public:
A()
{
cout << "A< T1*, T2*>" << endl;
}
private:
T1* _al;
T2* _a2;
};
void test1()
{
A<int, char> a1;
A<int, int>a2;
A<int*, int*>a3;
}
运行结果为
在类的使用特化时我们需要在已有的类中再次定义这个类,但是类的后面需要加上括号,括号中的内容是类型,原来的类中的template中有几个参数它就有几个参数,特化的内容不同,没有限制的相同,特化的template中的尖括号为没有精准限制的(它不是一个模糊的,不明确的例如int等,但是参数是T*就需要将T写上)。
三.模板的分离编译
3.1链接错误
我们将一个类进行分离编译,代码如下:
//.h
namespace bit
{
template<class T, size_t N = 10>
class array
{
public:
int size() const;
private:
T _array[N];
int _size=0;
};
}
//.c
namespace bit
{
template<class T, size_t N >
int array<T,N>::size() const
{
return _size;
}
}
我们实例化一次运行可以看到出现了链接错误
链接错误就是找不到地址,我们可以实验一下,只在.h文件中写一个func函数,调用的时候就会出现链接错误,代码如下:
//.h为
void func();
//.c直接调用
这是由于在编译时有这个声明就可以了,连接时才会进行合并call地址,call的地址就是func定义的地方,但是没有定义,所以出现链接错误。 我们将func函数的定义写好后我们看汇编代码
它会call一下地址。
3.2链接错误是如何产生的
在编译阶段开始编译器会把模板进行实例化,由于我们是模板的分离编译,所以我们会造成在.h文件中没有定义的地址,.c文件中有定义但是不知道实例化成什么
3.3如何正确分离编译
我们需要将声明和定义在同一个.h文件中,这样在编译阶段就会实例化,不需要在链接找地址,我们的代码如下:
template<class T, size_t N = 10>
class array
{
public:
int size() const;
private:
T _array[N];
int _size=0;
};
template<class T, size_t N >
int array<T, N>::size() const
{
return _size;
}
3.4按需实例化
我们看代码
template<class T, size_t N = 10>
class array
{
public:
int size() const;
void print()
{
cout << size(2);
}
private:
T _array[N];
int _size=0;
};
template<class T, size_t N >
int array<T, N>::size() const
{
return _size;
}
我们可以看到print函数中是错误的,但是我们不适用print这个函数,编译器就不会报错,一旦使用这个就会出错,我们看两个实验:
bit::array<int> a1;
cout<<a1.size();
bit::array<int> a1;
a1.print();
造成这个结果的就是因为编译器会进行按需实例化,当我们需要的时候才会进行实例化,随着版本的更新,编译器在很久以前如果里面少写一个分号也可以编译过去,只要我们不去调用。