从零开始实现数据结构(一) 动态数组
动态数组是所有数据结构中最简单的一种,甚至在很多的语言中,数组本身就是可以不定长的。因为在学习c++的时候,使用动态数组的各种操作都不是很方便(数据结构的学习最好还是c或c++,基础打好了其他的语言数据结构就很简单)。所以开始学习如何去实现一个像STL中的vector一样的动态数组。
创建数组基类CArray
因为后面还准备写一个有序数组,所以这里使用一个CArray类,把数组的各种基本特性先创建好。后面需要写什么数组,就可以写它下面的子类,来完成特殊的功能。
对于一个数组,我觉得需要的基本功能大概三个方面:
1.在主函数中创建数组对象并初始化,这点肯定是必须的。
2.数组的容量以及当前数组所含有的元素个数。
3.打印数组,较为方便的输出数组中的元素。
对于插入元素,因为不同的数组有不同的插入需求,所以考虑是在子类中再写数组的插入与删除。
首先创建CArray这个类,即CArray.h和CArray.cpp两个文件。在头文件CArray.h中,定义好基本的方法与成员变量:
CArray.h文件的代码如下:
#pragma once
class CArray
{
public:
//成员函数
CArray();//构造函数,即用来初始化
~CArray();//析构函数,使用完后释放空间
void Print();//打印数组的元素
int getCapacity();//得到数组的容量
int getSize();//得到数组当前的元素个数
//成员变量
int* mp_Array;//存放数组对象的指针
int m_Size;//数组当前的元素个数
int m_Capacity;//数组的容量
};
mp_Array是一个指针变量,指向我们要创建数组的存储地址。用指针来操作数组应该是很方便的,毕竟c++的标准数组其实也是一个常指针。
写完头文件,然后在CArray.cpp文件中,将头文件声明的函数具体功能写出来。
主义在构造函数中我们现在只需要定义初始数组当前元素个数为0就可以了,可以先不用把数组的容量定义好,因为不同的数组有不同的需求,这些可以放在它的子类去定义。
CArray.cpp文件的代码如下:
#include "CArray.h"
#include<iostream>
CArray::CArray()//构造函数初始化
{
m_Size = 0;
}
CArray::~CArray()//析构函数
{
delete[] mp_Array;
}
int CArray::getCapacity()//得到数组的容量
{
return m_Capacity;
}
int CArray::getSize()//得到数组当前的元素个数
{
return m_Size;
}
void CArray::Print()//打印数组所有元素
{
printf("\nprint all date : \n");
for (int i = 0; i < m_Size; i++)
{
printf("%d\n", mp_Array[i]);
//在使用打印函数的时候,一定已经创建好了数组对象
//所以这里的mp_Array[i]是可以使用的,在后面的文件中我们会进行赋值
}
}
到这里一个数组的基类就建立好了,当然对于数组还有其他的功能可以实现,在这里只完成数组的基本功能。
创建动态数组类CDynamicArray
对于动态数组来说,我们需要的功能如下:
1.初始化动态数组对象。
2.插入元素,因为数组是一个连续的控件,在数组中插入效率很低,需要把插入位置后面的元素全部后移。所以这里的插入元素只实现在尾部插入是最好的。
3.删除元素,和插入元素同理,删除尾部元素。
4.查找该数组里某一个值的位置
首先创建好CDynamicArray.h和CDynamicArray.cpp文件。在头文件CDynamicArray.h声明我们需要的函数。
CDynamicArray.h文件的代码如下:
#pragma once
#include "CArray.h"
class CDynamicArray ://这个类要继承前面写的数组基类
public CArray
{
public:
CDynamicArray();//构造函数
~CDynamicArray();//析构函数
void PushBack(int value);//在数组的末尾插入元素,里面的参数为要插入的数据
void Remove();//移除数组末尾的一个元素
int Find(int value, bool isPrint);//查找某一个元素,返回的是位置
//这里的isPrint是我用来测试时添加的打印语句,在找到以后可以打印该元素的位置
//也可以不使用该参数
void Clear();//清空该数组所有的元素
void Sort(int start, int end, bool up);//有参数的数组排序,参数是要参与排序的起始位置和末尾位置
//第三个参数是排序是从小到大还是从大到小
void Sort();//无参数的数组排序,默认整个数组都排序,并且从小到大
};
要实现的基本功能大概就是这些,也可以自由添加更多的功能,只需要在头文件中声明函数,然后在cpp文件中实现相应的功能即可。
动态数组具体的实现
1.构造函数
初始化数组对象,因为在数组的基类中只初始化了数组当前的元素个数,在这里要初始化数组的容量,以及开辟一段新的内存空间给数组,并赋值给函数指针。这里默认初始内存大小为10(可以自由设置)。
CDynamicArray::CDynamicArray()
{
m_Capacity = 10;
mp_Array = new int[m_Capacity];//注意new一个内存空间的写法,
}
mp_Array = new int[m_Capacity]是开辟了一个新的内存,并把内存的地址赋给了数组对象中的指针,以后就可以使用这个指针来操作整个内存空间了。
2.插入元素
这也是动态数组最重要的部分。对于动态数组来说,最重要的特点就是数组的容量是可以动态变化的,可以根据数组里面元素的增加而自动变长。但实际是每一个数组必须都有一个内存大小,只是说对于用户使用的时候,这个数组好像是没有长度,可以任意添加元素。
所以实际上的动态数组应该是先给定一个初始的内存大小,让用户可以添加元素。当添加的元素达到了目前数组的容量,即m_Size=m_Capacity,这个时候如果再添加元素,内存就不够了。我们可以开一个更大内存的地址空间,然后把原来数组里面的所有元素,全部复制过去。这样就实现了一个基本的“任意长度”的功能。具体步骤参考注释。
//插入元素
void CDynamicArray::PushBack(int value)
{
//数组容量还足够时,直接把值存到相应的位置
if (m_Size != m_Capacity)
{
mp_Array[m_Size] = value;
m_Size++;
return;
}
//如果数组容量不够,则:
//1.先开一个新内存空间(一般容量为原来的2倍)
//2.然后把原来的数据复制过去
//3.然后delete原来的内存
//4.再让mp_Array指针指向新的内存空间
//5.把容量m_Capacity改为原来的2倍
//6.把要插入的值放入相应的位置,并且元素个数 m_Size+1
int* pNewSpace = new int[m_Capacity * 2];//开一个新的内存空间
memcpy(pNewSpace, mp_Array, sizeof(int)*m_Capacity);
//复制一段内存空间,里面的参数分别是
//新的地址,复制源地址,复制源的长度(单位是字节,所以这里要使用sizeof(int)*m_Capacity)
delete[] mp_Array;
mp_Array = pNewSpace;
m_Capacity *= 2;
mp_Array[m_Size] = value;//容量已够,开始赋值
m_Size++;
}
3.删除数组末尾的元素
这一步很简单,只需要把当前数组元素个数m_Size-1就可以,并不需要去改动要删除的这个值,在下次插入的时候会自动覆盖掉。
//删除末尾的元素
void CDynamicArray::Remove()
{
m_Size -= 1;
}
4.查找
查找数组中具体值的方法有很多,因为该数组并不是有序的,所以直接顺序查找比较简单,即从数组的开始位置一直查找到m_Size的位置
//查找指定值的元素,返回元素所在的位置
int CDynamicArray::Find(int value, bool isPrint)
{
for (int i = 0; i < m_Size; i++)
{
if (mp_Array[i] == value)
{
if (isPrint)
printf("the position of date \"%d\" is %d\n", value, i);
return i;
}
//没找到就返回-1
if (i == m_Size - 1)
{
if (isPrint)
printf("the position of date \"%d\" is not in array\n", value);
return -1;
}
}
}
5.清空数组
清空所有元素和删除元素类似,直接把m_Size置0即可,数组只能访问到m_Size大小的位置。
//清空所有元素
void CDynamicArray::Clear()
{
m_Size = 0;
}
6.排序
关于排序算法有很多种,这里选择使用冒泡排序。具体可以参考另一篇关于排序的文章
https://blog.csdn.net/nsytsqdtn/article/details/88321571
在动态数组的头文件中定义了两种排序,一种是有参数,可以自定义范围的排序;另一种是无参数,整个数组一起排序。
这是有参数的排序:
//有参数,有范围的排序
void CDynamicArray::Sort(int start, int end, bool up)//第三个参数true则为从小到大
{
if (start >= 0 && end < m_Size)//防止排序的范围越界
{
if (up)//判断第三个参数,来决定从小到大还是从大到小,true则为从小到大
{
//冒泡排序,从小到大
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] > mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
else//从大到小的排序
{
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] < mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
}
}
无参数的排序:
//无参数,整个数组的排序(只能从小到大)
void CDynamicArray::Sort()
{
int start = 0, end = m_Size - 1;
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] > mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
CDynamicArray.cpp文件的代码如下:
#include "CDynamicArray.h"
#include<stdlib.h>
#include<string.h>
#include<iostream>
CDynamicArray::CDynamicArray()
{
m_Capacity = 10;
mp_Array = new int[m_Capacity];//注意new一个内存空间的写法,
}
CDynamicArray::~CDynamicArray()
{
}
//插入元素
void CDynamicArray::PushBack(int value)
{
//数组容量还足够时,直接把值存到相应的位置
if (m_Size != m_Capacity)
{
mp_Array[m_Size] = value;
m_Size++;
return;
}
//如果数组容量不够,则:
//1.先开一个新内存空间(一般容量为原来的2倍)
//2.然后把原来的数据复制过去
//3.然后delete原来的内存
//4.再让mp_Array指针指向新的内存空间
//5.把容量m_Capacity改为原来的2倍
//6.把要插入的值放入相应的位置,并且元素个数 m_Size+1
int* pNewSpace = new int[m_Capacity * 2];//开一个新的内存空间
memcpy(pNewSpace, mp_Array, sizeof(int)*m_Capacity);
//复制一段内存空间,里面的参数分别是
//新的地址,复制源地址,复制源的长度(单位是字节,所以这里要使用sizeof(int)*m_Capacity)
delete[] mp_Array;
mp_Array = pNewSpace;
m_Capacity *= 2;
mp_Array[m_Size] = value;//容量已够,开始赋值
m_Size++;
}
//删除末尾的元素
void CDynamicArray::Remove()
{
m_Size -= 1;
}
//查找指定值的元素,返回元素所在的位置
int CDynamicArray::Find(int value, bool isPrint)
{
for (int i = 0; i < m_Size; i++)
{
if (mp_Array[i] == value)
{
if (isPrint)
printf("the position of date \"%d\" is %d\n", value, i);
return i;
}
//没找到就返回-1
if (i == m_Size - 1)
{
if (isPrint)
printf("the position of date \"%d\" is not in array\n", value);
return -1;
}
}
}
//清空所有元素
void CDynamicArray::Clear()
{
m_Size = 0;
}
//有参数,有范围的排序
void CDynamicArray::Sort(int start, int end, bool up)//第三个参数true则为从小到大
{
if (start >= 0 && end < m_Size)//防止排序的范围越界
{
if (up)//判断第三个参数,来决定从小到大还是从大到小,true则为从小到大
{
//冒泡排序,从小到大
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] > mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
else//从大到小的排序
{
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] < mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
}
}
//无参数,整个数组的排序(只能从小到大)
void CDynamicArray::Sort()
{
int start = 0, end = m_Size - 1;
for (int j = end; j > start; j--)
{
for (int i = start; i < j; i++)
{
if (mp_Array[i] > mp_Array[i + 1])
{
int temp = mp_Array[i];
mp_Array[i] = mp_Array[i + 1];
mp_Array[i + 1] = temp;
}
}
}
}
动态数组的所有功能函数就都已经实现,我们还需要在主程序中来调用一下前面写的动态数组
主函数Array.cpp
在主函数中要使用前面所写的动态数组,就只需要像创建类对象一样把动态数组的对象创建出来就可以使用了。首先还是创建一个新文件Array.cpp。
创建对象的方法有两种,一种是CDynamicArray* dArray = new CDynamicArray()。这种用new创建对象的方法需要手动去释放空间,所以相对使用起来更复杂。在主函数中推荐使用第二种创建对象的方式,即CDynamicArray dArray。
我们使用动态数组来插入数据,并且进行排序,然后打印整个数组
Array.cpp文件的代码如下:
#include "pch.h"
#include<iostream>
using namespace std;
#include "CDynamicArray.h"
using namespace std;
int main()
{
int n;
cin >> n;
//动态数组测试
CDynamicArray dArray;//创建动态数组对象
for (int i = 0; i < n; i++)
{
int temp;
cin >> temp;
dArray.PushBack(temp);//把手动输入的数据插入到数组的末尾
}
dArray.Sort(0,dArray.getSize()-1,false);
//数组从第0位置到最后一个位置一起排序
//这里和无参排序效果一样
dArray.Print();//打印整个数组
return 0;
}
运行结果如下:
输入:
10
9 15 5 30 11 7 6 51 1 40
输出:
print all date :
51
40
30
15
11
9
7
6
5
1
也可以在主函数中具体测试插入数据以后,数组的容量和当前元素的个数具体是怎么样变化的,在这里就不做多的测试了。
至此,就实现了一个动态数组,相比c++标准的数组,自己写出来的东西,调用起来还是会方便一些。因为没有使用模板元编程,数组的数据类型只设置了int,而且修改起来也不会很方便。所以更好的使用c++的动态数组可以参考STL中的vector。
原文地址:https://www.cnblogs.com/nsytsqdtn/p/11380044.html