案例描述: 实现一个通用的数组类,要求如下:
-
可以对内置数据类型以及自定义数据类型的数据进行存储。
-
将数组中的数据存储到堆区
-
构造函数中可以传入数组的容量
-
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
-
提供尾插法和尾删法对数组中的数据进行增加和删除
-
可以通过下标的方式访问数组中的元素
-
可以获取数组中当前元素个数和数组的容量
需求分析:
一、类的基本结构:
设计一个MyArray的类,类内属性为:
- 类内维护一个数组:T* item = new T[5];
- 维护表示一个关于数组容量的属性:m_Capacity。
- 维护表示一个数组元素个数的属性:m_Size。
属性写为private权限。
二、数据结构:
维护的这个数组要开辟到堆区。在堆区中维护的这个数据类型必须是通用的,即泛型的,支持多样性。所以堆中数组元素类型为T。
为此:数组中每个元素为 T* item = new T[5];
三、接口功能:
对外要提供一些功能函数接口:public:
- 构造函数(传入容量):传入参数,指定数组的容量。
- 拷贝构造:
- operator=:运算符重载。类内存在堆区的数据,为了防止 浅拷贝重复释放 的问题。
- 利用下标的方式:访问数组元素
- 尾插法:
- 尾删法:
- 获取数组的容量
- 获取数组的元素个数。
- 析构函数:
四:一步步实施:
这个需要内容比较多,不适合写在一个文件里面,所以分文件写:类模板分文件的编写,最好将声明和实现写在一起:都放在.hpp文件中。
先写一个类模板的文件:MyArray.hpp文件。
#include<iostream>
using namespace std;
template<typename T>
class MyArray
{
public:
// 初始化:有参构造 参数 容量
MyArray(int capacity)
{
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
// 测试代码:
cout << "MyArray 有参构造 被调用。" << endl;
}
// 拷贝构造
MyArray(const MyArray& arr)
{
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//this->pAddress // 开辟在堆区,所以需要深拷贝,从堆中,重新开辟一块空间,拷贝过去。
this->pAddress = new T[arr.m_Capacity];
// 将arr中的数据都拷贝过来。
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
cout << "MyArray 拷贝构造 被调用。" << endl;
}
// operator= 防止浅拷贝问题。
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];
// 数据全拷贝。
for (int i = 0; i < arr.m_Capacity; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
cout << "MyArray 运算符重载operator= 被调用。" << endl;
return *this; // 返回自身。
}
// 析构函数:
~MyArray()
{
if (this->pAddress!=NULL)
{
delete[] this->pAddress;
this->pAddress = NULL; // 置空,防止称为野指针。
cout << "MyArray 析构函数 被调用。" << endl;
}
}
private:
T* pAddress; // 指针指向堆区开辟的真实的数组。
int m_Capacity; // 数组的容量
int m_Size; // 数组的大小
};
先把基本框架写出来,然后做个测试:在程序中写一个测试案例。补充打印测试。
先包含头文件:#include "MyArray.hpp"
void test01()
{
int len = 5;
MyArray<int> arr(len);
MyArray<int> arr1(arr); // 拷贝构造方式。
MyArray<int> arr2(10);
arr2 = arr; // 运算符重载。
}
阶段性测试:已经完成前面几个任务,只剩下最后三个任务:
先写:尾插法、尾删法
// 尾插法
void Push_Back(const T& val)
{
// 插入之前先判断容量是否等于大小。
if (this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val; // 在数组末尾插入数据。
this->m_Size++; // 更新数组大小。
}
// 尾删法
void Pop_Back()
{
// 让用户访问不到最后一个元素。即为尾删,逻辑删除。
if (this->m_Size == 0)
{
return;
}
this->m_Size--;
}
下标访问:
// 下标访问元素:返回类型为元素类型T,如果需要将返回值作为左值存在,那么需要返回引用。
T& operatoar[](int index)
{
return this->pAddress[index];
}
返回数组容量、大小
// 返回数组容量
int getCapacity()
{
return this->m_Capacity;
}
// 返回数组大小
int getSize()
{
return this->m_Size;
}
为了打印输出,我写了个友元来显示每一项:
friend void printArray(const MyArray<T>& arr)
{
for (int i = 0; i < arr.m_Size; i++)
{
cout << arr.pAddress[i] << " ";
}
cout << endl;
}
void test01()
{
int len = 5;
MyArray<int> arr(len);
MyArray<int> arr1(arr); // 拷贝构造方式。
MyArray<int> arr2(10);
arr2 = arr; // 运算符重载。
}
void test02()
{
int len = 5;
MyArray<int> arr(len);
for (int i = 0; i < len; i++)
{
arr.Push_Back(i + 1);
}
cout << "初始化arr:";
printIntArray(arr); // 友元;
// 测试尾删法:
arr.Pop_Back();
cout << "测尾删arr:";
printIntArray(arr); // 友元;
// 测试尾插法;
arr.Push_Back(99);
printIntArray(arr); // 友元;
cout << arr.getCapacity() << endl;
cout << arr.getSize() << endl;
}
以上是关于内置数据类型的案例。来看看自定义类型案例。
#pragma once
#include<iostream>
using namespace std;
template<typename T1, typename T2>
class Person
{
public:
Person() {}; // 不加这个默认构造函数,会报错。
Person(T1 name, T2 age);
void showPerson();
private:
T1 m_Name;
T2 m_Age;
};
// 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << ", 年龄:" << this->m_Age << endl;
}
我们直接套用前面几节中关于Person.hpp的类模板。然后来测试。
void printPersonArray(MyArray<Person<string, int>>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
arr[i].showPerson();
}
}
// 看看自定义类数组效果
void test03()
{
Person<string, int> p1("张三", 18);
Person<string, int> p2("李四", 14);
Person<string, int> p3("王五", 15);
Person<string, int> p4("赵六", 16);
Person<string, int> p5("田七", 17);
MyArray<Person<string, int>> arr(5);
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
printPersonArray(arr);
}
报错了;
没有合适的默认构造函数可用。因此需要在Person.hpp中,将默认构造函数,加进去,即:Person(){}。否则会报上面的错误。
类模板中引用其他类模板,被引用类模板要加上默认构造函数。
此外两个深拷贝存在这样一个问题:待我技术成长了再来解决。