相信大家都自己写过交换函数 Swap( ) 吧 , 实现起来非常简单, 没有什么难度. 但是如果是做一些项目开发的时候, 有十几种甚至几十种数据类型的时候, 如果要你写十几遍甚至几十遍的 Swap() 函数, 想必大家都会疯吧~
今天要介绍的泛型编程就是解决这一问题的 ! 耐心看完, 你一定会有所收获 !
1. 泛型编程
为了解决开头说到的问题, 减轻程序猿的工作量, 大佬们就发明了泛型编程的概念. 正所谓 “世界是懒人创造的” ~
泛型编程就是引入了template关键字
后面跟着一个泛型参数, 当编译器运行到这个函数/类的时候会根据当前的数据类型去实例化生成对应类型的代码.
实际上程序的工作量是没有减轻的, 但是这无疑让我们程序猿的工作减轻了非常多, 这, 就足够了~…
接下来我们看看具体的函数模板和类模板
2. 函数模板
函数模板的定义方式 :
template<class/typename T1, class/typename T2, …>
返回值类型 函数名(参数列表){}
就拿上面说的Swap()函数来举例吧
//函数模板
template<typename T>
void Swap(T& a, T& b) {
T tmp = b;
b = a;
a = tmp;
}
- 函数模板本身不是函数, 他只是一个模具, 不是可执行的代码
- 编译器在编译阶段会根据参数类型不同, 利用函数模板生成对应参数类型的可执行代码
下面给出测试栗子
void test() {
int a = 1, b = 2;
double c = 1.0, d = 2.0;
char e = 'e', f = 'f';
Swap(a, b); // 生成Swap(int, int)
Swap(c, d); // 生成Swap(double, double)
Swap(e, f); // 生成Swap(char, char)
}
就像注释写的那样, 我们可以通过调试时的反汇编看到函数确确实实是不一样的
我们可以看到, 程序在编译时调用了不同的函数, 这其实就是函数模板的实例化.
接下来我们就说说实例化
函数模板的实例化
1. 隐式实例化: 让编译器根据实参推演模板参数的实际类型
这也不用多说什么, 直接给出一个例子感受一下就知道了
template<class T>
T Add(const T& a, const T& b) {
return a + b;
}
void test() {
int a = 1, b = 2;
double c = 1.0, d = 2.0;
char e = 'e', f = 'f';
//隐式实例化, 编译器自动推导
Add(a, b);
Add(c, d);
Add(e, f);
}
直接调用就是隐式实例化
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template<class T>
T Add(const T& a, const T& b) {
return a + b;
}
void test() {
int a = 1, b = 2;
double c = 1.0, d = 2.0;
char e = 'e', f = 'f';
//强制类型转换 --> 不推荐
Add(a, (int)d);
//显式实例化: 函数<实例化类型>(参数列表) --> 推荐
Add<int>(a, d);
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
模板参数的匹配规则
1. 函数模板和普通函数同时出现, 并且普通函数的参数可以完全匹配, 那么编译器会调用普通函数, 不会进行函数模板的实例化
毕竟有现成的, 编译器也很懒丫~
2. 如果函数模板和普通函数共存, 用函数模板可以生成参数类型更加匹配的函数, 则会进行实例化(更加精确)
也就是普通函数如果不是完全匹配的话, 还是会使用函数模板进行实例化生成最匹配的函数
这没什么问题吧…很好理解的…
3. 如果手动指明要显式实例化, 则进行实例化, 不会调用普通函数
下面给出具体的例子
template<class T>
T Add(const T& a, const T& b) {
return a + b;
}
//普通函数和函数模板共存
int Add(int& a, int& b) {
return a + b;
}
void test2() {
int a = 1, b = 2;
//如果参数类型和普通函数完全匹配, 则优先调用普通函数, 不会进行函数模板的实例化
Add(a, b);
double c = 1.0;
//如果函数模板可以生成参数类型更加匹配的函数, 则会执行实例化 (更精确)
Add(a, c);
//如果指明要显式实例化, 则要执行实例化, 不会调用普通函数
Add<int>(a, b);
}
3. 类模板
1. 定义方式
与函数模板类似, 在类前面加上
template<class T1, class T2, …, class Tn>
template<class T>
struct seqList {
T* _array;
size_t _size = 0;
size_t _capacity = 10;
seqList(size_t num = 10)
:_array(new T[num])
{}
void push_back(T x) {
_array[_size++] = x;
}
};
2. 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
类模板必须显式实例化
类型: 类名<参数类型> ---> 类型 != 类名
下面给出例子
void test3() {
//实例化必须为显示实例化
seqList<int> sq;
seqList<char> sq2;
seqList<double> sq3;
sq.push_back(1);
sq.push_back(2);
sq.push_back(3);
sq2.push_back('a');
sq2.push_back('b');
sq2.push_back('c');
sq3.push_back(1.0);
sq3.push_back(2.0);
sq3.push_back(3.0);
}
到这里就结束了, 这里只对模板进行简单的介绍, 只要能把模板使用起来就好, 后面会有更详细的解读~