在C++里,不考虑具体数据类型 的编程模式叫做泛型编程,泛型也是一种数据类型,只不过它是一种用来代替所有类型的“通用类型”。
泛型编程通过函数模板和类模板来实现泛型编程。
当我们想写个Swap()交换函数时,通常这样写:
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
但是这个函数仅仅只能支持int类型,如果我们想实现交换double,float,string等等时,就还需要从新去构造Swap()重载函数,这样不但重复劳动,容易出错,而且还带来很大的维护和调试工作量。更糟的是,还会增加可执行文件的大小。
我们可以通过函数模板解决这个问题:
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
Swap 泛型写法中的 T 不是一个具体的数据类型,而是泛指任意的数据类型。
函数模板其实是一个具有相同行为的函数家族
函数模板的语法规则如下
template 关键字用于声明开始进行泛型编程
typename 关键字用于声明泛指类型
template
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
函数模板的应用
自动类型推导调用
具体类型显示调用
1、为什么函数模板能够执行不同的类型参数?
其实编译器对函数模板进行了两次编译:
第一次编译时,首先去检查函数模板本身有没有语法错误
第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.
2、如何 验证 呢?
编译程序生成.o文件,然后通过nm命令查看符号表
g++ -c 函数模板.cpp nm 函数模板.o
从符号表中可以非常直观的看到生成了两个不同的符号。
需要注意的是:函数模板是不允许隐式类型转换的,调用时类型必须严格匹配
也就说如下代码是非法的:
int a;
float b;
Swap(a, b);
3、函数模板的特点
数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:
编译器是无法自动推导返回值类型的
可以从左向右部分指定类型参数
#include
using namespace std;
template
T1 add(T2 a, T3 b)
{
T1 ret;
ret = static_cast(a + b);
return ret;
}
int main() {
int c = 12;
float d = 23.4;
cout << add(c, d) << endl;
cout << add(c, d) << endl;
}
4、函数模板的重载
函数模板跟普通函数一样,也可以被重载
#include
using namespace std;
template
void fun(T a)
{cout << "void fun(T a)" << endl;}
template
void fun(T1 a, T2 B)
{cout << "void fun(T1 a, T2 b)" << endl;}
int main() {
int a = 0;
float b = 0.0;
fun(a);
fun(a, b);
fun(b, a);
fun<>(a, b);
}
5、总结
还记得我们上次实现的Array类吗?在Array类中我们只能操作 int类型的数据,如果需要操作char,float类型的数据我们该如何处理呢?难道我们再重新实现一个类吗?当然不用,我们可以使用 类模板 来实现。
在实际工作中,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以使用类模板来实现。
templateclass 类名{ //类定义....... };
这样我们就定义了一个 简单的类模板,其中的T代表任意的类型,可以出现在类模板中的任意地方,与函数模板不同的是,使用类模板构造对象时必须显示的指定数据类型,编译器无法自动推导,例如test
编译器对类模板的处理方式和函数模板相同
编译器 从类模板通过具体类型产生不同的类
编译器在声明的地方对类模板代码本身进行编译
编译器在使用的地方对参数替换后的代码进行编译
由于类模板的编译机制不同 , 所以不能像普通类一样分开实现后在使用时只包含头文件,在工程实践上 , 一般会把类模板的定义直接放到头文件中!!
只有被调用的类模板成员函数才会被编译器生成可执行代码!!!
上面的类模板好像已经实现了add加法运算。但是却不能支持指针类型。其实,类模板也可以像函数重载一样, 类模板通过特化的方式可以实现特殊情况.
类模板特化:
表示可以存在多个相同的类名,但是模板类型都不一致(和函数重载的参数类似)
特化类型有 完全特化和 部分特化两种类型
完全特化表示显示指定类型参数,模板声明只需写成template<>,并在类名右侧指定参数,比如:
#include
using namespace std;
template
class Operator
{
public:
Operator()
{cout << "Operator" << endl;}
void add(T1 a, T2 b)
{cout << a + b << endl;}
};
template <>
class Operator
{
public:
Operator()
{cout << "Operator" << endl;}
void add(int a, int b)
{cout << a + b << endl;}
};
int main() {
//匹配完全特化类模板
Operator Op1;
//匹配正常的类模板
Operator Op2;
return 0;
}
#include
using namespace std;
template
class Operator
{
public:
void add(T1 a, T2 b)
{cout << a + b << endl;}
};
template
class Operator
{
public:
void add(T* a, T* b)
{cout << *a + *b << endl;}
};
int main() {
Operator Op1;
Operator Op2;
return 0;
}
#include
using namespace std;
template
class Operator
{
public:
void add(T1 a, T2 b)
{
cout << "add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
template
class Operator
{
public:
void add(T a, T b)
{
cout << "add(T a, T b)" << endl;
cout << a + b << endl;
}
};
template
class Operator
{
public:
void add(T1* a, T2* b)
{
cout << "add(T1* a, T2* b)" << endl;
cout << *a + *b << endl;
}
};
template <>
class Operator
{
public:
void add(void* a, void* b)
{
cout << "add(void* a, void* b)" << endl;
cout << "add void* Error" << endl;
}
};
int main() {
int *p1 = new int(1);
float *p2 = new float(1.25);
Operator Op1;
Op1.add(1, 1.5);
Operator Op2;
Op2.add(1, 5);
Operator Op3;
Op3.add(p1, p2);
Operator Op4;
Op4.add(NULL, NULL);
delete p1;
delete p2;
return 0;
}
class A
{
public:
A(int temp = 0)
{this->temp = temp;}
~A(){}
private:
int temp;
};
template
class B: public A
{
public:
B(T t = 0) : A(666)
{this->t = t;};
~B(){}
private:
T t;
};
template
class A
{
public:
A(T t = 0)
{this->t = t;}
~A(){}
private:
T t;
};
class B:public A
{
public:
B(int temp = 0): A(666)
{this->temp = temp;}
~B(){}
private:
int temp;
};
#include
using namespace std;
template
class A
{
T1 x;
T2 y;
};
template
class B: public A
{
T1 x1;
T2 x2;
};
template
class C: public B
{T x3;};
// 类模板继承模板类
template
class D: public A
{T x4;};
class E
{int x4;};
template
class F: public E
{T x5;};
int main() {
return 0;
}