C++封装向量-线性表

Posted Zip Zou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}

结果仍然符合预期
自定义类型测试

反思你的封装过程

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

以上是关于C++封装向量-线性表的主要内容,如果未能解决你的问题,请参考以下文章

C++封装栈

引用向量的部分片段?

C++容器

C++封装链式表-链表

如何在 C++ 中使用向量对的向量制作邻接表?

数据结构----线性表顺序和链式结构的使用(c)