一.泛型编程:
引例:交换两个数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
根据每次交换类型的不同,函数就不同很麻烦,所以祖师爷发明了模板
二.函数模板
这样子就很轻松了:
template <typename T>
void Swap(T& x,T& y)
{
T tmp = x;
x = y;
y = tmp;
}
其中typename是关键字,也可以拿class代替,T是名字,取啥都行。
看以下代码:其中两个Swap调用的是不是同一个函数
#include <iostream>
using namespace std;
template <typename T>
void Swap(T& x,T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 1, b = 2;
Swap(a,b);
double d1 = 1.1, d2 = 2.2;
Swap(d1,d2);
return 0;
}
答案是完全不一样的,因为浮点型和整型的存储方式都不一样,交换方式肯定不一样。
编译器根据需要,推断类型,生成对应的函数,这个过程叫做模板实例化:
反汇编可以看出:
还有一个注意的点,C++库里面有swap,以后可以不用自己写:
#include <iostream>
using namespace std;
int main()
{
int a = 1, b = 2;
swap(a,b);
double d1 = 1.1, d2 = 2.2;
swap(d1,d2);
return 0;
}
下面调用模板的方式不可取:两个类型不同,却用的同一个T类型,有歧义
#include <iostream>
using namespace std;
template <typename T>
T Add(const T& left,const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add(a1,d1) << endl;
return 0;
}
有以下几种解决方案:
第一种方案是强转类型
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add((double)a1,d2) << endl;
cout << Add(a1,(int) d2) << endl;
return 0;
}
会发现答案不一样,因为用的模板不一样
第二种方案就是显示实例化
这个意思就是不用推了,直接明说T的类型
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add<int>(a1,d2) << endl;
cout << Add<double>(a1, d2) << endl;
return 0;
}
第三种方案写两个参数,这样就不会有歧义了
template <typename T1,typename T2>
T1 Add(const T1& left,const T2& right)
{
return left + right;
}
同名的模板和函数能同时存在吗?
答案是可以:
#include <iostream>
using namespace std;
template <typename T>
T Add(const T& left,const T& right)
{
return left + right;
}
int Add(const int& left, const int& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add(a1,a2) << endl;
cout << Add(d1, d2) << endl;
return 0;
}
通过调试可以得知:
1.有现成,吃现成的(匹配)
2.有现成的,但是不够匹配的,有模板,就会选择自己实例化模板
三.类模板
引例:
#include <iostream>
using namespace std;
template <typename T>
class Stack
{
public:
void Push(const T& x)
{
;
}
private:
T* _a;
int _top;
int _capacity;
};
int main()
{
Stack<int> str1;//显示实例化
Stack<double> str2;//显示实例化
return 0;
}
类模板不能推演实例化了,只能显示实例化,因为没有传参的问题,所以无法推演类型,只能自己指定,并且str1与str2两个类不是同一个类
可能就会想为什么不用typedef,这里讲以下,假设下面这个代码str1存int,str2存double怎么办。用只能用typedef改名,定义两份,太过于麻烦
int main()
{
Stack s1;// int
Stack s2;// double
return 0;
}
然后我在学习list的时候遇到了一个模板的问题:ListNode< T >*到底是什么意思
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
};
我是这样理解的:比如我要用这个模板,创建了一个变量叫做Node,< int >表示我要用int模板啦,然后ListNode< T > * _next就变成了ListNode< int > * _next 。_next表示我的类型是ListNode*,_date类型是int。
int main()
{
ListNode<int> Node;
return 0;
}
四.非类型模板参数
函数参数(T 对象1,T 对象2)(T表示类型)
模板参数 <class 类型1,class 类型2>
引例:这里想要创建10个int大小的a1,和1000个int大小的a2
template<class T>
class array
{
private:
T _array[N];
};
int main()
{
array<int> a1;//10
array<int> a2;//1000
return 0;
}
常用的办法就是typedef或再写一个类
typedef N 1000//这样写反而委屈了a1,过多的空间溢出
所以有了非类型模板参数:类型 + 常量
template<class T,size_t N>
class array
{
private:
T _array[N];
};
int main()
{
array<int,10> a1;//10
array<int,1000> a2;//1000
return 0;
}
这里的大小已经在编译的时候已经确定了
非类型模板参数也有限制:
template<string str>
class A
{
};
int main()
{
A<"1111111"> a1;
return 0;
}
C++20之前只支持整型做非类型模板参数(size_t ,int ,char…)
模板也支持缺省参数的,比如栈,队列的适配容器,规则也与函数缺省相似
template<class T,size_t N = 10>
class array
{
private:
T _array[N];
};
int main()
{
array<int> a1
return 0;
}
按需实例化:运行会发现以下代码没有报错
namespace bit
{
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index)
{
size(1);//这里语法错了size()就没有参数
return _array[index];
}
size_t size()const{return _size;}
bool empty()const{return 0 == _size;}
private:
T _array[N];
size_t _size;
};
}
int main()
{
bit::array<int,10> a1;
cout << a1.empty() << endl;
return 0;
}
根据模板实例化->半成品模板->实例化成具体的类/函数->语法编译
实例化类的时候,会按需求实例化(调用哪个成员函数就实例化哪个)
没有调用operator[],没有检查出来,只有调用,才会实例化,才会细致检查语法错误。
五.模板的特化
函数特化:
引例:指针的比较很麻烦,可能会出错
class Date
{
public:
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);
}
private:
int _year;
int _month;
int _day;
};
template<class T>
bool Less(T left,T right)
{
return left < right;
}
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = new Date(2022,7,7);
Date* p2 = new Date(2022, 7, 8);
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
所以有模板的特化:这里有模板才能特化,不能只有特化
template<class T>
bool Less(T left,T right)
{
return left < right;
}
//针对某些特殊类型可以特殊处理
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
也可以这样:
template<>
bool Less(Date* left, Date* right)
{
return *left < *right;
}
如果想和别的类型共用模板也可以这样:(这里就不是特化了)
template<class T>
bool Less(T* left, T* right)
{
return *left < *right;
}
当然这里函数重载也行:(比较推荐用函数重载)
bool Less(Date* left,Date* right)
{
return *left < *right;
}
类特化
跟函数差不多,但比函数精简
template<class T1, class T2>
class Date
{
public:
Date() { cout << "Date<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<>
class Date<char, char>
{
public:
Date() { cout << "Date<char, char>" << endl; }
};
template<class T>//偏特化/半特化
class Date<T, char>
{
public:
Date() { cout << "Date<T, char>" << endl; }
};
template<class T1, class T2>//注意这里别写成class T1*,是类型!!!!!
class Date<T1*,T2*>
{
public:
Date() { cout << "Date<T1*, T2*>" << endl; }
};
template<>
class Date<char*, char*>
{
public:
Date() { cout << "Date<char*, char*>" << endl; }
};
template<class T1,class T2>
class Date<T1&, T2>
{
public:
Date() { cout << "Date<T1&, T2>" << endl; }
};
int main()
{
Date<int, int> d1;
Date<char, char> d2;
Date<int, char>d3;
Date<char*, char*> d4;
Date<int*, string*> d5;
Date<int&, string> d6;
return 0;
}
六.模板分离编译:
这里先说以下结论:尽量声明和定义都写在.h文件里
引例:会发现他会报一个链接错误,func只是与函数size的一个对比
如果把func函数的定义注释掉,那么就会报两个链接错误
说明大概率就是size函数找不到定义
这里说一下编译的时候size和func都只有声明。编译的时候,检查一下函数名鹅参数匹配,没问题就暂时过了
func和size都是既有声明和定义的
那为什么size链接的时候找不到地址,func可以呢?
其主要原因没有实例化,定义的地方不知道T实例化成什么类型,调用的地方知道实例化什么类型,但没有定义。
所以实例化即可:这样把T为int,N=10实例化出来了
那么为什么模板的定义放到.h就不会出现链接错误了?
因为调用函数的时候,都会往上面找,.h预处理展开后,实例化模板时,既有声明也有定义,就直接实例化了。
编译时,有函数定义,就直接有地址,就不需要在链接时取符号表里面找