C++初阶(八)--初识模板
目录
引入
一、什么是模板
二、函数模板
1.函数模板的概念
2.函数模板的格式
template关键字
模板参数列表
3.函数模板的原理
4.函数模板的实例化
5.数模板的匹配原则
三、类模板
1.类模板的定义格式
2.类模板的实例化
引入
在编程的世界里,我们经常会遇到这样的情况:需要编写一些功能相似但处理的数据类型不同的函数或类。比如说,我们可能要写一个函数来交换两个整数的值,代码大概是这样的:
// 交换两个整型
void Swapi(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
// 交换两个双精度浮点型
void Swapd(double* p1, double* p2)
{double tmp = *p1;*p1 = *p2;*p2 = tmp;
}
因为C语言不支持函数重载,所以用于交换不同类型变量的函数的函数名是不能相同的,并且传参形式必须是址传递,不能是值传递。
而在学习了C++的函数重载和引用后,我们又会用如下方法实现两个数的交换:
// 交换两个整型
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;
}
你看,这两个函数的逻辑几乎一模一样,只是处理的数据类型不同。这样每次遇到不同类型都要重新写一遍类似的代码,不仅麻烦,而且代码冗余度很高。这时候,模板就闪亮登场啦!它就像是一把万能钥匙,可以让我们编写一次代码,就能适用于多种不同的数据类型,大大提高了代码的复用性和可维护性。
一、什么是模板
想象你是一个超级厉害的玩具制造商,你有一个神奇的模具。这个模具本身不是一个具体的玩具,但只要你往里面注入不同的材料(比如塑料、橡胶等),它就能生产出各种各样具体的玩具,比如小汽车、小玩偶等。
在编程世界里,模板就像是这个神奇的模具。它不是一个实实在在能直接运行的程序部分,但它定义了一种通用的 “形状” 或者说 “框架”,当你给它提供具体的类型(就如同往模具里注入不同材料),它就能生成针对该类型的具体代码,就像生产出不同的玩具一样。
二、函数模板
1.函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2.函数模板的格式
让我们来看看如何用模板解决上面交换值的问题吧。首先是函数模板的格式:
template <模板参数列表>
返回类型 函数名(函数参数列表) {// 函数体,包含对参数进行操作并返回结果等逻辑
}
template
关键字
这是用于声明接下来要定义的是一个函数模板的关键字,告诉编译器后面的代码是基于模板机制来编写的。
模板参数列表
放在尖括号 <>
内,用于指定函数模板可以接受哪些类型或非类型参数。
- 类型参数:
- 使用
typename
或class
关键字来声明一个类型参数。例如:
- 使用
template <typename T>
// 或者
template <class T>
用例:
template<typename T>
void swap(T& a, T& b) {T temp = a;a = b;b = temp;
}
这里的 template<typename T>
就是在告诉编译器,我们要定义一个模板函数,其中的 T
是一个模板参数,它可以代表任何数据类型哦。当我们在调用这个函数的时候,编译器会根据我们传入的实际参数类型自动推断出 T
应该是什么类型,然后生成相应的函数代码。比如说:
int main()
{int a = 5, b = 10;swap(a, b); // 这里编译器会自动推断出T为int类型,并生成对应的swapInt函数float c = 3.14, d = 2.71;swap(c, d); // 这里编译器会自动推断出T为float类型,并生成对应的swapFloat函数return 0;
}
注意:typename是用来定义模板参数的关键字,也可以用class代替,但是不能用struct代替。
3.函数模板的原理
函数模板是一个蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于double类型也是如此。
4.函数模板的实例化
用不同类型的参数使用模板时,称为模板的实例化。模板实例化分为隐式实例化和显示实例化。
隐式实例化:让编译器根据实参推演模板参数的实际类型
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = 10, b = 20;int c = Add(a, b); //编译器根据实参a和b推演出模板参数为int类型return 0;
}
特别注意:使用模板时,编译器一般不会进行类型转换操作。所以,以下代码将不能通过编译:
int a = 10;double b = 1.1;int c = Add(a, b);
因为在编译期间,编译器根据实参推演模板参数的实际类型时,根据实参a将T推演为int,根据实参b将T推演为double,但是模板参数列表中只有一个T,编译器无法确定此处应该将T确定为int还是double。
此时,我们有两种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面说到的显示实例化。
显示实例化
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.2, d2 = 20.2;//显示实例化cout << Add<int>(a1, d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}
注意:使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。
隐式实例化:
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.2, d2 = 20.2;//自动推导类型,隐式实例化cout << Add(a1, (int)d1) << endl;cout << Add((double)a1, d1) << endl;return 0;
}
5.数模板的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = 10, b = 20;int c = Add(a, b); //调用非模板函数,编译器不需要实例化int d = Add<int>(a, b); //调用编译器实例化的Add函数return 0;
}
对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{return x + y;
}
int main()
{int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数return 0;
}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译return 0;
}
因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2
三、类模板
1.类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
例如:
template <class T>
class Stack
{
public:Stack(size_t capacity = 5){_array = new T(capacity);_capaicty = capacity;_size = size;}void Push(const T& data);private:T* _array;size_t _capaicty;size_t _size;
};//声明和定义分离的写法
template <class T>
void Stack<T>::Push(const T& data)
{//......_array[_size++] = data;
}int main()
{//实例化生成对应的类,这里是两个不同的类型//Stack是类名,Stack<int>才是类型Stack<int> st1;Stack<double> st2;
}
注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。 除此之外,类模板不
支持分离编译,即声明在xxx.h文件中,而定义却在xxx.cpp文件中。
2.类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后面根<>,然后将实例化的类型放在<>中即可。
// Stack是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double
本篇博客到此结束,如有问题,评论区留言~