我们在该案例中要编写一个能够储存任何类型(包括用户自定义类型)的数组
代码展示:
text.cpp执行源文件
#include<iostream>
using namespace std;
#include"MyArray.hpp"
#include<string>;
//定义一个用来测试的类
class person
{
public:
//MyArray<person>arr(5)开辟空间用了无参构造
//要是编写了有参构造就没有了系统默认的无参构造,所以要自己再编写无参构造,要不然会报错
//无参构造
person() {}
//有参构造
person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
//姓名
string m_name;
//年龄
int m_age;
};
//输出类的数据
void Print_Array(MyArray<person>& arr)
{
for (int i = 0; i < arr.Get_Size(); i++)
{
cout << "姓名:" << arr[i].m_name << " " << "年龄:" << arr[i].m_age << endl;
}
}
int main()
{
MyArray<person>arr(5);
person p1("小明",13);
person p2("小红", 18);
person p3("小王", 23);
arr.Push_back(p1);
arr.Push_back(p2);
arr.Push_back(p3);
Print_Array(arr);
return 0;
}
MyArray.hpp类模板定义与实现头文件
关于类模板分文件编写的知识点此链接:类模板分文件编写
#pragma once
#include<iostream>
using namespace std;
template <class T>
class MyArray
{
public:
//构造函数
MyArray(int capacity)
{
//cout << "构造函数" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->PAddress = new T[this->m_Capacity];
}
//析构函数(释放在堆区中开辟的空间)
~MyArray()
{
//cout << "析构函数" << endl;
if (PAddress)
{
delete[]PAddress;
this->PAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
}
//拷贝构造函数
//涉及在类中属性有堆区开辟空间的操作就要自己编写拷贝构造函数防止出现深拷贝和浅拷贝的问题
MyArray(const MyArray& arr)//注意我们传入的参数是不能改变的,所以要记得加const
{
//cout << "拷贝构造函数" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝操作
//重新在堆区创建一个空间
this->PAddress = new T[arr.m_Capacity];
for (int i = 0; i < arr.m_Size; i++)
{
this->PAddress[i] = arr.PAddress[i];
}
}
//重载‘=’符号,进行对象的拷贝
//由于要满足比如a=b=c这样的操作,所以重载‘=’符号的返回值应该是对象本身,这样才能继续实现赋值操作
//operator+要重载的运算符作为重载运算符函数的函数名
MyArray& operator=(const MyArray& arr)//和拷贝构造函数一样,该参数不改变加上const
{
//首先要判断在等号左方的对象中是否已经有了数据
//cout << "重载‘=’符号" << endl;
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];
for (int i = 0; i < arr.m_Size; i++)
{
this->PAddress[i] = arr.PAddress[i];
}
return *this;
}
//重载‘[]’符号,实现对象能通过向[]符号中传入下标,返回数组中该下标对应的数据
//要想实现arr[1]=100,让[]返回的值可以作为左值,返回的就应该是T类型的引用
T& operator [](int index)
{
return this->PAddress[index];
}
//尾插法
void Push_back(const T&num)
{
//分析数组已经满了的情况
if (this->m_Capacity == this->m_Size)
{
return;
}
this->PAddress[this->m_Size] = num;
this->m_Size++;
}
//尾删法
void Pop_back()
{
//分析数组已经为空无法删除的情况
if (this->m_Size == 0)
{
return;
}
//数组进行逻辑上的删除即可
this->m_Size--;
}
//获取数组容量
int Get_Capacity()
{
return this->m_Capacity;
}
//获取数据个数
int Get_Size()
{
return this->m_Size;
}
private:
//数组容量
int m_Capacity;
//数据个数
int m_Size;
//维护数组的指针
T* PAddress;
};
值得注意的地方:
1.我们自己编写的这个数组类涉及到在堆区中开辟空间,所以要特别注意拷贝构造函数的深拷贝和浅拷贝问题,由系统给出的拷贝构造函数就是浅拷贝,这会导致我们在进行拷贝操作时出现错误,所以我们要重写拷贝构造函数,将其改为深拷贝。
ps:深拷贝和浅拷贝简单来说就是:在拷贝指针时浅拷贝会直接将指针原封不动的拷贝另一个对象中,这会导致两个对象中的指针指向的都是同一个堆区中的空间,这样在析构函数中就会导致重复释放堆区中的空间,而深拷贝会在拷贝指针的同时重新在堆区中创建一个新的空间,让指针指向这个新的空间,就解决了重复释放堆区空间的问题
2.重载‘ = ’符号(operator+要重载的运算符作为重载运算符函数的函数名),我们可以实现对象之间的赋值操作,当然在重载‘ = ’符号时我们也要注意深拷贝和浅拷贝的问题,我们重载运算符函数的返回值都是很有讲究的,一开始不知道返回什么可以先设为void,由于要满足a=b=c这样的操作,所以在b=c操作实现后应该返回b本身,所以返回的是*this,又因为要满足a=100这样的操作,对象要作为左值,所以函数类型为MyArray&。
3.重载‘ [ ] ’符号,由于我们要实现a[index]输出在数组中下标为index数据的操作,所以我们要重载‘ [ ] ’符号,返回的值是数组中的数据,所以函数类型为T&
4.在编写用来进行检验的类时要注意在编写了带参数的构造函数后,还要再编写一个不带参数的构造函数,因为类模板需要声明即将存入的数据类型,所以我们在创建自定义的数组MyArray<person>arr(5);要用到person,这里需要用到person类的无参构造函数,但我们编写了有参构造函数就会导致系统不提供无参构造函数,所以我们自己还要提供无参构造函数。
类模板案例-数组类封装完成