第3章 拷贝控制_第1页
第3章 拷贝控制_第2页
第3章 拷贝控制_第3页
第3章 拷贝控制_第4页
第3章 拷贝控制_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章拷贝控制本章主要内容:对象传递、复制和赋值 具有动态分配的类

拷贝构造 拷贝赋值

C++11移动构造

C++11移动赋值 std::move应用 典型范例——链表表示的集合类实现

*链集合向量空间扩充探讨

计算机学院李卫明对象的传递涉及对象的复制、赋值或转移。拷贝控制函数由类的拷贝构造、移动构造、拷贝赋值、移动赋值和析构五个函数组成。前面我们设计和实现的类,数据成员类型都是内置数据类型、固定大小数组、或类类型,这样的类类型一般无需定义上述五个拷贝控制函数,不影响这些类型对象的复制、赋值、转移,也不影响它们的正确析构。像集合、栈、字符串、向量等具有比较复杂的状态的对象,往往需要采用动态分配,如用链表存储集合内的元素、用动态分配的连续空间存放向量元素等,这些类需要定义和实现五个拷贝控制函数。

标准库提供的vector、string、list等类模板就是在动态分配基础上实现的。计算机学院李卫明3.1 对象传递、复制和赋值C++函数参数可以传递对象的引用、对象的地址、对象数组,也可以传递对象复制的副本。C++函数参数传递对象的引用:实参是对象名,形参本质上是实参的别名,实参和形参是同一个对象。对于大型对象,引用传递具有极高传递效率,是C++函数间最为普遍的参数传递方式,如果希望函数处理期间对象不发生变化,一般声明常引用。

假设CSet是一个类,常引用声明和调用形式如下: CSetUnion(constCSet&rhs); C=A.Union(B);计算机学院李卫明C++函数参数传递对象的地址:实参是对象的地址,形参是类指针类型,形参指向实参对象。对于大型对象,一样具有极高传递效率,但不如引用传递方式直接明了,较少采用,如果希望函数处理期间对象不发生变化,可以声明指针所指对象不可变。传递对象地址时,函数声明和调用形式如下: CSetUnion(constCSet*pSet); C=A.Union(&B);计算机学院李卫明C++函数参数传递对象数组:实参是对象数组名,形参是元素具有类类型的数组,形参实际复制了代表数组起始地址的实参值。对于大型对象数组,具有极高传递效率,一般传递对象数组需求较少。传递对象数组时,函数声明和调用形式如下:doubleTotalSize(CCircleallCircles[],intiCount);x=TotalSize(circles,n);//circles是CCircle对象组成的数组计算机学院李卫明C++函数参数采用传值形式:实参是对象名,形参是根据实参对象在运行栈上新复制构造的对象,构造完成后,形参和实参是2个独立的对象,形参变化,实参不变。对于大型对象,复制效率较低,一般在确实必要时才采用。函数声明和调用形式如下: voidDoSomeThing(CSampleobj); DoSomeThing(obj);计算机学院李卫明与参数传递类似,关于函数返回值类型,也有返回对象引用、对象指针和对象值类型三种方式。对于局部对象,由于函数执行完毕后局部对象会析构,因此,不可返回局部对象的引用或指针,否则,根据函数返回结果去访问对象会导致不确定的错误结果。当函数返回对象值类型时,编译器会通过复制构造一个临时副本对象返回,因此,函数返回值具有对象类型时,函数可以返回局部对象。计算机学院李卫明C++程序设计中,还经常需要显式根据对象A复制构造一个新对象B,如:CSampleB(A);或CSampleB{A};或CSampleB=A;除上述参数传值、函数返回对象和显式复制构造外,如果对象作为另一个类型大对象的子对象,随着大对象的复制构造,子对象也会复制构造。对象的复制是通过复制构造函数完成的,复制构造函数也称为拷贝构造函数。

通常,C++编译器会自动合成复制构造函数,合成的复制构造函数实际效果是逐个数据成员的复制。如果一个类的所有数据成员类型都是内置数据类型、固定大小数组,或类类型,编译器合成的复制构造函数就是我们需要的效果,如同前面简单集合类所示,对于这样的类,我们无需特殊处理就可以使用复制构造。计算机学院李卫明类似情况,C++程序中经常需要给对象赋值,如:CSampleA,B;...B=A;上述赋值语句将对象A赋值给对象B。执行赋值前已存在2个独立对象A、B,赋值完成后2个对象状态相同:对象B变成对象A一样的状态。注意,这与前述对象A复制构造对象B不同,复制构造前只有一个对象A存在,复制构造完成后有2个独立且状态一样的对象。对象的赋值是通过赋值运算符完成的。通常,C++编译器会自动合成赋值运算符,合成的赋值运算符实际效果是逐个数据成员的赋值。如果一个类的所有数据成员类型都是内置数据类型、固定大小数组,或类类型,编译器合成的赋值运算符就是我们需要的效果,如同前面简单集合类所示,对于这样的类,我们无需特殊处理就可以使用赋值。计算机学院李卫明//使用缺省复制构造函数和赋值运算符的示例。classTime{private:

inthour; //时

intminute; //分

intsecond; //秒public:

Time(inth=0,intm=0,ints=0) :hour(h),minute(m),second(s){} //构造函数

voidSet(inth,intm,ints) //设置时间

{hour=h;minute=m;second=s;} voidShow()const //显示时间

{cout<<hour<<":"<<minute<<":"<<second<<endl;}};intmain() //主函数main(){ Timet1(6,16,18),t2; //构造函数的参数都采用默认值

t1.Show(); //显示时间6:16:18 t2=t1; //利用合成赋值函数赋值 t2.Show(); //显示时间6:16:18 Timet3(t1); //利用合成拷贝构造函数构造对象t3 t3.Show(); //显示时间6:16:18}计算机学院李卫明3.2 具有动态分配的类C++程序中,并非所有类的数据成员类型都是内置数据类型、固定大小数组,还经常遇到一些如集合、栈、字符串、向量等对象,具有比较复杂的状态,为表示这些状态复杂多变的对象,往往需要采用动态分配,如用链表表示集合内的元素、用动态分配的连续空间存放向量内容等等,如标准库的vector、string、set等类模板就是在动态分配基础上实现的。对于具有动态分配的类,使用编译器合成的拷贝构造函数和复制赋值运算符,运行时会导致严重问题,但编译器并不会发出警告。计算机学院李卫明下面以链集合类和简单字符串类为例,分析需要动态分配的类如何设计和实现拷贝控制函数。

链集合类用带头结点的单链表存储集合内元素,不限定集合元素个数。图3.1链集合对象内存状态示意图计算机学院李卫明

图3.2链集合类对象的拷贝构造前状态示意图图3.3编译器合成的链集合类拷贝构造效果图计算机学院李卫明3.2.1 拷贝构造使用默认复制构造函数可能出现运行时错误默认复制构造函数只简单地将源对象的数据成员的值复制给目的对象的相应数据成员当类具有指针成员时,缺省拷贝构造函数为浅层复制。当一个类中包含指针类型的数据成员,并且通过指针在构造函数中动态申请了存储空间,在析构函数中通过指针释放了动态存储空间,这种情况下默认复制构造函数将会出现运行时错误。A对象指针成员B对象浅层复制计算机学院李卫明//使用默认复制构造函数出现运行时错误的示例。classString{private:

char*strValue; //串值public:

String(char*s="") //构造函数

{ if(s==NULL)s=""; //将空指针转化为空串

strValue=newchar[strlen(s)+1]; //分配存储空间

strcpy(strValue,s); //复制串值

} ~String(){delete[]strValue;} //析构函数

voidShow(){cout<<strValue<<endl;} //显示串

};intmain() //主函数main(){ Strings1(“test"); //调用普通构造函数的生成对象s1 Strings2(s1); //调用默认复制构造函数的生成对象s2 s1.Show(); //显示串s1 s2.Show(); //显示串s2}程序运行时可能的屏幕输出如下:testtest请按任意键继续...当用户按任一键时,屏幕将会显示类似DebugAssertionFailed!的错误计算机学院李卫明在执行“Strings1(“test”);”语句时,构造函数动态地分配存储空间,并将返回的地址赋给对象s1的成员变量strValue,然后把“Test”拷贝到这块空间中:执行语句“Strings2(s1);”时,系统将调用默认的复制构造函数,负责将对象s1的数据成员strValue中存放的地址值赋值给对象s2的数据成员strValue:当遇到对象的生命期结束需要撤销对象时,首先由s2对象调用析构函数,将strValue成员所指向的字符串“Test”所在的动态空间释放:在对象s1自动调用析构函数之前,对象s1的数据成员strValue指向已释放的内存空间,因此在s1调用析构函数时,无法正确执行析构函数代码“delete[]strValue”,从而导致出错计算机学院李卫明编译器合成的拷贝构造也称为浅复制构造,对于采用动态分配的链集合类并不适用。链集合类需要重载拷贝构造函数,才能达到正确效果,如图3.4所示。这样重载的拷贝构造函数称为深复制构造。深复制构造完成后A、B对象相互独立存在,互不影响。拷贝构造函数形式如下:CSet(constCSet&rhs);图3.4链集合类重载的拷贝构造效果图计算机学院李卫明定义字符串类复制构造函数解决动态内存的问题定义复制构造函数,采用深层复制,通过复制指针数据成员strValue所指向的动态空间中的内容。这样,两个对象的指针成员strValue就拥有不同的地址值,指向不同的动态存储空间,但两个动态空间中的内容完全一样。计算机学院李卫明//定义复制构造函数避免默认构造函数的副作用。classString{private:

char*strValue; //串值public:

String(char*s="") //构造函数

{ if(s==NULL)s=""; //将空指针转化为空串

strValue=newchar[strlen(s)+1];//分配存储空间

strcpy(strValue,s); //复制串值

} String(constString©) //复制构造函数

{ strValue=newchar[strlen(copy.strValue)+1];//分配空间

strcpy(strValue,copy.strValue); //复制串值

} ~String(){delete[]strValue;} //析构函数

voidShow(){cout<<strValue<<endl;} //显示串 };intmain() //主函数main(){ Strings1(“test"); //调用普通构造函数的生成对象s1 Strings2(s1); //调用复制构造函数的生成对象s2

s1.Show(); //显示串s1 s2.Show(); //显示串s2 ……}程序运行时屏幕输出如下:testtest请按任意键继续...计算机学院李卫明

使用复制构造的三种情形

一.构造对象时使用同类对象显式初始化: CSet B;...CSet A=B;或CSetA(B);或CSetA{B};//C++11二.函数调用参数调用采用传值方式时,实参采用拷贝构造方式传递给形参voidFun(CSample obj); ...Fun(someObj);三.函数调用返回值对象时queue<int> GetIntQueue(){ queue<int>inputQueue;.... returninputQueue;}此外,作为对象的成员(子对象)随宿主对象的复制构造而复制构造计算机学院李卫明如用户没有为一个类重载赋值运算符,编译程序将生成一个默认赋值运算符函数,把源对象的数据成员逐个赋值给目的对象的相应数据成员.对于一般的类,使用默认赋值运算符函数都能正常地工作,但当一个类中包含有指针类型的数据成员,并且通过指针在构造函数中动态申请了存储空间,在析构函数中通过指针释放了动态存储空间,这种情况可能会出现运行时错误;类似于拷贝构造情况。一般地,如果一个类重载了拷贝构造函数,而且使用了=,那么也需要重载=。计算机学院李卫明3.2.2 拷贝赋值

图3.5链集合类对象的赋值前状态示意图图3.6B=A;链集合类编译器合成的拷贝赋值效果图计算机学院李卫明//使用赋值运算符出现运行时错误的示例。classString{private:

char*strValue; //串值public:

String(constchar*s="") //构造函数

{ if(s==NULL)s=""; //将空指针转化为空串

strValue=newchar[strlen(s)+1]; //分配存储空间

strcpy(strValue,s); //复制串值

} String(constString©) //复制构造函数

{ strValue=newchar[strlen(copy.strValue)+1];//分配空间

strcpy(strValue,copy.strValue); //复制串值

} ~String(){delete[]strValue;} //析构函数

voidShow()const{cout<<strValue<<endl;} //显示串};……计算机学院李卫明intmain() //主函数main(){ Strings1("try"),s2; //定义对象

s2=s1; //使用默认赋值运算符函数

s1.Show(); //显示串s1 s2.Show(); //显示串s2}程序运行时屏幕可能输出如下:trytry请按任意键继续...当用户按任一键时,屏幕将会显示类似DebugAssertionFailed!的错误计算机学院李卫明在执行“Strings1(“try”);”语句时,构造函数动态地分配存储空间,并将返回的地址赋给对象s1的数据成员strValue,然后把“try”拷贝到这块空间中执行语句“s2=s1;”时,由于没有为类String重载赋值运算符,系统将调用默认赋值运算符函数,负责将对象s1的数据成员strValue中存放的地址值赋值给对象s2的数据成员strValue对象s1复制给对象s2的仅是其数据成员strValue的值,并没有把strValue指向的动态存储空间进行复制,当遇到对象的生命期结束需要撤销对象时,首先由s2对象调用析构函数,将strValue成员所指向的字符串“try”所在的动态空间释放在对象s1自动调用析构函数之前,对象s1的数据成员strValue指向已释放的内存空间,因此在s1调用析构函数时,无法正确执行析构函数代码“delete[]strValue”,从而导致出错。计算机学院李卫明编译器合成的拷贝赋值运算也称为浅拷贝赋值,对于采用动态分配的类并不适用。

链集合类需要重载赋值运算符,才能达到正确效果,释放B集合对象的原链表,再将A对象的链表复制过来,复制后状态如图3.7所示。这样重载的赋值运算称为深拷贝赋值。深拷贝赋值完成后A、B对象相互独立存在,互不影响。拷贝赋值运算符重载形式如下:CSet&operator=(constCSet&rhs);图3.7链集合类重载的拷贝赋值效果图计算机学院李卫明字符串类重载赋值运算符对于字符串类,也应重载赋值运算符,复制指针数据成员strValue所指向的动态空间中的内容。这样,两个对象的指针成员strValue就拥有不同的地址值,指向不同的动态存储空间:计算机学院李卫明赋值运算符=重载的一般形式C++规定赋值运算符=只能重载为类的成员函数,一般重载格式为:类名&类名::operator=(const类名&源对象){ if(this!=&源对象) { //目的对象与源对象不是同一个对象

…… //复制被被赋值对象

} return*this; //返回目的对象}计算机学院李卫明//重载赋值运算符避免使用默认赋值运算符的严重问题。classString{private:

char*strValue; //串值public:

String(constchar*s="") //构造函数

{ if(s==NULL)s=""; //将空指针转化为空串

strValue=newchar[strlen(s)+1];//分配存储空间

strcpy(strValue,s); //复制串值

} String(constString©) //复制构造函数

{ strValue=newchar[strlen(copy.strValue)+1];//分配空间

strcpy(strValue,copy.strValue); //复制串值

} String&operator=(constString©); //重载赋值运算符

~String(){delete[]strValue;} //析构函数

voidShow()const{cout<<strValue<<endl;} //显示串};……计算机学院李卫明String&String::operator=(constString©) //重载赋值运算符{ if(this!=©) {//目的对象与源对象不是同一个对象 delete[]strValue;

strValue=newchar[strlen(copy.strValue)+1];//分配空间

strcpy(strValue,copy.strValue);//复制串值

} return*this; //返回目的对象}intmain() //主函数main(){ Strings1("try"),s2; //定义对象

s2=s1; //使用重载赋值运算符

s1.Show(); //显示串s1 s2.Show(); //显示串s2}程序运行时屏幕输出如下:trytry请按任意键继续...计算机学院李卫明//重载赋值运算符的另一种好方法,更符合异常安全特性。……String&String::operator=(constString&rhs) //重载赋值运算符{ String copy(rhs); //此时,如果发生异常,不影响原对象 swap(strValue,copy.strValue);

return*this; //返回目的对象}intmain() //主函数main(){ Strings1("try"),s2; //定义对象

s2=s1; //使用重载赋值运算符

s1.Show(); //显示串s1 s2.Show(); //显示串s2}程序运行时屏幕输出如下:trytry请按任意键继续...计算机学院李卫明

我们通过重载拷贝构造函数和拷贝赋值解决了具有动态分配类的对象的拷贝构造和赋值问题,可保证我们程序的正确执行效果。

考虑如下语句:CSetC=A.Union(B);执行过程中集合A与集合B进行并运算,结果先保存在一个局部对象中,并运算返回后,编译器通过重载的拷贝构造将结果局部对象复制给临时匿名返回值集合对象,局部对象消失,执行析构函数,释放链表,最后,再根据匿名返回值对象复制构造对象C,C得到正确结果,匿名返回值对象消失,执行析构函数,释放链表。这个过程结果正确,也没有造成内存泄漏,但内含若干次不必要的链表复制和释放,造成极大性能浪费,效率极低。计算机学院李卫明3.2.3 C++11移动构造

C++11之前主要通过编译器优化解决这个效率问题,但有些情况下,编译器无法解决这一效率问题,如交换2个链集合对象语句:CSettmp=A;A=B;B=tmp;这些语句执行过程中存在多次链表复制和销毁,极大影响了执行效率。计算机学院李卫明C++11引入了移动构造和移动赋值用于解决这一类问题,移动构造也称转移构造。

根据A对象移动构造B对象前,A、B对象状态与图3.2所示拷贝构造时状态一样。A对象事后无需使用,只需简单的将A对象链表转移给B对象,A对象不再拥有链表,A对象指针置nullptr,消失时析构函数不再释放链表,实现高效完美的资源即链表的转移,这也是转移构造名称的由来。图3.8是转移后的效果图。图3.2链集合类的移动构造前效果图图3.8链集合类的移动构造效果图计算机学院李卫明如果我们用临时对象给对象赋值,也存在类似情况。考虑如下语句:C=A.Union(B);执行过程中集合A与集合B并运算,结果先保存在一个局部对象中,并运算返回后,编译器先将保存在局部复制一个临时匿名返回值集合对象,局部对象消失,执行析构函数,释放链表,最后,再根据匿名返回值对象赋值给对象C,C得到正确结果,匿名返回值对象消失,执行析构函数,释放链表。这个执行过程结果正确,也没有造成内存泄漏,但内含若干次不必要的链表复制、赋值和释放,造成极大性能浪费,效率极低。同样,C++98主要通过编译器优化解决这个效率问题,但有些情况下,编译器无法解决这一效率问题,如前面交换2个链集合对象。C++11引入了移动赋值用于解决这一问题,移动赋值也称转移赋值。计算机学院李卫明3.2.4 C++11移动赋值将对象A移动赋值给B时,A、B对象状态与移动构造时不同,与图3.5所示复制赋值时相同,赋值前对象B已存在,A、B对象事先均拥有链表。

图3.5链集合类的移动赋值前效果图计算机学院李卫明如果A对象事后无需使用,只需先释放B对象的链表,再将A对象链表转移给B对象,A对象不再拥有链表,A对象指针置nullptr,消失时析构函数不再释放链表,实现高效完美的资源即链表的转移。图3.9是这样方法处理后的移动赋值效果示意图。

图3.9链集合类的移动赋值效果图1

计算机学院李卫明更好、更简单的方法。

当前对象废弃的资源(链表)转移给临时对象A,将来临时对象消失时会执行析构函数,完成资源的释放。图3.10是采用这一处理方法的移动赋值效果示意图。图3.10链集合类的移动赋值效果图2计算机学院李卫明转移构造函数和转移赋值运算符一般形式如果传进来的对象是一个临时对象(马上就销毁),我们自然希望能够继续使用这个临时对象的空间,这样可以节省申请空间和复制的时间。C++11支持新移动构造和移动赋值机制,它将临时对象(马上就销毁)资源转移至新对象,显著提高执行效率。语法形式如下:CSample(CSample&&rhs);CSample&operator=CSample(CSample&&rhs);保证不会抛出异常时,最好声明:CSample(CSample&&rhs)noexcept;CSample&operator=CSample(CSample&&rhs)noexcept;现代C++STL库已据此作修正.noexcept声明无异常,有利于容器内使用时优化性能(如vector扩展空间时,会调用无异常的移动版本来完成搬动,不会调用有异常的移动版本),如有异常,不可声明noexcept。

本章第4节链集合向量空间扩充探讨,说明了需要声明noexcept的理由。移动构造和移动赋值后必须保证源对象可正常析构,一般应该可以正常重新赋值(内容已变空)。计算机学院李卫明#include<iostream>#include<memory>#include<cstring>usingnamespacestd;classString{private: char*strValue; //串值public: String(constchar*s=""); //构造函数 String(constString©); //复制构造函数 String(String&©)noexcept; //移动构造函数 String&operator=(constString©); //重载赋值运算符 String&operator=(String&©)noexcept;//移动赋值 StringReverse()const;

//返回逆序字符串 ~String(){delete[]strValue;} //析构函数 voidShow()const; //显示串};计算机学院李卫明String::String(constchar*s) //构造函数{if(s==NULL)s=""; //将空指针转化为空串strValue=newchar[strlen(s)+1];//分配存储空间strcpy(strValue,s); //复制串值}String::String(constString©) //复制构造函数{strValue=newchar[strlen(copy.strValue)+1];//分配空间strcpy(strValue,copy.strValue); //复制串值}String::String(String&©) noexcept //移动构造函数{strValue=copy.strValue;//转移字符串值copy.strValue=NULL; //已转移,避免再次delete}计算机学院李卫明String&String::operator=(constString©) //重载赋值运算符{ if(this!=©) {//目的对象与源对象不是同一个对象 delete[]strValue; strValue=newchar[strlen(copy.strValue)+1];//分配空间 strcpy(strValue,copy.strValue);//复制串值 } return*this; //返回目的对象}String&String::operator=(String&©)noexcept{ //移动赋值char*t=strValue;strValue=copy.strValue;copy.strValue=t;//交换字符串值return*this;}计算机学院李卫明StringString::Reverse()const//返回逆序字符串{StringstrResult(*this);intiLength=strlen(strValue);for(inti=0;i<iLength/2;i++){charc=strResult.strValue[i];strResult.strValue[i]=strResult.strValue[iLength-1-i];strResult.strValue[iLength-1-i]=c;}returnstrResult;}voidString::Show()const//显示串{if(strValue)cout<<strValue<<endl;elsecout<<endl;}

计算机学院李卫明intmain() //主函数main(){ Strings1("try"),s2,s3; //定义对象 s2=s1; //使用重载赋值运算符 s3=s1.Reverse();//返回临时字符串对象使用移动赋值 s1.Show(); //显示串s1 s2.Show(); //显示串s2 s3.Show(); //显示串s2 Strings4(std::move(s1));

//强制使用移动构造,头文件 //<utility> s1.Show(); //显示串s1 s4.Show(); //显示串s4}计算机学院李卫明

一般来说,没有使用动态分配的类无需定义拷贝控制函数;具有动态分配的类需要同时定义组成拷贝控制的五个函数:拷贝构造函数、拷贝赋值运算符、析构函数、移动构造函数、移动赋值运算符,拷贝控制函数中析构函数用来释放对象占用的资源;后两个函数是C++11新引入的,这个法则就是通常所述的三/五法则。如果类没有定义自己的拷贝构造函数、拷贝赋值、移动构造函数、移动赋值,编译器会给所有的类提供合成的拷贝构造函数、拷贝赋值、移动构造函数、移动赋值。如果类已含有复制构造、复制赋值、移动构造、移动赋值函数之一,编译器不会合成其它几个函数。当然,如果由于类具有无法拷贝构造、拷贝赋值、移动构造、移动赋值的数据成员时,编译器也无法合成相应函数。除已删除相应功能的特殊容器外,C++11标准库提供的容器都已实现拷贝构造、拷贝赋值、移动构造、移动赋值和析构这五个拷贝控制函数。现代C++程序设计需要表示复杂对象状态时,应该优先使用STL提供的容器作为类成员,这样就无需使用动态分配,这样的类也就无需定义拷贝控制有关函数,直接由编译器合成即可。计算机学院李卫明3.2.5 std::move应用使用类的拷贝控制函数时,一般由编译器决定实际使用的是拷贝版还是移动版,针对临时对象如函数返回值对象,编译器使用移动构造或移动赋值,对于具有名字的普通对象,编译器会使用拷贝构造或拷贝赋值。如果需要对具有名字的普通对象使用移动构造或移动赋值,C++标准库提供了std::move函数模板,视指定具名对象为临时对象,拷贝或赋值时使用移动构造或移动赋值。std::move可适合各类对象,使用方法与普通函数基本相同,关于函数模板,详见第6章。如下述函数交换2个链集合对象时,使用移动构造和移动赋值完成交换,具有非常高的效率,算法时间复杂性为O(1)。voidswapSet(CSet&A,CSet&B){CSettmp=std::move(A);//根据A移动构造tmpA=std::move(B); //B移动赋值给AB=std::move(tmp); //tmp移动赋值给B}实际无需自己定义上述函数,C++标准库提供了函数模板swap。可以直接调用:swap(A,B);完成对象交换。只要对象的类具有高效率、无异常的移动构造、移动赋值,这样的交换就是高效、无异常的。计算机学院李卫明3.3 典型范例——链表表示的集合类实现样例Ex3.1综合以上分析,设计实现了功能较完备的集合类,集合内元素采用带头结点单链表表示,集合元素类型为整形,元素递增排序。样例里集合类支持集合显示和元素增加、查询,并支持集合并运算,运算结果返回集合对象;样例设计和实现了拷贝构造和拷贝赋值、移动构造和移动赋值、析构函数五个拷贝控制函数,不会有内存泄漏。样例利用该集合类完成了基本测试,测试程序输入数据开始为两个正整数m,n;后续m个整数构成集合A,再后续n个整数构成集合B,输出集合A、B和他们的并集。限于篇幅,具体样例代码和介绍参见附件。计算机学院李卫明3.4 *链集合向量空间扩充探讨STL提供了程序设计中广泛使用的向量类模板。如果向量元素类型为链集合时,STL向量类模板就可以产生链集合向量模板类,它的实例就是链集合向量,是一种复杂的典型容器对象。链集合向量可以存放若干链集合对象,并且具有根据需要调用pushback在尾部添加链集合对象的能

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论