C++中有两种编程思想,一种是面对对象编程,另一种则是泛型编程。泛型编程主要利用的技术就是模板。
C++中提供两种模板机制:函数模板和类模板
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表,在实际传参或调用时才会确定实际的数据类型。在进行运算逻辑完全一致,但运算参数类型不一致的情景中表现尤为突出。
语法:
template
函数声明或定义
解释:
template--声明创建模板,使得编译器不会报错
typename--表明其后面的符号是一种数据类型(可用class代替typename)
T----通用的数据类型,名称可以替换,通常为大写字母T
eg:
#include
using namespace std;
//eg:实现数据的交换函数(适用于不同类型)
//若不使用函数模板,则每次需要交换不同数据类型的时候,都需要根据不同数据类型,重写交换函数
//函数模板
template//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwape(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
//两种方式使用函数模板,从而确认T的实际数据类型
//1、自动类型推导。即编译器会根据传入参数的类型,自动推导出 T的类型
mySwape(a, b);
cout << "a=" << a << " b=" << b << endl;
//2、显示指定类型。即在调用时,用<>指明T的实际数据类型
double c = 1.1,d=2.2;
mySwape(c, d);
cout << "c=" << c << " d=" << d << endl;
system("pause");
return 0;
}
1、自动类型推导,必须推导出一致的数据类型T才能使用
2、只要使用了模板,那么模板中必须要确定出T的数据类型,才可以使用,即使函数在传参和使用过程中并未使用到T。(即只要使用了模板,必须确定T的实际数据类型)
1、普通函数调用时可以发生自动类型转换(隐式类型转换)
2、函数模板调用时,如果利用自动给类型推导,则函数模板不会发生隐式类型转换
3、如果利用显示指定类型方式(即已经明确规定T的实际数据类型),则函数模板可以发生隐式类型转换
1、如果函数模板和普通函数均可实现要求,则优先调用普通函数。
2、如果函数模板可以产生更好的匹配,则优先调用函数模板
3、函数模板也能发生函数重载
4、可以通过空模板参数列表来强制调用函数模板
注:对于调用优先级,编译器会优先调用既能满足要求,又省事的函数。
对于1而言,函数模板比普通函数多一个确定T的实际数据类型的步骤,因此优先调用普通函数。
对于2而言,例如:
对于上述示例而言,由于传入参数是char型,而普通函数参数类型是int型,若要调用普通函数,则需发生隐式类型转换。而调用函数模板,仅需将T确定为char型即可。因此模板的匹配度更高,调用模板。
对于4.强制调用模板语法为:
mySwap<>(c, d);//中间加<>
类模板作用:
建立一个通用类,类中的成员,数据类型可以不具体指定,用一个虚拟的类型来代表
语法:
template
类
解释:
template--声明创建模板
typename--表面其后面的符号是一种数据类型,可以用class代替
T--通用的数据类型,名称可以替换,通常为大写字母
eg:
#include
using namespace std;
template//类中有两个不同的参数类型,因此需要有两个虚拟参数类型
class person {
public:
person(nameType name,ageType age)//有参构造
{
m_name = name;
m_age = age;
}
void show()
{
cout << "姓名:" << m_name << endl;
cout << "年龄:" << m_age << endl;
}
nameType m_name;
ageType m_age;
};
int main()
{
//实例化对象
person p ("张三",18);//<>中的参数称为类模板参数列表,用于确定虚拟参数类型的实际参数类型
p.show();
system("pause");
return 0;
}
1、类模板没有自动类型推导的使用方式,只能用显示指定类型方式
2、类模板在模板参数列表中可以有默认参数
eg:
#include
using namespace std;
template//class ageType=int称为默认参数形式,即将ageType默认成int类型
class person {
public:
person(nameType name,ageType age)//有参构造
{
m_name = name;
m_age = age;
}
nameType m_name;
ageType m_age;
};
int main()
{
person p ("张三",18);//拥有默认参数时,可以不指定实际参数类型
system("pause");
return 0;
}
类模板中成员函数创建时期和普通类中成员函数创建时机是有区别的:
1、普通类中的成员函数一开始就可以创建
2、类模板中的成员函数在调用时才创建
类模板实例化的对象,向函数传参的方式有三种:
1、指定传入类型----直接在函数中显示对象数据类型
2、参数模板化----将对象的参数变为模板进行传递
3、整个类模板化---将这个对象类型模板化进行传递
eg:
#include
using namespace std;
#include
template
class person {
public:
person(T1 name, T2 age)//有参构造
{
this->m_name = name;
this->m_age = age;
}
void show()
{
cout << "姓名:" << this->m_name << "\t年龄" << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
//指定传入类型
void print1(person &p)//即在写函数参数时,显示指定传入参数的类型(类比于 int a)
{
p.show();
}
//参数模板化
template
//注:此处是调用print函数,因此能够进行自动类型推导
void print2(person& p)
{
p.show();
cout << "T1自动类型推导的类型为:" << typeid(T1).name() << endl;//typeid用于显示虚拟数据类型的自动类型推导的结果
cout << "T2自动类型推导的类型为:" << typeid(T2).name() << endl;
}
template
void print3(T &p)
{
p.show();
cout << "T自动类型推导的类型为:" << typeid(T).name() << endl;
}
int main()
{
person p("张三",18);
print1(p);//指定传入类型
personp1("李四", 20);
print2(p1);//参数模板化
personp2("王五", 16);
print3(p2);
system("pause");
return 0;
}
总结:通过类模板创建的对象,可以有三种方式向函数中进行传参
使用比较广泛的是第一种:指定传入的类型
当类模板碰到继承时,需要注意以下几点:
1、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存(因为不知道T的数据类型)
2、如果想灵活指定出父类中T的类型,则子类也需变为类模板
#include
using namespace std;
#include
template
class base {//定义一个父类
public:
T name;
};
class son :public base//定义一个子类,以public的方式继承base父类,并将T指定为int型
{
public:
void print()
{
cout << "成功调用子类son的print函数" << endl;
}
};
template
class son1 :public base
{
public:
T2 age;
void print()
{
cout << "成功调用子类son1的print函数" << endl;
}
};
int main()
{
son s;
s.print();
son1 s1;//string指定T1的数据类型,并将T1的数据类型传给父类base中的T。int指定T2的数据类型
s1.print();
system("pause");
return 0;
}
总结:类模板中成员函数类外实现时,需要加上模板参数列表
#include
using namespace std;
#include
template
class person {
public:
person(T1 name,T2 age);
void show();
T1 m_name;
T2 m_age;
};
//类外实现
template
person::person(T1 name, T2 age)//为了区别普通的类外构造函数,在类模板构造函数类外实现时,需要加模板参数列表<>
{
this->m_name = name;
this->m_age = age;
}
template
//与普通的函数相比,类模板中的函数首先需指明类作用域,即person::,其次需要加用以区分普通类函数和类模板函数
//由于引用了T1、T2,因此需要加上template
void person::show()
{
cout << "姓名: " << this->m_name << "\t年龄: " << this->m_age << endl;
}
int main()
{
personp("张三",18);
p.show();
system("pause");
return 0;
}
分文件编写一般是声明在头文件中,实现在.cpp文件中。
因此类模板在分文件编写时,由于类模板中成员函数创建时期是在调用阶段,导致分文件编写时链接不到类成员函数的具体实现的可执行文件.cpp。因此当调用实例化对象后的成员函数时,编译器会显示:无法解析的外部指令(即编译器找不到成员函数的定义)
解决办法:
1、直接包含成员函数定义的.cpp文件
2、将成员函数声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
注:主流方式是第二种,将类模板成员函数写到一起,并更改后缀名为.hpp
全局函数类内实现:直接在类内声明友元即可。
全局函数类外实现:需要提前让编译器知道全局函数的存在
eg:
#include
using namespace std;
#include
//由于是全局函数,因此不需要加作用域
//由于使用了自定义类,因此需要提前让编译器知道有这样的person类.由于person是一个模板,因此需要加上类模板参数列表
template
class person;
template
void print2(personp)//类外实现
{
cout << "姓名: " << p.m_name << "\t年龄: " << p.m_age << endl;
}
template
//2、由于模板中成员函数是在调用时才创建,
class person {
public:
//1、全局函数,类内实现.若无友元,则在类内实现的全部函数无法被编译器所识别
friend void print1(personp)
{
cout << "姓名: " << p.m_name << "\t年龄: " << p.m_age << endl;
}
//2、全局函数,类内声明(友元)
//由于下方实现是函数模板的实现,而声明是普通函数的声明,因此需要在声明时加上空模板参数列表。使其变成函数模板声明
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在,即需要将函数实现放在声明之前。
friend void print2<>(personp);
person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
int main()
{
person p("张三",18);
print1(p);
person p1("李四", 20);
print2(p1);
system("pause");
return 0;
}
总结:由于全局函数配合友元类外实现较复杂,因此建议全局函数做类内实现,用法简单,而且编译器可以直接识别。