C++封装向量-线性表
Posted Zip Zou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++封装向量-线性表相关的知识,希望对你有一定的参考价值。
封装前的考虑
在C++中有很丰富的库,当属STL模板,STL的设计和优化都为我们提供了应有的功能。然而对于新手而言,尝试进行一个封装,会使得自己更加熟悉面向对象。
面向对象三大特性:封装、继承、多态。这也是面向对对象语言相对面向过程而言,最大的优势和特点。面向对象使得程序更加利于维护,让设计人员更加关注设计,要想真正的理解面向对象的特性,则必须要清楚和掌握这三大规律。
在C++中,STL提供了Vector类,表示向量,其本质则是线性表的实现,并且可以在内部实现自动扩容,并且借助迭代器,可以很方便很快速的对表中元素进行访问和遍历。
因此我们手动封装的线性表,可以有以下的功能:
- 自动扩容
- 迭代器访问元素
- 下标式快速访问元素值
- 移除元素
- 增加元素
- 长度管理
上述这些功能,都是需要一个向量所需要提供的功能,并且基于面向对象的设计而言,采用迭代器模式的设计,可以很好的减小容器对象与数据之间的紧密耦合,通过迭代器去遍历向量表,可以很大程度的增加遍历的安全性和便利性。
在该设计中,可进行三个类的设计: 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++封装向量-线性表的主要内容,如果未能解决你的问题,请参考以下文章