😈个人主页: 起名字真南
👿个人专栏:【数据结构初阶】 【C语言】 【C++】
目录
- 1 引言
- 2 函数模板
- 2.1 通过函数重载的方式实现swap函数
- 2.2 通用使用函数模板的方式实现swap函数
- 2.2.1 函数模板格式
- 2.3 函数模板实现原理
- 2.4 函数模板的实例化
- 2.4.1 隐式实例化
- 2.4.2 显示实例化
- 2.4 函数模板的特化
- 3 类模板
- 3.1 类模板的定义格式
- 3.2 类模板的实例化
1 引言
C++ 模板是实现泛型编程的强大工具,允许我们编写适用于多种数据类型的通用代码。通过模板,我们可以创建函数和类,而不需要为每种数据类型重复代码。例如,一个简单的交换函数可以通过模板实现,以支持
int
、double
、string
等不同类型。
模板不仅减少了代码重复,还提升了代码的灵活性和复用性。接下来,我们将介绍 C++ 中的函数模板、类模板和模板特化,带你了解如何用模板编写高效、通用的代码。
2 函数模板
函数模板是 C++ 提供的用于定义通用函数的一种方式。通过函数模板,你可以创建一个函数,使它能够处理不同类型的数据,而不必为每种类型编写单独的函数。
2.1 通过函数重载的方式实现swap函数
#include<iostream>using namespace std;
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;
}
void swap(char& x, char& y)
{char tmp = x;x = y;y = tmp;
}
我们通过函数重载的方式虽然能够实现不同类型数据之间的交换但是还存在一些缺点:
1 :需要实现的代码量比较大,需要为每一种类型都编写一段代码
2 :代码的可扩展性比较差,每当有新的类型出现都需要手动添加代码
3: 如果函数的参数比较多当这些参数类型可以组合时,可能需要定义大量重载函数。
4 : 代码的可维护性比较差,出现错误的时候修改的工程量比较大
2.2 通用使用函数模板的方式实现swap函数
2.2.1 函数模板格式
template<typename T1,typename T2,typename T3......>
返回值类型 函数名(参数列表)
{函数体}
template<typename T>
void swap(T& t1, T& t2)
{T tmp = t1;t1 = t2;t2 = tmp;
}
注意模板中的typename是用来定义模板参数的关键字,可以换成class,(但是切记不能使用struct)
2.3 函数模板实现原理
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数用来调用。比如当double类型使用函数模板时,编译器通过对实参类型的推演,将T 推演成double类型,然后专门产生一份处理double类型的代码
2.4 函数模板的实例化
在 C++ 中,函数模板的实例化是指在调用函数模板时为其指定具体的数据类型。编译器会根据传入的参数类型自动推导出模板参数类型,或者你也可以显式地指定类型。
2.4.1 隐式实例化
template<typename T>
void swap(T& t1, T& t2)
{T tmp = t1;t1 = t2;t2 = tmp;
}
int main()
{int a = 10;double b = 20.0;swap(a, b);return 0;
}
这里需要注意,如果这样写编译器是无法通过编译的,因为在函数模板中两个参数是相同的,可是我们在调用swap函数的时候传入的a,b两个参数分别是int,double两种类型,所以我们要知道在进行推演的时候是不支持隐式类型转换的
我们有两个解决方法:
- 强制类型转换:
int main()
{int a = 10;double b = 20.0;swap(a, (int)b);return 0;
}
如果我们输入了这串代码依旧会报错这是因为什么呢?(编译环境VS2022其他版本可能不会出现这种问题)
出现这个问题的原因也是在发生在类型推导的过程中的,因为在推导第一个函数参数是a的类型是int类型,所以T推演的结果就是int类型,但是当推导第二个类型的时候虽然我们通过强制类型转换(int)将double类型的变量b转换成了int类型但是编译器还是会按照他的第一个类型进行推演,因为推演的过程发生在类型转换之前,因为推导的过程是在函数编译的阶段进行的
2.4.2 显示实例化
int main()
{int a = 10;double b = 20.0;swap<int>(a, b);return 0;
}
在函数名后面的<>中指定模板参数的实际类型,但是如果类型不匹配编译器依然还是会报错,报错的愿意同隐式实例化。
那么我们到底该如何去解决这种问题呢?
第一种方法 :
我们将模板参数的类型从一个增加为两个:
template<typename T1, typename T2>
void swap(T1& t1, T2& t2)
{T1 tmp = t1;t1 = t2;t2 = tmp;
}
第二种解决方法:我们就需要函数模板的特化来解决这种问题
2.4 函数模板的特化
所谓函数模板的特化,就是指我们虽然有了一个函数模板但是为了一些特殊的函数类型我们将原代码重新编写与之对应的类型。比如我们上面的swap函数只能进行相同类型数据的转换,但是并不能满足实际的情况所以我重载了一个新的swap函数,这个新的函数就叫做特化。
template<typename T>
void swap(T& t1, T& t2)
{T tmp = t1;t1 = t2;t2 = tmp;
}void swap(int& x, double& y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 10;double b = 20.0;swap(a, b);return 0;
}
3 类模板
3.1 类模板的定义格式
template<class c1, class c2.......>
class 类模板名
{
//类内成员定义
};
template<class T>
class Stack
{
public:Stack(size_t capacity = 4){_arr = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _arr;size_t _size;size_t _capacity;
};template<class T>
void Stack<T>::Push(const T& data)
{_arr[_size] = data;++_size;
}int main()
{Stack<int> s1;Stack<double> s2;s1.Push(1);s2.Push(2.0);return 0;
}
运行结果:
3.2 类模板的实例化
类模板的实例化与函数模板的实例化不同,类模板实例化需要在类名的后面加上<>,然后再里面加上类型,如果缺少就会报错,不会像函数模板一样进行推导。
//Stack是类名,Stack<int>才是类型Stack<int> s1;Stack<double> s2;