【C++基础十】泛型编程—模板
- 1.什么是模板
- 2.函数模板的实例化:
- 2.1隐式实例化
- 2.2显示实例化
- 3.函数模板参数的匹配规则
- 4.什么是类模板
- 5.类模板的实例化
- 6.声明和定义分离
1.什么是模板
void swap(int& a, int& b)
{
int tmp = 0;
tmp = a;
a = b;
b = tmp;
}
void swap(double& a, double& b)
{
double tmp = 0;
tmp = a;
a = b;
b = tmp;
}
void Swap(char& a, char& b)
{
char temp = a;
a = b;
b = temp;
}
正常来说,对于不同类型的变量进行交换,需要实现不同的swap函数,这样实现有些太繁琐了
为了解决相似函数的不同调用问题,C++提出泛型编程,编写与类型无关的通用代码,实现代码复用,即模板
模板主要分为函数模板和类模板
模板格式:
template <typename T1, typename T2,…,typename Tn>//一次性可以定义多个类型
typename是用来定义模板参数的关键字,也可以使用class,两者目前是没区别的,但是由于STL大部分用的class,所以建议使用class
template <class T1, class T2,…,class Tn>//一次性可以定义多个类型
swap函数模板:
template <class T>
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
写好上面的代码后,传int类型的变量进去,T就会被实例化为int,以此类推
2.函数模板的实例化:
2.1隐式实例化
实参传给形参后,编译器自动推演模板类型
template <class T>
T add(T& left, T& right)
{
return left + right;
}
int main()
{
int a = 1;
int b = 2;
double p1 = 1.0;
double p2 = 2.0;
//同类型进行可以正常运行
add(a, b);//自动推演类型为int
add(p1, p2);//自动推演类型为double
//-----------
addd(a, p1);//a与p1是不同类型,会报错
return 0;
}
不同类型去模板推演会出现歧义,a传过去将T推演成int,而p1传过去把T推演成double,T无法确定推演int还是double
2.2显示实例化
在函数名后的<>中指定模板参数的类型
template <class 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.1;
Add<int>(a1, d1);//显示实例化
}
指定T的类型为int ,因为d1是double类型,所以在传参时会发生隐式类型转换变成int,若无法转换成功编译器将会报错
3.函数模板参数的匹配规则
模板函数和普通函数可以同时存在
通过调试可以发现调用的是普通函数,因为成本更低,使用模板还需要实例化生成代码,而普通函数可以直接使用
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
Add(10,20)//调用非模板
Add(11.1,6.3);//调用模板
return 0;
}
在调用函数时若参数和非模板函数匹配,那么编译器会优先调用非模板函数
若非模板函数不匹配或模板函数更匹配,那么编译器会优先调用模板函数
4.什么是类模板
template <class T1, class T2, …, class Tn>//和函数模板类似,类模板也可以同时定义多个模板参数
class 类模板名
{
// 类内成员定义
};
有typedef的存在为什么还有类模板?
typedef int STdatatype;
class stack
{
private:
STdatatype* _a;
size_t top;
size_t capacity;
};
int main()
{
stack s1;//想要S1存储int
stack s2;//想要S2存储double
return 0;
}
如果想要改变栈储存的类型可以选择改变typedf定义的类型
但是若想要两个栈分别储存不同的数据类型typedef做不到
两份类的代码几乎是一致的,但若想达到目的就需要再拷贝一份出来,就太繁琐了
一个简易的顺序表:
所有实际类型需要出现的地方用T代替
template<class T>
class Vector
{
public :
Vector(size_t capacity = 10)
: _Data(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
T& operator[](size_t pos)
{
assert(pos < _size);
return _Data[pos];
}
private:
T* _Data;
size_t _size;
size_t _capacity;
};
5.类模板的实例化
类模板只能显示实例化
,这样就可以达到s1存储int,S2存储double
template <class T>
class stack
{
public:
stack(int capacity=4)
{
_a = new T[capacity];
_top = 0;
_capacity = capacity;
}
~stack()
{
delete[]_a;
_capacity = _top = 0;
}
private:
T* _a;
size_t top;
size_t capacity;
};
int main()
{
stack <int>s1;//想要S1存储int
stack <double>s2;//想要S2存储double
return 0;
}
6.声明和定义分离
对于模板,
vector
是类名而不是类型,加上实例化的模板参数后vector<T>
才是类型
析构函数在类外面定义 ,需要使用类型vector < T>
,而T作为模板需要调用template < class T >
必须要再加上类模板template并且要指定类域
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
~Vector();//类中的声明析构函数
void push_back(T x);//类中声明函数
private:
T* _Data;
size_t _size;
size_t _capacity;
};
template <class T>//析构函数在类外面定义 要加上模板
Vector<T>::~Vector()
{
detele[]_pData;
_pData = nullptr;
_size = _capacity = 0;
}
template<class T>//模板类的函数在类外定义,要加上模板
void Vector<T>::push_back(T x)
{
_Date[_size] = x;
_size++;
}
int main()
{
Vector<int> v;
return 0;
}