模板的使用
1、C++函数模板
项目需求: 实现多个函数
用来返回两个数的最大值,要求能支持char类型、int类型、double
//template关键字告诉C++编译器 我要开始泛型编程了,请你不要随意报错
//T - 参数化数据类型
template <typename T>
T Max(T a,T b){
return a > b ? a : b;
}
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout << "max(1, 2) = " << Max(x, y) << endl; //实现参数类型的自动推导
cout << "max(1, 2) = " << Max<int>(x, y) << endl; //显示类型调用
float a = 2.0;
float b = 3.0;
cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
system("pause");
return;
}
函数模板:实际上时建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。
定义形式:模板说明+函数定义+函数模板调用
template <类型形式参数表>
类型 函数名(形式参数表) //形式参数表 要和 类型形式参数表一样
{
//语句序列
}
类型形式参数表的形式: typename T,typename T1,...,typename Tn 或 class T,class T1,...,class Tn
形式参数表:T a, T1 b, ... ,Tn z;
函数模板的调用:
max<int>(a,b); //显示类型调用
max(a,b); //自动数据类型推导
模板函数:
编译生成的模板函数
1、函数模板和函数重载
- 两者允许并存
- 函数模板不允许自动类型转化
- 普通函数能够进行自动类型转换
#include <iostream>
using namespace std;
template <typename T1,typename T2>
void Swap(T1& a, T2& b) {
T1 t;
t = a;
a = b;
b = t;
cout << "Swap 模板函数被调用了" << endl;
}
void Swap(char& a, int& b) {
int t;
t = a;
a = b;
b = t;
cout << "Swap 普通函数被调用了" << endl;
}
int main(void) {
char cNum = 'c';
int iNum = 65;
//第一种情况 模板函数和普通函数并存,参数类型和普通重载函数更匹配
//调用普通函数
Swap(cNum, iNum);//Swap 普通函数被调用了
//第二种类型 不存在普通函数,函数模板会隐式数据类型转换嘛?
//结论是:不提供隐式的数据类型转换,必须是严格的匹配
Swap(cNum, iNum);
//当函数模板 和 普通函数都符合调用时,优先选择普通函数
//如果显式的使用函数模板,则使用<>类型列表
Max<>(a,b);
system("pause");
return 0;
}
2、函数模板可以嵌套使用
template <typename T>
T Max(T a,T b){
cout<<"调用 T Max(T a,T b)"<<endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a,T b,T c){
cout<<"调用 T Max(T a, T b, T c)"<<endl;
return Max(Max(a,b),c);
}
函数模板的调用机制
- 编译器并不是把函数模板处理成能够处理任意类型的函数
- 编译器从函数模板通过具体类型产生不同的函数
2、类模板的使用
语法规则
#include <iostream>
using namespace std;
template <typename T>
class A {
public:
//函数参数列表使用虚拟类型
A(T t = 0 ) {
this->t = t;
}
//成员函数返回值使用虚拟类型
T getT() {
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
//模板类作为参数列表
void PrintA(A<int> &a){
cout<<a.getT()<<endl;
}
int main(void) {
//1、类模板定义类对象,必须显示指定类型
//2、模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(66);
cout << a.getT() << endl;
PrintA(a);
system("pause");
return 0;
}
1、类模板和继承
- 继承中父子类和模板类的结合情况
- 1、父类是一般类,子类是模板类 和普通继承的用法类似
- 2、子类是一般类,父类是模板类 在继承时必须在子类里实例化父类的类型参数
- 3、子类父类都是模板类 把上面两个要求都做到
#include <iostream>
using namespace std;
//class B {
//public:
// B(int t) {
// this->b = t;
// }
//private:
// int b;
//};
template <typename T>
class A {
public:
//函数参数列表使用虚拟类型
A(T t = 0 ){
this->t = t;
}
//成员函数返回值使用虚拟类型
T getT() {
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
class B :public A<int> {
public:
B(int t):A<int>(666) {
this->b = t;
}
private:
int b;
};
int main(void) {
//1、类模板定义类对象,必须显示指定类型
//2、模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(66);
cout << a.getT() << endl;
system("pause");
return 0;
}
2、类模板的三种写法
-
所有的类模板函数写在类的内部
-
所有的类模板函数写在类的外部,在一个
cpp
中-
#include <iostream> using namespace std; template <typename T> class A { public: A(T t = 0); T getT(); A operator + (const A& other); void print(); private: T t; }; template<typename T> A<T>::A(T t) { this->t = t; } template<typename T> T A<T>::getT() { return t; } template<typename T> A<T> A<T>::operator+(const A<T> & other) { A<T> tmp;//类的内部类型可以显示声明也可以不显示 tmp.t = this->t + other.t; return tmp; } template<typename T> void A<T>::print() { cout << this->t << endl; } int main(void) { A<int> a(666), b(888); A<int> tmp = a + b; tmp.print();//1554 system("pause"); return 0; }
-
在同一个
cpp
文件中,把模板类的成员函数放到类的外部,需要以下几点:- 函数声明 template <类型形式参数表>
- 类的
成员函数前的类限定域
说明必须带上虚拟参数列表 返回的变量
是模板类的对象时必须带上虚拟参数列表- 成员函数
参数
中出现模板类的对象
时必须带上虚拟参数列表 - 成员函数
内部没有限定
-
-
所有的类模板函数写在类的外部,在不同的
.h和.cpp
中-
demo.h
-
template <typename T> class A { public: A(T t = 0); T &getT(); A operator + (const A &other); void print(); private: T t; }
-
-
demo.cpp
-
#include "demo.h" #include <iostream> template<typename T> A<T>::A(T t) { this->t = t; } template<typename T> T A<T>::getT() { return t; } template<typename T> A<T> A<T>::operator+(const A<T> & other) { A<T> tmp;//类的内部类型可以显示声明也可以不显示 tmp.t = this->t + other.t; return tmp; } template<typename T> void A<T>::print() { cout << this->t << endl; }
-
-
-
特殊情况 友元函数 少用
-
#include <iostream> using namespace std; template <typename T> class A { public: A(T t = 0); //声明一个友元函数,实现对两个A类对象进行加法操作 template <typename T> friend A<T> addA(const A<T>& a, const A<T>& b); T getT(); A operator + (const A& other); void print(); private: T t; }; template <typename T> A<T>::A(T t) { this->t = t; } template <typename T> T A<T>::getT() { return t; } template <typename T> A<T> A<T>::operator+(const A<T> & other) { A tmp;//类的内部类型可以显示声明也可以不显示 tmp.t = this->t + other.t; return tmp; } template <typename T> void A<T>::print() { cout << this->t << endl; } //A类的友元函数 template <typename T> A<T> addA(const A<T>& a, const A<T>& b) { A<T> tmp; tmp.t = a.t + b.t; return tmp; } int main(void) { A<int> a(666), b(888); A<int> tmp = a + b; A<int> tmp1 = addA<int>(a, b); tmp.print();//1554 tmp1.print(); system("pause"); return 0; }
-
结论:
-
类内部声明友元函数,必须写成以下格式
-
template<typename T> friend A<T> add<T>(A<T> &a,A<T> &b);
-
-
友元函数实现,必须写成:
-
template<typename T> A<T> add(A<T> &a,A<T> &b){ //... }
-
-
友元函数调用必须写成
-
A<int> c4 = add<int>(c1,c2);
-
-
-
类模和static数据成员
- 从
类模板实例化的每个模板类
有自己的类模板数据成员,该模板类的所有对象
共享一个static数据成员
- 和非模板类的static数据成员一样,模板类的
static数据成员
也应该在文件范围定义和初始化
static数据成员
也可以使用虚拟类型参数T
类模板使用总结
-
先写出一个实际的类
-
将此类中准备改变的类型名(如int 要改变为float或char)改用一个自己指定虚拟类型名
-
在类声明前面加入一行,格式为:
-
template <typename 虚拟类型参数>
如:
template
class A
{…}; //类体
-
-
用类模板定义对象时,用以下形式:
-
类模板名<实际类型名> 对象名;
或 类模板名<实际类型名> 对象名(实参表列);
如:
A cmp;
A cmp(3,7);
-
-
如果在类模板外定义成员函数,应写成类模板形式:
-
template<typename 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列){…}
-
关于类模板的几点补充
-
- 类模板的类型参数可以有一个或多个,每个类型前面都必须加typename 或class,如:
-
template <typename T1,typename T2> class someclass {…};
-
在定义对象时分别代入实际的类型名,如:
-
someclass<int, char> object;
-
- 和使用类一样,使用类模板时要
注意其作用域
,只有在它的有效作用域内用使用它定义对象。
- 和使用类一样,使用类模板时要
-
- 模板类也可以有
支持继承,有层次关系
,一个类模板可以作为基类
,派生出派生模板类
。
- 模板类也可以有