动态数组(Dynamic Array)是指动态分配的、可以根据需求动态增长占用内存的数组。为了实现一个动态数组类的封装,我们需要考虑几个问题:new/delete的使用、内存分配策略、类的四大函数(构造函数、拷贝构造函数、拷贝赋值运算符、析构函数)、运算符的重载。涉及到的知识点很多,鉴于本人水平有限,在这里只做简单的介绍。

一、内存分配策略

当用new为一个动态数组申请一块内存时,数组中的元素是连续存储的,例如 vector和string。当向一个动态数组添加元素时,如果没有空间容纳新元素,不可能简单地将新元素添加到内存中的其他位置——因为元素必须连续存储。所以必须重新分配一块更大的内存空间,将原来的元素从旧位置移动到新空间中,然后添加新元素,释放旧的内存空间。如果我们每添加一个新元素,就执行一次这样的内存分配和释放操作,效率将会慢到不行。

为了避免上述的代价,必须减少内存重新分配的次数。所以我们采取的策略是:在不得不分配新的内存空间时,分配比新的空间需求更大的内存空间(通常为2倍)。这样,在相当一段时间内,添加元素时就不用重新申请内存空间。注意,只有当迫不得已时才可以分配新的内存空间。

二、类的四大函数

一个C++类一般至少有四大函数,即构造函数、拷贝构造函数、拷贝赋值运算符、析构函数。如果类未自己定义上述函数,C++编译器将为其合成4个默认的版本。但是往往编译器合成的并不是我们所期望的,为此我们有必要自己定义它们。

构造函数

类的构造函数(constructor)用来初始化类对象的非static数据成员,无论何时只要类的对象被创建,就会执行构造函数。

class Foo {
public:
Foo(); // 构造函数
Foo(string &s);
// ...
};

构造函数的名字和类名相同,没有返回类型。类可以包含多个构造函数(重载),它们之间在参数数量或类型上需要有所区别。构造函数有一个初始化部分和一个函数体,成员的初始化是在函数体执行之前完成的。

拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数(copy constructor)

class Foo {
public:
Foo();
Foo(const Foo&); // 拷贝构造函数
// ...
};

拷贝构造函数定义了如何用一个对象初始化另一个同类型的对象。拷贝初始化通常使用拷贝构造函数来完成。拷贝初始化发生在下列情况中:

  • 使用等号(=)初始化一个变量
  • 将一个对象作为实参传递给一个非引用类型的形参
  • 从一个返回类型为非引用类型的函数返回一个对象
  • 用花括号列表初始化一个数组中的元素

拷贝赋值运算符

类的拷贝赋值运算符(copy-assignment operator)是一个名为operator=的函数。类似于其他任何函数,它也有一个返回类型和一个参数列表。

class Foo {
public:
Foo();
Foo& operator=(const Foo&); // 赋值运算符
// ...
};

拷贝赋值运算符定义了如何将一个对象赋值给另一个同类型的对象。赋值运算符是一个成员函数也是一个二元运算符,其左侧运算对象就绑定到隐式的this指针,右侧运算对象作为显式参数传递。注意:为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用。

析构函数

类的析构函数(destructor)用来释放类对象使用的资源并销毁类对象的非static数据成员,无论何时只要一个对象被销毁,就会自动执行析构函数。

class Foo {
public:
~Foo(); // 析构函数
// ...
};

析构函数的名字由波浪号(~)加类名构成,也没有返回类型。由于析构函数不接受参数,因此它不能被重载。析构函数有一个函数体和一个析构部分,销毁一个对象时,首先执行析构函数体,然后按初始化顺序的逆序销毁成员。

三、运算符的重载

重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样,重载的运算符也包含返回类型、参数列表、函数体,比如拷贝赋值运算符。

当我们定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。有些运算符必须作为成员,而另一些运算符作为普通函数比作为成员更好:

  • 赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。
  • 复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。
  • 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减、解引用运算符,通常应该是成员。
  • 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。

当然,除了赋值运算符之外,我们还需要为动态数组定义下标运算符operator []。下标运算符必须是成员函数。为了让下标可以出现在赋值运算符的任意一端,下标运算符函数通常返回所访问元素的引用。

四、动态数组类的封装

下面给出了动态数组DArray类的接口:

class DArray
{
private:
double *m_Data; // 存放数组的动态内存指针
int m_Size; // 数组的元素个数
int m_Max; // 预留给动态数组的内存大小
private:
void Init(); // 初始化
void Free(); // 释放动态内存
inline bool InvalidateIndex(int nIndex); // 判断下标的合法性
public:
DArray(); // 默认构造函数
DArray(int nSize, double dValue = 0); // 构造函数,设置数组大小,默认值为dValue
DArray(const DArray& arr); // 拷贝构造函数
DArray& operator=(const DArray& arr); // 拷贝赋值运算符
~DArray(); // 析构函数 void Print(); // 输出显式所有数组元素的值
int GetSize(); // 获取数组的大小(元素个数)
void SetSize(int nSize); // 重新设置数组的大小,若nSize小于原大小,截断;否则,新元素置0
double GetAt(int nIndex); // 获取指定位置元素
void SetAt(int nIndex,double dValue); // 重置指定元素的值
void PushBack(double dValue); // 追加一个新元素到数组末尾
void DeleteAt(int nIndex); // 删除指定位置地元素
void InsertAt(int nIndex, double dValue); // 插入一个新的元素到数组中
double operator[](int nIndex) const; // 重载下标运算符[]
};

下面是我的实现:

void DArray::Init()
{
m_Size = 0; // 默认情况下数组不包含元素
m_Max = 1;
m_Data = new double[m_Max];
} void DArray::Free()
{
delete [] m_Data;
} bool DArray::InvalidateIndex(int nIndex)
{
if(nIndex>=0 && nIndex<m_Size)
return false;
else
return true;
} // 默认构造函数
DArray::DArray()
{
Init();
} // 构造函数
DArray::DArray(int nSize, double dValue)
{
if(nSize == 0)
Init();
else
{
m_Size = nSize;
m_Max = nSize;
m_Data = new double[m_Max];
for(int i=0; i<nSize; ++i)
m_Data[i]=dValue;
}
} // 拷贝构造函数
DArray::DArray(const DArray& arr)
{
m_Size = arr.m_Size; /*复制常规成员*/
m_Max = arr.m_Max;
m_Data = new double[m_Max]; /*复制指针指向的内容*/
memcpy(m_Data, arr.m_Data, m_Size*sizeof(double));
} // 拷贝赋值运算符
DArray& DArray::operator=(const DArray& arr)
{
if(this == &arr) /*自赋值*/
return *this;
m_Size = arr.m_Size;
m_Max = arr.m_Max;
/* 先将右侧对象拷贝到临时对象中,然后再销毁左侧对象*/
double *m_Temp = new double[m_Max];
memcpy(m_Temp, arr.m_Data, m_Size*sizeof(double));
delete [] m_Data;
m_Data = m_Temp; return *this;
} // 析构函数
DArray::~DArray()
{
Free();
} // 打印数组
void DArray::Print()
{
if(m_Size == 0)
{
cout << "Error: The empty array can't be Printed." << endl;
exit(0);
}
else
{
for(int i=0; i<m_Size; ++i)
cout << m_Data[i] << " ";
cout << endl;
}
} // 获取数组大小
int DArray::GetSize()
{
return m_Size;
} // 重置数组大小
void DArray::SetSize(int nSize)
{
if(nSize < m_Size) /*截断*/
{
for(int i=nSize; i<m_Size; ++i)
m_Data[i] = 0;
}
if(m_Size<=nSize && nSize<=m_Max) /*新增元素置0*/
{
for(int i=m_Size; i<nSize; ++i)
m_Data[i] = 0;
}
if(nSize > m_Max) /*需要重新分配空间*/
{
m_Max = nSize;
double *temp = new double[m_Max];
memcpy(temp, m_Data, m_Size*sizeof(double));
for(int i=m_Size; i<nSize; ++i)
temp[i] = 0;
delete [] m_Data;
m_Data = temp;
}
m_Size = nSize; /*设置数组大小*/
} // 获取指定位置元素
double DArray::GetAt(int nIndex)
{
if(InvalidateIndex(nIndex))
{
cout << "Error: the index of GetAt is invalid!" << endl;
exit(0);
}
return m_Data[nIndex];
} // 设置指定位置元素的值
void DArray::SetAt(int nIndex, double dValue)
{
if(InvalidateIndex(nIndex))
{
cout << "Error: the index of SetAt is invalid!" << endl;
exit(0);
}
else
{
m_Data[nIndex] = dValue;
}
} // 追加一个新元素到数组末尾
void DArray::PushBack(double dValue)
{
if(m_Size < m_Max)
{
m_Data[m_Size] = dValue;
}
else
{
m_Max = m_Max*2;
double* temp = new double[m_Max];
memcpy(temp, m_Data, m_Size*sizeof(double));
delete [] m_Data;
m_Data = temp;
m_Data[m_Size] = dValue;
}
++m_Size; /*数组大小加1*/
} // 从数组中删除一个元素
void DArray::DeleteAt(int nIndex)
{
if(InvalidateIndex(nIndex))
{
cout << "Error: the index of DeleteAt is invalid." << endl;
exit(0);
}
else
{
for(int i=nIndex; i<m_Size; ++i)
m_Data[i] = m_Data[i+1];
m_Data[m_Size-1] = 0;
--m_Size;
}
} // 插入一个新元素到指定位置
void DArray::InsertAt(int nIndex, double dValue)
{
if(nIndex<0 || nIndex>m_Size)
{
cout << "Error: the index of InsertAt is invalid!" << endl;
exit(0);
} if(m_Size < m_Max) /* 未满,插入 */
{
for(int i=m_Size-1; i>=nIndex; --i)
m_Data[i+1] = m_Data[i];
m_Data[nIndex] = dValue;
}
else /* 重新分配空间 */
{
m_Max = m_Max*2;
double* temp = new double[m_Max];
memcpy(temp, m_Data, m_Size*sizeof(double));
delete [] m_Data;
m_Data = temp;
for(int i=m_Size-1; i>=nIndex; --i)
m_Data[i+1] = m_Data[i];
m_Data[nIndex] = dValue;
}
++m_Size; /* 数组大小加1 */
} // 重载下标运算符[]
double DArray::operator[](int nIndex) const
{
if(nIndex<0 || nIndex>=m_Size)
{
cout << "Error: the index in [] is invalid!" << endl;
exit(0);
}
return m_Data[nIndex];
}

经过简单的测试,暂时还没有发现Bug。可能测试并不全面,如果你发现了问题,希望你在评论里告诉我,万分感谢!!!

附:String类的实现

C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。

如果你弄懂了上面DArray类的写法,那么实现String类应该就不难了。因为面试官一般只是想考查你能不能正确地写出构造函数、析构函数、拷贝构造函数、拷贝赋值运算符以及+、[ ]、<<、>>运算符重载等等。下面给出一个String类的接口,你可以自己试试手实现一下:

class String{
friend ostream& operator<< (ostream&,String&); //重载<<运算符
friend istream& operator>> (istream&,String&); //重载>>运算符
public:
String(); // 默认构造函数
String(const char* str); // 带参构造函数
String(const String& rhs); // 拷贝构造函数
String& operator=(const String& rhs); // 拷贝赋值运算符
String operator+(const String& rhs) const; //operator+
bool operator==(const String&); //operator==
bool operator!=(const String&); //operator!=
char& operator[](unsigned int); //operator[]
size_t size() const;
const char* c_str() const;
~String(); // 析构函数
private:
char *m_data; // 用于保存字符串
};

DArray类和String类的源码及测试代码下载:http://download.csdn.net/detail/lisong694767315/7691997

C++学习之动态数组类的封装的更多相关文章

  1. 模仿.NET框架ArrayList写一个自己的动态数组类MyArrayList,揭示foreach实现原理

    通过.NET反编译工具可以查看到ArrayList内部的代码,发现ArrayList并非由链表实现,而是由一个不断扩容的数组对象组成. 下面模仿ArrayList写一个自己的MyArrayList. ...

  2. 封装动态数组类Array

    功能: 1.增.删.改.查 2.扩容.缩容 3.复杂度分析 4.均摊复杂度 5.复杂度震荡 分析动态数组的时间复杂度: 分析resize的时间复杂度: public class Array<E& ...

  3. 【java集合总结】-- 数组总结+自己封装数组类

    一.前言 本篇文章总结目前学习的有关数组方面的知识,首先总结一下数组相关的核心概念,然后在封装一个自己的泛型动态数组类(ava已经封装的有现成的,自己封装只是为了加深理解),最后再学习解析下Array ...

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

    Python序列类型 在本博客中,我们将学习探讨Python的各种"序列"类,内置的三大常用数据结构--列表类(list).元组类(tuple)和字符串类(str). 不知道你发现 ...

  5. C++——模板、数组类

    1.函数模板:可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计. 声明方法:template<typename 标识符> 函数声明 求绝对值的模板 #in ...

  6. 一篇文章让你了解动态数组的数据结构的实现过程(Java 实现)

    目录 数组基础简单回顾 二次封装数组类设计 基本设计 向数组中添加元素 在数组中查询元素和修改元素 数组中的包含.搜索和删除元素 使用泛型使该类更加通用(能够存放 "任意" 数据类 ...

  7. 算法入门 - 动态数组的实现(Java版本)

    静态数组 Java中最基本的数组大家肯定不会陌生: int[] array = new int[6]; for (int i = 0; i < array.length; i++){ array ...

  8. vc++基础班[28]---动态数组及动态链表的讲解

    C++中也有相应的动态数组.动态链表.映射表的模板类,就是STL中的:vector.list.map 他们属于C++标准中的一部分,对于程序的移植性来说也是不错的,但是在MFC编程中使用 CArray ...

  9. 动态数组java实现

    数组是一种顺序存储的线性表,所有元素的内存地址是连续的. 动态数组相对于一般数组的优势是可以灵活地添加或删除元素.而一般数组则受限于固定的内存空间.只能有限的添加元素 动态数组(Dynamic Arr ...

随机推荐

  1. 两个input可能会用到的小方法

    1.一个普通的input元素,在不被 form包裹的时候,如何跳转或搜索 var oInput = document.getElementsByTagName('input')[0]; oInput. ...

  2. Selenium常用方法及函数

    新建实例driver = webdriver.Chrome() 1.获取当前页面Url的函数方法:current_url实例:driver.current_url 2.表单的提交方法:submit解释 ...

  3. dede其他栏目页的logo没有完整显示怎么办?

    在首页完全没有问题,可是点击关于我们.联系我们.加入我们的时候logo图标是缺失的,这时候怎么办? 其实这个是css样式的问题,只要找到相对应页面的css,改一下他们的宽就可以了,如果高不够就自己调整 ...

  4. 华硕(ASUS)X554LP笔记本重装win7后网卡和USB驱动问题的解决

    以前在其它笔记本上采用U盘克隆安装winxp系统非常顺利,各种硬件驱动能自动识别并安装. 手上有一台别人的华硕(ASUS)X554LP笔记本,原装win8.1,用不惯,想装个win7旗舰版. 照例去系 ...

  5. (转)解决office软件无法卸载也无法安装的顽固问题

    原文地址 http://jingyan.baidu.com/article/f3ad7d0fcfe32509c3345bab.html 有时会出现office下载失败,然后又无法重新安装,导致offi ...

  6. (转) 淘淘商城系列——Redis五种数据类型介绍

    http://blog.csdn.net/yerenyuan_pku/article/details/72855562 Redis支持五种数据类型:string(字符串),hash(哈希),list( ...

  7. R语言学习 - 热图美化

    实际应用中,异常值的出现会毁掉一张热图.这通常不是我们想要的.为了更好的可视化效果,需要对数据做些预处理,主要有对数转换,Z-score转换,抹去异常值,非线性颜色等方式. 对数转换 为了方便描述,假 ...

  8. spring思想分析

    摘要: EveryBody in the world should learn how to program a computer...because it teaches you how to th ...

  9. wpf 自定义Button按钮

    创建ButtonEx类 public class ButtonEx : Button { static ButtonEx() { DefaultStyleKeyProperty.OverrideMet ...

  10. java.net.MalformedURLException: no protocol: www.baidu.com

    URL url = new URL("www.baidu.com");改为 URL url = new URL("http://www.baidu.com");