STL:vector
Posted 小键233
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:vector相关的知识,希望对你有一定的参考价值。
一和二中吧STL 的基础都说明白了,那我们尝试着实现一下第一个容器:vector
(好敷衍呀~)
vector 算是比较简单的一种容器了,但饶是如此,我都写了好久(主要是其他的各种函数费时间)
准备工作
在直接开始说vector 的时候的时候,我会假设你懂得以下函数的运用(最好自己去实现一下吧)
- uninitialized_copy
- uninitialized_copy_n
- uninitialized_fill
- uninitialized_fill_n
- copy
- copy_n
- fill
- fill_n
- copy_backward
- lexicographical_compare
- equal
- distance
- advance
STL 的设计非常出色,容器负责管理数据,各种算法反而通过迭代器进行。迭代器充当了容器和算法的粘合剂。这个概念一直贯穿中STL。
vector
vector 的内容很多,不会全部都讲,这里只说一些我觉得可以说的内容。
我很建议直接打开C++ 官网,看vector 的公有函数接口。C++11 的标准已经加入了移动语义,但是这里不讨论移动语义的内容,所以会忽略掉它。
迭代器
vector 的迭代器就是原生的指针。
vector 维护的是一个连续线性的空间。
它里面的typedef 如下:
template<typename T, typename Alloc = alloc>
class vector
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef simple_alloc<T, Alloc> data_alloc;
}
vector 的迭代器支持随机存储,是 Random Access Iterator。
数据结构与内存分配策略
曾经我很好奇vector 里面的数据是怎么维护的。一般而言,受直观思维的影响,我会认为有一个指针存储数据,一个变量维护长度信息,然后加上一些其他的变量。
然而真相是,vector 中只有三个原生指针!分别是:
iterator start; //数据的开始
iterator finish; //数据的结束
iterator end_of_storage; //拥有内存的结束
所以sizeof(vector) == 12 。(32位的机器),我面试的时候被问到,但是那时并不清楚。
这个设计在我看来非常简洁,而且出色。
之前我也做过线性表的数据结构实现,里面用到了pos 位置的概念。
但是如果这样子,当你在多线程中使用的时候,要额外考虑到pos 位置有没有被其他线程改动,从而要求每一步都要重新定位pos。
多么坑爹!
所以,当我看到vector 的设计时,实在是被惊艳到了。(这么说感觉我很low 的样子,好吧,实际就很low)
可能你也不知道上一段我在说什么,没关系,我也不在乎。
下面说说它的内存分配策略。
vector 的空间是连续的,每次如果申请的内存不够了,那么它会重新申请内存,并且把数据挪到新的空间中。
每次重新申请内存的大小是原来的两倍。如果原来的空间为0,那么申请1。
大概类似于这样的计算:
const size_type old_size = size();
size_type alloc_length = old_size > 0 ? (old_size<<1) : 1;
你在下面就会看到了:)
insert
insert 函数可以说是vector 的一个核心函数吧。vector 代码很多,但是逻辑复杂一点的就那几个。
先看一下C++ 官网上,insert 的声明:
//single element (1)
iterator insert (const_iterator position, const value_type& val);
//fill (2)
iterator insert (const_iterator position, size_type n, const value_type& val);
//range (3)
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
//move (4)
iterator insert (const_iterator position, value_type&& val);
//initializer list (5)
iterator insert (const_iterator position, initializer_list<value_type> il);
一共有5个,去掉4 和5 就只有三个了(4不再讨论范围内,5我也不懂 =_= )
注意到,从c++98 到 c++11 ,insert 的返回值有所改变了。
代码如下:
/** insert 函数,返回指向最近插入的那个元素的迭代器
*/
iterator insert(const_iterator position, const value_type& val)
{
size_type n = position - begin();
if( finish != end_of_storage && position == end() )
{
construct(finish, val);
++finish;
}
else
insert_aux( const_cast<iterator>(position), val);
return begin() + n;
}
iterator insert(const_iterator position, size_type n, const value_type& val);
//下面针对 n 的参数重载了 int 和long,下面有解释
iterator insert(const_iterator position, int n, const value_type& val)
{
return insert(position, static_cast<size_type>(n), val);
}
iterator insert(const_iterator position, long n, const value_type& val)
{
return insert(position, static_cast<size_type>(n), val);
}
/** 要考虑到迭代器的不同类型,这里进行了一次萃取
*/
template<typename Input_iter>
iterator insert(const_iterator position, Input_iter first, Input_iter last)
{
size_type n = position - begin();
range_insert(position, first, last, _iterator_category(first) );
return begin() + n;
}
仔细一看,你就会发现,fill 的版本中,对第二个参数分别进行了int 和long 的重载。他们都是接受整型数据,这样的重载会不会画蛇添足呢?
不会。
仔细比对一下range 和 fill 的函数签名,你会发现,它们的区别在于后面两个参数,其中range 后面接受两个参数类型由模板参数决定。
这会导致什么情况,也许你想到了,就是当我们期望调用fill 版本的insert ,并且后面两个参数类型一样,如都是int 或者都是long 的时候,它会推导为调用 range 版本。类似这样:
vector<int> v;
int n = 100;
int val = 0;
v.insert(v.begin(), n, val);
我们期望fill 版本中间的那个参数能明确地接受表示数量的整型参数 ,所以才对int 和long 进行重载。但是这个还是不够的,因为还有char。但是,基于考虑到很少有人会将char 视为表示数量的类型,就不加以考虑了。
类似的问题,也出现在vector 的构造函数中。
一开始我的解决方法是用萃取技术,将所有表示数量的类型萃取出来。但是这样导致的问题是代码不够清晰明了,所以,就采用了重载的方法。这也是SGI 中的解决方法。
代码中还涉及到insert_aux, range_insert 和 insert 的fill版本的实现。其实它们都差不多,下面给出range_insert 的实现:
template<typename T, typename Alloc>
template<typename Input_iter>
void vector<T, Alloc>::range_insert(const_iterator position, Input_iter first,
Input_iter last, input_iterator_tag)
{
iterator pos = const_cast<iterator>(position);
for(; first != last; ++first)
{
pos = insert(pos, *first);
++pos;
}
}
template<typename T, typename Alloc>
template<typename Forward_iter>
void vector<T, Alloc>::range_insert(const_iterator pos, Forward_iter first,
Forward_iter last, forward_iterator_tag)
{
iterator position = const_cast<iterator>(pos);
if(first != last)
{
//后面的编写和insert_aux 差不多
size_type n(0);
distance(first, last, n);
if( size_type( end_of_storage - finish ) > n)
{
size_type elems_after = finish - position;
if( elems_after > n) //往后挪,腾出位置
{
iterator insert_end = finish - n;
uninitialized_copy(insert_end, finish, finish);
copy(position, insert_end, insert_end);
copy(first, last, position);
finish += n;
}
else
{
iterator mid = first;
advance(mid, elems_after);
iterator now = uninitialized_copy(mid, last, finish); //TO DO advance
finish = copy(position, finish, now);
copy(first, mid, position);
}
}
else
{
//重新分配内存
size_type old_size = size();
size_type new_size = old_size + (old_size > n? old_size: n);
iterator new_start = data_alloc::allocate(new_size);
iterator new_finish = new_start;
XJ_TRY
{
new_finish = uninitialized_copy(start, position, new_start);
new_finish = uninitialized_copy(first, last, new_finish);
new_finish = uninitialized_copy(position, finish, new_finish);
}
XJ_CATCH
{
destroy(new_start, new_finish);
data_alloc::deallocate(new_start, new_size);
throw;
}
destroy(start, finish);
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = start + new_size;
}
}
}
range_insert 对两种迭代器进行了重载:input_iterator and forward_iterator
我并不是很明白为什么要这么做。先记下来,也许以后就明白了。
range_insert 的行为可以表示如下:
如果容量足够的话:
- 如果插入点之后的元素数量(记为after) > 要插入的数量(记为n),那么先将后面那部分uniitialized_copy 到相应的位置,然后将前面那部分copy 到相应的位置,最后将插入的数据copy 到腾出的空间中。
- 如果after <= n ,那么先将插入数据的后面那部分uninitialized_copy 到相应的位置,然后的after 的元素uninitalized_copy 到相应的位置,最后把剩下的数据copy 到腾出的空间中
如果容量不够的话:
- 那么开辟新的空间,依次copy 进去就好
erase
erase 是删除元素的操作,标准的函数签名如下:
iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);
98 和 11 的标准在于参数由iterator 变为 const_iterator
这个函数行为倒是清晰明了,如下:
/** erase 函数
*/
iterator erase (const_iterator pos)
{
iterator position = const_cast<iterator>(pos);
if( position + 1 != finish)
copy(position+1, finish, position);
--finish;
destroy(finish);
return position;
}
iterator erase (const_iterator first, const_iterator last)
{
iterator pos = const_cast<iterator>(first);
iterator i = copy(iterator(last), finish, pos);
destroy(i, finish);
finish = i;
return pos;
}
at 与 opeator []
at 和 operator [] 都是随机获取元素的操作。以前没仔细看文档,并不明白它们的区别。
据C++ 的标准,at 是有进行下标安全检查,operator[] 就没有。
实现如下:
void _check_range(size_type n)
{
if(n >= this->size() )
{
printf("vector out of range ");
abort();
}
}
/** at funciton ,这个会抛出异常
*/
reference at(size_type index)
{
_check_range(index);
return (*this)[index];
}
其实check range 这么直接粗暴并不好,正确的行为应该是抛出异常。但是为了练习使用,简单一点就好。
operator [] 的实现如下:
/** operator []
*/
reference operator [] (size_type _n)
{
return start[_n];
}
const_reference operator [] (size_type _n) const
{
return start[_n];
}
emplce 与 emplce_back
这两个函数都是C++11 才加进来的。
emplace 在制定的位置初始化一个元素,并且插入它。
emplace_back 就是在后面初始化一个元素,并且插入它。
我想说这个是因为它用到了可变参数。
先看签名:
template <class... Args>
iterator emplace (const_iterator position, Args&&... args);
template <class... Args>
void emplace_back (Args&&... args);
可变参数的语法展开来说又是一堆了,详细的可以看[1]。
实现倒是意外的简单:
/** emplace and emplace_back
*/
template< typename ... Args>
iterator emplace(const_iterator pos, Args&& ... args)
{
return insert(pos, T(args...) );
}
template<typename ... Args>
void emplace_back( Args&& ... args)
{
push_back(T(args ... ) );
}
reverse_iterator
reverse_iterator 是反向迭代器,是一个颠倒黑白的小能手。
先看需求,一般来说遍历元素如下:
for(typename vector<int>::iterator iter = v.begin();
iter!=v.end(); ++iter)
{
...
}
*有些编译器不需要typename ,但是G++ 要
现在我希望能够以同样直观的方式,逆向遍历元素,类似这样:
for(typename vector<int>::reverse_iterator riter = v.rbegin();
riter != v.rend(); ++riter);
{
...
}
那这个时候就需要reverse_iterator 上场了。
它的实现如下:
/** _reverse_iterator
*/
template<typename Iterator>
class _reverse_iterator
{
protected:
Iterator current;
public:
typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
typedef typename iterator_traits<Iterator>::value_type value_type;
typedef typename iterator_traits<Iterator>::difference_type difference_type;
typedef typename iterator_traits<Iterator>::pointer pointer;
typedef typename iterator_traits<Iterator>::reference reference;
typedef Iterator iterator_type;
typedef _reverse_iterator<Iterator> self;
/** constructor function
*/
_reverse_iterator() {}
explicit _reverse_iterator(iterator_type x) : current(x) {}
_reverse_iterator(const self& x):current(x.current) {}
iterator_type base() const { return current; }
reference operator* ()
{
iterator_type tmp = current;
return *--tmp;
}
pointer operator -> ()
{
return &(this->operator*()); //获得最原始的那个地址
}
self& operator ++ ()
{
--current;
return *this;
}
self operator ++ (int)
{
self tmp = *this;
--current;
return tmp;
}
self& operator -- ()
{
++current;
return *this;
}
self operator -- (int)
{
self tmp(*this);
++current;
return tmp;
}
self operator + (difference_type n) const
{
return self(current-n);
}
self& operator += (difference_type n)
{
current -= n;
return *this;
}
self operator - (difference_type n) const
{
return self(current + n);
}
self& operator -= (difference_type n)
{
current += n;
return *this;
}
reference operator [] (difference_type n) const
{
return *(*this+n);
}
};
/** _reverse_iterator relational operator
*/
template<typename Iterator>
inline bool operator == (const _reverse_iterator<Iterator>&x, const _reverse_iterator<Iterator>& y)
{
return (x.base() == y.base() );
}
template<typename Iterator>
inline bool operator != (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
return !(x==y);
}
template<typename Iterator>
inline bool operator < (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
return y.base() < x.base(); //这是诡异的逻辑
}
template<typename Iterator>
inline bool operator > (const _reverse_iterator<Iterator>& x,const _reverse_iterator<Iterator>& y)
{
return x.base() < y.base();
}
template<typename Iterator>
inline bool operator <= (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
return !(x>y);
}
template<typename Iterator>
inline bool operator >= (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
return !(x<y);
}
/** _reverse_iterator binary operator function
*/
template<typename Iterator>
inline typename _reverse_iterator<Iterator>::difference_type
operator - (const _reverse_iterator<Iterator>& x, const _reverse_iterator<Iterator>& y)
{
return y.base()-x.base();
}
template<typename Iterator>
inline _reverse_iterator<Iterator> operator + (const typename _reverse_iterator<Iterator>::difference_type n, const _reverse_iterator<Iterator>& x)
{
return _reverse_iterator<Iterator>(x.base()-n);
}
代码略长,并且完整地定义了6 种 relational operator 。
至少可以给我们一些提示,设计一个类的时候,要像设计一个type 一样。
它的命名并不是 reverse_iterator
,而是 _reverse_iterator
。
这么做的原因是,typedef 的时候,名字不能重复,很快你就会看到解释了。
但是后文依然会说reverse_iterator,这样比较方便。
设计好reverse_iterator 之后,我们就可以在vector 中加东西了:
// typedef reverse_iterator<iterator> reverse_iterator;
//这样会报错,但是标准的代码就是那么写的. i don't know how to deal with it;
typedef _reverse_iterator<const_iterator> const_reverse_iterator;
typedef _reverse_iterator<iterator> reverse_iterator;
注释的那行解释了命名的原因。
有了这些,我们就可以设计 rbegin
and rend
:
/** rbegin and rend
*/
reverse_iterator rbegin() { return reverse_iterator( end() ); }
const_reverse_iterator rbegin() const { return const_reverse_iterator( end() ); }
reverse_iterator rend() { return reverse_iterator( begin() ); }
const_reverse_iterator rend() const { return const_reverse_iterator( begin() ); }
shrink_to_fit
shrink_to_fit 是C++11 才有的。它的行为是将容量压缩到size 大小。
它的加入就是为了解决vector 的空间一旦申请,除了生命周期结束,期间不会归还内存的问题。
实现如下:
/** shrink_to_fit
*/
void shrink_to_fit()
{
const size_type n = size();
if(capacity() != n)
{
iterator new_start = 0;
if(n>0)
{
new_start = allocate_copy( n, begin(), end() );
destroy(begin(), end() );
}
deallocate();
start = new_start;
end_of_storage = finish = start + n;
}
}
在了解vector 的空间分配策略后,才能更好地使用它。
代码在github 上可以得到。
[参考资料]
[1] 使用C++11变长参数模板 处理任意长度、类型之参数实例 - yanxiangtianji的专栏
以上是关于STL:vector的主要内容,如果未能解决你的问题,请参考以下文章