动态数组模板类

本文需要说明的是一种动态数组模板类(Array),可用于自定义的需要连续空间的容器,能有效得利用分配的空间,提供较高效的数组对象操作,和使用引用计数减少内存复制拷贝。

Array与std::vector或std::array不同。Array存储连续的对象,并且在对象内存前开辟一份数组

描述块对数组进行描述。Array存储的数据数量是动态的,可以通过setLength调整,同时可以使用+、

+=运算符向数组中追加数据。多个数组实例允许使用同一份数据内容,并且不会重复创建内容,这是该数

组类最重要的特性以及主要涉及目的,即当从一个Array对象构造一个新的Array对象或者将他赋值给其

他Array时不会产生新的内存申请和拷贝。多个Array的数据指向相同的内存区,并且通过引用计数管理

该共同的内存区。当所有指向该内存区的Array对象都被销毁后,该内存会被释放。对一个空的Array追

加内容时,他会自动开辟新的内存区用于存储新数据。对已经包含内容的Array追加或改写(通过[]下标

运算)时,如果数组包含的内区还被其他数组引用,则会自动解除对原有内存区的引用并创建新的内存区

存储新的数据。

使用注意:频繁的对数据进行追加操作是十分低效的,在已经确定要追加数量的情况下先调用setLength或者

setCapacity设置最终长度或容量再进行追加将提高性能。

使用注意:Array设计只用于提供数据存取,因此数组中存放的内容不会被调用构造函数和析构函数。

具体的代码实现如下:

template <typename T>
class Array
{
public:
	typedef T TYPE;
	struct ArrayDesc
	{
		volatile int refer;
		volatile unsigned int capacity;
		volatile unsigned int length;
	};
protected:
	T* m_ptr;
public:
	Array()
	{
		m_ptr = NULL;
	}
	Array(const Array<T> &another)
	{
		m_ptr = NULL;
		operator = (another);
	}
	Array(const T* ptr, size_t length)
	{
		m_ptr = NULL;
		setLength(length);
		memcpy(m_ptr, ptr, length * sizeof(*ptr));
	}
	Array(size_t length)
	{
		m_ptr = NULL;
		setLength(length);
	}
	~Array()
	{
		setLength(0);
	}
	inline Array<T>& operator = (const Array<T> &another)
	{
		if (m_ptr != another.m_ptr)//检查不是同一个对象
		{
			setLength(0);//释放本对象的内存
			if (another.m_ptr)
			{
				ArrayDesc *pDesc = ((ArrayDesc*)another.m_ptr) - 1;//获取另一对象的数组描述
				lock_inc(pDesc->refer);//原子操作增加数组描述的引用计数
				m_ptr = (T*)(pDesc + 1);//把另一对象的数组指针复制到本对象的数组指针
			}
		}
		return *this;
	}
	inline Array<T> operator + (const Array<T> &another)
	{
		Array<T> result;
		if (m_ptr || another.m_ptr)
		{
			size_t myCount = length();
			size_t anotherCount = another.length();
			result.setLength(myCount + anotherCount);//预留目标的内存空间
			T* destPtr = result.m_ptr;
			if (myCount > 0)
			{
				memcpy(destPtr, m_ptr, myCount * sizeof(*m_ptr));//拷贝本对象的数据内存
				destPtr += myCount;
			}
			if (anotherCount > 0)
			{
				memcpy(destPtr, another.m_ptr, anotherCount * sizeof(*m_ptr));//拷贝另一个对象的数据内存
				destPtr += myCount;
			}
		}
		return result;//拷贝复制结果对象
	}
	inline Array<T> operator + (const T &another)//追加一个数据对象,返回的是新的数据对象的拷贝
	{
		size_t myCount = length();
		Array<T> result(myCount + 1);//预留结果对象的内存空间
		if (myCount > 0)
			memcpy(result.m_ptr, m_ptr, myCount * sizeof(*m_ptr));//拷贝本对象的数据到目标内存
		result.m_ptr[myCount] = another;//拷贝追加的数据对象
		return result;
	}
	inline Array<T>& operator += (const Array<T> &another)//追加一个动态数组对象
	{
		size_t len = another.length();
		if (len > 0 )
			cat(another.m_ptr, another.length());
		return *this;
	}
	inline Array<T>& operator += (const T &another)//追加一个数据对象,返回的是本对象
	{
		size_t myCount = length();
		setLength(myCount + 1);
		m_ptr[myCount] = another;
		return *this;
	}
	inline T& operator [] (size_t index)//获取动态数组的数据索引上的数据对象的引用
	{
		if (!m_ptr)
			return *(T*)NULL;
		ArrayDesc *pDesc = ((ArrayDesc*)m_ptr) - 1;
		assert(index < pDesc->length);//检查索引不超出动态数组对象的长度
		duplicate();
		return m_ptr[index];
	}
	inline const T& operator [] (size_t index) const //获取动态数组的数据索引上的数据对象的引用(cosnt类型)
	{
		if (!m_ptr)
			return *(T*)NULL;
		ArrayDesc * const pDesc = ((ArrayDesc* const)m_ptr) - 1;
		assert(index < pDesc->length);//检查索引不超出动态数组对象的长度
		return m_ptr[index];
	}
	inline bool operator == (const Array<T> &another) const//判断数据对象是否是同一个对象
	{
		return m_ptr == another.m_ptr;
	}
	inline bool operator != (const Array<T> &another) const//判断数据对象是否不是同一个对象
	{
		return m_ptr != another.m_ptr;
	}

	inline const T* ptr() const//获取只读的内容数据指针
	{
		return m_ptr;
	}

	inline T* own_ptr()//获取可写的内容数据指针
	{
		duplicate();//复制本对象的数据内容到一个新的对象上,本对象就成了一个新的对象,旧的对象的数据内存需要在别处有管理
		return m_ptr;
	}
	inline size_t length() const//获取本对象的数据数组长度
	{
		if (!m_ptr)
			return 0;
		ArrayDesc * const pDesc = ((ArrayDesc* const)m_ptr) - 1;
		return pDesc->length;
	}
	inline Array<T>& cat(const T* ptr, size_t count)//追加一个或连续的多个数据对象到本数组对象的末尾
	{
		return insert(length(), ptr, count);
	}
	inline Array<T>& insert(size_t index, const T* ptr, size_t count)//在指定索引位置插入一个或者连续的多个数据对象
	{
		if (count > 0)
		{
			ArrayDesc *pMyDesc = m_ptr ? ((ArrayDesc*)m_ptr) - 1 : NULL;
			size_t myCount = pMyDesc ? pMyDesc->length : 0;//本数组的数据对象数

			assert(index <= myCount);

			//判断目标是否是来自自己内存区域的数据,如果是来自自己内存区域的,那么可能
			//在setLength后ptr指针失效,此时需要重新确定ptr的位置。
			bool isMySelf = false;
			size_t inMyPos = 0;

			if (pMyDesc && (ptr >= m_ptr) && (ptr <= (m_ptr + pMyDesc->capacity)))
			{
				isMySelf = true;//目标对象的起始地址在本对象数组的内存区域内
				inMyPos = ptr - m_ptr;//在内存区域的索引位置
				if (inMyPos >= index)
					inMyPos += count;//重新定位需要拷贝的对象的起始位置ptr(跟setLength的实现有关)
			}

			setLength(myCount + count);//预留本对象的数据内存大小
			if (index < myCount)
				memmove(&m_ptr[index + count], &m_ptr[index], sizeof(*ptr) * (myCount - index));//拷贝会被覆盖的数据内存到末尾。当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确
			if (isMySelf)
				memcpy(&m_ptr[index], &m_ptr[inMyPos], count * sizeof(*m_ptr));//拷贝需要拷贝的数据对象到指定的位置
			else memcpy(&m_ptr[index], ptr, count * sizeof(*m_ptr));
		}
		return *this;
	}
	inline Array<T>& remove(size_t index, size_t count)
	{
		if (count > 0)
		{
			duplicate();//拷贝本数组对象到新的内存

			ArrayDesc *pMyDesc = m_ptr ? ((ArrayDesc*)m_ptr) - 1 : NULL;
			size_t myCount = pMyDesc ? pMyDesc->length : 0;

			assert(index < myCount);
			if (count > myCount - index)
				count = myCount - index;
			size_t copyCount = myCount - index - count;
			if (copyCount > 0)
				memcpy(&m_ptr[index], &m_ptr[index + count], sizeof(*m_ptr) * copyCount);
			setLength(myCount - count);
		}
		return *this;
	}
	inline Array<T>& setLength(const size_t length)
	{
		assert(length >= 0);

		ArrayDesc *pDesc = NULL;
		if (m_ptr) pDesc = ((ArrayDesc*)m_ptr) - 1;

		if (length == 0)//回收本数组对象
		{
			if (pDesc)
			{
				if (lock_dec(pDesc->refer) <= 0)//引用计数为0,释放本数组对象的内存
					free(pDesc);
				m_ptr = NULL;
			}
		}
		else if (pDesc)
		{
			if (length != pDesc->length)
			{
				if (pDesc->refer > 1)//引用计数大于1时返回的是内存拷贝的数组对象
				{
					lock_dec(pDesc->refer);//减少本对象的引用计数
					ArrayDesc *pNewDesc = (ArrayDesc*)calloc(1, sizeof(*pDesc) + sizeof(*m_ptr) * (length + 1));//分配新的内存对象,calloc分配连续内存,初始化已分配的内存为0,返回数组
					pNewDesc->capacity = (unsigned int)length;
					pNewDesc->length = (unsigned int)length;
					pNewDesc->refer = 1;
					m_ptr = (T*)(pNewDesc + 1);//把本数组对象重新定位到新的数组对象内存
					size_t lengthMin = pDesc->length < length ? pDesc->length : length;
					memcpy(m_ptr, pDesc + 1, lengthMin * sizeof(*m_ptr));//拷贝本数组对象内存到新的对象的内存
				}
				else //引用计数为1是返回的就是本对象,有效长度和内存大小根据需求调整
				{
					if (length >= pDesc->capacity)//需要的内存大小大于本数组对象的包含的内存大小
					{
						pDesc = (ArrayDesc*)realloc(pDesc, sizeof(*pDesc) + sizeof(*m_ptr) * (length + 1));
						m_ptr = (T*)(pDesc + 1);//重新定位本数组对象的数据指针到新的内存地址
						memset(&m_ptr[pDesc->length], 0, sizeof(*m_ptr) * (length - pDesc->capacity + 1));//把数组对象末尾的无效对象初始化
						pDesc->capacity = (unsigned int)length;//内存大小
						pDesc->length = (unsigned int)length;//数组大小
					}
					else pDesc->length = (unsigned int)length;//截短本数组对象的有效长度
				}
				memset(&m_ptr[length], 0, sizeof(*m_ptr));//数组有效长度后面的那个对象初始化为0(在数组对象为字符串时有作用)
			}

		}
		else //数组数据对象指针没有内存,则分配新的内存并把本数组对象数据指针重新定位到新的内存上
		{
			ArrayDesc *pNewDesc = (ArrayDesc*)calloc(1, sizeof(*pDesc) + sizeof(*m_ptr) * (length + 1));
			pNewDesc->capacity = (unsigned int)length;
			pNewDesc->length = (unsigned int)length;
			pNewDesc->refer = 1;
			m_ptr = (T*)(pNewDesc + 1);
		}
		return *this;
	}
	inline size_t capacity() const
	{
		if (m_ptr)
		{
			ArrayDesc *pDesc = ((ArrayDesc*)m_ptr) - 1;
			return pDesc->capacity;
		}
		return 0;
	}
	inline Array<T>& setCapacity(size_t capacity)
	{
		size_t myCount = length();
		//容量必须大于数量。如果要进行清空操作,则请调用setLength(0)
		if (capacity > myCount)
		{
			setLength(capacity);
			ArrayDesc *pDesc = ((ArrayDesc*)m_ptr) - 1;
			pDesc->length = myCount;
		}
		return *this;
	}
protected:
	inline void duplicate()//拷贝复制本数组对象到新的内存
	{
		if (m_ptr)
		{
			ArrayDesc *pDesc = ((ArrayDesc*)m_ptr) - 1;//记录本数组对象的描述块指针
			if (pDesc->refer > 1)
			{
				ArrayDesc *pNewDesc = (ArrayDesc*)malloc(sizeof(*pDesc) + sizeof(*m_ptr) * (pDesc->capacity + 1));//多预留了一个数据对象的大小
				pNewDesc->capacity = pDesc->capacity;
				pNewDesc->length = pDesc->length;
				pNewDesc->refer = 1;
				m_ptr = (T*)(pNewDesc + 1);//把本数组对象重新定位到新的内存
				memcpy( m_ptr,pDesc + 1,, (pDesc->length + 1) * sizeof(*m_ptr));//拷贝本数组对象的原来的数据内存到新的数组对象内存

				lock_dec(pDesc->refer);
			}
		}
	}
};
时间: 2024-08-01 15:52:45

动态数组模板类的相关文章

STL模板类--数据结构与算法

STL提供了一些模板类,实现了<数据结构>中的一些数据结构类型 在写代码时用到栈,队列等数据结构时可以充分利用STL模板类,会发现特别好用. 想起C语言中用数组实现栈和队列,简直就是噩梦. C++是世界上最好的语言...(just kidding !!!) 顺序容器:动态数组vector;deque链表list; 关联容器:set/multiset有序值:map/multimap有序键值对 一. 动态数组vector类 1.定义:#include<vector>  std::vec

用最复杂的方式学会数组(Python实现动态数组)

Python序列类型 在本博客中,我们将学习探讨Python的各种"序列"类,内置的三大常用数据结构--列表类(list).元组类(tuple)和字符串类(str). 不知道你发现没有,这些类都有一个很明显的共性,都可以用来保存多个数据元素,最主要的功能是:每个类都支持下标(索引)访问该序列的元素,比如使用语法 Seq[i].其实上面每个类都是使用 数组 这种简单的数据结构表示. 但是熟悉Python的读者可能知道这3种数据结构又有一些不同:比如元组和字符串是不能修改的,列表可以修改.

一个数组类【模板类】

这学期的大作业感觉挺简单的,就是写一个模板类MyList,实现一些Python中的list的操作(类似于c++中的vector,但是不支持迭代器).这些功能都很简单,唯一麻烦的就是模板类特别烦,特别是友元函数,首先要声明这个类,然后声明是函数的声明,然后是类中友元函数的声明,最后是实现.友元函数的声明还有一个问题就是声明时在函数名后面要加上一个<>就像这样: friend void Qsort<>(T a[],int low,int high,bool less); 还有一个要注意

动态顺序表 与 双向链表的模板类

//////////////////////////////////////////////////////////////////////// /////////////////////泛型编程之动态顺序表的模板///////////////////////// //////////////////////////////////////////////////////////////////////// #include<iostream> #include<string> u

动态数组类

在动态数组类中,通过类的成员函数访问数组元素,可以在每次访问之前检查一下下标是否越界,使得数组下标越界的错误能够及早被发现.这种检查,可以通过C++的assert来进行.assert的含义是"断言",它是标准C++的cassert头文件中定义的一个宏,用来判断一个条件表达式的值是否为true,如果不为true,则程序会中止,并且报告出错误,这样就很容易将错误定位. 以下是一个简单的动态数组类示例 #include<iostream> #include<cassert&

爪哇国新游记之十五----泛型动态数组类

import java.lang.reflect.Array; /** * 泛型动态数组类 * */ public class DynamicArray<T extends Object>{ private T[] arr; private Class<T> type; private int currCount; private static final int InitSize=2; public DynamicArray(Class<T> type){ this.

C++中使用模板,new创建2维动态数组

1 // 使用模板和new创建2维动态数组 2 3 #include<iostream> 4 #include<exception> 5 #include<cstdlib> 6 #include<iomanip> 7 using namespace std; 8 9 template<class Type> 10 bool Make2DArray(Type **&x,int rows,int cols) 11 { 12 int i; 13

封装动态数组类Array

功能: 1.增.删.改.查 2.扩容.缩容 3.复杂度分析 4.均摊复杂度 5.复杂度震荡 分析动态数组的时间复杂度: 分析resize的时间复杂度: public class Array<E> { private E[] data; private int size; // 构造函数,传入数组的容量capacity构造Array public Array(int capacity){ data = (E[])new Object[capacity]; size = 0; } // 无参数的构

C++ Primer 笔记——动态数组

1.动态数组定义时也需要指明数组的大小,但是可以不是常量. int i; int arr[i]; // 错误,数组的大小必须为常量 int *p = new int[i]; // 正确,大小不必是常量 2.虽然我们通常称 new T[ ] 分配的内存为动态数组,但我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针.所以不能对动态数组调用begin或end,也不能用for语句来处理动态数组中的元素. 3.默认情况下,new分配的对象,不管是单个分配的还是数组中的,都是默认初始化的.我们