类模板可以建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。
类模板的语法如下:
template<typename T>
类
这种语法与函数模板一致,具体的内容请见上一篇博客。
先举一个小的代码样例:
//类模板
template<class NameType, class AgeType>
//人类
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_age = age;
this->m_name = name;
}
void ShowPerson()
{
cout << "姓名为" << this->m_name << endl;
cout << "年龄为" << this->m_age << endl;
}
string m_name;
int m_age;
};
void test()
{
Person<string, int> p1("张三", 12);
p1.ShowPerson();
}
类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导的使用方式:
//类模板与函数模板的区别
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_name = name;
this->m_age = age;
}
void ShowPerson()
{
cout << "姓名为" << this->m_name << endl;
cout << "年龄为" << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test08()
{
string name = "张三";
int age = 12;
Person p1(name, age);
}
样例代码如上,当使用如上代码时,会报如下错误:
之所以报这么错误就是因为类模板不像函数模板一样,它并不能自动推导出传入数据的类型,因此会报如上错误。
将调用类模板的代码改为:
Person<string, int>(name, age);
即可解决错误。
2.类模板在模板参数列表中可以有默认参数:
template<class NameType, class AgeType = int>
其样例如上,在定义类模板时可以指定参数列表中的默认参数类型,当我们在调用这个类时就不需要再指定默认参数的参数类型了:
Person<string>p1(name, age);
这是类模板的一个特有的功能,在函数模板中是没有这个功能的。
类模板中成员函数和普通类中成员函数创建时机是有区别的:
1、普通类中的成员函数一开始就可以创建
2、类模板中的成员函数在调用时才创建
类模板实例化出的对象,向函数传参的方式,一共有三种:
1.指定传入的类型:直接显示对象的数据类型
//类模板对象做函数参数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
void ShowPerson()
{
cout << "姓名" << this->m_name << endl;
cout << "年龄" << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
//指定传入的类型
void printPerson1(Person<string, int>&p)
{
p.ShowPerson();
}
void test09()
{
Person<string, int>p1("张三", 19);
printPerson1(p1);
}
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;
3.整个类模板化:将这个对象类型模板化进行传递,代码样例如下:
//整个类模板化
template<class T>
void printPerson3(T &p)
{
p.ShowPerson();
cout << "T的类型为: " << typeid(T).name() << endl;
}
当类模板碰到继承时,需要注意一下几点:
1、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存,则此时会报如下错误,之所以发生如下错误是因为当子类继承父类时,并不知道自己要分配多少内存空间。
当发生如上错误时,需要指定一下T的类型,方法如下:
class son:public base<int>
2、如果想灵活指定出父类中T的类型,子类也需变为类模板,样例如下:
template<class T1, class T2>
class son:public base<T2>
{
T1 A;
};
在这里语法就比较简单,直接上例子啦
重点在这里:person
//类模板成员函数的类外实现
template<class T1, class T2>
class person
{
public:
person(T1 name, T2 age);
void ShowPerson()
{
cout << "姓名: " << this->m_name << "年龄" << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
//类外实现
template<class T1, class T2>
person<T1, T2>::person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到,这是我们有如下两个解决方式:
解决方式1:直控包含.cpp源文件,样例如下:
#include"person.cpp"
void test12()
{
person<string, int> p1("张三", 12);
p1.showPeson();
}
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。(主流方法)
我们首先创建一个person.hpp的文件,将声明与实现全部写入到.hpp中:
#pragma once
#include
using namespace std;
#include
//类模板分文件编写问题以及解决
template<class T1, class T2>
class person
{
public:
person(T1 name, T2 age);
void showPeson();
T1 m_name;
T2 m_age;
};
template<class T1, class T2>
void person<T1, T2>::showPeson()
{
cout << "年龄为: " << this->m_age << "姓名为: " << this->m_name << endl;
}
template<class T1, class T2>
person<T1, T2>::person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
在调用这个类模板时,我们可以直接调用#include"person.hpp"
即可
全局函数类内实现:直接在类内声明友元即可,样例如下:
template<class T1, class T2>
class person
{
//全局函数类内实现
friend void printPerson(person<T1, T2> 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 test13()
{
person<string, int> p1("张三", 13);
printPerson(p1);
}
全局函数类外实现:需要提前让编译器知道全局函数的存在,样例如下:
//提前让编译器知道类模板的存在
template<class T1, class T2>
class person;
//类外实现
template<class T1, class T2>
void printPerson2(person<T1, T2> p)
{
cout << "姓名: " << p.m_name << "年龄: " << p.m_age << endl;
}
template<class T1, class T2>
class person
{
//如果我们的全局函数是类外实现的话,需要让编译器提前知道这个函数的存在
friend void printPerson2<>(person<T1, T2> p);
public:
person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
在类外实现全局函数时,我们需要让编译器知道我们全局函数的存在,所以我们需要将函数的实现体写在类模板的上方,但是函数的实现体中又包含了类模板,所以在函数的实现体之前要声明一下类模板的存在。
案例描述:实现一个通用的数组类,要求如下:
1、可以对内置数据类型以及自定义数据类型的数据进行存储
2、将数组中的数据存储到堆区
3、构造函数中可以传入数组的容量
4、提供对应的拷贝构造函数以及operator=防止浅拷贝问题
5、提供尾插法和尾删法对数组中的数据进行增加和删除
6、可以通过下标的方式访问数组中的元素
7、可以获取数组中当前元素个数和数组的容量
针对要求1-4我们可以使用如下的代码进行实现,首先为了实现第一条的功能,很明显我们主要一个数组类的模板来实现该要求;针对问题二,为了将数组存放到堆区,我们需要在堆区内new一块我们需要的内存,同时需要在析构函数中对开辟的内存进行手动释放;针对问题三,我们很容易的实现出来,这里特别注意一点,我们在调用构造函数时,我们需要将类中的属性进行初始化;针对问题四,我们直接使用以前的方法实现即可。
//通用的数组类
#pragma once
#include
using namespace std;
template<class T>
class MyArray
{
public:
//传入数组的初始容量
MyArray(int Capacity)
{
this->m_Capacity = Capacity;
//初始的数组大小为0
this->m_Size = 0;
//在堆区先开辟出一个指定容量的内存空间
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造函数
MyArray(const MyArray& arr)
{
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
//防止浅拷贝
MyArray& operator=(const MyArray& arr)
{
//判断原来堆区是否有数据,如果有,则先释放
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T & val)
{
//判断容量是否等于数组大小
if (this->m_Capacity == this->m_Size)
{
cout << "数组容量已经达到上限!" << endl;
return;
}
//在数组的末尾插入数据
this->pAddress[this->m_Size] = val;
//更新现在数组的大小
this->m_Size++;
}
//尾删法
void Pop_Back()
{
//让用户访问不到最后一个元素,即为尾删
if (this->m_Size == 0)
{
cout << "无数据" << endl;
return;
}
//更新现在数组的大小
this->m_Size--;
}
//通过下标的方式来访问数组中的元素
T& operator[](int index)
{
return this->pAddress[index];
}
//返回数组的容量
int GetCapacity()
{
return this->m_Capacity;
}
//返回数组的大小
int GetSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T * pAddress; //指针,指向堆区开辟的数组
int m_Capacity;//数组容量
int m_Size; //数组大小
};
测试代码:
#include
using namespace std;
#include"MyArray.hpp"
#include
void PrintIntArray(MyArray <int>& arr)
{
for (int i = 0; i < arr.GetSize(); i++)
{
cout << arr[i] << endl;
}
}
void test14()
{
MyArray<int> arr1(5);
for (int i = 0; i < 5; i++)
{
//利用尾插法向数组中插入数据
arr1.Push_Back(i);
}
cout << "arr1: " << endl;
PrintIntArray(arr1);
cout << "arr1的大小" << arr1.GetSize() << endl;
cout << "arr1的容量" << arr1.GetCapacity() << endl;
MyArray<int> arr2(arr1);
cout << "arr2的打印输出" << endl;
PrintIntArray(arr2);
//尾删
arr2.Pop_Back();
cout << "arr2的大小" << arr2.GetSize() << endl;
cout << "arr2的容量" << arr2.GetCapacity() << endl;
}
class person
{
public:
person() {};
person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void printPersonArray(MyArray<person>& arr)
{
for (int i = 0; i < arr.GetSize(); i++)
{
cout << "姓名: " << arr[i].m_name << "年龄: " << arr[i].m_age << endl;
}
}
void test15()
{
MyArray<person> arr(10);
person p1("张三", 13);
person p2("李四", 14);
person p3("王五", 12);
//将数据插入到数组中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
printPersonArray(arr);
cout << "arr的大小" << arr.GetSize() << endl;
cout << "arr的容量" << arr.GetCapacity() << endl;
}
int main() {
test15();
system("pause");
return 0;
}