云收天彩色
木叶落秋声
目录
函数模板
函数模板的实现
函数模板的实例化
模板参数的匹配原则
参数模板推不出来的情况
类模板
类模板的定义格式
类模板的实例化
契子 ✨
我们在学 C语言 的时候应该都写过交换两个数的函数 swap 吧
当时我们只是写了 int 类型,那么如果是 char、double类型呢?是不是像这样:写多个 swap 函数
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;
}
以上使用函数重载虽然可以实现,但是有一下几个不好的地方:
<1>重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数 |
<2>代码的可维护性比较低,一个出错可能所有的重载均出错 |
那能不能用一个模板,让编译器根据不同的类型利用该模板来生成代码呢?
就像以下的杯子一样,只要确定好具体的模型,就可以填充自己想要的颜色
答案是有的,接下来我将带大家了解模板的基本操作:
函数模板
概念:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
格式:
template<typename T1, typename T2,......,typename Tn>
注意:typename 是用来定义模板参数关键字,也可以使用 class
template<class T1, class T2,......,class Tn>
函数模板的实现
举个栗子~
#include<iostream>
using namespace std;
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 2;
cout << "a = " << a << " " << "b = " << b << endl;
Swap(a, b);
cout << "a = " << a << " " << "b = " << b << endl;
double c = 1.1, d = 2.2;
cout << "c = " << c << " " << "d = " << d << endl;
Swap(c, d);
cout << "c = " << c << " " << "d = " << d << endl;
return 0;
}
我们发现不管是 int 类型还是 double 类型都可以往里套,不仅如此 指针 类型也可以调用
int a = 1, b = 2;
int* pa = &a;
int* pb = &b;
库里面的 swap :
⭐我们发现库里面的 swap 函数的底层代码就是用模板!!!
就像我们古代的活字印刷术,名人的手稿诗集就是模板,通过模板我们可以印刷出各种类型
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
函数模板的实例化
用不同类型的参数使用函数模板时 ,称为函数模板的 实例化模板参数实例化分为: 隐式实例化和显式实例 化隐式实例化:让编译器根据实参推演模板参数的实际类型显式实例化:在函数名后的<>中指定模板参数的实际类型
先举个栗子~
#include<iostream>
using namespace std;
template<typename T>
T Add(const T& n, const T& m)
{
return n + m;
}
int main()
{
int a = 1, b = 2;
double c = 1.1, d = 2.2;
Add(a, b);
Add(c, d);
return 0;
}
以上的代码很简单~我们传了同类型的两个参数 int类型的a和b、double类型的c和d
像这种能自动推演出 T 的类型的就是隐式实例化
那么我们能不能传不同类型的两个参数呢比如说:a和c
我们发现是不行的,原因如下:
在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过 实参a将T推演为int 类型,通过 实参c将T推演为double 类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为 int 或者 double 类型而报错
那有没有什么解决办法呢?
方法一:用户自己来强制转化✨
Add(a, (int)c);
Add((double)a, c)
因为 double类型 的 c 强转成 int类型 ,精度会丢~
方法二:显示实例化✨
Add<int>(a, c)
Add<double>(a, c)
显示实例化就是现将数据进行隐式类型转化成自己指定类型(不让编译器自动推演参数)
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
以上的:
<1> c 就是隐式类型转化成 double
<2>c 就是隐式转换成 int
方法三:利用 auto 自动推导 ✨
template<typename T1, typename T2>
auto Add(const T1& n, const T2& m)
{
return n + m;
}
以上代码如果返回值是整数就会自动推导成 int ,是小数则自动推导成 double
这样精度就不会丢失~
模板参数的匹配原则
<1>一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函 数<2> 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
#include<iostream>
using namespace std;
int Add(int left, int right)
{
cout << "Add(int left, int right)" << endl;
return left + right;
}
template<class T>
T Add(T left, T right)
{
cout << "T Add(T left, T right)" << endl;
return left + right;
}
int main()
{
Add(1, 2);
Add<int>(1, 2);
return 0;
}
简单来讲就是模板和用模板创造的函数 Add 可以同时存在,如果数据类型刚好和 Add 匹配,那么就可以直接用(不调用模板),如果不匹配可以隐式类型转换就可以将就用没有就用模板,当然如果我们想用模板的话,我们可以显示实例化
🌤️举个栗子~你想吃蛋糕了,如果有你喜欢吃的蛋糕肯定是有现成的吃现成的,如果没有可以将就一下吃别的,若是一个蛋糕都没有,就要求店员有模板做
参数模板推不出来的情况
举个栗子~我们来看看以下代码,Func(1)是否推演T成功
#include<iostream>
using namespace std;
template<class T>
T* Func(int a)
{
T* p = (T*)operator new(sizeof(T));
new(p)T(a);
return p;
}
int main()
{
int* ret = Func(1);
return 0;
}
因为我们之前 T 的推演是编译器通过对实参类型的推演的,而这里我们的参数并不是 T
那怎么办呢?
这里我们的显示实例化起到很大作用
int* ret = Func<int>(1);
编译器推导不出来,我们就自己指定类型
类模板
类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
举个栗子~我们之前写过的栈
#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using StackDataUsing = int;
class Stack
{
public:
void Init(size_t newcapacity = 4)
{
StackDataUsing* newNode = new StackDataUsing[newcapacity];
if (newNode == nullptr)
{
perror("new");
exit(-1);
}
data = newNode;
capacity = newcapacity;
top = 0;
}
void Destory()
{
delete data;
data = nullptr;
top = capacity = 0;
}
void push(StackDataUsing x)
{
if (capacity == top)
{
StackDataUsing* newNode = new StackDataUsing[2 * capacity];
if (newNode == NULL)
{
perror("new");
exit(-1);
}
data = newNode;
capacity *= 2;
}
data[top++] = x;
}
StackDataUsing Top()
{
return data[top - 1];
}
void Pop()
{
assert(top > 0);
top--;
}
bool Empty()
{
return top == 0;
}
private:
StackDataUsing* data;
size_t capacity;
size_t top;
};
int main()
{
Stack st1; //int
Stack st2; //double
return 0;
}
我们之前想创建两个 double 、int 类型的栈还需要写两个类来进行,但是现在不必了
因为我们升级了~
#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
template<class T>
class Stack
{
public:
void Init(size_t newcapacity = 4)
{
T* newNode = new T[newcapacity];
if (newNode == nullptr)
{
perror("new");
exit(-1);
}
data = newNode;
capacity = newcapacity;
top = 0;
}
void Destory()
{
delete data;
data = nullptr;
top = capacity = 0;
}
void push(const T& x)
{
if (capacity == top)
{
T* newNode = new T[2 * capacity];
if (newNode == NULL)
{
perror("new");
exit(-1);
}
data = newNode;
capacity *= 2;
}
data[top++] = x;
}
T Top()
{
return data[top - 1];
}
void Pop()
{
assert(top > 0);
top--;
}
bool Empty()
{
return top == 0;
}
private:
T* data;
size_t capacity;
size_t top;
};
我们只要将原来 StackDataUsing 的位置置为 T 即可,T 就是对应的数据类型
类模板的实例化
类模板实例化与函数模板实例化不同, 类模板实例化需要在类模板名字后跟 <> ,然后将实例化的类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
Stack<int> st1; //int
Stack<double> st2; //double
我们的模板是写给编译器的,编译器通过实例化帮我们创建对应的类
注意: Stack<int> 和 Stack<double>是两个不同的类
总结:
我们在函数模板通常是通过参数进行 T 类型的推导,而类模板这是直接显示实例化
还有一个小点需要注意~如果我们要做声明与定义怎么办?
template<class T>
class Stack
{
public:
void push(const T& x);
// ...
private:
T* data;
size_t capacity;
size_t top;
};
template<class T>
void Stack<T>::push(const T& x)
{
if (capacity == top)
{
T* newNode = new T[2 * capacity];
if (newNode == NULL)
{
perror("new");
exit(-1);
}
data = newNode;
capacity *= 2;
}
data[top++] = x;
}
声明与定义分离必须模板化✨
冉冉星河远
共与乘舟还
先介绍到这里啦~
有不对的地方请指出💞