1 swap操作
swap是STL泛型操作的一种。这种操作的时间复杂度极低,用于两个容器内容的交换。
例如定义vector vi1和vi2, vi1.swap(vi2),就将vi1和vi2的内容交换了。
2 问题
iterator实际上是一种指针,可以指向容器的任意位置。例如vector::iterator it1 = vi1.begin();
这两个操作本身很简单,但是怪异的一点是swap前后,迭代器不失效,原来指向什么内容,swap后还是指向什么内容。
例如下面这段代码:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1(10,1);
vector<int> v2(100,2);
vector<int>::iterator it1 = v1.begin();
vector<int>::iterator it2 = v2.begin();
cout<< "&v1[0]=" << &v1[0] << "\t\t" << "*it1=" <<*it1 << endl;
cout<< "&v2[0]=" << &v2[0] << "\t\t" << "*it1=" <<*it2 << endl;
v1.swap(v2);
cout<<"after v1.swap(v2)"<<endl;
cout<< "&v1[0]=" << &v1[0] << "\t\t" << "*it1=" <<*it1 << endl;
cout<< "&v2[0]=" << &v2[0] << "\t\t" << "*it1=" <<*it2 << endl;
return 0;
}
结果如下:
结果表明:
(1)swap前后,*vt1和*vt2没有改变,但是&v1[0]和&v2[0]却发生了改变。
(2)实际上,交换操作的过程是这样的:
类对象地址不变,但是begin()操作指向的首地址改变,&v1[0]等效于begin操作,也改变。
而迭代器指向的是“地址”,没有改变,所以会产生这样的效果。
下面这一段详细的程序可以验证:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1(10,1);
vector<int> v2(100,2);
vector<int>::iterator it1 = v1.begin();
vector<int>::iterator it2 = v2.begin();
cout<<"v1的属性:"<<endl;
cout<< "&v1=" << &v1 << "\n"
<< "&v1[0]=" << &v1[0] << "\t\t" << "v1[0]=" << v1[0] << "\n"
<< "it1=" << it1 << "\t\t" << "*it1=" << *it1 << "\n"
<<"v1.begin()="<<v1.begin() << "\t" << "*v1.begin()="<<*v1.begin()
<< endl;
cout<<"v2的属性:"<<endl;
cout<< "&v2=" << &v2 << "\n"
<< "&v2[0]=" << &v2[0] << "\t\t" << "v2[0]=" << v2[0] << "\n"
<< "it2=" << it2 << "\t\t" << "*it2=" << *it2 << "\n"
<<"v2.begin()="<<v2.begin() << "\t" << "*v2.begin()="<<*v2.begin()
<< endl;
v1.swap(v2);
cout<<"---------after v1.swap(v2)----------"<<endl;
cout<<"v1的属性:"<<endl;
cout<< "&v1=" << &v1 << "\n"
<< "&v1[0]=" << &v1[0] << "\t\t" << "v1[0]=" << v1[0] << "\n"
<< "it1=" << it1 << "\t\t" << "*it1=" << *it1 << "\n"
<<"v1.begin()="<<v1.begin() << "\t" << "*v1.begin()="<<*v1.begin()
<< endl;
cout<<"v2的属性:"<<endl;
cout<< "&v2=" << &v2 << "\n"
<< "&v2[0]=" << &v2[0] << "\t\t" << "v2[0]=" << v2[0] << "\n"
<< "it2=" << it2 << "\t\t" << "*it2=" << *it2 << "\n"
<<"v2.begin()="<<v2.begin() << "\t" << "*v2.begin()="<<*v2.begin()
<< endl;
return 0;
}
结果如下:
结果表明:
(1)这里&v1和&v1[0]不是同一个地址,前者是对象地址,后者是第一个元素地址。和数组是不同的。
(2)swap前后,确实只有begin的地址被互换了。
(3)只互换begin,整个vector内容可以互换吗?答案是肯定的,vector的访问是基于首地址+偏移的,类似于数组。
所以如果将首地址互换,vector访问到的所有元素都互换了。
内存示意图:
3 原理
3.1 vector类
vector内部是维护一个动态数组,大概样子是这样:
class vector
{
T* begin;
T* finish;
T* capacity;
};
vector::iterator虽说不一定是用指针实现,但是一定保存了数组中元素的真实内存地址
(其实也不是一定,实现也可以保存begin和偏移量,但这么做明显太复杂,实际中都是直接保存元素地址的)。
vector使用swap的时候,其实交换的是双方begin, finish, capacity这三个指针的值。
在这个过程中,所有从该vector创建的iterator本身都不会发生任何改变(打个比方就是你换一个手机号,那些存了你以前手机号的人手头的电话号码并不会随之发生改变,除非你告诉他们)。
因为iterator没有改变,所以你打印iterator本身的地址时,得到的仍旧是原来的值。
另外,动态数组本身并没有被销毁,只是改变了所属。
所以实际两个vector中所有元素的物理地址都没有改变,iterator指向的依然是原来的元素。
3.2 swap函数
下面这一段代码摘抄自stl_vector.h,我们主要关注begin和swap操作。
/**
* Returns a read/write iterator that points to the first
* element in the %vector. Iteration is done in ordinary
* element order.
*/
iterator
begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_start); }
可见begin操作是获取_M_impl._M_start这个值。
void _M_swap_data(_Vector_impl& __x)
{
std::swap(_M_start, __x._M_start);
std::swap(_M_finish, __x._M_finish);
std::swap(_M_end_of_storage, __x._M_end_of_storage);
}
swap操作也是对Mstart这个值进行处理。
3.3 模拟仿真
下面是一种常见的迭代器实现方案的简化代码:
template <typename T>
struct base_iterator
{
base_iterator(T* p):m_ptr(p) {}
T* operator->() { return m_ptr; }
// 剩下的若干重载运算符。。。
T* m_ptr;
};
template <typename T>
class vector
{
typedef base_iterator<T> iterator;
T* begin;
T* finish;
T* capacity;
iterator begin() { return iterator(begin); }
};
获取begin()迭代器实际上就是用begin指针创建了一个iterator对象而已。
4 参考文献
[1]http://www.software8.co/wzjs/cpp/5047.html 作者:henrystark
[2]http://bbs.csdn.net/topics/390410638?page=1 作者:cnm_1314 和 tofu_