C++封装向量-线性表

封装前的考虑

在C++中有很丰富的库,当属STL模板,STL的设计和优化都为我们提供了应有的功能。然而对于新手而言,尝试进行一个封装,会使得自己更加熟悉面向对象。

面向对象三大特性:封装、继承、多态。这也是面向对对象语言相对面向过程而言,最大的优势和特点。面向对象使得程序更加利于维护,让设计人员更加关注设计,要想真正的理解面向对象的特性,则必须要清楚和掌握这三大规律。

在C++中,STL提供了Vector类,表示向量,其本质则是线性表的实现,并且可以在内部实现自动扩容,并且借助迭代器,可以很方便很快速的对表中元素进行访问和遍历。

因此我们手动封装的线性表,可以有以下的功能:

  1. 自动扩容
  2. 迭代器访问元素
  3. 下标式快速访问元素值
  4. 移除元素
  5. 增加元素
  6. 长度管理

上述这些功能,都是需要一个向量所需要提供的功能,并且基于面向对象的设计而言,采用迭代器模式的设计,可以很好的减小容器对象与数据之间的紧密耦合,通过迭代器去遍历向量表,可以很大程度的增加遍历的安全性和便利性。

在该设计中,可进行三个类的设计: Sequeence 抽象基类,ZArray类,Iterator 抽象基类, ZArrayIterator类,其中两个抽象基类中分别提供了序列类的基本操作,增删查改等,Iterator基本定义了迭代器的方法接口,可以很好的实现多态,并且可以拥有很好的扩展性,因为对序列数据容器而言,其逻辑结构可以是连续的向量式的,也可以是链式的,由此他们之间的遍历方式都不同,访问方式也不同,因此为了可扩展性,可以进行抽象,使得迭代器和序列容器进行交互,而无需使用具体的子类,并且可以很好的进行扩展。那么在这里,面向对向的特性则充分包含了进去,封装是完好类设计的基础,继承则是扩展的必经之路,多态则是在面向抽象编程的基础。



值得注意的是,由于在C++中容器类都采用模板的方式去进行封装,由此需要注意的是,最好将.h的定义和.cpp的定义为一个文件中,否则在某些编译环境中可能造成link错误!

学会如何去抽象

  • 首先我们进行Sequence类的抽象定义
//
//  Sequence.hpp
//  Array
//
//  Created by 邹智鹏 on 16/7/3.
//  Copyright ? 2016年 Frank. All rights reserved.
//

#ifndef Sequence_hpp
#define Sequence_hpp

#include <stdio.h>
#include "Iterator.hpp"

namespace ZTemplate {

    typedef unsigned int z_size;    // 用于表示大小,为无符号整形
    typedef long rank;              // 用于表示秩
    template<class T>
    class Sequence {
    public:
        /**
         * 定义函数指针,用于表示两个值的比较, 若两个值相等,返回0,若val1 > val2 返回1, 否则返回-1
         */
        typedef int (*__FUNC_COMPARE_)(const T &val1, const T &val2);
        /**
         * 插入到最后一个位置
         */
        virtual void push_back(const T& val) = 0;
        /**
         * 从指定位置中,移除元素,并返回该元素的副本,若为指针,请自行进行内存管理
         * @param pos 元素位置
         * @return 返回该元素值
         */
        virtual T pop(const rank pos) = 0;
        /**
         * 访问指定位置的值
         * @param pos 元素所在的位置
         * @return 返回该位置的元素值
         */
        virtual const T &at(const rank pos) const = 0;
        /**
         * 移除指定位置元素
         * @param pos 元素所在位置
         * @return 返回是否移除成功
         */
        virtual bool remove(const rank pos) = 0;
        /**
         * 根据指定值,在序列中进行查找,若查找到符合条件的则进行移除
         */
        virtual bool remove(T &val1, __FUNC_COMPARE_ compare);

        /**
         * 获取迭代器
         * @return 返回迭代器
         */
        virtual Iterator<T> &iterator() = 0;
        /**
         * 重载访问器
         * @param pos 元素位置
         * @return 返回元素引用
         */
        virtual T& operator[](const rank pos) = 0;
    protected:
    };
}

template<class T>
bool ZTemplate::Sequence<T>::remove(T &val1, __FUNC_COMPARE_ compare) {
    Iterator<T> &curIterator = iterator();  // 获取到迭代器实例
    bool found = false;
    rank i = 0; // 秩
    while (!found && curIterator.hasNext()) {
        if (compare(val1, curIterator.data()) == 0) {
            // 两者值相等
            found = true;
            break;
        }
        i++;
        curIterator = curIterator.next();
    }
    return remove(i);// 移除指定位置

}

#endif /* Sequence_hpp */
  • 对迭代器进行抽象

迭代器需要提供的功能为容器的访问:下一个、是否右下一个、获取当前迭代器的值元素

//
//  Iterator.hpp
//  Array
//
//  Created by 邹智鹏 on 16/7/3.
//  Copyright ? 2016年 Frank. All rights reserved.
//

#ifndef Iterator_hpp
#define Iterator_hpp

#include <stdio.h>

namespace ZTemplate {
    template<class T>
    class Iterator {
    public:
        /**
         * 默认构造函数
         */
        Iterator<T>(){}

        /**
         * 下一个元素
         * @return 返回下一个迭代器
         */
        virtual Iterator<T> &next() = 0;

        /**
         * 获取迭代器值
         * @return 返回值
         */
        virtual const T data() const = 0;

        /**
         * 是否含有下一个元素
         * @return 返回是否有后续元素
         */
        virtual bool hasNext() = 0;

        /**
         * 重载++
         */
        virtual Iterator<T>& operator++() = 0;
        virtual Iterator<T>& operator++(int) = 0;
    };
}

#endif /* Iterator_hpp */

学会面向接口编程

在上面的过程中,我们已经定义了所需要的接口,并且对各种容器和所需要的迭代器进行了抽象,拥有了统一的接口,我们可以针对不同的实例进行扩展。在这里,我们先对向量进行扩展!

  • 对向量的封装实现
//
//  Array.hpp
//  Array
//
//  Created by 邹智鹏 on 16/7/3.
//  Copyright ? 2016年 Frank. All rights reserved.
//

#ifndef Array_hpp
#define Array_hpp

#include <stdio.h>
#include <cstring>
#include "Sequence.hpp"

namespace ZTemplate {
    template<class T>
    class ZArrayIterator;
    template<class T>
    class ZArray : public Sequence<T>{
    public:
        /**
         * 默认构造
         */
        ZArray<T>();
        /**
         * 根据容量构造向量
         * @param capacity 容量
         */
        ZArray<T>(z_size capacity);
        /**
         * 插入到最后一个位置
         */
        virtual void push_back(const T &val);
        /**
         * 从指定位置中,移除元素,并返回该元素的副本,若为指针,请自行进行内存管理
         * @param pos 元素位置
         * @return 返回该元素值
         */
        virtual T pop(const rank pos);
        /**
         * 访问指定位置的值
         * @param pos 元素所在的位置
         * @return 返回该位置的元素值
         */
        virtual const T &at(const rank pos) const;
        /**
         * 移除指定位置元素
         * @param pos 元素所在位置
         * @return 返回是否移除成功
         */
        virtual bool remove(const rank pos);
        /**
         * 获取迭代器
         * @return 返回迭代器
         */
        virtual Iterator<T> &iterator();
        /**
         * 返回长度信息
         * @return 返回数组的有效长度
         */
        z_size length()const{return _length;}

        /**
         * 重载访问器
         * @param pos 元素位置
         * @return 返回元素引用
         */
        T& operator[](const rank pos);

        /**
         * 析构函数
         */
        ~ZArray<T>();
        friend class ZArrayIterator<T>;
    protected:
        T *_array; // 实际存储空间
        z_size _length; // 数组长度
        z_size capacity; // 最大容量
        Iterator<T> *_iterator; // 迭代器
        /**
         * 缩容
         */
        T *shink();
        /**
         * 扩容
         */
        T *expand();
    };
    /**迭代器类*/
    template<class T1>
    class ZArrayIterator : public Iterator<T1> {
    protected:
        rank pointTo;// 当前游标
        ZArray<T1> *_array;
    public:

        /**
         * 构造函数
         * @param array 用于遍历数组
         */
        ZArrayIterator<T1>(ZArray<T1> &arr):Iterator<T1>(){this->_array = &arr;}
        /**
         * 下一个元素
         * @return 返回下一个迭代器
         */
        virtual Iterator<T1> &next();

        /**
         * 获取迭代器值
         * @return 返回值
         */
        virtual const T1 data() const;

        /**
         * 是否含有下一个元素
         * @return 返回是否有后续元素
         */
        virtual bool hasNext();
        /**
         * 重置前置++
         */
        virtual Iterator<T1>& operator++();
        /**
         * 重载后置++
         */
        virtual Iterator<T1>& operator++(int);
    };
}
template<class T>
ZTemplate::ZArray<T>::ZArray():Sequence<T>() {
    static z_size size = 5;
    _array = new T[size];
    capacity = size;
    _length = 0;
    _iterator = new ZArrayIterator<T>(*this);
}

template<class T>
ZTemplate::ZArray<T>::ZArray(z_size capacity) {
    _array = new T[capacity];
    this->capacity = capacity;
    _length = 0;
    _iterator = new ZArrayIterator<T>(*this);
}

template<class T>
void ZTemplate::ZArray<T>::push_back(const T &val) {
    if (_length == capacity) {
        _array = expand();
    }
    _array[_length] = val;
    _length++;
}

template<class T>
T ZTemplate::ZArray<T>::pop(const rank pos) {
    T val =  _array[pos]; // 要返回的值
    remove(pos);
    return val;
}

template<class T>
const T& ZTemplate::ZArray<T>::at(const rank pos) const{
    return _array[pos];
}

template<class T>
bool ZTemplate::ZArray<T>::remove(const rank pos) {
    if (pos >= _length) {
        return false;
    }
    for (rank i = pos; i < _length - 1; i++) {
        _array[i] = _array[i + 1];
    }
    _length--;
    return true;
}

template<class T>
T* ZTemplate::ZArray<T>::shink() {
    T *newLocate = new T[capacity >> 1];
    for (int i = 0; i < _length; i++) {
        newLocate[i] = _array[i];
    }
    delete _array;
    _array = newLocate;
    return newLocate;
}

template<class T>
T * ZTemplate::ZArray<T>::expand() {
    T *newLocate = new T[capacity << 1];
    for (int i = 0; i < _length; i++) {
        newLocate[i] = _array[i];
    }
    delete _array;
    _array = newLocate;
    return _array;
}

template<class T>
ZTemplate::ZArray<T>::~ZArray<T>() {
    delete _array;
    _array = nullptr;
    _length = 0;
    capacity = 0;
}

template<class T>
T & ZTemplate::ZArray<T>::operator[](const rank pos) {
    return _array[pos];
}

template<class T1>
ZTemplate::Iterator<T1>& ZTemplate::ZArrayIterator<T1>::next() {
    pointTo++;
    return *this;
}

template<class T1>
bool ZTemplate::ZArrayIterator<T1>::hasNext() {
    return pointTo < _array->length();
}

template<class T1>
const T1 ZTemplate::ZArrayIterator<T1>::data() const {
    return _array->_array[pointTo];
}

template<class T>
ZTemplate::Iterator<T> &ZTemplate::ZArray<T>::iterator() {
    return *(new ZArrayIterator<T>(*this));
}

template<class T1>
ZTemplate::Iterator<T1> &ZTemplate::ZArrayIterator<T1>::operator++() {
    next();
    return *this;
}

template<class T1>
ZTemplate::Iterator<T1> &ZTemplate::ZArrayIterator<T1>::operator++(int i) {
    ZTemplate::Iterator<T1> &it = *(new ZArrayIterator<T1>(*this));
    next();
    return it;
}

#endif /* Array_hpp */
  • 对向量迭代器的具体实现
/**迭代器类*/
    template<class T1>
    class ZArrayIterator : public Iterator<T1> {
    protected:
        rank pointTo;// 当前游标
        ZArray<T1> *_array;
    public:

        /**
         * 构造函数
         * @param array 用于遍历数组
         */
        ZArrayIterator<T1>(ZArray<T1> &arr):Iterator<T1>(){this->_array = &arr;}
        /**
         * 下一个元素
         * @return 返回下一个迭代器
         */
        virtual Iterator<T1> &next();

        /**
         * 获取迭代器值
         * @return 返回值
         */
        virtual const T1 data() const;

        /**
         * 是否含有下一个元素
         * @return 返回是否有后续元素
         */
        virtual bool hasNext();
        /**
         * 重置前置++
         */
        virtual Iterator<T1>& operator++();
        /**
         * 重载后置++
         */
        virtual Iterator<T1>& operator++(int);
    };


上述的迭代器的具体实现,由于避免循环包含的缘故,需要定义在同一个头文件中,因此在此处仅给出定义代码,实现代码可在向量的具体实现里找到

由此而言,一个基本的向量便封装完成,由于采用的是模板的方式进行封装,因此该方式可以容纳任何数据类型,这其中则不论是int、char、float甚至是Student等自定义类型,而容器类对象,只负责在内存中存储。这便是STL的核心思想!


学会如何对自己的类测试

在C++中,我们知道,都是有main函数作为入口,那么我们便可以在main函数中对类进行测试。与其他平台和语言不同,JAVA等语言拥有丰富的库提供它进行单元测试,而C++则相对较少,一次使用main函数作为测试是一项常见的方式!

在main函数中,需要对每个函数进行覆盖,从而实现对类的测试,达到较为准确的测试效果。

//
//  main.cpp
//  Array
//
//  Created by 邹智鹏 on 16/7/3.
//  Copyright ? 2016年 Frank. All rights reserved.
//

#include <iostream>
#include "Array.hpp"
#include "Iterator.hpp"
using namespace ZTemplate;

int main(int argc, const char * argv[]) {
    // insert code here...
    ZArray<int> *array = new ZArray<int>(6);
    array->push_back(5);
    array->push_back(6);
    Iterator<int> &it = array->iterator();
    while (it.hasNext()) {
        std::cout << it.data() << std::endl;
        it++;// 在重载中,有使用next()方法,则测试该方法即可
    }
    std::cout << " length: " << array->length() << std::endl;
    std::cout << array->at(0) << std::endl;
    std::cout << array->pop(0) << "    after length:" << array->length() << std::endl; // 在pop中,有对remove的调用,则只覆盖该方法即可
    return 0;
}


以上为测试的简单代码,匹配了int型,可以看到正确的结果!



在进行自定义类型的测试

//
//  main.cpp
//  Array
//
//  Created by 邹智鹏 on 16/7/3.
//  Copyright ? 2016年 Frank. All rights reserved.
//

#include <iostream>
#include "Array.hpp"
#include "Iterator.hpp"
using namespace ZTemplate;

class Student {
public:
    Student(){name = "", age = 0;}
    Student(std::string n, int a){name = n, age = a;}
    Student(const Student &stu){name = stu.name, age = stu.age;}
    void display() const{std::cout << "name:" << name << "age:" << age << std::endl;}
private:
    std::string name;
    int age;
};

int main(int argc, const char * argv[]) {
    // insert code here...
    ZArray<Student> *array = new ZArray<Student>;
    array->push_back(Student("zz", 19));
    array->push_back(Student("ee", 20));
    Iterator<Student> &it = array->iterator();
    while (it.hasNext()) {
        it.data().display();
        it++;// 在重载中,有使用next()方法,则测试该方法即可
    }
    std::cout << " length: " << array->length() << std::endl;
    array->at(0).display();
    array->pop(0).display();
    std::cout << "    after length:" << array->length() << std::endl; // 在pop中,有对remove的调用,则只覆盖该方法即可
    return 0;
}


结果仍然符合预期

反思你的封装过程

到此,你的一个自定义类的封装过程已经基本结束,在这里,你可以基本的看到面向对象的基本雏形,而结束之后,更应该考虑该方式是否合理,是否有更优秀的方式去设计!

时间: 2024-08-04 09:01:43

C++封装向量-线性表的相关文章

数据结构与算法之线性表

前言 上一篇<数据结构和算法之时间复杂度和空间复杂度>中介绍了时间复杂度的概念和常见的时间复杂度,并分别举例子进行了一一说明.这一篇主要介绍线性表. 线性表属于数据结构中逻辑结构中的线性结构.回忆一下,数据结构分为物理结构和逻辑结构,逻辑结构分为线性结构.几何结构.树形结构和图形结构四大结构.其中,线性表就属于线性结构.剩余的三大逻辑结构今后会一一介绍. 线性表 基本概念 线性表(List):由零个或多个数据元素组成的有限序列. 注意: 1.线性表是一个序列. 2.0个元素构成的线性表是空表.

第二章 线性表

2.1线性表类型定义 线性表描述:A=(a1,a2,...an);A为线性表的名称,ai为线性表 的数据元素. 线性表的离散定义:B=<A,R>,A包含n个结点(a1,a2,...an),R中只包含一个关系,即线性关系,R={(ai-1,ai)|i=1,2,....,n}, 一般的线性表的操作可以包含以下几种: * public linklist()  建立一个空的线性表. * public linklist(collection c)  将collection c中的数据依次建立一个线性表.

线性表的两种存储方式解析.

顺序存储: typedef struct _tag_LinkNode { int length; int capacity; void **node; }Link; 用以上结构体表达,length表示线性表目前有多少元素,capacity表示整个线性表的容量(创建之时已固定) 而这个node,最不容易理解,可以抽象为一个指针数组.每个元素都指向一个业务节点的内存地址,在创建之时必须与capacity动态绑定,代表可以容纳多少个业务节点. 在封装内部方法时,核心思想是业务层将业务节点转换成void

线性表的相关操作

1.对线性表的操作 #include <stdio.h>#include "2-1.h" List* List_Create(){    return NULL;} void List_Destroy(List* list){ } void List_Clear(List* list){ } int List_Length(List* list){    return 0;} int List_Insert(List* list, ListNode* node, int p

数据结构与算法之----线性表

01线性表 1.线性表的判断方式就是元素有且只有一个直接前驱和直接后继,元素可以为空,此时叫做空表 2.抽象数据类型标准格式 ADT 抽象数据类型名 DATA 数据元素之间逻辑关系的定义 Operation 操作 endADT 3.操作伪代码 Operation InitList(*L): 初始化操作,建立一个空的线性表L ListEmpty(L): 判断线性表是否为空表,如果为空返回true,否则返回false ClearList(*L): 将线性表清空(实际情况不是删除元素,而是将内存中的元

【数据结构】 线性表的顺序表

线性表是一种最为常用的数据结构,包括了一个数据的集合以及集合中各个数据之间的顺序关系.线性表从数据结构的分类上来说是一种顺序结构.在Python中的tuple,list等类型都属于线性表的一种. 从抽象数据类型的线性表来看,一个线性表应该具有以下这些操作(以伪代码的形式写出): ADT List: List(self) #表的构造操作,创建一个新表 is_empty(self) #判断一个表是不是空表 len(self) #返回表的长度 prepend(self,elem) #在表的开头加入一个

线性表的链式存储

线性表的链式存储 线性表的链式存储 基本概念 设计与实现 实现代码 优缺点 1. 基本概念 链式存储定义 为了表示每个数据元素与其直接后继元素之间的逻辑关系,每个元素除了存储本身的信息外,还需要存储指示其直接后继的信息. 表头结点 链表中的第一个结点,包含指向第一个数据元素的指针以及链表自身的一些信息 数据结点 链表中代表数据元素的结点,包含指向下一个数据元素的指针和数据元素的信息 尾结点 链表中的最后一个数据结点,其下一元素指针为空,表示无后继. 2.设计与实现 在C语言中可以用结构体来定义链

数据结构与算法 1 :基本概念,线性表顺序结构,线性表链式结构,单向循环链表

[本文谢绝转载] <大纲> 数据结构: 起源: 基本概念 数据结构指数据对象中数据元素之间的关系  逻辑结构 物理结构 数据的运算 算法概念: 概念 算法和数据结构区别 算法特性 算法效率的度量 大O表示法 时间复杂度案例 空间复杂度 时间换空间案例 1)线性表: 线性表初步认识: 线性表顺序结构案例 线性表顺序结构案例,单文件版 线性表的优缺点 企业级线性表链式存储案例:C语言实现 企业级线性表链式存储案例:C语言实现 单文件版 企业级线性表链式存储案例,我的练习  线性表链式存储优点缺点

数据结构与算法复习第一天——基础概念,线性表

数据结构的一些基本术语: 数据:客观事物的符号表示 数据元素:数据集合中的一个个体 数据项 组成数据元素 数据对象是数据的子集 由相同性质的数据元素构成 数据结构:带有结构的数据元素的集合 数据结构可以用一个四元组表示(D,L,S,O) data ,logical structrue ,storage struction operation操作 L——集合,线性,树形,图形 S——存储结构,有顺序,链式,散列 线性表——线性结构,数据元素之间一对一的关系, N个具有相同类型的数据元素的有限序列