vector 常用方法(转载)

来源:互联网 发布:ps淘宝店招制作教程 编辑:程序博客网 时间:2024/06/05 00:31
转载:http://blog.tianya.cn/blogger/post_show.asp?BlogID=1862414&PostID=17400310
Vector用于存储对象数组
  
  常用方法
  
  1.push_back 在数组的最后添加一个数据
  
  2.pop_back 去掉数组的最后一个数据
  
  3.at 得到编号位置的数据
  
  4.begin 得到数组头的指针
  
  5.end 得到数组的最后一个单元+1的指针
  
  6.front 得到数组头的引用
  
  7.back 得到数组的最后一个单元的引用
  
  8.max_size 得到vector最大可以是多大
  
  9.capacity 当前vector分配的大小
  
  10.size 当前使用数据的大小
  
  11.resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
  
  12.reserve 改变当前vecotr所分配空间的大小
  
  13.erase 删除指针指向的数据项
  
  14.clear 清空当前的vector
  
  15.rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
  
  16.rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
  
  17.empty 判断vector是否为空
  
  18.swap 与另一个vector交换数据
  在C++中使用STL Container可以提高程序的开发效率,一般情况下, 是不会产生什么问题的。 但是,有的时候还是应该非常小心, 因为容器实现机制的原因,,会让你遇到一些无法意料的问题。例如最近在写一个分支定界算法求解0/1背包问题的程序时,就遇到了关于vector的一个问题:

成算法中使用了一个vector模拟搜索树(CNode为树节点数据类型),,另外用一个queue或priority_queue实现活动节点的队列或优先队列, 队列中的活动节点只保存指向搜索树中的对应节点的指针,也就是这里的vector::iterator,在向树中增加节点时,同时也将其指针添加到活动节点队列中。以后取活动节点时, 取出指针, 再到树中取指针指向节点的数据。

基本上,想法很naive, 但是执行起来莫名奇妙地出错, 而且出错点都一样, 就是连续添加两个活动节点后,再取一个活动节点(先添加的那个),这时指针指向的数据显然是错的,但是指针指向的地址是正确的, 这个没有问题。 想了很久,突然想起来vector有个预分配空间的做法,会不会我使用时默认预分配空间增量为2,每次连续添加两个节点时就会重新增加2个预留空间。。。但是那样会改变以前已经分配单元的内容吗?难道增加分配空间时将以前的空间释放了?

查看c++ primer里关于vector的介绍, 发现说为了支持随机访问, vector是始终使用连续的空间的, 每次重新预留空间时, 都分配一个新的空间, 将以前的数据copy过来, 然后释放以前的空间... 原来如此啊. 由于预留空间增量依赖于具体实现, 所以不同情况下的大小是不一样的, 我通过输出运行过程中的capacity观察到新的空vector的capacity大小为0, 新增一个节点后为1, 再增一个为2, 再增一个为4... 看来开始以1递增, 后来以2递增的(别的机器上有可能不一样, 事实上, 这个增量可以用vector<>::reserve(int) 来指定的).

最后的结论是: vector在增加节点时有可能使以前保存的iterator无效!

解决方法:

由于程序框架没有问题的, 我不想做大的改动, 想将vector换成list试试: 编译报告说list中CNode没有定义, 我使用的方式是:

class CNode;
typedef list::iterator TPNode;
class CNode
{
...
TPNode parent;
};

这种方法在vector时是可以的, 但是list就不行了, 我估计是list定义问题, 需要包含的类型得到定义(definition)而非仅仅声明(declaration). 再换成deque, 也报错, 是说iterator什么什么的... 怒!

后来想既然vector始终是连续存放数据元素的, 虽然指针有可能变, 但是元素相对于头或尾的位置是不会变化的, 所以可以用 vector::different_type来代替原来的iterator, 取值时考虑这个区别就可以了. 而且以前的程序中判断加节点是否成功是判断添加节点对应的iterator是否为NULL来确定的, 所以我考虑用相对于vector::end()的量来判断(若为0, 也就是NULL, 是不可能的, 因为end()指向的是最后元素后的位置), 这样前面的程序都不用动了, 哈哈! I am so clever!... 嗯? 由于每次添加的活动节点都是树中最后扩展的节点, 也就是说这个于end()的相对量始终是-1或1(依据是end()减, 还是减end()), 晕倒! 那么就用相对于begin()的偏移量吧, 不过那样NULL就不再是无效的量了, 不爽! 而且更大的麻烦在: 我使用优先队列时自定义的比较函数需要访问树中的节点, 如果用指针就很直接了, 否则用偏移量必须要知道那个vector的基准, 那样....模块或者说类之间的耦合太大了, 显然不好.

算了, 自己写个树结构吧. 针对这个程序的应用, 树结构很简单:

typedef CNode *TPNode;
struct _TreeItem;
typedef struct _TreeItem *TPTre
在C++中使用STL Container可以提高程序的开发效率,一般情况下, 是不会产生什么问题的。 但是,有的时候还是应该非常小心, 因为容器实现机制的原因,,会让你遇到一些无法意料的问题。例如最近在写一个分支定界算法求解0/1背包问题的程序时,就遇到了关于vector的一个问题:

成算法中使用了一个vector模拟搜索树(CNode为树节点数据类型),,另外用一个queue或priority_queue实现活动节点的队列或优先队列, 队列中的活动节点只保存指向搜索树中的对应节点的指针,也就是这里的vector::iterator,在向树中增加节点时,同时也将其指针添加到活动节点队列中。以后取活动节点时, 取出指针, 再到树中取指针指向节点的数据。

基本上,想法很naive, 但是执行起来莫名奇妙地出错, 而且出错点都一样, 就是连续添加两个活动节点后,再取一个活动节点(先添加的那个),这时指针指向的数据显然是错的,但是指针指向的地址是正确的, 这个没有问题。 想了很久,突然想起来vector有个预分配空间的做法,会不会我使用时默认预分配空间增量为2,每次连续添加两个节点时就会重新增加2个预留空间。。。但是那样会改变以前已经分配单元的内容吗?难道增加分配空间时将以前的空间释放了?

查看c++ primer里关于vector的介绍, 发现说为了支持随机访问, vector是始终使用连续的空间的, 每次重新预留空间时, 都分配一个新的空间, 将以前的数据copy过来, 然后释放以前的空间... 原来如此啊. 由于预留空间增量依赖于具体实现, 所以不同情况下的大小是不一样的, 我通过输出运行过程中的capacity观察到新的空vector的capacity大小为0, 新增一个节点后为1, 再增一个为2, 再增一个为4... 看来开始以1递增, 后来以2递增的(别的机器上有可能不一样, 事实上, 这个增量可以用vector<>::reserve(int) 来指定的).

最后的结论是: vector在增加节点时有可能使以前保存的iterator无效!

解决方法:

由于程序框架没有问题的, 我不想做大的改动, 想将vector换成list试试: 编译报告说list中CNode没有定义, 我使用的方式是:

class CNode;
typedef list::iterator TPNode;
class CNode
{
...
TPNode parent;
};

这种方法在vector时是可以的, 但是list就不行了, 我估计是list定义问题, 需要包含的类型得到定义(definition)而非仅仅声明(declaration). 再换成deque, 也报错, 是说iterator什么什么的... 怒!

后来想既然vector始终是连续存放数据元素的, 虽然指针有可能变, 但是元素相对于头或尾的位置是不会变化的, 所以可以用 vector::different_type来代替原来的iterator, 取值时考虑这个区别就可以了. 而且以前的程序中判断加节点是否成功是判断添加节点对应的iterator是否为NULL来确定的, 所以我考虑用相对于vector::end()的量来判断(若为0, 也就是NULL, 是不可能的, 因为end()指向的是最后元素后的位置), 这样前面的程序都不用动了, 哈哈! I am so clever!... 嗯? 由于每次添加的活动节点都是树中最后扩展的节点, 也就是说这个于end()的相对量始终是-1或1(依据是end()减, 还是减end()), 晕倒! 那么就用相对于begin()的偏移量吧, 不过那样NULL就不再是无效的量了, 不爽! 而且更大的麻烦在: 我使用优先队列时自定义的比较函数需要访问树中的节点, 如果用指针就很直接了, 否则用偏移量必须要知道那个vector的基准, 那样....模块或者说类之间的耦合太大了, 显然不好.

算了, 自己写个树结构吧. 针对这个程序的应用, 树结构很简单:

typedef CNode *TPNode;
struct _TreeItem;
typedef struct _TreeItem *TPTre
eItem;
typedef struct _TreeItem
{
TPNode valueptr;
TPTreeItem nextptr;
} TTreeItem;



class CTree 
{
public:
CTree();
virtual ~CTree();

private:
TPTreeItem m_tree;
TPTreeItem m_last;

public:
//增加一个树节点, 并返回新增节点的指针
TPNode addTreeNode(const CNode &cnode);
//清空树
void clear();
};

基本思想是一个链表保存节点指针, 其中的指针指向实际的节点. 其实, 这里的m_tree用vector来实现是一点问题都没有的, 不过自己写一个也不麻烦, 以后再优化吧, 哈哈.

这样的话, 程序其他部分都不用改了, 搞定!
原创粉丝点击