C++实现动态数组功能

数组

数组是一种线性表数据结构。它用一组连续内存空间,来存储一组具有相同数据类型数据。

1.线性表:数据存储像一条线一样的结构,每个线性表上的数据最多只有前和后的两个方向,如数组、链表、队列、栈等都是这种结构,所以实现的数组的动态操作,其他结构也可轻易的类似实现。更重要的是,在这之后看源码就可大大降低难度。(博主自己看的是STL源码剖析)
2.非线性表:如二叉树、堆、图等。
3连续内存空间和相同数据类型:当数组作插入、删除操作时,为了保证数据的连续性,往往需要做大量的数据搬移工作,效率很低。

动态数组功能实现

1.数组初始化

考虑到扩容时数据搬移可能会发生的内存泄露,博主这里采用两只手的原则,即设定一个内存标志位 ItemsFlag 。当 ItemsFlag = 0,using preitems;当 ItemsFlag = 1,using items。下文扩容部分有具体实现。默认数组容量10。

enum { MAX = 10 };
explicit GenericArray(int ss = MAX);
template<class T>
GenericArray<T>::GenericArray(int ss) : capacity(ss),counts(0)
{
 itemsFlag = 0;
 preitems = new T[capacity];
 items = nullptr;
}

2.析构函数

释放内存。

template<class T>
GenericArray<T>::~GenericArray()
{
 if (preitems != nullptr)
 delete[]preitems;
 if (items != nullptr)
 delete[]items;
}

3.检查下标

检查要操作的下标是否在数组容量范围内。

template<class T>
bool GenericArray<T>::checkIndex(int index)
{
 if (index < 0 || index >= capacity)
 {
  int cap = capacity - 1;
  cout << "Out of the range! Please ensure the index be
  in 0 ~ " << cap << '\n';
  return false;
 }
 return true;
}

4.获取元素数目和容量、判断数组空和满

int count()const { return counts; }
int getCapacity()const { return capacity; }
bool isEmpty()const { return counts == 0; }
bool isFull()const { return counts >= capacity; }

5.取索引对应值、按索引修改值、打印输出、是否包含某值

template<class T>
T GenericArray<T>::get(int index)
{
 if (!itemsFlag)
 return preitems[index];
 else
 return items[index];
}
void GenericArray<T>::set(int index, T elem)
{
 if(checkIndex(index))
 {
 if (!itemsFlag)
 preitems[index] = elem;
 else
 items[index] = elem;
 return;
 }
}
template<class T>
void GenericArray<T>::printArray()const
{
 for (int i = 0; i < counts; i++)
 if (!itemsFlag)
  cout << preitems[i] << '\t';
 else
  cout << items[i] << '\t';
 cout << '\n';
 return;
}
template<class T>
bool GenericArray<T>::contains(T arr)
{
 for (int i = counts - 1; i >= 0; i--)
 if (!itemsFlag)
 {
  if (arr == preitems[i])
  return true;
 }
 else
 {
  if (arr == items[i])
  return true;
 }
 return false;
}

6.查找某值下标、删除某值

查找某值的下标时,要考虑到该值在数组中是否重复,所以博主用了一个结构体 findArrIndex 来存储该值重复的次数和对应的下标。

struct findArrIndex
{
 int numIndex;
 int *findIndex;
};
template<class T>
void GenericArray<T>::find(T arr, findArrIndex *ps)
{
 ps->findIndex = new int[counts];
 ps->numIndex = 0;
 for (int i = 0, j = 0; i < counts; i++, j++)
 if (!itemsFlag)
 {
  if (arr == preitems[i])
  {
  (ps->findIndex)[j] = i;
  (*ps).numIndex++;
  cout << i << '\t';
  }
 }
 else
  if (arr == items[i])
  {
  (ps->findIndex)[j] = i;
  (*ps).numIndex++;
  cout << i << '\t';
  }
 cout << '\n';
 return;
}
template<class T>
void GenericArray<T>::removeElement(findArrIndex *ps)
{
 for (int i = ps->numIndex; i > 0; i--)
 remove((ps->findIndex)[i - 1]);
 delete[](ps->findIndex);
}
template<class T>
void GenericArray<T>::set(int index, T elem)
{
 if(checkIndex(index))
 {
 if (!itemsFlag)
 preitems[index] = elem;
 else
 items[index] = elem;
 return;
 }
}

7.扩容

添加数据操作时需判断数组容量是否足够,若不够需考虑扩容。

template<class T>
void GenericArray<T>::renewCapacity()
{
 cout << "The array's capacity is small! Renew capacity.\n";
 if (capacity < 1000)
 capacity = capacity << 1;
 else
 capacity = capacity >> 1 + capacity;
 if (!itemsFlag)
 {
 itemsFlag = 1;
 items = new T[capacity];
 for (int i = 0; i<counts; i++)
  *(items + i) = *(preitems + i);
  //items[i]=proitems[i];
 //cout << items << '\n';
 //cout << preitems << '\n';
 delete[]preitems;
 preitems = nullptr;
 }
 else
 {
 itemsFlag = 0;
 preitems = new T[capacity];
 for (int i = 0; i<counts; i++)
  *(preitems + i) = *(items + i);
 delete[]items;
 items = nullptr;
 }
}

8.添加数据:数组添加数据包括按索引下标插值、数组头插值、数组尾插值。实质上后两种都可以通过调用按索引下标插值函数实现。前文也提到过,数组添加操作中复杂的是大量的数据搬移工作:将某个元素按索引下标插入到数组第k个位置,需要将k ~ n部分的元素向后搬移一位,然后插入元素,更新元素数目。若插入到数组尾,时间复杂度O(1);插入到数组头,时间复杂度O(n);插入的平均时间复杂度为(1+2+…+n)/n = O(n)。
另外,还有一个优化问题:若数组是无序数组,则插入时不需要搬移数据:若将某个元素插入到数组第k个位置,首先将该位置的元素移动到数组末尾,然后将待插入元素插入到第k个位置,时间复杂度O(1)。

template<class T>
void GenericArray<T>::add(int index, T elem)
{
 if (isFull())
 {
 cout << "Array is full!" << '\n';
 renewCapacity();
 }
 if (checkIndex(index))
 if(!itemsFlag)
 {
  for (int i = counts; i > index; i--)
  preitems[i] = preitems[i - 1];
  preitems[index] = elem;
 }
 else
 {
  for (int i = counts; i > index; i--)
  items[i] = items[i - 1];
  items[index] = elem;
 }
 counts++;
 return;
}

template<class T>
void GenericArray<T>::addFirst(T elem)
{
 add(0, elem);
}

template<class T>
void GenericArray<T>::addLast(T elem)
{
 add(counts, elem);
}

9.删除数据:数组删除数据包括按索引下标删除、数组头删除、数组尾删除。实质上后两种都可以通过调用按索引下标删除函数实现。与前文类似,数组删除操作中复杂的也是大量的数据搬移工作:按索引下标将某个元素删除,需要将k+1 ~ n部分的元素向前搬移一位,更新元素数目。若删除数组尾,直接元素数目减一即可,时间复杂度O(1);删除数组头,时间复杂度O(n);删除的平均时间复杂度(1+2+…+n)/n = O(n)。
另外,有一个优化问题:如果想多次删除数组中的值,可以先对要删除的值做好标记,做完标记后一次删除,这样就大大减少了搬移的次数。

template<class T>
T GenericArray<T>::remove(int index)
{
 if (!isEmpty())
 {
 if (checkIndex(index))
 {
  if (!itemsFlag)
  {
  T temp = preitems[index];
  for (int i = index+1; i < counts; i++)
   preitems[i - 1] = preitems[i];
  counts--;
  return temp;
  }
  else
  {
  T temp = items[index];
  for (int i = index + 1; i < counts; i++)
   items[i - 1] = items[i];
  counts--;
  return temp;
  }
 }
 }
 else
 {
 cout << "Array is empty!" << '\n';
 return -1;
 }
}
template<class T>
T GenericArray<T>::removeFirst()
{
 return remove(0);
}
template<class T>
T GenericArray<T>::removeLast()
{
 return remove(counts - 1);
}

好啦,基本上就这么多了。

最后总结一下,多看源码还是很重要的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android Java调用自己C++类库的实例讲解

    Android Java 如何调用自己的 C++ 的类库 下面以 Java 调用 C++ 的加法运算函数为例,做简单说明. (使用 Android Studio 3 编译) 首先编译 c++ 类库 创建独立目录存放 c++ 文件,例如 "app/src/main/cpp/add.cpp",内容如下 #include <jni.h> extern "C" JNIEXPORT jint JNICALL Java_com_example_liyi_demo_U

  • C++实践Time类中的运算符重载参考方法

    [项目-Time类中的运算符重载] 实现Time类中的运算符重载. class CTime { private: unsigned short int hour; // 时 unsigned short int minute; // 分 unsigned short int second; // 秒 public: CTime(int h=0,int m=0,int s=0); void setTime(int h,int m,int s); void display(); //二目的比较运算符

  • C++实践数组作数据成员的参考

    [项目 - 数组作数据成员]下面是设计好的一个工资类(Salary): class Salary { public: void set_salarys( );//输入职工工资(输入-1标志着工资输入结束),工资保存到salary数组中,实际人数保存到number中: void add_salarys(int x); //给每个人涨x元工资 void sort_salarys(); //对工资由大到小排序 void show_salarys( ); //显示工资信息 private: double

  • C++实践分数类中运算符重载的方法参考

    [项目-分数类中的运算符重载] (1)实现分数类中的运算符重载,在分数类中可以完成分数的加减乘除(运算后再化简).比较(6种关系)的运算. class CFraction { private: int nume; // 分子 int deno; // 分母 public: //构造函数及运算符重载的函数声明 }; //重载函数的实现及用于测试的main()函数 (2)在(1)的基础上,实现分数类中的对象和整型数的四则运算.分数类中的对象可以和整型数进行四则运算,且运算符合交换律.例如:CFrac

  • 剑指offer之C++语言实现链表(两种删除节点方式)

    1 问题 用C++语言实现链表 2 代码实现 #include <iostream> #include <stdlib.h> using namespace std; class List { public: List(); ~List(); List* createNode(int value);//创建节点 bool insertNode(List *node);//插入节点 void printList();//打印节点 bool deleteNode(List *node)

  • C++实践数组类运算的实现参考

    [项目-数组类运算的实现] 设计数组类Array,为了实现测试函数中要求的功能,请补足相关的函数(构造.析构函数)和运算符重载的函数. 实现策略提示:可以将测试函数中的语句加上注释,取消一句的注释,增加相应的函数,以渐增地实现所有的功能,避免全盘考虑带来的困难. class Array { private: int* list; //用于存放动态分配的数组内存首地址 int size; //数组大小(元素个数) public: //成员函数声明 }; //要求测试函数能够运行出正确.合理的结果:

  • 一张图总结C++中关于指针的那些事

    指向对象的指针,指向数据成员的指针,指向成员函数的指针: 数组即指针,数组的指针,指针数组: 指向函数的指针,指向类的成员函数的指针,指针作为函数参数,指针函数: 指针的指针,指向数组的指针:常指针,指向常对象的指针: -- 大哥,这些都是什么鬼?! 用下面一张图全概括.用例子对照图示,有感觉,就用术语将概念大声地念出来,动员所有的感官参与,搞清楚这些,不是事. 图如下: 总结 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持.如果你想

  • C++小知识:C/C++中不要按值传递数组

    错误的代码: ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) { memcpy( mat, src, sizeof( src ) ); } 说明: 有时候程序员会忘记 C/C++ 里数组不能按值传递给函数.当你试图这样做时,是数组的指针(第一个元素的地址)而不是整个数组被传递.我们还应该记住,方括号中的数字没有任何意义.它们仅仅是程序员所做的标志,记录了传递数组的『假定』大小.事实上,你也可以传递一个大小完全不同的数组.例如,下面的代码就会成功编译

  • C++实践排序函数模板项目的参考方法

    [项目-排序函数模板] 已知 void Sort(int a[],int size); void Sort(double a[],int size); 是一个函数模板的两个实例,其功能是将数组a中的前size个元素按从小到大顺序排列.试设计这个函数模板. 参考解答: #include<iostream> using namespace std; template<class T> void Sort(T set[],int n) { int i,j; T temp; for(i=1

  • C++二维数组中数组元素存储地址的计算疑问讲解

    关于二维数组中数组元素的存储地址,有同学问出了个好问题. 在我的课件中,出现了下面的讲解: a[i][j]的地址是p+(i*col+j)*d(d是数组元素所占的字节数). 同学编程序进行验证,出问题了: 地球停止转动了也必须先赞这种学习方式! 同学问:"老师,第一张图的4,我怎么觉得不对呢?第二张图我没4,结果好像也对,这里面差在哪呢?" 我的答复:"两个都对." 第一张图在讲原理,是我们在人脑里面要以"字节"为单位计算,p为首地址,单位是字节,

随机推荐