目录
一,模板的概述
二,函数模板
1.函数模板的定义
2.函数模板的注意事项
3.函数模板的重载
4.函数模板的局限性
三,类模板
1.类模板的概念
2.类模板定义方式
3.类模板的成员函数在类外实现
4.函数模板作为类模板的友元
5.在写程序时模板的头文件与源文件分离时的问题。
一,模板的概述
c++提供了函数模板(function template),即建立一个通用的函数,不具体定义函数的参数类型以及函数类型,以他为模板。与该函数功能相同的函数我们定义时,直接利用模板,不必重新定义,大大提高了写代码的效率,其次根据模板定义不同的参数的类型以及函数的类型,实现函数不同的功能。总的来说,C++中的模板是一种支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。C++程序由类和函数组成,模板也分为类模板和函数模板。
这也是c++泛型编程思想:模板。
二,函数模板
1.函数模板的定义
关键字 template
c++提供了关键字tmplate用来实现模板类型的虚拟化,在调用模板时,编译器会自动地将虚拟的类型具体化,对于函数类型,就是函数模板,对于类的抽象化类型,就是类模板。
在这里我们还会用到typename 也是一个关键字,专门用来表示函数模板中的类型
例如:
/利用template将T抽象成一个虚拟类型,即T认为是一个类型
//以交换函数来举例
template<typename T> void myswap(T &x, T &y)//该类型抽象只作用该函数
{
T tmp = x;
x = y;
y = tmp;
}
//再调用函数模板时,会自动将虚拟类型具体化
int main()
{
int a = 10; int b = 20;
myswap(a, b);//函数调用时会根据参数类型自动推导出T是哪一个类型
cout << a << " " << b << endl;
//20 10
return 0;
}
可以看到函数模板在面对不同的参数类型都可以实现函数功能,展现了函数模板的多功能。
函数模板会被编译器编译两次:
第一次编译:对函数模板本身编译,即将函数源代码转化为机器码:预处理阶段。
第二次编译:函数调用处将T的类型具体化。
对于实例化过程:
C++ 函数模板中的类型具体化是在函数编译的过程中完成的,当使用函数模板创建一个具体类型的函数时,编译器会根据实际参数的类型来生成对应的函数代码,这个过程也被称为模板类或模板函数的实例化。在实例化时,编译器会将函数模板中的类型参数具体化为实际的类型,同时在编译器内部生成对应的代码。
需要注意的是,C++ 中的函数模板并不是一种动态类型或者泛型编程的方式,而是一种静态的模板化机制。因此,在编译阶段,编译器会将模板代码生成为具体的函数代码,以提高代码的效率和可靠性。
函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
2.函数模板的注意事项
1.对于函数模板与普通函数,在函数调用时,函数时会优先选择普通函数。
如下图:函数在调用时优先选择普通函数
2.普通函数和模板函数,像调用函数模板只能强制使用函数模板。
在调用时函数名与参数中间添加<>,表示强制调用函数模板。
当然我们也可以改变其中的类型<double>,强制调用模板类型为double。
3.函数模板自动类型推导时,不能对函数的参数进行自动类型转换。
template<typename T>
void print(T a, T b)
{
cout << a << b << endl;
cout << "函数模板" << endl;
}
int main()
{
int a = 10; char b = 'c';
print(a, b);
}
类型不相同就不会调用,这里编译器会自动报错。
3.函数模板的重载
函数模板也可以同名,根据函数的参数以及雷响,在调用会自动调用对应的模板,也就是函数模板的重载。
template<typename T> void myprint(T a, T b)
{
cout << a << b << endl;
cout << "调用双参数模板" << endl;
}
template<typename T> void myprint(T a)
{
cout << a << endl;
cout << "调用单参数模板" << endl;
}
int main()
{
int a = 10;
myprint(a);
return 0;
}
4.函数模板的局限性
对于函数模板,这里的函数类型都是编译器认识的的,倘若遇到自定义类型如结构体或类时,该类型函数模板无法识别,函数无法实现其功能。解决办法如下:
一,一般是将运算符重载。
template<typename T> void myprint (T a)
{
cout << a << endl;
}
class Data
{
friend ostream& operator<<(ostream& out, Data x);
public:
Data(int x)
{
data= x;
}
~Data(){}
private:
int data;
};
ostream& operator<<(ostream& out, Data x)
{
out << x.data;
return out;
}
template<typename T> void myprint(T a)
{
cout << a << endl;
}
int main()
{
Data x(10);
int a = 10;
myprint(a);
myprint(x);//需将函数里的所有运算符重载
return 0;
}
二,具体化函数模板
再写函数模板,将之前的函数模板的类型删掉,重新具体实现函数模板的类型以及参数以及函数实现。
template<typename T> void myprint (T a)
{
cout << a << endl;
}
class Data
{
friend void myprint<Data>(Data a);
public:
Data(int x)
{
data= x;
}
~Data(){}
private:
int data;
};
//具体实例化模板
template<> void myprint<Data>(Data a)
{
cout << a.data << endl;
}
int main()
{
Data x(10);
int a = 10;
myprint(a);
myprint(x);//需将函数里的运算符重载
return 0;
}
三,类模板
1.类模板的概念
与函数模板的概念相类似,有时有两个类或多个类的功能是大体相同的,仅仅因为数据类型不一样,于是乎c++提供了函数模板近似的类模板,用于实现所需数据的类型参数化。
2.类模板定义方式
类模板定义方式如下:
//类模板
//这里的类型就不用typename,为了区分模板,这里就用class表示类
template<class T1, class T2> class Data
{
private:
T1 a;
T2 b;
public:
Data(){}
Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
void showData()
{
cout<<a<<" "<<b<<endl;
}
};
对于类模板,在实例化对象时,不能自动推导类型:
int main()
{
//不能这样定义
//Data obj(10,20);
//在实例化对象时,是无法自动推导出类中数据类型,需要标明
Data<int,int >obj(10, 20);
obj.showData();
return 0;
}
3.类模板的成员函数在类外实现
若成员函数在类外实现,则需要注意其类模板的作用域是只在类中的,而成员函数在类外定义时就需要重新在成员函数前定义类模板。
//类模板
template<class T1, class T2> class Data
{
private:
T1 a;
T2 b;
public:
Data(){}
Data(T1 a, T2 b);
void showData();
};
template<class T1, class T2>Data<T1,T2>::Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
template<class T1, class T2>void Data<T1,T2>::showData()
{
cout<<a<<" "<<b<<endl;
}
4.函数模板作为类模板的友元
函数可以作为类的友元函数,那么函数模板亦可作为类模板中友元函数模板
template<class T1, class T2>class Data
{
template<typename T3, typename T4>friend void myPrintData(Data<T3, T4>& ob);//声明为友元
private:
T1 a;
T2 b;
public:
Data() {}
Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
};
//函数模板
template<typename T3, typename T4> void myPrintData(Data<T3, T4>& ob)
{
cout << ob.a << " " << ob.b << endl;
}
int main()
{
Data<int, char> ob1(100, 'A');
myPrintData(ob1);
}
在函数模板定义中我们可以看到其参数为类,而我们这里是类模板,故参数是需要实例化类的对象,其次需要引用代表对对象的操作。
其次还需要注意设置友元时,其中的template虚拟的类型也需要紧跟。
其中类中的数据类型在这里是所定义的类模板的类型。
5.在写程序时模板的头文件与源文件分离时的问题。
头文件定义类模板.
源文件中实现类中的成员函数(注意需加上数据类型)。如:
template<class T1,class T2> Data<T1,T2>::Data()
{
....
};
其次在主函数文件实例化对象时进行两次编译,第一次编译在预处理阶段,在头文件,编译类或函数本身,第二次编译在源文件中推导类型或指明类型,可是此时之前第一次编译类中的数据以及成员函数找不到了,第二次无法调用无法具体化并实现函数。
记得包含上源文件主函数才会通过。