目录
1、模板的概述:
C++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。C++提供两种模板机制:函数模板和类模板。
总结:
C++面向对象编程思想:封装、继承、多态
C++泛型编程思想:模板
模板的分类:函数模板、类模板
将功能相同,类型不同的函数(类)的类型抽象成虚拟的类型。当调用函数(类实例化对象)的时候,编译器自动将虚拟的类型具体化。这个就是函数模板(类模板)。
2、函数模板
2.1、函数模板的定义方式
关键字:template
#include <iostream> #include <string> using namespace std; template <typename T> void swap_all(T&x,T&y) //函数作用:通过使用函数模板交换任意类型的变量的值 { T temp = x; x = y; y = temp; }int main()
{
int a = 10,b = 20;
swap(a,b); //隐式类型推导
//swap<int>(a,b); //显示类型推导(推荐使用)
cout << "a = " <<a<<" b = "<<b<<endl;
return 0;
}
2.2、函数模板注意事项
(1)显示类型推导参数和推导的类型必须一致。
(2)如果有普通函数和模板函数都能匹配优先调用模板函数,如在调用时用显示调用(省略类型的方式 swap_all<>(1,2))则会调用模板函数。
2.3、函数模板和普通函数区别
(1)函数只可以有一种数据类型相匹配,模板有多种类型相匹配
(2)隐式推导优先使用普通函数,只有普通函数不匹配才使用函数模板
(3)函数模板只有在调用时,才会构建函数,而普通函数是在编译时。
(4)普通函数调用时候可以发生自动类型转换,而函数模板不行。
2.4、函数模板的重载
和普通函数的重载相似。也是同一个作用域类函数名相同参数列表不同。
以下是参数顺序不同的重载:
template <class T1,class T2>void swap1(T2 a,T1 b)
template <class T1,class T2>void swap1(T1 a,T2 b)
注意,在函数参数顺序不同的重载中,实例化的时候不可以是相同类型。
例如 swap1<int ,int >(1,2)则没法匹配哪个函数。
以下是参数个数不同的重载
template <class T1,class T2> void swap1(T1 a,T2 b)
template <typename T2> void swap1(T2 t)
传入不同的参数,调用不同的版本
2.5、函数模板的非类型参数
希望传入函数一个特定的确定参数,但不希望放在小括号中。可以通过非类型参数实现。在模板<>中加了一个参数,但这个参数不是类型参数,是具体的某一个值。
格式:template<class T,基本数据类型 变量名>
返回值 函数名(T& 变量)
例如:template <class T,int size>
void showArr(T* arr);
注意:在上例子中,size是通过模板传入到函数中,可以当做普通变量使用
非类型参数都是常量,在函数中不允许修改,只可以使用,定义非类型参数变量时候,需要加const。
例子: 写一个函数模板,打印数组的所有内容。(但不可以在参数中加入数组大小)。
template<class Y,int size>
void show_size(Y*x)
{
for(int i = 0;i<size;i++)
{
cout<<x[i]<<" ";
}
cout<<endl;
}
int main()
{
int arr[5]={2,4,6,8,10};
const int arrSize = sizeof(arr)/sizeof(arr[0]);
show_size<int,arrSize>(arr);
return 0;
}
2.6、多文件编程实现函数模板
在定义函数或者类的时候,会.h和cpp文件,定义和申明分开。但是在C++中使用模板这样做会出现连接错误,原因:函数模板是在调用时确定的版本,而调用时.h中没有函数实现,出现连接错误,找不到函数体,如果分开后,编译会出现连接错误。
解决方法:
(1)在main中#include <xxx.h> 和 #include <xx.cpp>
(2)模板的定义和申明都放在.h头文件中。
3、类模板
3.1、类模板的定义
建立一个通用的类,如果希望成员变量的类型是任意的,可以用类模板实现。类模板不是一个实际的类型,是虚拟的,和ppt模板一样,实例化的时候才会构建类。
格式:template<typename T>
class test { T age; };
实例化: 类名<类型> 变量名(参数);
代码如下:
template <class t1,class t2>
class person
{
private:
t1 a;
t2 b;
public:
person();
person(t1 a,t2 b);
};
template <class t1,class t2>
person<t1,t2>::person()
{
a = 0;
b = 0;
}
template <class t1,class t2>
person<t1,t2>::person(t1 a, t2 b)
{
this->a = a;
this->b = b;
}
int main(int argc, char *argv[])
{
person<int ,char> p1(10,'a');
return 0;
}
3.2、类模板和函数模板区别
函数模板可以使用隐式类型推导,但类模板不可以,必须显示推导
类模板在定义template时候,可以加上默认参数。template<class T1,class T2=string>
在模板中的默认参数类型中,如果所有模板参数都是缺省,但在类进行实例化的时候,尖括号不可以省略
3.3、类模板的友元函数
using namespace std;
template <class t1,class t2>
class person
{
private:
t1 a;
t2 b;
public:
person();
person(t1 a,t2 b);
template <typename t3,typename t4>
friend void show_person(person<t3,t4> &ob);
};
template <class t1,class t2>
person<t1,t2>::person()
{
a = 0;
b = 0;
}
template <class t1,class t2>
person<t1,t2>::person(t1 a, t2 b)
{
this->a = a;
this->b = b;
}
template <typename t3,typename t4>
void show_person(person<t3,t4> &ob) //函数模板做为类模板的友元函数
{
cout<<ob.a<<" "<<ob.b<<endl;
}
int main(int argc, char *argv[])
{
person<int ,char> p1(10,'a');
show_person(p1); //函数模板作为类模板的友元函数的调用
return 0;
}
3.4、类模板的继承
普通类继承:class son:public father{}
模板类继承:普通子类继承模板类;
格式:class 子类:public 父类<指定类型>
构建对象:和普通对象的构建一样 类名 对象(参数列表);
类模板继承类模板
格式: template<class T1,class T2>
class son: public person<T1,T2> {}
当类模板碰到继承时,需要注意以下几点:
(1)当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T
的类型
(2)如果不指定,编译器无法给子类分配内存
(3)如果想灵活指定出父类中T
的类型,子类也需为类模板
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son: public Base //错误,必须要知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想灵活指定父类中T的类型,子类也需要变成类模板
template<class T1,class T2>
class Son2 : public Base<T2>
{
public:
Son2()
{
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
T1 obj;
};
void test02()
{
Son2<int,char> s2;
}
int main()
{
test02();
system("pause");
return 0;
}
3.5、类模板对象作为函数参数
一共有三种传入方式:
(1)指定传入的类型:直接显示对象的数据类型
(2)参数模板化:将对象中的参数变为模板进行传递
(3)整个类模板化:将这个对象类型模板化进行传递
示例:
//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定传入类型
void printPerson1(Person<string, int>&p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("孙悟空", 199);
printPerson1(p);
}
// 2、参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2>&p)
{
p.showPerson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int>p("猪八戒", 90);
printPerson2(p);
}
// 3、整个类模板化
template<class T>
void printPerson3(T &p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>p("唐僧", 60);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}