函数模板
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
模板:将 算法 与 数据类型 相分离;
定义:
template <typename T>
void mySwap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
对于上面的函数体,我想大家很熟悉,就是一个交换两个数的值的过程,但有所不同的是 并没有明确指明 形参的类型 都将这些类型用 T 来代替。如果将 T 全都替换为 int ,这就是一个实现整型数据交换值的函数,如果将 T 全部替换为 char ,这就是一个实现字符型数据交换值的函数。
那么,像这样的函数,我们成为 函数模板。
使用规则
1.普通函数调用时支持类型的隐式转换,函数模板调用时不支持类型的隐式转换;
例如:
#include <iostream>
using namespace std;
// 普通函数 在被调用时 如果 实参类型 与 形参类型 不一致时 会自动转换
void print(int a, int b)
{
cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
int a = 10, b = 20;
char c = 'A';
print(a, b); // a = 10, b = 20
print(a, c); // a = 10, b = 65
return 0;
}
#include <iostream>
using namespace std;
// 函数模板 不支持 参数类型的隐式转换 ,调用时 没有明确 指明哪一种类型 函数模板 不会进行转换
// 如果调用时 明确指明使用 哪一种数据类型 那么函数模板 会进行数据类型转化
template<typename T>
void print(T a, T b)
{
cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
int a = 10, b = 20;
char c = 'A';
print(a, b);
print(a, c); // 编译报错:没有找到与参数列表 匹配的函数模板“print”实例
//print<int>(a, c); // 编译通过,通过<int>告知编译器 去构造一个 int 型的模板函数 ,那么在使用时 系统会自动转换
return 0;
}
2.函数模板和普通函数可以共存
1)当 函数模板 和 普通函数 都可以使用时,调用 普通函数。如果想调用 函数模板 ,可以用空参数列表 <> 进行说明;例如: print<>(a, b);
2)当 函数模板 提供一个更优的选择,优先调用 函数模板;
// 普通函数
void print(char c, int a)
{
cout<<"a = "<< a <<", c = " << c << endl;
}
// 函数模板
template<typename T>
void print(T c, T a)
{
cout<<"a = "<< a <<", c = " << c << endl;
}
int main()
{
int a = 10, b = 20;
// 普通函数也可以打印 a 与 b 的值,只是会进行隐式的类型转换
// 由于 函数模板 的存在,对于 a 与 b 两个相同类型的 数据,编译器更愿意去 构造一个 int 类型的模板函数 供使用
print(a, b);
return 0;
}
3.函数模板 可以重载
template <typename T>
void print(T a, T b, T c)
{
cout<<"a = "<< a <<", b = "<< b <<", c = "<< c <<endl;
}
template<typename T>
void print(T a, T b)
{
cout << "a = " << a << ", b = " << b << endl;
}
函数模板机制
编译器会对函数模板进行两次编译:
1.在声明的地方对模板代码本身进行编译;
2.在调用的地方对参数替换后的代码进行编译。
说明:通过 函数模板 产生的函数 被称为 模板函数。
类模板
类模板用于实现类所需数据的类型参数化。
模板类的语法
#include <iostream>
using namespace std;
template <typename T>
class AA
{
public:
AA()
{
}
AA(T a)
{
this->a = a;
}
void print ()
{
cout << "a = " << a << endl;
}
private:
T a;
};
int main()
{
// 注意:函数模板支持隐式调用,类模板不支持隐式调用,必须说明模板类型
AA<int> a(10);
return 0;
}
类模板对象做函数参数传递时:
1)写一个具体的 模板类 对象
template <typename T>
void print(AA<int> &a)
{
a.print();
template <typename T>
void print(AA<double> &a)
{
a.print();
}
2)使用函数模板
template <typename T>
void print(AA<T> &a)
{
a.print();
}
继承时:
1)派生一个具体的类,继承自一个具体的模板类
class BB:public AA<int>
{
public:
BB(int a, int b):AA(a)
{
this->b = b;
}
private:
int b;
};
2)派生一个 类模板
template <typename T>
class C:public AA<T>
{
public:
C(int a, T c):AA(a)
{
}
private:
T c;
};
类的实现
以 复数类 (Complex)为例进行说明
1.类的实现放在类的内部
nclude <iostream>
using namespace std;
template <typename T>
class Complex
{
// 友元函数写到类的内部
// 虽然写到了类的内部,但是该函数还是友元函数,不是类的内部函数
friend ostream &operator << (ostream &out, Complex &c)
{
out << c.a << " + " << c.b << "i";
return out;
}
friend Complex mySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
public:
Complex(T a = 0, T b = 0)
{
this->a = a;
this->b = b;
}
Complex operator +(Complex &c)
{
Complex tmp(a + c.a, b + c.b);
return tmp;
}
private:
T a;
T b;
};
int main()
{
Complex<int> c1(1, 2), c2(3, 4), c;
// mySub的实现 虽然是写到了类的内部,但是它还是一个友元函数
// 如果是类的内部函数 调用如下: c1.mySub(c2)
// 但因为 mySub 是友元函数,实际是一个外部的全局函数,所以只能用如下调用:mySub(c1, c2);
c = mySub(c1, c2);
cout << c1 << endl;
cout << c << endl;
c = c1 + c2;
cout << c << endl;
return 0;
}
2.类的实现放在类的外部(同一文件中)
类的成员函数在类的外部实现时,需要将成员函数写成函数模板;
#include <iostream>
using namespace std;
// 声明类
template <typename T>
class Complex;
// 函数声明
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend ostream &operator<< <T>(ostream &out, Complex<T> &c);
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
public:
Complex(T a = 0, T b = 0);
Complex operator +(Complex &c);
private:
T a;
T b;
};
template <typename T>
Complex<T>::Complex(T a = 0, T b = 0)
{
this->a = a;
this->b = b;
}
template <typename T>
Complex<T> Complex<T>::operator +(Complex &c)
{
Complex tmp(a + c.a, b + c.b);
return tmp;
}
template <typename T>
ostream & operator << (ostream &out, Complex<T> &c)
{
out << c.a << " + " << c.b << "i";
return out;
}
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
int main()
{
Complex<int> c1(1, 2), c2(3, 4), c;
c = mySub(c1, c2);
cout << c1 << endl;
cout << c << endl;
c = c1 + c2;
cout << c << endl;
return 0;
}
3.类的实现放在类的外部(不同文件中)
只需要将函数实现“cpp”文件,改名为“hpp”作为 main.cpp 的头文件即可。
总结:
1)对于在类的内部可 直接实现 的函数,放在类的外部实现时,类中的原型声明不变;
例如:构造函数、运算符重载(除了 外部 实现的 那 4 种)
template <typename T>
Complex<T>::Complex(T a = 0, T b = 0); //外部原型
//前面的 Complex<T> 说明是 Complex 的成员函数
Complex(T a = 0, T b = 0); //类中声明
template<typename T>
Complex<T> Complex<T>::operator+(Complex &c); // 外部原型
// 第一个 Complex<T> 是返回值
// 第二个 Complex<T> 说明是Complex 的成员函数
Complex operator+(Complex &c); // 类中声明
2.对于只能通过 友元函数 实现的运算符重载函数,在类中声明时 需要在函数名后,“()”前加上 < T >,以及在类名后加上 < T >;在外部实现时,函数原型 只需要在类名后加上< T >
friend ostream operator << <T>(ostream &out, Complex<T> &obj);
//类中声明
template <typename T>
ostream operator << (ostream &out, Complex<T> &obj);
// 类外部函数原型
3.对于其他的友元函数(无法在类的内部,改变左值的),在类中声明时,在函数名后,“()”前加上< T >,以及在类名后 加上 < T >;在类的外部声明时, 需要在类的上面,对 类 和 函数 进行声明;
friend Complex<T> mySub<T>(Complex<T> &c1, Complex<T> &c2);
// 类中声明
template <typename T> // 声明类
class Complex;
template <typename T> // 声明函数
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
template <typename T> // 外部实现 函数原型
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
类模板中的 static 关键字
对属于 同一类型的 模板类的对象 共享 static
对不属于 同一类型的 模板类的对象 不共享 static
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
T a;
static T sa;
};
template <typename T>
T A<T>::sa = 1;
int main()
{
A<int> a;
a.sa = 10;
A<double> d;
cout << "sa = " <<d.sa << endl; // sa = 1
cout << "sq = " <<A<int>::sa << endl; // sa = 10
return 0;
}