1 类模板成员函数类外实现
学习目标: 能够掌握类模板中的成员函数类外实现
场景描述: 创建一个类,类中只写函数的声明,类外写实现
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);//类内声明
void showPerson();//类内声明
T1 mname;
T2 mage;
};
//构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->mname = name;
this->mage = age;
}
//成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->mname << "\t年龄:" << this->mage << endl;
}
注意点: 构造函数/成员函数类外实现
1.构造函数前要加作用域——Person::
2.在构造函数前,声明类模板的参数,让编译器知道T1、T2的存在——template<class T1, class T2>
3.加模板的参数列表,表明Person是一个类模板——<T1, T2>
,如果没有这个,Person只是一个普通类的类外实现
总结: 类模板中成员函数类外实现时,需要加上模板参数列表
2 类模板分文件编写
学习目标: 掌握类模板成员函数分文件编写产生的问题以及解决方式
问题: 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方法:
- 解决方法1:直接包含.cpp源文件
- 解决方法2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
场景描述: 创建头文件写类声明,创建源文件写类实现
头文件Person.h
#pragma once
#include <iostream>
using namespace std;
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 mname;
T2 mage;
};
源文件Person.cpp
#pragma once
#include <iostream>
using namespace std;
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 mname;
T2 mage;
};
测试文件:
#include <iostream>
using namespace std;
//类模板分文件编写
#include "Person.h"
void test()
{
Person<string, int> p1("Annas", 25);
p1.showPerson();
}
int main()
{
test();
cout << endl;
system("pause");
return 0;
}
解释:
此时无法正常编译,因为类模板中Person构造函数和showPerson成员函数在一开始分文见编写时是不会创建的,成员函数的创建时机是在调用阶段,之前的学习笔记有讲到,导致分文件编写时链接不到。
具体来说,在测试文件中,如果包含“Person.h”这个头文件,即使编译器找到Person类,不会生成类中的Person和showPerson这两个成员函数,不会链接到Person.cpp源文件,所以不会生成这两个函数。也就是说,在一开始头文件中,类的成员函数并没有创建,导致无法链接到Person.cpp,从而不能调用类的两个成员函数。
解决办法1:包含源文件,不常用
在测试文件中,把包含的头文件“Person.h”的后缀“.h”改成“.cpp”,就可以正常调用了。相当于让编译器直接找到类的两个成员函数的具体实现。
#include "Person.cpp"
但是这种方法在实际中并不常用,通常不会让程序员直接看源码,而是看头文件。
解决办法2:将.h和.cpp的内容写到一起,并将头文件.h的后缀名改为.hpp,常用
//Person.hpp文件
#pragma once
#include <iostream>
using namespace std;
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 mname;
T2 mage;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->mname = name;
this->mage = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->mname << "\t年龄:" << this->mage << endl;
}
//测试文件
#include "Person.hpp"
注意点:
-
.hpp文件中的类一般都是类模板
-
.hpp是约定俗成的
- 直接包含**.hpp**文件
总结:
知道分文见编写出现的问题以及两种解决方法
主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp
3 类模板友元
学习目标: 掌握类模板配合友元函数的类内和类外实现
全局函数的实现:
- 全局函数类内实现:直接在类内声明友元即可
- 全局函数类外实现:需要提前让编译器知道全局函数的存在
场景描述: 创建一个类,让全局函数类内实现和类外实现,访问类的私有属性
类内实现-代码展示:
template<class T1, class T2>
class Person
{
//全局函数类内实现 做友元 让printPerson可以访问Person的私有属性
friend void printPerson1(Person<T1, T2> p)//Person<T1, T2>是类模板
{
cout << "姓名:" << p.mname << "\t年龄:" << p.mage << endl;
}
public:
Person(T1 name, T2 age)
{
this->mname = name;
this->mage = age;
}
private:
T1 mname;
T2 mage;
};
注意点: 全局函数在类内实现时:只需要在类内声明友元即可,加friend关键字
类外实现-代码展示:
//让编译器提前知道printPerson2中的Person是类模板
template<class T1, class T2> class Person;//类模板声明
//如果声明了函数模板,全局函数的实现可以写在后面,否则需要将实现体写在类内的前面,让编译器提前知道
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
cout << "姓名:" << p.mname << "\t年龄:" << p.mage << endl;
}
template<class T1, class T2>
class Person
{
//全局函数类外实现 类内声明
//如果全局函数是函数模板且类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson2<>(Person<T1, T2> p);//加空模板参数列表<>
public:
Person(T1 name, T2 age)
{
this->mname = name;
this->mage = age;
}
private:
T1 mname;
T2 mage;
};
注意点:
如果只是在类外写了全局函数的实现体,没有其他声明。此时编译器不知道printPerson3是什么类型的函数,是普通函数还是类模板的成员函数?
其实,printPerson3是普通函数,在类内仅做了声明。但在类外,由于printPerson3的前面加了函数模板声明template<class T1, class T2>
,所以printPerson3在类外是一个函数模板的实现。由于printPerson3在类内、类外不一致,导致无法正常编译。因此,有如下步骤:
首先,为了在类内表明printPerson3是一个函数模板的声明,需要在类内做友元时加空模板参数列表<>,即friend void printPerson3<>(Person<T1, T2> p);
其次,如果全局函数是函数模板,且类外实现,需要让编译器提前知道这个函数的存在。也就是将printPerson3的实现体放在类模板的前面。如果声明了函数模板,全局函数的实现也可以写在后面。
最后,还要声明printPerson3中的Person是一个类模板。
**总结:**建议全局函数做类内实现,用法简单,而且编译器可以直接识别