第六十七讲 细说vector(3)

哦,对了,昨天我们给了一个实例,实例几乎囊括了我们所说的内容,但是留下一个奇怪的问题,我们一遍一遍地重置iterator,这是为什么呢?当然有些是可以理解的,比如说:
—————————–
while(p != a.end())

cout<<*p++<<endl;


p = a.begin();


—————————–
这样重置p我们可以理解,因为在while循环结束后,p指向vector的最后一个元素,所以我们重置他是可以理解的,因为接下来我们还要用他,那么接下来的又会是怎么回事呢?
——————————–
vector<int>::iterator p=a.begin();


a.insert(p+1,1);


p = a.begin();


……
p = a.begin();


a.erase(p,p+5);


p = a.begin();


——————————-

在这里,我们明显的觉得p应该就是指向第一个元素的迭代器,但为什么我们还要重置呢?还记得我们以前迭代器的那几讲吗?当我们插入或者是删除元素时,是有可能改变容器本身的,就比如,他可能会迁移到其他地方,就算不迁移到其他地方,不管是插入元素或者是删除元素,也足以令一部分迭代器失效,所以,如果我们不重置p的话,我们接下来使用的会是无效的迭代器,这足以令程序宕掉,所以,为了安全起见,无论我们是插入元素还是删除元素,当我们要用迭代器时,就记得重迭代器。

这样解释大家应该明白了吧,但是就算我们可以这么操作,当我们真要在中间某个部位删除或者插入的时候,我们有更好的选择,嗯,为了不打乱vector的节奏,先等等再说吧。

现在我们再来看看vector的一些操作:
——————————–
template<typename T,typaname A=allocator<T>>
class vector{
public:

//

//

size_type size() const;



size_type max_size() const;



size_type capacety() const;


void resize(size_type n,T t=T());


void reserve(size_type n);



bool empty() const;


void swap(vector&

);



//

//
};


———————————

如果我们想要获取容器中的元素个数的时候,我们可以使用size()这个函数,max_size()这个函数得到的是容器的最大规模,通常是当前使用内存剩余量,而capacety()函数得到的则是当前分配内存的容量,也就是当前可容纳最多的元素个数。

resize(n,t)是更改容器的大小,如果n小于size(),那么最后得到的是容器的前n个元素,如果n>size(),那么多余size()的元素空位用t来初始化。那么reserve(n)的功能又是什么呢?他的功能看上去和resieze()差不多,但是又不一样,reserve(n)只是构造出一个能够容纳n个元素的空壳而已,不初始化,但是当n>max_size()的时候,就会抛出length_error的异常,这里给个友情提示,如果我们能够大致估计得到我们会储存多少元素的话,那么在使用vector之前,我们先使用这个函数,让他划分出一大块内存出来,这样一来,以后无论我们储存多少元素,都不会引起重新分配,还记得以前我们说过的吗?重新分配内存以为这要迁移一次数据,而且当数据比较大的时候,会发生多次重新分配的情况,伴随着的同样是数据迁移,这是效率的问题,作为C++程序员,如果这里都不考虑的话,那么还不如去学习java又或者C#,还简单易学呢。

empty()的定义式如下:
———————–
bool empty(){

return size() == 0;


}
———————–

所以,我们可以看得出,如果empty()为真,那么说明容器真的为空了。下面的才是重点,swap()这个函数本来早就想说的,但是一直没机会说,现在正好赶上,所以我们来谈谈这个函数,我们都知道标准库里面有这么个函数:
—————————-
namespace std{
template<typename T>

void swap(T&

t1,T&

t2){

T
temp = t1;


t1 = t2;


t2 = temp;


}
}
—————————–

这个函数只要是T支持赋值操作符就能够完美运行,但是如果这个函数就拿去交换两个vector,那么效率就会太低,因为这样对每一次swap的调用需要付出三次复制对象的代价,但是对某些类型来说,这种复制对象的过程就显得太过没有必要了,最为典型的就是以指针指向某对象的类型,当我们使用缺省swap的时候,我们原本只是打算交换两个指针,但是std::swap()并不知道这一点,他不但会交换指针,还会交换内部数据,这样一来,效率严重下滑,现在我们就是要来解决这个问题的——针对某个类型特化swap()。
——————————-
template<typename T,typename A>

void vector<T>::swap(vector<T>&

t){

using str::swap;



swap(*this,t);

//这里只是伪码,不必当真,我只是模拟这种技术,标准库里面的东西很复杂,如果大家想要弄清楚,可以去查看源码(在安装目录下include文件夹里可以找到该文件。
}
———————————
这里的swap是成员函数,我们还要特化一下缺省的swap,我们可以这样做:
———————————–
template<typename T,typename A>

void std::swap(vector<T>&

t1,vector<T>&

t2){

t1.swap(t2);


}
————————————
想必大家注意到了,我们这个并非全特化,而是偏特化,因为我们的vector本身就是一个模板类,所以对于我们不能够使用全特化的方式来特化swap,现在当我们调用swap的时候,编译器会优先选择我们为vector特化的版本。

那么是不是有人会问,怎么全特化呢?看下面,假如我们有这么一个class:
———————————–
class Base{
public:

……

void swap(Base&

b){

using std::swap;


swap(*this,b);


}

……

……
};


/*——下面是全特化—————-*/
namespace std{
template<>

void swap<Base>(Base&

a,Base&

b){

a.swap(b);



}
}
—————————————
上面这个就是我们为Base全特化的swap,当我们要交换Base对象的时候,编译器会优先选择专门化的swap,不过注意一点,std是个奇怪的命名空间,他是不允许用户添加内容,但是可以允许用户进行特化。
关于这一点,大家应该清楚了吧,当我们打算要用swap来置换两个对象的时候,我们应该为该对象写一个public的成员函数(swap),确保他不抛出异常,(如果是模板类)我们还要定义一个非成员的swap来调用成员函数(swap),如果是非模板类,那么特化要进行特化std::swap。
接下来,swap还有个奇妙之处,还记得我以前说过吗?当我们要回收一个vector的内存时,我们不是使用clear,也不是使用erase,而是使用swap,erase和clear通常只是删除元素,而不保证内存得到回收,要想回收内存,我们只能使用swap,让他和一个空的vector置换,如下:
————————————-

void f(vector<T>&

c){

vector<T>temp = c;



c.swap(temp);


}
———————————-
当我们程序执行出第二个花括号后,temp就会自动析构,内存得到回收,今天的东西可能有些不好理解,首先可能是我说得不够精彩,第二这些内容确实都是不容易弄清楚的,无论是写一个高效率的swap还是写使用swap回收内存都不是容易弄得懂的。
今天先这样吧,下一讲我们可以结束vector了,有不懂的就直接提问吧,一般下班回来看到后基本都会回复。
================================
回复D直接查看目录


原文始发于微信公众号(

C/C++的编程教室

):第六十七讲 细说vector(3)

|

发表评论