资讯动态

C++动态数组类的封装实例

发布时间:2026/10/4 6:26:14 来源:尧图企业网站定制
C中的动态数组Dynamic Array是指动态分配的、可以根据需求动态增长占用内存的数组。为了实现一个动态数组类的封装我们需要考虑几个问题new/delete的使用、内存分配策略、类的四大函数构造函数、拷贝构造函数、拷贝赋值运算符、析构函数、运算符的重载。涉及到的知识点很多对此本文只做简单的介绍。一、内存分配策略当用new为一个动态数组申请一块内存时数组中的元素是连续存储的例如 vector和string。当向一个动态数组添加元素时如果没有空间容纳新元素不可能简单地将新元素添加到内存中的其他位置——因为元素必须连续存储。所以必须重新分配一块更大的内存空间将原来的元素从旧位置移动到新空间中然后添加新元素释放旧的内存空间。如果我们每添加一个新元素就执行一次这样的内存分配和释放操作效率将会慢到不行。为了避免上述的代价必须减少内存重新分配的次数。所以我们采取的策略是在不得不分配新的内存空间时分配比新的空间需求更大的内存空间通常为2倍。这样在相当一段时间内添加元素时就不用重新申请内存空间。注意只有当迫不得已时才可以分配新的内存空间。二、类的四大函数一个C类一般至少有四大函数即构造函数、拷贝构造函数、拷贝赋值运算符、析构函数。如果类未自己定义上述函数C编译器将为其合成4个默认的版本。但是往往编译器合成的并不是我们所期望的为此我们有必要自己定义它们。1.构造函数类的构造函数constructor用来初始化类对象的非static数据成员无论何时只要类的对象被创建就会执行构造函数。123456classFoo {public:Foo();// 构造函数Foo(string s);// ...};构造函数的名字和类名相同没有返回类型。类可以包含多个构造函数重载它们之间在参数数量或类型上需要有所区别。构造函数有一个初始化部分和一个函数体成员的初始化是在函数体执行之前完成的。2.拷贝构造函数如果一个构造函数的第一个参数是自身类类型的引用且任何额外参数都有默认值则此构造函数是拷贝构造函数copy constructor。123456classFoo {public:Foo();Foo(constFoo);// 拷贝构造函数// ...};拷贝构造函数定义了如何用一个对象初始化另一个同类型的对象。拷贝初始化通常使用拷贝构造函数来完成。拷贝初始化发生在下列情况中使用等号初始化一个变量将一个对象作为实参传递给一个非引用类型的形参从一个返回类型为非引用类型的函数返回一个对象用花括号列表初始化一个数组中的元素3.拷贝赋值运算符类的拷贝赋值运算符copy-assignment operator是一个名为operator的函数。类似于其他任何函数它也有一个返回类型和一个参数列表。123456classFoo {public:Foo();Foo operator(constFoo);// 赋值运算符// ...};拷贝赋值运算符定义了如何将一个对象赋值给另一个同类型的对象。赋值运算符是一个成员函数也是一个二元运算符其左侧运算对象就绑定到隐式的this指针右侧运算对象作为显式参数传递。注意为了与内置类型的赋值保持一致赋值运算符通常返回一个指向其左侧运算对象的引用。4.析构函数类的析构函数destructor用来释放类对象使用的资源并销毁类对象的非static数据成员无论何时只要一个对象被销毁就会自动执行析构函数。12345classFoo {public:~Foo();// 析构函数// ...};析构函数的名字由波浪号~加类名构成也没有返回类型。由于析构函数不接受参数因此它不能被重载。析构函数有一个函数体和一个析构部分销毁一个对象时首先执行析构函数体然后按初始化顺序的逆序销毁成员。三、运算符的重载重载的运算符是具有特殊名字的函数它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样重载的运算符也包含返回类型、参数列表、函数体比如拷贝赋值运算符。当我们定义重载的运算符时必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。有些运算符必须作为成员而另一些运算符作为普通函数比作为成员更好赋值、下标[ ]、调用( )和成员访问箭头-运算符必须是成员。复合赋值运算符一般来说应该是成员但并非必须这一点与赋值运算符略有不同。改变对象状态的运算符或者与给定类型密切相关的运算符如递增、递减、解引用运算符通常应该是成员。具有对称性的运算符可能转换任意一端的运算对象例如算术、相等性、关系和位运算符等因此它们通常应该是普通的非成员函数。当然除了赋值运算符之外我们还需要为动态数组定义下标运算符operator []。下标运算符必须是成员函数。为了让下标可以出现在赋值运算符的任意一端下标运算符函数通常返回所访问元素的引用。四、动态数组类的封装下面给出了动态数组DArray类的接口123456789101112131415161718192021222324252627classDArray{private:double*m_Data;// 存放数组的动态内存指针intm_Size;// 数组的元素个数intm_Max;// 预留给动态数组的内存大小private:voidInit();// 初始化voidFree();// 释放动态内存inlineboolInvalidateIndex(intnIndex);// 判断下标的合法性public:DArray();// 默认构造函数DArray(intnSize,doubledValue 0);// 构造函数设置数组大小默认值为dValueDArray(constDArray arr);// 拷贝构造函数DArray operator(constDArray arr);// 拷贝赋值运算符~DArray();// 析构函数voidPrint();// 输出显式所有数组元素的值intGetSize();// 获取数组的大小元素个数voidSetSize(intnSize);// 重新设置数组的大小若nSize小于原大小截断否则新元素置0doubleGetAt(intnIndex);// 获取指定位置元素voidSetAt(intnIndex,doubledValue);// 重置指定元素的值voidPushBack(doubledValue);// 追加一个新元素到数组末尾voidDeleteAt(intnIndex);// 删除指定位置地元素voidInsertAt(intnIndex,doubledValue);// 插入一个新的元素到数组中doubleoperator[](intnIndex)const;// 重载下标运算符[]};下面是实现方法123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220voidDArray::Init(){m_Size 0;// 默认情况下数组不包含元素m_Max 1;m_Data newdouble[m_Max];}voidDArray::Free(){delete[] m_Data;}boolDArray::InvalidateIndex(intnIndex){if(nIndex0 nIndexm_Size)returnfalse;elsereturntrue;}// 默认构造函数DArray::DArray(){Init();}// 构造函数DArray::DArray(intnSize,doubledValue){if(nSize 0)Init();else{m_Size nSize;m_Max nSize;m_Data newdouble[m_Max];for(inti0; inSize; i)m_Data[i]dValue;}}// 拷贝构造函数DArray::DArray(constDArray arr){m_Size arr.m_Size;/*复制常规成员*/m_Max arr.m_Max;m_Data newdouble[m_Max];/*复制指针指向的内容*/memcpy(m_Data, arr.m_Data, m_Size*sizeof(double));}// 拷贝赋值运算符DArray DArray::operator(constDArray arr){if(this arr)/*自赋值*/return*this;m_Size arr.m_Size;m_Max arr.m_Max;/* 先将右侧对象拷贝到临时对象中然后再销毁左侧对象*/double*m_Temp newdouble[m_Max];memcpy(m_Temp, arr.m_Data, m_Size*sizeof(double));delete[] m_Data;m_Data m_Temp;return*this;}// 析构函数DArray::~DArray(){Free();}// 打印数组voidDArray::Print(){if(m_Size 0){cout Error: The empty array cant be Printed. endl;exit(0);}else{for(inti0; im_Size; i)cout m_Data[i] ;cout endl;}}// 获取数组大小intDArray::GetSize(){returnm_Size;}// 重置数组大小voidDArray::SetSize(intnSize){if(nSize m_Size)/*截断*/{for(intinSize; im_Size; i)m_Data[i] 0;}if(m_SizenSize nSizem_Max)/*新增元素置0*/{for(intim_Size; inSize; i)m_Data[i] 0;}if(nSize m_Max)/*需要重新分配空间*/{m_Max nSize;double*temp newdouble[m_Max];memcpy(temp, m_Data, m_Size*sizeof(double));for(intim_Size; inSize; i)temp[i] 0;delete[] m_Data;m_Data temp;}m_Size nSize;/*设置数组大小*/}// 获取指定位置元素doubleDArray::GetAt(intnIndex){if(InvalidateIndex(nIndex)){cout Error: the index of GetAt is invalid! endl;exit(0);}returnm_Data[nIndex];}// 设置指定位置元素的值voidDArray::SetAt(intnIndex,doubledValue){if(InvalidateIndex(nIndex)){cout Error: the index of SetAt is invalid! endl;exit(0);}else{m_Data[nIndex] dValue;}}// 追加一个新元素到数组末尾voidDArray::PushBack(doubledValue){if(m_Size m_Max){m_Data[m_Size] dValue;}else{m_Max m_Max*2;double* temp newdouble[m_Max];memcpy(temp, m_Data, m_Size*sizeof(double));delete[] m_Data;m_Data temp;m_Data[m_Size] dValue;}m_Size;/*数组大小加1*/}// 从数组中删除一个元素voidDArray::DeleteAt(intnIndex){if(InvalidateIndex(nIndex)){cout Error: the index of DeleteAt is invalid. endl;exit(0);}else{for(intinIndex; im_Size; i)m_Data[i] m_Data[i1];m_Data[m_Size-1] 0;--m_Size;}}// 插入一个新元素到指定位置voidDArray::InsertAt(intnIndex,doubledValue){if(nIndex0 || nIndexm_Size){cout Error: the index of InsertAt is invalid! endl;exit(0);}if(m_Size m_Max)/* 未满插入 */{for(intim_Size-1; inIndex; --i)m_Data[i1] m_Data[i];m_Data[nIndex] dValue;}else/* 重新分配空间 */{m_Max m_Max*2;double* temp newdouble[m_Max];memcpy(temp, m_Data, m_Size*sizeof(double));delete[] m_Data;m_Data temp;for(intim_Size-1; inIndex; --i)m_Data[i1] m_Data[i];m_Data[nIndex] dValue;}m_Size;/* 数组大小加1 */}// 重载下标运算符[]doubleDArray::operator[](intnIndex)const{if(nIndex0 || nIndexm_Size){cout Error: the index in [] is invalid! endl;exit(0);}returnm_Data[nIndex];}经过简单的测试暂时还没有发现Bug。可能测试并不全面感兴趣的读者可以进一步测试并完善该程序。附String类的实现C 的一个常见面试题是让你实现一个 String 类限于时间不可能要求具备 std::string 的功能但至少要求能正确管理资源。如果你弄懂了上面DArray类的写法那么实现String类应该就不难了。因为面试官一般只是想考查你能不能正确地写出构造函数、析构函数、拷贝构造函数、拷贝赋值运算符以及、[ ]、、运算符重载等等。下面给出一个String类的接口你可以自己试试手实现一下

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑