零.前言
在书写函数或类时,我们可能遇到这样的问题,明明结构一模一样但是只由于某个参数的类型不同就需要重新写函数,或者构建重载函数,重新书写类就更加麻烦了,并且使程序显得冗长,本文将介绍C++中的模板,从而解决相似函数重复书写问题。
1.泛型编程
(1)问题引入
假设我们要实现一个两数交换的程序:
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
cout << a << " " << b << endl;
}
这看起来既简洁,又容易,但是如果我们再加两个double类型的变量c和d呢,要交换它们的值就需要再建立一个函数。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Swap(double& c, double& d)
{
double tmp = c;
c = d;
d = tmp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
double c = 1.1, d = 2.2;
Swap(c, d);
cout << a << " " << b << endl;
cout << c << " " << d << endl;
}
我们知道两个Swap函数构成了函数重载,但如果我们还需要将两个char类型的变量进行交换呢,那么就还需要构建两个char类型的变量。
通过观察发现,这几个函数除了参数类型不一样之外,其他的内容都一模一样,如果有这样的需求,一直建立函数会很麻烦。有没有一种只写一个函数就可以实现全类型操作的方法呢?
C++为了解决这一问题,提出了泛型编程的概念。
(2)泛型编程概念
泛型编程,即编写与类型相关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
泛型编程中的模板分为两种,一种是函数模板,一种是类模板。
2.函数模板
(1)概念
函数模板代表了一个函数家族,该模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
(2)格式
template<class T1,class T2,.....class Tn>//class也可以换成typename
//函数的具体实现
(3)举例
以我们上一个例子为例,实现多种类型的两数交换:
template<class T>
void Swap(T& a, T& b)//建立一个模板
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
double c = 1.1, d = 2.2;
Swap(c, d);
cout << a << " " << b << endl;
cout << c << " " << d << endl;
}
这里根据模板,当传入a和b时,T自动识别为int类型,当传入c和d时,T自动识别为double类型。
如果a和b,c和d的类型不同呢?
template<class T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
int a = 1, b = 2;
double c = 1.1, d = 2.2;
cout << Add<int>(a,c) << endl;//默认类型转换
}
我们可以通过Add(a,c)来将结果转换成int型,这其中会发生隐式类型转换。
同理我们也可以通过Add(b,d)将其转换成double类型。
(4)原理
当我们使用模板时,我们调用的并不是模板本身,而是调用的模板实例化出来的函数。
即当我们传入不同类型的参数时,实际上编译器也会自动生成对应的函数。
cout << Add(a, b) << endl;
cout << Add(c, d) << endl;
我们可以通过反汇编来观察这两段代码的底层实现:
会发现这两段语句调用的Add()的函数地址时不同的。
当函数模板与普通函数均存在时优先调用普通函数。
3.类模板
(1)举例
与在函数中同理,当类中有需要更改类型的变量时,我们也可以使用模板进行操作。
我们使用的依然是实例化之后的类,这里使用栈来举例。
template<class T>
class Stack
{
private:T* a;
int _top;
int _capacity;
public:
Stack()
{
//...
}
//...
};
int main()
{
Stack<int> a;
}
这里我们知道,对于类模板来说是无法对类型进行推测的,因此我们必须通过<>来告知编译器类型。
(2)类外定义
对于类来说,我们可以进行类内声明,类外定义,那么类外定义的函数如何获知类型呢?
这就需要我们指定模板的同时,还要指定类域:
class Stack
{
private:T* a;
int _top;
int _capacity;
public:
Stack()
{
//...
}
//...
void Push(const T& x);//类内声明
};
template<class T>//指定模板
void Stack<T>::Push(const T& x)//指定类域
{
//类外定义
}
int main()
{
Stack<int> a;
}
4.总结
函数模板的实质就是编译器自动识别(人为规定)某一类或函数的参数类型,从而进行实例化并调用的过程。用好模板可以使代码更加简洁美观,减少不必要的多余劳动。由于会进行实例化,所以并不会提高程序的执行效率。如果文章对你有帮助,欢迎互赞互评,感谢各位大佬进行斧正支持。
总结
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于 java开发 的学习思路及方向。从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的 点击我的Gitee获取。
还有 高级java全套视频教程 java进阶架构师 视频+资料+代码+面试题!
全方面的java进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。