第六十八讲 细说vector(4)

由于每次都是临时写出来的东西,所以每次都没有时间回去检查,当然程序是通过验证的,所以至于文字方面会一塌糊涂,今天看到有人回复说,关于昨天说的swap有些看不懂,我回头看了下,好像节奏真的太过快了些,更是让人无法理解,虽然一开始我们说了针对指针指向一个对象,但是有些朋友可能还是会被后面太过简略的swap给绕晕了头,所以,想了下,在结束vector之前,我们还是把swap说清楚吧。

通常情况下,我们是可以使用缺省的swap的,而且不会对性能有所影响,但是当我们面对以指针指向对象的时候,这个时候,我们需要做的不过只是交换两个指针而已,但是缺省的swap不会明白我们的用意,而是交换所有的数据成员,一如我们上一讲所说的,交换数据成员带来的开销复制赋值,如果成员过多,付出的代价就会过高,相反,如果我们只是交换指向两个对象的指针,岂不是很轻松解决了吗?

说到这里,首先,我们得明白什么是以指针指向对象,内含真正的数据,还记得我们以前说过的一种技巧吗?就是让接口和实现彻底分离的手法,其实,接口类就是我们所说的指针对象,真正的数据在实现类中,如同下面:
————————————
class Base{
public:

………

………
private:

int ia,ib,ic;



double da.db,dc;



vector<int>vc;


};


————————————-

如果我们声明一个Base对象,我们会发现他里面包含了很多数据成员,当我们需要swap两个Base对象的时候会有至少12次赋值操作和6次的复制构造,当然,我们还没有算上vector的交换,那可能才是真正的大头,所以,如果我们使用缺省的swap版本来交换两个Base对象,效率可想而知,那么如果我们把接口和实现分离的话,我们可以再得到一个class:
————————————-
class Derive{
public:

……

……

Derive(const Derive&

);



Derive&

operator=(const Derive&

rhs){

if(this != &

rhs){

delete ptr;


ptr = new Base();


*ptr = *rhs.ptr;


}

return *this;


}

…………
private:

Base *ptr;


};


———————————–

如果我们有两个Derive对象,d1,d2,当我们使用std::swap(d1,d2)的时候,他就会默默的把两个对象的内部数据全都交换,这下可糟糕了,如果vector里面有大量的数据,这不是要花很长时间吗?我们本来只需要交换Derive里面的ptr就够了,所以,这时候,我们就要为Derive特化std::swap,让他知道我们需要他做些什么。
————————————
namespace std{
template<>//"

<>"

表示专门化,全特化

void swap<Derive>(Derive&

d1,Derive&

d2
{

swap(d1.ptr,d2.ptr);


}
}
————————————-

嗯,可能大家注意到了,这个全特化的版本看上去合理,好像是这么回事,而且我们想要的也就是这么一回事,不过大家更感兴趣的可能会是d1.ptr和d2.ptr了,因为ptr是Derive的私有成员,所以std::swap是无权访问他的,所以这个特化版本是无法通过编译的,那么我们该怎么解决呢?

就算上一讲中我说太过笼统,以至于一些朋友无法看懂,但是我相信有一句话,相信所有朋友都明白的,那就是,我们应该定义一个public成员函数,然后让std::swap去调用他就好,所以,我们可以这样添加一个public函数:
——————————

void Derive::swap(const Derive&

rhs){

using std::swap;



swap(ptr,rhs.ptr);


}
——————————-

现在我们再把上面的特化函数修改一下:
——————————–
namespace std{
template<>

void swap<Derive>(Derive&

d1,Derive&

d2
{

d1.swap(d2);


}
}
——————————–
现在一切都变得完美了,他不但可以顺利通过编译,还能够和std里的容器保持一致性,因为我们STL里面所有容器都是这么实现的。

还有一个问题,有人问,为什么说要写一个不抛出异常的swap呢?难道一个swap函数还会导致程序抛出异常?

嗯,为了说明这么问题,我们再来看看swap的过程:
———————————–
namespace std{
template<typename T>

void swap(T&

t1,T&

t2){

T
temp = t1;


t1 = t2;


t2 = temp;


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

看到这个原型是不是应该明白了呢,如果是复制构造还是赋值操作都极有可能抛出异常,现在应该明白了,自我赋值等等都极有可能在这里面出现,所以要写一个不抛出异常的swap,在复制构造和复制操作的时候就要极为小心严谨,否则以后跟踪bug的时候会痛苦死人的。

关于偏特化没啥好说的,大家结合上一讲和这一讲的内容就明白了,现在我们来说说vector剩下的内容,其实,到现在,我们需要探究的就是vector的构造函数了:
————————————
template<typename T,typaname A=allocator<T>>
class vector{
public:

//

//

explicit vector(const A&

= A());

//1

explicit vector(size_type n,const T&

t=val,const A&

= A());

//2

template<typename In>

vector(In first,In last,const A&

= A());

//3

vector(const vector&

);

//4

vector&

operator=(const vector&

);

//5

void assign(size_type n,const T&

t);

//6

template<typename In>

void assign(In first,In last);

//7

~vector();



//

//
};


—————————————

vector带有一组完整的构造函数,所以我们可以很方便的创建vector的对象,explicit的作用就是防止隐式转换:
vector<int>a(1000)这是表示我们创建容量大小为1000的vector,而不是创建一个元素为1000的vector,这就是explicit的作用,防止1000隐式转换为int。

第二个构造函数我们可以创建一个大小为n,把所有元素都初始化为val的vector。

第三个是一个模板构造函数,关于模板构造函数很有意思,不过这里不说了,要说起来又要至少得花费一个章节的内容,当然,我们以后会说。这里面的In其实就是一个迭代器,表示输入迭代器,有了这个构造函数后,我们可以用一个数组,或者一个链表,又或者一个队列来构造vector:
—————————-
vector<int>a(b,b+5);


vector<int>a(a.begin(),b.end();


—————————–
第四个是个复制构造函数,我们可以用一个vector对构造另一个vector,这个不难理解。
第五个是复制操作符,也不难理解。
第六个有些难以理解,不过也还好了,只是这期间不但包含了赋值,还包含了复制构造,例如:
————————-
vector<int>a(10,0);


a.assign(5,2);


————————-
我们重点来看第二句,第二句的意思是,将5个2的副本构造一个vector,然后给a赋值,所以最后我们的a包含了5个2(原本是10个0)。
理解了第六句后再来看第七句是不是一眼就明了了呢?
ok,关于vector就这样吧,我不是专业的,但是给大家展示的细节绝对是学习vector最好的资料,当然错别字可能会比较多,不过希望真的不要影响到大家的理解,好了,就这样吧,节后再见了。
============================

想了想还是给大家留一个练习吧:计算几何图形的面积,要求至少能够计算两种图形的面积,输出应该如下:
—————————
几何图形的名称:圆
几何图形的面积:3.1415926
几何图形的名称:矩形
几何图形的面积:1

—————————

记住,我们现在是在学C++,我希望大家能够把C++知识应用进去。
==============================
回复D直接查看目录


原文始发于微信公众号(

C/C++的编程教室

):第六十八讲 细说vector(4)

|

发表评论