类模板的作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
template
模板类声明
解释:
template声明创建模板
typename表明其后面的符号是一种数据类型,可以用class代替
T是通用的数据类型,名称可以替换,通常为大写字母
示例:
#include
using namespace std;
//类模板
template
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << "age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person p1("孙悟空", 999);
//先创建了版本的类定义,再将括号里的东西传进去
p1.showPerson();
}
int main()
{
test01();
return 0;
}
运行结果是
name:孙悟空age:999
我们必须注意的一个点是仅在程序包含模板并不会生成模板类,必须请求实例化。也就是说,必须要将模板的泛型换为具体类型
我们可能有点懵,来看个例子
#include
using namespace std;
//类模板
template
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << "age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
int main()
{
int a=10;
return 0;
}
上面这个例子没有生成类定义,他只是告诉编译器如何生成类定义 ,这一点和函数模板是相同的
类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数
示例://类模板与函数模板的区别
先看类模板的
template //指定默认参数
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
//Person p("孙悟空", 1000);错误的,类模板无法用自动类型推导
Personp("孙悟空", 1000);//正确,只能用显式指定类型推导
p.showPerson();
}
void test02()
{
Personp("猪八戒", 999); //类模板在参数列表中有默认参数
}
int main()
{
test01();
system("pause");
return 0;
}
再来看函数模板的
#include
using namespace std;
//其实可以这么写template,但是不建议
template
void A(T a)
{
cout << a << endl;
}
void B()
{
A(3.0);//自动类型推断
}
int main()
{
B();
}
类模板中成员函数和普通类中成员函数创建时机是有区别的:
1,普通类中的成员函数一开始就可以创建
2,类模板中的成员函数在调用时才创建(因为我们写下模板类,只是告诉编译器如何去定义一个类,但是又不会创建类对象出来,只是一团虚的东西)
示例:
//类模板中成员函数的创建时机
class Person1
{
public:
void showPerson1()
{
cout << "Person1 show" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2 show" << endl;
}
};
template
class Myclass
{
public:
T obj;
//类模板中的成员函数在调用的时候才创建,所以不会报错
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
void test01()
{
Myclassm;
m.func1();
//因为我们没有创建Person2的版本啊,所以也没有showPerson2这个函数啊
//m.func2(); 无法调用
}
int main()
{
test01();
system("pause");
return 0;
}
学习目标:类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
指定传入的类型:直接显示对象的数据类型
参数模板化:将对象中的参数变为模板进行传递
整个类模板化:将这个对象类型模板化进行传递
示例:
//类模板对象做函数参数
template
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&p)
{
p.showPerson();
}
void test01()
{
Personp("孙悟空", 199);
printPerson1(p);
}
// 2、参数模板化
template
void printPerson2(Person&p)
{
p.showPerson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
Personp("猪八戒", 90);
printPerson2(p);
}
// 3、整个类模板化
template
void printPerson3(T &p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Personp("唐僧", 60);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
运行结果:
注:使用比较广泛的是指定传入类型的传参方式
当类模板碰到继承时,需要注意以下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需为类模板
示例:
//类模板与继承
template
class Base
{
T m;
};
//class Son: public Base //错误,必须要知道父类中的T类型,才能继承给子类
class Son :public Base
{
};
void test01()
{
Son s1;
}
//如果想灵活指定父类中T的类型,子类也需要变成类模板
template
class Son2 : public Base
{
public:
Son2()
{
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
T1 obj;
};
void test02()
{
Son2 s2;
}
int main()
{
test02();
system("pause");
return 0;
}
示例:
//类模板成员类外实现
template
class Person
{
public:
Person(T1 name, T2 age);
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();
/*{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}*/
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
void test01()
{
Personp("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
如果工程中需要利用多个类模板,那么将这些类模板都写在同一个文件中将会导致代码可读性变差,所以有必要对类模板进行分文件编写,但是类模板的分文件编写面临着一些问题,以下是类模板分文件编写面临的问题及解决方法。
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制的
示例1:(未进行分文件编写)
template
class Person
{
public:
Person(T1 name, T2 age);
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();
/*{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}*/
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
void test01()
{
Personp("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
实例2:(进行分文件编写,利用.cpp)
1.创建头文件person.h,写一些声明
#pragma once
#include
using namespace std;
#include
template
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
2.创建person.cpp,写具体实现
#include "person.h"
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
3.main函数编写
错误代码:
#include
using namespace std;
#include
#include "person.h"
void test01()
{
Personp("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
注:因为如果包含person.h文件,那么编译器将会看到person.h中的代码。但是由于类模板中的成员函数一开始是不创建的,导致编译器没有看到person.cpp中的代码,所以执行test01时,无法解析其中的代码。
正确代码:(不常用)
#include
using namespace std;
#include
#include "person.cpp"
void test01()
{
Personp("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
注:就是将person.h文件改成了person.cpp代码。编译器首先看到了person.cpp文件,因为person.cpp文件中有person.h文件,编译器又看到了person.h文件,所以能够解析test01中的代码。但是一般很少直接包含.cpp文件的,所以这个方法不常用。
实例3:(分文件编写,利用.hpp)
将person.h和person.cpp的内容写到一起,并将后缀名改为.hpp,这是类模板分文件编写最常用的方式
1.编写person.hpp文件:
#pragma once
#include
using namespace std;
#include
template
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template
Person::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template
void Person::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
2.编写main函数
#include
using namespace std;
#include
#include "person.hpp"
void test01()
{
Personp("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
为模板类提供非模板友元函数,像下面这样子做就可以了
#include
using namespace std;
void A(int a)//非模板友元函数
{
cout << a << endl;
}
template
class AA
{
private:
T a_;
public:
AA(T a):a_(a){}
friend void A(int a);//友元声明
};
int main()
{
A(2);//正常使用
//AA a(2);模板类不能自动类型推断
AA a(2);//OK
}
上面这个友元函数将成为所以模板实例化的友元 。例如它将是AA
假如我们要为友元函数提供模板类参数,可以像下面这样子做吗?
friend void A(AA&t);
答案是不行的,要提供模板类参数,必须指明具体化,像下面这两种做法都是可以的
template
class AA
{
friend void A(AA&t);
friend void B(AA&t);
....
}
说白了就是来使类的每一个具体化都获得一个与友元匹配的具体化
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在
1.全局函数的类内实现
template
class Person
{
//全局函数类内实现
friend void printPerson(Person p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Personp("Tom", 30);
printPerson(p);
}
int main()
{
test01();
system("pause");
return 0;
}
2.全局函数类外实现
//提前让编译器知道Person类的存在
template
class Person;
//类外实现
template
void printPerson(Person p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template
class Person
{
//全局函数类外实现
//加空模板参数列表
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson<>(Person p);
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Personp("Tom", 30);
printPerson(p);
}
int main()
{
test01();
system("pause");
return 0;
}
注:需要注意各个函数声明之间的顺序。在Person类模板中有友元的声明friend void printPerson<>(Person
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
说白了就是每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的
template
class AA
{
.....
templatefeiend void A(A&,B&);
.....
}
到目前为止,上面所有例子用的都是隐式实例化,即它们声明一个或多个对象,指出所需类型,而编译器使用通用模板提供的处方生成具体的类声明
AA a;
需要注意的是:需要对象之前,不会生成类的隐式实例化。
AA*t;
t=new AA;
//第二句导致编译器生成类定义,并根据该定义创建一个对象
显式实例化声明必须位于模板定义所在的名称空间里。
格式大概是
template class 类模板名<具体类型>;
我们看个例子
#include
using namespace std;
void A(int a)
{
cout << a << endl;
}
template
class AA
{
private:
T a_;
public:
AA(T a):a_(a){}
friend void A(int a);
};
template class AA;//注意不能放main函数里面
int main()
{
A(2);
//AA a(2);
AA a(2);
}
显式具体化是指在类模板外部对类模板的特定类型进行具体化。通过显式具体化,我们可以为模板类给出特定类型的定义,以覆盖默认的通用定义。
以下是一个示例,展示如何显式具体化一个类模板:
// 定义一个类模板
template
class MyTemplate {
public:
MyTemplate(T value) : m_value(value) {}
void print() {
std::cout << "Generic template: " << m_value << std::endl;
}
private:
T m_value;
};
// 显式具体化模板类的特定类型
template<>
class MyTemplate {
public:
MyTemplate(int value) : m_value(value) {}
void print() {
std::cout << "Specialized template for int: " << m_value << std::endl;
}
private:
int m_value;
};
int main() {
MyTemplate obj1(3.14);
obj1.print(); // 输出:Generic template: 3.14
MyTemplate obj2(42);
obj2.print(); // 输出:Specialized template for int: 42
return 0;
}
在上述示例中,定义了一个模板类 MyTemplate
,它可以用于任意类型的参数 T
。然后,通过显式具体化,我们为模板类 MyTemplate
的特定类型 int
提供了特殊化的定义,其中包含一个特定的成员函数 print
,用于输出特定类型的值。
在 main
函数中,我们创建了两个 MyTemplate
的对象,一个是 MyTemplate<double>
类型的,另一个是 MyTemplate<int>
类型的。当调用它们的 print
成员函数时,将根据对象类型的特化定义,分别输出不同的结果。
这样,通过显式具体化,我们可以为特定类型提供自定义的定义,以满足特定的需求。