从零开始实现数据结构(一) 动态数组

从零开始实现数据结构(一) 动态数组

动态数组是所有数据结构中最简单的一种,甚至在很多的语言中,数组本身就是可以不定长的。因为在学习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

时间: 2024-10-11 18:27:56

从零开始实现数据结构(一) 动态数组的相关文章

(2)redis的基本数据结构是动态数组

redis的基本数据结构是动态数组 一.c语言动态数组 先看下一般的动态数组结构 struct MyData { int nLen; char data[0]; }; 这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针,用空数组有这样的优势: 1.不需要初始化,数组名直接就是所在的偏移   2.不占任何空间,指针需要占用int长度空间,空数组不占任何空间.  这个数组不占用任何内存,意味着这样的结构节省空间: 该数组的内存地址就和他后面的元素的地址相同,意味着无需初始化,数组名就是后面元素的地

第二十二篇 玩转数据结构——构建动态数组

1.. 数组基础 数组就是把数据码成一排进行存放. Java中,数组的每个元素类型必须相同,可以都为int类型,string类型,甚至是自定义类型. 数组的命名要语义化,例如,如果数组用来存放学生的成绩,那么命名为scores就比较合适. 索引(index)是数组中的一个重要概念,它是我们给数组中的每个元素分配的编号,从0开始,依次递增.如果数组中存放了n个元素,第一个元素的索引是0,最后一个元素的索引是n-1. 通过索引,我们可以对数组中的元素进行快速访问,例如,我们访问索引为2的元素也就是数

nginx学习七 高级数据结构之动态数组ngx_array_t

1 ngx_array_t结构 ngx_array_t是nginx内部使用的数组结构.nginx的数组结构在存储上与大家认知的C语言内置的数组有相似性,比如实际上存储数据的区域也是一大块连续的内存.但是数组除了存储数据的内存以外还包含一些元信息来描述相关的一些信息,并且可以动态增长.下面 我们从数组的定义上来详细的了解一下.ngx_array_t的定义位于src/core/ngx_array.c|h里面. struct ngx_array_s { void *elts;//数组的首地址 ngx_

数据结构入门——动态数组

数组的一大缺点就是长度定义后不能再改变,此程序实现了动态数组,类似于Java中的ArrayList的结构,有增.删.排序.遍历.扩容追加等功能. 动态数组的实现: /* 2013年2月16日19:18:35 此程序将数组中的元素进行追加.删除.排序.遍历输出等操作. 与java中的各方法相同,从而更加深入理解java中的方法. */ # include <stdio.h> # include <malloc.h> # include <stdlib.h> struct

算法与数据结构基础1:动态数组

恶补算法与数据结构,从很基础的开始,先看动态数组的实现. // array.h #include <iostream> #include <cstring> #include <cstdlib> using namespace std; class Array { public: // ************************************************************************** // 类的四大函数:构造函数.拷贝构

数据结构动态数组

数组具有固定的容量,我们需要在初始化时指定数组的大小.有时它会非常不方便并可能造成浪费. 因此,大多数编程语言都提供内置的动态数组,它仍然是一个随机存取的列表数据结构,但大小是可变的.例如,在 C++ 中的 vector,以及在 Java 中的 ArrayList. #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { // 1. init

数据结构学习---堆栈的动态数组实现及链表实现

堆栈 [链表实现堆栈] 优点:可以无限增添元素,只要内存足够, 缺点:内存中存储位置不连续 typedef int ElementType; //只能向头部插入元素,因为如果在尾部插入,删除时,找不到上一个节点/ //因为链表是单向的 //所以 push pop 操作在头结点进行 class Stack{ public: Stack(){ S=(Stack*)malloc(sizeof(Stack)); //建立一个不存数据的头结点,方便增删节点 S->Next=NULL; sz=0; } bo

通用型动态数组的总结

基本数据结构之-通用型动态数组 动态数组的应用主要是对于长度未知的数组,先开辟一段空间来存储数据,当空间不够时,在开辟两倍的空间来存储数据 和普通数组的区别就是,我们可以不用关心数组的长度的问题,唯一需要关注的就是数据的类型是自定义数据类型还是基本数据类型,但是不论是基本数据类型还是自定义的数据类型,都需要自定义两个函数,这两个函数时遍历(打印)函数和比较函数,因为,在传递的是地址,没法再里面判断是什么类型,只能交给使用者去定义它的想关的函数, 先说基本的结构: 为了适应更多的数据类型,我们存储

队列的三种实现(静态数组、动态数组及指针)

本文有关栈的介绍部分参考自网站数据结构. 1. 队列  1.1 队列的定义 队列(Queue)是只允许在一端进行插入,而在另一端进行删除的运算受限的线性表. (1)允许删除的一端称为队头(Front). (2)允许插入的一端称为队尾(Rear). (3)当队列中没有元素时称为空队列. (4)队列亦称作先进先出(First In First Out)的线性表,简称为FIFO表.    队列的修改是依先进先出的原则进行的.新来的成员总是加入队尾(即不允许"加塞"),每次离开的成员总是队列头