C++修炼:模板初见
目录 1、泛型编程 2、函数模板2.1、模板使用2.2、模板样式:2.3、函数模板实例化2.3.1、隐式实例化2.3.2、显式实例化 2.4、模板参数匹配规则3、类模板首先我们来看下面一个场景:我们实现了三个加法函数,因为有函数重载的支持,我们在调用函数的时候会自动调用匹配的函数,但是这未免也太麻烦了吧?要写这么多重复的代码,那么有没有一种方式,告诉编译器一个模子,让编译器自动生成这些函数呢?C+
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!
我的博客:<但凡.
我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》
欢迎点赞,关注!
目录
1、泛型编程
首先我们来看下面一个场景:
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
char Add(char x, char y)
{
return x + y;
}
我们实现了三个加法函数,因为有函数重载的支持,我们在调用函数的时候会自动调用匹配的函数,但是这未免也太麻烦了吧?要写这么多重复的代码,那么有没有一种方式,告诉编译器一个模子,让编译器自动生成这些函数呢?
C++中是支持这种“模子”的。而泛型编程是一种编写与具体数据类型无关的代码,以实现代码重用的编程范式。模板是泛型编程的基础。
2、函数模板
2.1、模板使用
我先写一个函数模板的例子,一会再告诉大家是一个什么样子的格式:
#include<iostream>
using namespace std;
template<typename T>
T Add(T& x, T& y)
{
return x + y;
}
int main()
{
int a = 1;int b = 2;
cout << Add(a, b) << endl;
double c = 1.5, d = 2.0;
cout << Add(c, d) << endl;
}
运行结果:
上面我们就实现了一个模板,让我们编译器自己生成这些函数。
2.2、模板样式:
函数模板样式:
template<typename T1,typename T2...>
在函数中我们用T1,T2......代替变量和返回值类型。
typename是用来定义模板参数关键字,也可以使用class。但是切记不能用struct。
其实模板就像是一个蓝图一样,告诉编译器这个函数是什么样子的,然后再编译器编译阶段,我们的编译器再去推导出这个函数。
2.3、函数模板实例化
实例化分为显示实例化和隐式实例化。我们先来说隐式实例化。
2.3.1、隐式实例化
上面那串代码我们就是使用的隐式实例化,其实就是让编译器自己去推导怎么生成这个函数。
那么这时候就可能会有问题出现,如果我们看下面这样一个场景:
简单来讲,如果我们的模板和传入的参数发生了类型冲突,就会报错。在不改变模板的前提下这种情况有两种解决方式,第一,用户手动去强制转换掉其中一个参数,让模板和类型匹配上。第二,使用显式实例化。
2.3.2、显式实例化
显式实例化就是告诉编译器我们的模板参数关键字到底是什么。
我们对上面的代码进行更改:
#include<iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add<int>(a1, d1);
Add<double>(d1, a2);
}
尖括号里面的内容就是我们的T应该是的类型。这时候编译器就会强制转换不对的类型,让他和你给定的类型匹配上。
我们也可以这样,同时给两个类型:
#include<iostream>
using namespace std;
template<class T,class T1>
T Add(const T& left, const T1& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add<int,double>(a1, d1);
Add<double,int>(d1, a2);
}
2.4、模板参数匹配规则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这 个非模板函数。
其实就是说,我们可以写成这样:
#include<iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int Add(int& left, int& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
Add(a1, a2);
}
那么其实这样的话对于调用Add函数有很多种情况。我在这直接给出结论:优先更匹配,其次现成的。
什么意思?如果现在我们进行传参,他和现有函数完全匹配,那就直接调用这个现有函数;如果和现有函数不匹配,但是模板能够生成更匹配的,那就调用模板;如果又和现成的不匹配,也没办法生成更匹配的函数,那就用现成的函数。
3、类模板
在接触过函数模板之后类模板也就很容易理解了。
类模板的定义格式:
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
类模板举例:
#include<iostream>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
//类模板中函数的定义
template<typename T>//注意这一行不能少
void Stack<T>::Push(const T& data)
{
_array[_size] = data;
++_size;
}
int main()
{
Stack<int> A;
Stack<double> B;
}
需要注意的是,Stack是类名,而Stack<int>才是类型名。
类模板的声明和定义不能文件分离,否则会报错。
好了,今天的内容就分享到这,我们下期再见!
更多推荐
所有评论(0)