您现在的位置是:首页 >学无止境 >learn C++ NO.8——初识模板(函数模板、类模板)网站首页学无止境
learn C++ NO.8——初识模板(函数模板、类模板)
文章目录
引言
现在是北京时间2023年6月5号13.31分,距离上一篇博客发布已过一周。期间还零零散散进行了一些期末考试,这也说明了我的大一时光快要结束了。我也想抓着期末的尾巴,好好的复习一下前面的所学内容,争取这周能够更一下简单数据结构的博客。
1.泛型编程
1.1.什么是泛型编程?
泛型编程是一种编程范式,它可以让代码更加通用和灵活。它通过使用类型参数来实现代码的重用,而不是为每种类型编写不同的代码。具体来说,泛型编程允许我们编写可以适用于多种类型的代码,而不必为每种类型都编写一套代码。这样可以提高代码的复用性和可维护性,同时也可以减少代码的冗余和错误。举一个生活中的例子,泛型编程其实是类似于格式化的生产工艺品,只要有一个模具,就可以通过这股模具来大批量生产一样的东西,使生产效率上升。而模板可以复用代码,根据模板参数来适配不同的类型,使得一套通用代码可以适配多种场景。编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
2.函数模板
2.1.什么是函数模板
函数模板是一种通用的函数定义,可以用来定义多个具有相同基本结构但不同数据类型的函数。函数模板是通过在函数定义中使用一个或者多个类型参数来实现的。类型参数可以用来定义函数参数和返回值的数据类型,以及函数体内使用的变量和常量的数据类型。函数模板可以用于任意的数据类型,从而提高了代码的复用性和可读性。在实际的编程中,函数模板可以用于实现通用算法、容器类和其他的常用数据结构。
2.2.为什么需要函数模板
请看下面的场景,假设需要多个类型的变量进行交换操作,在前面的c语言和c++的代码写法是这样的。
// C
void SwapInt(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void SwapDou(double* x, double* y)
{
double tmp = *x;
*x = *y;
*y = tmp;
}
// CPP -->(函数重载)
void Swap(int& x,int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void Swap(double& x,double& y)
{
double tmp = x;
x = y;
y = tmp;
}
可以看到上面的代码的重复率很高,而且只有新需要的类型就要手动添加一份,非常麻烦。其次就是代码不方便维护,有一点错每份代码都要跟着修改,非常的不方便。于是乎我们的c++祖师爷们就整出了函数模板。下面正式介绍函数模板。
2.3.函数模板格式
//两种都可以
template<typename T1,typename T2,...>
//template<class T1,class T2,...>
返回类型 函数名(参数)函数定义
首先,需要写关键字template,后面紧跟一对尖括号。尖括号内部存放的是类型参数。然后换行写函数的定义。下面我以Swap函数来举例。
template<typename T>
void Swap(T& t1, T& t2)
{
T tmp = t1;
t1 = t2;
t2 = tmp;
}
2.4.函数模板实现原理
模板函数在被调用时,编译器会根据不同的数据类型,生成对应的函数实例。编译器会根据实际传入的参数类型,选择合适的函数实例进行调用。模板函数其实是类似我们能够生成各个颜色手机壳的那个模具,而参数类型类似于手机壳的颜色。当我们需要红色的手机壳,就将材料导入模具中并加上红色颜料,便得到了红色的手机壳。
通过调试转到反汇编查看汇编可以看到,编译器确实会自动生成对应数据类型的模板函数实例,并调用模板函数实例。
2.5.函数模板的实例化
用各种不同类型的参数去调用函数模板去实例化时,这一行为叫做函数模板的实例化。函数模板实例化分为两种,分别是显示实例化和自动推导类型实例化。下面就通过代码来举例。
1 #include<iostream>
2
3 using namespace std;
4
5 template<typename T>
6 T Add(const T& x, const T& y)
7 {
8 return x + y;
9 }
10
11 int main()
12 {
13 int i1 = 10;
14 int i2 = 20;
15 double d1 = 3.14;
16 double d2 = 1.23;
17
18 //编译器自动推导
19 cout << Add(i1,i2) << endl;
20 cout << Add(d1,d2) << endl;
21 cout << Add(i1,(int)d2) << endl;
22 cout << Add((double)i1,d2) << endl;
23
24 //显示实例化
25
26 cout << Add<double>(d1,d2) << endl;
27 cout << Add<int>(d1,d2) << endl;
28
29 return 0;
30 }
对于这里模板的模板参数需要用const修饰呢?这是因为int类型的参数提升成double类型参数会产生一个临时变量。而临时变量具有常属性,这里的const修饰模板参数后,可以避免权限放大的问题而导致编译器报错。 为什么编译器可以自动推导类型还需要显示实例化呢?请看下面的样例演示。
#include<iostream>
using namespace std;
template<typename T>
T* MyAlloc(size_t sz)
{
return new T[sz];
}
int main()
{
//必须显示实例化调用函数模板
int* pi = MyAlloc<int>(10);
return 0;
}
3.类模板
在没有类模板之前,使用C语言编写一些容器需要重复大量的模块。因为,C语言不支持类模板,这也是C语言没法提供标准容器库。类模板的出现标志着C++进入了一个新的时代,因为它带来的是STL标准库的横空出世。
//C语言
typedef int DataType;
class Stack
{
public:
Stack(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++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
如果我们在一个模块里既要用栈存int类型的数据,又要用栈存double类型的数据。该怎么办呢?
class StackInt
{
public:
StackInt(size_t capacity = 3)
{
_array = (int*)malloc(sizeof(int) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(int data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~StackInt()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
int* _array;
int _capacity;
int _size;
};
class StackDouble
{
public:
StackDouble(size_t capacity = 3)
{
_array = (double*)malloc(sizeof(double) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(double data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~StackDouble()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
double* _array;
int _capacity;
int _size;
};
int main()
{
StackInt SI;//存int类型
StackDouble SD;//存double类型
return 0;
}
这样实在是太麻烦了,不仅代码不容易维护,而且对编程效率有着极大的影响。所以我们的祖师爷们整出了类模板。类模板其实就是让编译器可以根据我们的需求实例化出各种类型的类,大大提高了我们的效率。
3.1.类模板定义格式
3.1.1.类模板语法
template<class T,class T2,...>
class classname
{
//类成员
};
注意:普通类的类名就是类的类型。模板类的的类名就是类名本身,而模板类的类型是类名<模板参数>
3.1.2.模板类的定义
// 类模板
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 = (T*)malloc(sizeof(T) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}*/
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
template<class T>
void Stack<T>::Push(const T& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
模板类的成员函数声明定义分离时,需要在每个成员函数声明函数模板。并制定对应的类模板的类型。
3.2.模板类的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
Stack<int> S1;
Stack<double> S2;