C++初阶——模板
一、概念引入
1.如何实现一个通用的交换函数,使它既可以用来交换各种类型的数据呢?
通过前面的学习,我们知道函数重载可以帮我们实现这一功能,代码如下:
运行结果如图:
使用函数重载虽然可以实现,但是有一下几个不好的地方:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
- 代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
- C++引入了
模板
的概念,就像在模具中浇筑一样,我们只需要浇筑不同的材料液,就能到由对应材料的物体。C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(数据类型
),来获得不同材料的铸件(即生成具体类型的代码
)
2.模板的分类
模板
分为函数模板
和类模板
,模板是泛型编程的基础。什么是泛型编程,泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段。
二、函数模板
1.函数模板的概念
函数模板
代表了一个函数家族,该函数模板与类型无关,在使用时被实例化,根据实参类型产生函数的特定类型版本。
2.函数模板的格式
template<typename T1, typename T2,...,typename Tn>
返回值类型
函数名(参数列表)
{ //语句 }
我们来简单的使用一下,就以交换函数为例,如图所示:
template
是C++泛型编程的核心关键字,用于定义与数据类型无关的代码模板,这里的语法是固定的格式,template<typename T1, typename T2,......,typename Tn>
typename
的意思就是类型名,表示这里的T
既可以是int T
,也可以是double T
,还可以是char T
等等。这里的传参传递的是引用,引用相较于指针更具优势,因此在前面我们也讲过能用引用传参的地方就尽量用引用传参。
总体看来,函数模板还是很好理解的,只是把之前的具体数据类型替换成了模板而已。
测试代码如下:
运行结果如图:
3.函数模板的原理
刚才我们写了一个函数模板,void Swap(T& x1, T& x2)
,函数模板是一个蓝图,它本身并不是函数,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double
类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double
类型,然后产生一份专门处理double
类型的代码,对于字符类型也是如此,编译器处理函数模板是要生成对应的具体函数的,如图所示:
4.函数模板的实例化
用不同类型的参数使用函数模板时,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
(1)隐式实例化:让编译器根据实参推演模板参数的实际类型
我们来看这样一段代码:
我们来分析一下,这里的第一个函数还是刚才的交换函数,我们已经很熟悉了,下面的一个Func函数
有一点不一样,这里安排了两个模板参数T1
和T2
。这里编译器会自动根据传递的参数类型来分别判断应该示例化成什么数据类型,比如说这里的Swap
传递的两个值必须是同一个数据类型,如果不同,就会出现矛盾,因为这里只有一个模板参数,如果前一个是int
型,实例化出的函数的功能是交换两个整数,如果后一个传递的是float
,编译器又会将它实例化成交换两个浮点型的函数,前后会出现矛盾,而且这里也不存在什么隐式类型转换,整型提升等等;而Func
函数接收的两个值可以是不同的数据类型,因为这里有两个不同的模板参数,不会出现矛盾。我们来看一下这里的运行结果:
如果Swap函数传递的不是同一个数据类型,就会出现报错:
(2)显式实例化:在函数名后的<>中指定模板参数的实际类型
我们来看这样一段代码:
这里的加法函数也只有一个模板参数,也就是说,只能实现同种数据类型的加法,如果是两个不同类型的数据相加,则需要在函数名Add
后的<>
中指定模板参数的实际类型,这就是显示实例化,就好比告诉编译器:到底听谁的。
运行结果如图:
5.模板参数的匹配原则
(1)一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
(2)对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
(3)模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
这里的普通函数虽然接受的参数是两个int
型,但如果传递的数据类型有所不同,它也会进行自动类型转换;而模板函数就会发生报错,在上文中也做出了详细解释。
三、类模板
1.类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{//类内成员定义
};
2.类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
我们以动态顺序表为例:
typedef int DataType;
class StackInt
{
public:StackInt(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~StackInt(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};typedef double DataType;
class StackDouble
{
public:StackDouble(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~StackDouble(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};int main()
{StackInt s1;//intStackDouble s2;//doublereturn 0;
}
这里写了两种顺序表,如果能合二为一,根据需要自动推演就更好了,所以我们使用类模板:
template<class T>
class Stack
{
public:Stack(size_t capacity = 3);void Push(const T& data);~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:T* _array;int capacity;int _size;
};template<class T>
Stack<T>::Stack(size_t capacity)
{_array = new T[capacity];_capacity = capacity;_size = 0;
}template<class T>
void Stack<T>::Push(const T& data)
{//CheckCapacity();_array[_size] = data;_size++;
}
这里采用了类里声明,类外定义的方式,要注意的是,在类外声明时,还要加上template关键字
声明模板参数,因为它的作用域只到紧靠着它的函数。
3.typename和class
在上面的代码中,我们注意到<>
中的typename
变成了class
,在C++模板编程中,typename
和class
均用于声明模板类型参数,但二者在语义、使用场景和代码意图上存在细微差异。至于具体是什么,我们以后会介绍。二者在绝大多数情况下没什么区别,建议优先使用typename
。
四、代码资源
本期内容涉及到的代码如下:
#include<iostream>using namespace std;//void Swap(int& left, int& right)
//{
// int tmp = left;
// left = right;
// right = tmp;
//}
//void Swap(char& left, char& right)
//{
// char tmp = left;
// left = right;
// right = tmp;
//}
//void Swap(double& left, double& right)
//{
// double tmp = left;
// left = right;
// right = tmp;
//}
//int main()
//{
// int a = 1;
// int b = 2;
// printf("交换前:a=%d,b=%d\n", a, b);
// Swap(a, b);
// printf("交换后:a=%d,b=%d\n", a, b);
//
// char c = 'a';
// char d = 'b';
// printf("交换前:%c,%c\n", c, d);
// Swap(c, d);
// printf("交换后:%c,%c\n", c, d);
//
// double e = 1.56;
// double f = 1.89;
// printf("交换前:e=%lf,f=%lf\n", e, f);
// Swap(e, f);
// printf("交换后:e=%lf,f=%lf\n", e, f);
//
// return 0;
//}//template<typename T>
//void Swap(T& x1, T& x2)
//{
// T tmp = x1;
// x1 = x2;
// x2 = tmp;
//}
//
//int main()
//{
// int a = 1;
// int b = 2;
// cout << a << ',' << b << endl;
// Swap(a, b);
// cout << a << ',' << b << endl;
//
// double c = 1.56;
// double d = 1.89;
// cout << c << ',' << d << endl;
// Swap(c, d);
// cout << c << ',' << d << endl;
//
// char e = 'a';
// char f = 'b';
// cout << e << ',' << f << endl;
// Swap(e, f);
// cout << e << ',' << f << endl;
//
// return 0;
//}// 泛型编程
// 函数模板// 函数模板实例化生成具体函数
// 函数模板根据调用,自己推导模板参数的类型,实例化出对应的函数//template<typename T>
//void Swap(T& x1, T& x2)
//{
// T tmp = x1;
// x1 = x2;
// x2 = tmp;
//}
//
//template<typename T1,typename T2>
//T1 Func(const T1& x, const T2& y)
//{
// cout << x << " " << y << endl;
// return x;
//}
//
//int main()
//{
// int a = 1;
// int b = 2;
// cout << a << ',' << b << endl;
// Swap(a, b);
// cout << a << ',' << b << endl;
//
// double c = 1.56;
// double d = 1.89;
// cout << c << ',' << d << endl;
// Swap(c, d);
// cout << c << ',' << d << endl;
//
// Func(5, 6);
// Func(1, 1.23);
//
// return 0;
//}//template<typename T>
//T Add(const T& left, const T& right)
//{
// return left + right;
//}
//
//int main()
//{
// int a = 1;
// int b = 2;
// cout << Add(a,b) << endl;
//
// double c = 1.5;
// double d = 1.6;
// cout << Add(c, d) << endl;
//
// //cout << Add(a, d) << endl;
//
// cout << Add<int>(a, d) << endl;
// cout << Add<double>(a, d) << endl;
//
// return 0;
//}//有些函数无法自动推,只能显示实例化
//template<typename T>
//T* Alloc(int n)
//{
// return new T[n];
//}
//
//int main()
//{
// double* p1 = Alloc<double>(10);
//
// return 0;
//}//typedef int DataType;
//class StackInt
//{
//public:
// StackInt(size_t capacity = 3)
// {
// _array = (DataType*)malloc(sizeof(DataType) * capacity);
// if (NULL == _array)
// {
// perror("malloc申请空间失败!!!");
// return;
// }
// _capacity = capacity;
// _size = 0;
// }
// void Push(DataType data)
// {
// // CheckCapacity();
// _array[_size] = data;
// _size++;
// }
// // 其他方法...
// ~StackInt()
// {
// if (_array)
// {
// free(_array);
// _array = NULL;
// _capacity = 0;
// _size = 0;
// }
// }
//private:
// DataType* _array;
// int _capacity;
// int _size;
//};
//
//typedef double DataType;
//class StackDouble
//{
//public:
// StackDouble(size_t capacity = 3)
// {
// _array = (DataType*)malloc(sizeof(DataType) * capacity);
// if (NULL == _array)
// {
// perror("malloc申请空间失败!!!");
// return;
// }
// _capacity = capacity;
// _size = 0;
// }
// void Push(DataType data)
// {
// // CheckCapacity();
// _array[_size] = data;
// _size++;
// }
// // 其他方法...
// ~StackDouble()
// {
// if (_array)
// {
// free(_array);
// _array = NULL;
// _capacity = 0;
// _size = 0;
// }
// }
//private:
// DataType* _array;
// int _capacity;
// int _size;
//};
//
//int main()
//{
// StackInt s1;//int
// StackDouble s2;//double
//
// return 0;
//}//template<class T>
//class Stack
//{
//public:
// Stack(size_t capacity = 3);
// void Push(const T& data);
// ~Stack()
// {
// if (_array)
// {
// free(_array);
// _array = NULL;
// _capacity = 0;
// _size = 0;
// }
// }
//private:
// T* _array;
// int capacity;
// int _size;
//};
//
//template<class T>
//Stack<T>::Stack(size_t capacity)
//{
// _array = new T[capacity];
// _capacity = capacity;
// _size = 0;
//}
//
//template<class T>
//void Stack<T>::Push(const T& data)
//{
// //CheckCapacity();
// _array[_size] = data;
// _size++;
//}// 专门处理int的加法函数
//int Add(int left, int right)
//{
// return left + right;
//}
// 通用加法函数
//template<class T>
//T Add(T left, T right)
//{
// return left + right;
//}
//void Test()
//{
// Add(1, 2); //与非模板函数匹配,编译器不需要特化
// Add<int>(1, 2); //调用编译器特化的Add版本
//}// 专门处理int的加法函数
//int Add(int left, int right)
//{
// return left + right;
//}
// 通用加法函数
//template<class T1, class T2>
//T1 Add(T1 left, T2 right)
//{
// return left + right;
//}
//void Test()
//{
// Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
// Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
//}//int Add(int left, int right)
//{
// return left + right;
//}
//int main()
//{
// int a = 1;
// double b = 1.89;
// cout << Add(a, b) << endl;
// return 0;
//}
五、本期总结+下期预告
本期内容我们讲解了C++中的模板,接下来就会正式进入C++标准模板库STL的讲解!
感谢大家的关注,我们下期再见!