模板是建立通用的膜具,大大提供复用性;
特点: 模板不能直接使用,它只是一个框架;模板通用不是万能的;
函数模板
C++泛型编程主要利用的技术就是模板。
C++提供两种模板机制:函数模板 和 类模板
函数模板的作用:建立一个通用的函数,其函数返回值类型和参数可以不具体指定,用一个虚拟的类型来代表;
语法: template <typename T> or template<class T> ;template表示声明一个模板,typename和class 表示后面的数T是一个通用的数据类型. T可以换成其它字符代替,不过默认用T(默认大写字母)
template<typename T>
void myswap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
//两种方式调用函数模板
//自动类型推导
myswap(a,b); // 编译器自动推导出传进去的a b类型, 使用自动类型推导 必须推导出一致的数据类型
// 显示指定类型
myswap<int> (a,b); // 调用函数模板的时候写上数据类型,必须指定T的数据类型,不然不能调用
}
自动类型推导必须推导出一致的数据类型才可以调用函数模板;
调用函数模板必须指定T的数据类型,否则无法调用
普通函数和函数模板的区别:
1、普通函数调用时可以发生自动类型转换(隐式类型转换)
2、自动类型推导调用函数模板时,不能发生自动类型转换(隐式类型转换)
3、如果使用显示指定类型,可以发生隐式类型转换
template <typename T>
T myadd(T a, T b)
{
return a + b;
}
int a = 10;
char b= 'c';
myadd(a,b); // 使用自动类型推导时调用函数模板不可以发生隐式类型转换
myadd<int>(a,b); //使用显示指定类型时可以发生自动类型转换
建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用数据类型
调用规则:
1、当普通函数和函数模板都能实现时,优先调用普通函数
2、可以通过空模板参数列表来强行调用函数模板
3、函数模板可以发生重载
4、当函数模板可以产生更好匹配,优先调用函数模板
int test(int a , int b)
{
return a + b;
}
template<typename T>
T test(T a , T b)
{
return a + b;
}
template <typename T> //函数模板重载
T test(T a, T b , T c)
{
return a + b + c;
}
int main()
{
int a = 10 ;
int b = 20;
test(a,b); // 当普通函数和函数模板都能实现时,优先调用普通函数
test<>(a,b); // 使用空模板参数列表 < > 可以强制调用函数模板
char c = 'c';
char d = 'd';
test(c,d); // c d 都是字符类型, 此时调用函数模板更方便,不用发生类型转换,优先调用函数模板
return 0;
}
如果提供了函数模板就不要提供普通函数,否则容易出现二义性
对于自定义的数据类型,函数模板需要做具体化的实现
template <typename T>
bool mycompare(T &a, T &b); //需要进行声明才可以这样使用
template<>bool mycompare(Person &a, Person & b) // template<> 指明是函数模板
{
if(a.name = b.name && a.age == b.age)
{
return true;
}
else
return false;
}
总结:利用具体化模板,可以解决自定义类型的通用化,学习模板并不是为了使用模板,而是在STL能够运用系统的提供的模板
类模板
类模板和函数模板的区别:
类模板没有自动类型推导的使用方式;类模板在模板参数列表中可以有默认参数;
template<class T =int> //将T默认设置为int 类型
void test(T a, )
{
return a;
}
成员函数的创建时机:
普通类的成员函数在一开始时就创建,类模板中的成员函数在调用时才创建
对象做函数参数:
1、指定传入的类型 -- 直接显示对象的数据类型(最常用)
2、参数模板化 -- 将对象的参数变成模板进行传递
3、整个类模板化,将这个对象类型模板化传递
void test1(Person<string,int> &p) //指定传入的数据类型
{
p.name = "lisi";
}
//将参数模板化
template<class T1,class T2>
void test02(Person<T1,T2> &P)
{
p.name = "wwu";
}
//整个类模板化
template<class T>
void test03(T &p)
{
p.name = "liu";
}
利用typeid(通用数据类型).name() 来查看此刻的通用数据类型
类模板与继承:
1、当子类继承的父类是一个类模板时,子类在声明的时候必须指定父类中T的数据类型,如果不指定,将无法给子类分配内存
2、如果想灵活指定父类中T的数据类型,则需将子类也进行模板化
template <class T>
class Person
{
public:
T name;
class Son : public Person //必须指定父类中的通用数据类型T,否则编译器无法给子类分配内存
{
};
class son : public Person <int>
//如果向让子类灵活指定父类中T的数据类型,需要将子类也模板化
template <class T1, class T2>
class son1 : public Person <T2>
{
};
成员函数类外实现:
类模板中成员函数类外实现时,需要加上模板参数列表
template<class T1,class T2>
class Person
{
public:
void showPerson();
T1 name ;
T2 age;
};
//类外实现需要加上模板声明 和指定作用域(需要加上参数列表)
template<class T1, class T2>
void Person<T1,T2> :: showPerson()
{
;
}
分文件编写:
问题:类模板中成员函数创建时机是在函数调用阶段,导致分文件编写时连接不到
解决:直接包含.cpp源文件; 或者将声明和实现写到一个文件中,并更名后缀名为:.hpp, hpp是约定的名称,并不是强制
总结:主流的解决方式是第二种,将成员函数声明和实现写到一起,把后缀名改为.hpp
类模板和友元:
1、全局函数类内实现 -- 直接在类内声明友元即可
2、全局函数类外实现 -- 提前让编译器知道全局函数的存在
template<class T1, class T2>
class Person ; // 声明类模板的存在
template <class T1,class T2>
void printf(Person<T1,T2> P) //全局函数类外实现, 需要声明是模板内的全局函数,
{
cout << p.m_Name << " " << p.m_age << endl;
}
template<class T1, class T2>
class Person
{
friend void printf(Person<T1,T2>p)
{
cout << p.m_Name << " " << p.m_age << endl; // 全局函数类内实现
}
//全局函数类外实现,需要在类内声明 ,且在类外实现
friend void fun<>(Person<T1,T2>&p); //需要加上类模板空参数列表
public :
T1 m_Name;
T2 m_age;
};