C++语言程序设计:第3讲 复制构造函数_第1页
C++语言程序设计:第3讲 复制构造函数_第2页
C++语言程序设计:第3讲 复制构造函数_第3页
C++语言程序设计:第3讲 复制构造函数_第4页
C++语言程序设计:第3讲 复制构造函数_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

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

文档简介

1、1 本讲知识要点本讲知识要点 2 #include #include using namespace std; class Time / (略略) ; int main() Time t1, t2(10, 20, 30), *p = t1.Show();/ 对象对象t1直接访问直接访问(调用调用)成员函数成员函数 t1.SetTime(8, 30, 0); / 发送消息给对象发送消息给对象t1 p-Show();/ 指针变量间接访问成员函数指针变量间接访问成员函数 t2.Show(); return 0; 对象的自我表现对象的自我表现 运行结果运行结果 00:00:00 08:30:00 10

2、:20:30 3 this指针指针 “身处代码区身处代码区”的成员函数是如何区分具体对象,操的成员函数是如何区分具体对象,操 作不同对象的数据空间的呢?例如:作不同对象的数据空间的呢?例如: 函数调用语句函数调用语句 t1.Show();如何找到对象如何找到对象t1的数据空间?的数据空间? Time:Time Time:SetTime Time:GetHour Time:GetMinute Time:GetSecond Time:Show :main 主函数返回值主函数返回值 保存状态,转移控制保存状态,转移控制 命令行参数命令行参数 栈底栈底 main p 初始化为初始化为 this - m

3、 = minute; this - s = second; 5 void Time:SetTime(Time *this, int hour, int minute, int second) this-h = hour; this-m = minute; this-s = second; int Time:GetHour(Time *this)return this-h; int Time:GetMinute(Time *this)return this-m; int Time:GetSecond(Time *this)return this-s; void Time:Show(Time *t

4、his) cout setfill(0) setw(2) h ”:” setw(2) m ”:” setw(2) s setfill( ) Show( p );/ 相当于传递指针变量相当于传递指针变量 t2.Show( return 0; 7 4.3.2 复制(拷贝)构造函数复制(拷贝)构造函数 拷贝构造函数被调用的总原则:拷贝构造函数被调用的总原则:当用一个已经存当用一个已经存 在的对象初始化一个正在创建的对象时,调用拷在的对象初始化一个正在创建的对象时,调用拷 贝构造函数贝构造函数。有如下三种常见场合。有如下三种常见场合 (1) 创建新对象时,用一个已经存在的对象作为初始化值创建新对象时,

5、用一个已经存在的对象作为初始化值 int main() Point A1(1,2); Point B(A1); /拷贝构造函数被调用拷贝构造函数被调用 coutB.GetX()endl; return 0; 8 4.3.2 拷贝构造函数拷贝构造函数 (2) 调用函数时,用实参(已经存在的对象)初始调用函数时,用实参(已经存在的对象)初始 化化值传递的形参值传递的形参(新创建的对象)(新创建的对象) void fun1(Point p) coutp.GetX()endl; void main() Point A2(1,2); fun1(A2); /调用拷贝构造函数调用拷贝构造函数 9 4.3.2

6、 拷贝构造函数拷贝构造函数 (3) 函数类类型对象函数类类型对象值返回值返回时,用函数所返回的表时,用函数所返回的表 达式(达式(一个已经存在的对象一个已经存在的对象)初始化)初始化值返回值返回时创时创 建的临时对象建的临时对象 Point fun2() Point A3(1,2); return A3; /调用拷贝构造函数调用拷贝构造函数 int main() Point B; B=fun2(); /注意:注意:VC适用,其他无适用,其他无 return 0; 10 4.3.2 拷贝构造函数拷贝构造函数 对于上述对于上述(2)-(3)两点,常称创建了对象的副本两点,常称创建了对象的副本 值传

7、递型形式参数对象(实参对象的副本)值传递型形式参数对象(实参对象的副本) 函数值返回时创建的临时对象(返回值的副本)函数值返回时创建的临时对象(返回值的副本) 创建副本需要花费时间和内存空间。因此,在今后的函数设创建副本需要花费时间和内存空间。因此,在今后的函数设 计中,计中,应该尽可能地使用引用型形式参数、引用返回类型来应该尽可能地使用引用型形式参数、引用返回类型来 避免拷贝构造对象避免拷贝构造对象。一般是在不得已的情况下才采用值传递。一般是在不得已的情况下才采用值传递 、值返回。、值返回。 然而,引用型形式参数传递即传递了对象本身,对引用型形然而,引用型形式参数传递即传递了对象本身,对引用

8、型形 式参数的修改,即是对实参对象的修改式参数的修改,即是对实参对象的修改 将形式参数设计成常量的引用(将形式参数设计成常量的引用(const),既可以避免拷贝构造,又),既可以避免拷贝构造,又 可以保护实参。可以保护实参。 不仅如此,调用这样的函数还能用常量对象作为实参。不仅如此,调用这样的函数还能用常量对象作为实参。 11 1 浅拷贝构造浅拷贝构造 下面将围绕下面将围绕名称类名称类的设计,按的设计,按“初步设计初步设计发现问题发现问题解决解决 问题问题”螺旋式递进,逐步展开讨论。螺旋式递进,逐步展开讨论。 为简单起见,仅设计一个数据成员为简单起见,仅设计一个数据成员 name 用于存储表示

9、名称用于存储表示名称 的字符串。的字符串。 为了看清何时调用了何种函数,在类的相关函数中添加一定为了看清何时调用了何种函数,在类的相关函数中添加一定 的输出语句。的输出语句。 C+有多种方法表示及处理字符串有多种方法表示及处理字符串 字符数组字符数组 字符指针字符指针 string类类 12 class ArrayName public: ArrayName(const char *pName=”NoName”); ArrayName(const ArrayName ArrayName(); void ChangeName(const char *pName); void Show() con

10、st; private: char name10; ; class PointName public: PointName(const char *pName=”NoName”); PointName(const PointName PointName(); void ChangeName(const char *pName); void Show() const; private: char *name; ; Sizeof(ArrayName) ? Sizeof(PointName) ? 10 4 Sizeof(void*) :4 13 思考问题思考问题 类类ArrayName对象的空间对象

11、的空间 基本空间基本空间 10字节,用于存放名字的具体内容;字节,用于存放名字的具体内容; 最多最多9个字符(另需存放串结束标志),超长时截断。个字符(另需存放串结束标志),超长时截断。 类类PointName对象的空间对象的空间 基本空间基本空间 sizeof(void*)字节(字节(4字节),用于存放一个地址值,字节),用于存放一个地址值, 所指之处为所指之处为C-字符串的首地址,字符串长度无限制;字符串的首地址,字符串长度无限制; 即名字的具体内容并不在对象的基本空间中!即名字的具体内容并不在对象的基本空间中! 资源空间资源空间 真正存放名字具体内容处。真正存放名字具体内容处。 如何定义

12、成员函数?如何定义成员函数? 如何定义成员函数?如何定义成员函数? ArrayName类的对象不需要其他资源类的对象不需要其他资源 14 / ArrayName.cpp #include #include using namespace std; class ArrayName public: ArrayName(const char *pName=”NoName”); ArrayName(const ArrayName ArrayName(); void ChangeName(const char *pName); void Show() const; private: char name1

13、0; ; 例例 (A) 15 ArrayName:ArrayName(const char *pName) strncpy(name, pName, sizeof(name); namesizeof(name)-1 = 0; cout”Constructing an object of ArrayName ” name ”.” endl; ArrayName:ArrayName(const ArrayName cout”COPY constructing an object of ArrayName” name ”.” endl; Arrayname:ArrayName() cout”Dest

14、ructing an object of ArrayName ” name ”.” endl; 注意此处为注意此处为sizeof 16 void ArrayName:ChangeName(const char *pName) cout ”ChangeName ” name; strncpy(name, pName, sizeof(name); namesizeof(name)-1 = 0; cout ” name ”.” endl; void ArrayName:Show() const cout ”ArrayName ” name ”.” endl; / / 类的设计完成类的设计完成 测试该

15、类的函数:测试该类的函数: 测试函数测试了测试函数测试了值传递、地址值传递、引用传递,值返回值传递、地址值传递、引用传递,值返回; 主函数中测试了主函数中测试了对象的创建、对象间的赋值运算对象的创建、对象间的赋值运算等。等。 注意此处为注意此处为sizeof 17 ArrayName fAN(ArrayName x, ArrayName *p, ArrayName r.ChangeName(Application); x.Show(); p-Show(); r.Show(); cout Return from fAN() . endl; return x; 18 int ArrayName_m

16、ain() ArrayName an1(Java Program); ArrayName an2(C+ Program); ArrayName an3(C# Program); cout nBegin fAN() . endl; fAN(an1, cout End of fAN() .n endl; an1.Show(); an2.Show(); an3.Show(); an2 = an1;/ 赋值操作赋值操作 cout nAfter assign an2 = an1; endl; an2.Show(); cout Return to Operating System. endl; retur

17、n 0; 19 ArrayName类的特点类的特点 对象的基本空间就是对象全部数据空间对象的基本空间就是对象全部数据空间 字符数组为容器存放字符串的内容;有字符数组为容器存放字符串的内容;有“最大长度最大长度”的限制的限制 关于成员函数定义中的说明关于成员函数定义中的说明 (1)由于数据成员)由于数据成员name是一个数组的数组名,不能对其赋值,需用是一个数组的数组名,不能对其赋值,需用 字符串拷贝函数。字符串拷贝函数。 (2)构造函数中使用)构造函数中使用 strncpy 函数是为了将从参数处传入的可能函数是为了将从参数处传入的可能 超长的字符串截短。成员函数超长的字符串截短。成员函数Cha

18、ngeName的情形是类似的。的情形是类似的。 (3)拷贝构造函数仅需要使用)拷贝构造函数仅需要使用 strcpy 函数即可,因为已经存在的函数即可,因为已经存在的 对象的数据成员字符串肯定不会超长。对象的数据成员字符串肯定不会超长。 (4)析构函数无须做其他事情,故仅输出一些信息。)析构函数无须做其他事情,故仅输出一些信息。 其实,若去掉类中的其实,若去掉类中的拷贝构造函数拷贝构造函数、析构函数析构函数,直接利用系,直接利用系 统提供的默认拷贝构造函数、默认的析构函数,除了无输出统提供的默认拷贝构造函数、默认的析构函数,除了无输出 语句外,程序的功能是相同的。语句外,程序的功能是相同的。 2

19、0 / PointName1.cpp #include #include using namespace std; class PointName1 public: PointName1(const char *pName=”NoName”) name = pName; void UpperName() strupr(name); void Show() const cout ” name ” endl; private: char *name; ; 例例(B) 反例之一反例之一 问:错在哪里?问:错在哪里? 答:错在构造函数的设计。答:错在构造函数的设计。 注:有些教材将这样的设计作为范例注

20、:有些教材将这样的设计作为范例 误导读者。误导读者。 21 int main() char str10 = ”Tom”, *ptr = new char10; strcpy(ptr, ”Jerry”); PointName1 pn1(str),pn2(ptr),pn3(”Snoopy”); pn1.Show(); pn2.Show(), pn3.Show(); cout ”Do something.” endl; strcpy(str, ”Winnie”); / 名字已被修改名字已被修改 pn1.Show(); / 对象却浑然不知对象却浑然不知 delete ptr; / 目标已不存在目标已不

21、存在 pn2.Show(); / 对象却浑然不知对象却浑然不知 pn3.UpperName(); / 对象不能操作自己的属性对象不能操作自己的属性 pn3.Show(); / 因为目标为常量因为目标为常量 return 0; 测试程序,暴露问题测试程序,暴露问题 22 反例之一错误的根本原因反例之一错误的根本原因 封装不严。事实上,没有封装封装不严。事实上,没有封装 对象数据成员对象数据成员(char *name;)的访问属性为的访问属性为private,得,得 到很好的保护。被封装,外部不能直接访问到很好的保护。被封装,外部不能直接访问name指针指针 ; 称称name所指向的目标为所指向的

22、目标为对象的间接属性对象的间接属性。反例中,对。反例中,对 象的间接属性未被封装。象的间接属性未被封装。 可见可见 对象的空间对象的空间 = = 对象的基本空间对象的基本空间 + + 对象的资源空间对象的资源空间 对象基本空间的封装容易表达、实现。对象基本空间的封装容易表达、实现。 对象的资源空间(间接属性的内容)也必须封装!对象的资源空间(间接属性的内容)也必须封装! 由谁来实现这种封装?由谁来实现这种封装? 怎样封装资源空间?怎样封装资源空间? 23 反例之一错误的根本原因反例之一错误的根本原因 错误的现象错误的现象 绕过对象,直接地、肆意操作对象的间接属性绕过对象,直接地、肆意操作对象的

23、间接属性 对象的间接属性内容被修改,对象却浑然不知对象的间接属性内容被修改,对象却浑然不知 对象的间接属性被释放(不存在了),对象却浑然不知对象的间接属性被释放(不存在了),对象却浑然不知 对象不能操作自己的属性,如对象不能操作自己的属性,如pn3 对象对象pn3的内容为字符串常量的内容为字符串常量 若若PointName1 pn4(str);使使pn4与与pn1的目标共享的目标共享 解决之道:解决之道: 封装、隐藏对象的间接属性;封装、隐藏对象的间接属性; 具体方法:对象拥有具体方法:对象拥有独立的、属于自己的独立的、属于自己的资源;资源; 该资源在构造对象时申请分配。该资源在构造对象时申请

24、分配。 需要重新定义构造函数!需要重新定义构造函数! 24 2 对象的资源空间对象的资源空间 创建对象时,由构造函数为该对象申请堆内存空间创建对象时,由构造函数为该对象申请堆内存空间 由直接属性(指针数据成员)记录该堆空间的首地址;由直接属性(指针数据成员)记录该堆空间的首地址; 尽量不向外界暴露该地址,使其为该对象所专有。尽量不向外界暴露该地址,使其为该对象所专有。 相应地,需要定义析构函数相应地,需要定义析构函数 在对象的生命期结束时,释放该堆内存资源在对象的生命期结束时,释放该堆内存资源 修改的程序见下面的例修改的程序见下面的例(C) 例例(C)解决了上述问题,但又引出新的问题,故仍然为

25、反例。解决了上述问题,但又引出新的问题,故仍然为反例。 25 / PointName2.cpp / 头文件,使用名字空间等语句(略)头文件,使用名字空间等语句(略) class PointName2 public: PointName2(const char *pName=”NoName”) name = new charstrlen(pName)+1; strcpy(name, pName); PointName2() if(name!=NULL) delete name; void UpperName() strupr(name); void Show() const cout ” nam

26、e ” endl; private: char *name; ; 例例(C) 反例之二反例之二 new操作所返回的地址只有操作所返回的地址只有 本对象记录、拥有本对象记录、拥有 注意此处为注意此处为strlen 26 int main() char str10 = ”Tom”, *ptr = new char10; strcpy(ptr, ”Jerry”); PointName2 pn1(str),pn2(ptr),pn3(”Snoopy”); pn1.Show(); pn2.Show(), pn3.Show(); cout ”Do something.” endl; strcpy(str,

27、”Winnie”); / 这个修改这个修改 pn1.Show(); / 不影响对象不影响对象pn1 delete ptr; / 这个释放这个释放 pn2.Show(); / 不影响对象不影响对象pn2 pn3.UpperName(); / 对象的属性自己作主对象的属性自己作主 pn3.Show();/ 以上均以上均OK PointName2 pn4 = pn1; / 浅拷贝构造浅拷贝构造, 引出新问题引出新问题 cout ”Return to Operatiing System.”endl; return 0; 测试程序,解决老问题,暴露新问题测试程序,解决老问题,暴露新问题 27 程序运行过

28、程分析程序运行过程分析 主函数执行主函数执行return 0;语句后,依次销毁语句后,依次销毁pn4,pn3,pn2和和 pn1 (调用它们的析构函数)(调用它们的析构函数) 销毁销毁pn4,将,将pn1的资源释放的资源释放(不应该)(不应该); 销毁销毁pn3、pn2(正确)(正确); 销毁销毁pn1,再次执行,再次执行delete释放同一处堆资源释放同一处堆资源(错误)(错误)。 T o0m 对象对象pn1的基本空间的基本空间对象对象pn1的资源空间的资源空间 对象对象pn4的基本空间的基本空间 默认拷贝构造(浅拷贝)的结果:默认拷贝构造(浅拷贝)的结果:

29、 不同对象的基本空间中的数据成员不同对象的基本空间中的数据成员 与与的值相等。的值相等。 即:指针指向相同的堆地址。即:指针指向相同的堆地址。 28 反例之二错误的根本原因反例之二错误的根本原因 拷贝构造的对象拷贝构造的对象pn4没有自己独立的资源空间没有自己独立的资源空间 导致共享其他对象私有的资源。这种导致共享其他对象私有的资源。这种“共享共享”是不好是不好 的。的。 解决方案解决方案 定义拷贝构造函数,实现对象资源的独立性。定义拷贝构造函数,实现对象资源的独立性。 具体地具体地 在拷贝构造函数中按已经存在的对象的尺寸申请属于在拷贝构造函数中按已经存在的对象

30、的尺寸申请属于 自己的资源空间大小;自己的资源空间大小; 拷贝已经存在对象的资源内容。拷贝已经存在对象的资源内容。 29 T o0m T o0m 对象对象pn1的基本空间的基本空间对象对象pn1的资源空间的资源空间 对象对象pn4的基本空间的基本空间 T o0m 对象对象pn4的资源空间的资源空间 PointName2(const PointName2 / 构造(申请)属于对象自己的资源空间构造(申请)属于对象自己的资源空间 strcpy(name, ); / 拷贝(复制)另一对象的资源内容拷贝(复制)另一对象的资源内容 3 深拷贝构造深拷贝构造

31、 30 3 深拷贝构造深拷贝构造 例例(D) 反例之三反例之三 增加深拷贝构造函数(改正反例之二的错误)增加深拷贝构造函数(改正反例之二的错误) 测试程序的测试程序的main函数中,引出新问题函数中,引出新问题 pn2 = pn1; / 赋值运算,又引出新问题赋值运算,又引出新问题 新问题表现在如下新问题表现在如下两个两个方面方面 对象对象pn2原来的资源丢失,无法释放原来的资源丢失,无法释放 对象对象pn2共享共享对象对象pn1的资源,将导致的资源,将导致 析构对象析构对象pn2时,释放时,释放pn1的资源的资源(不应该)(不应该) 析构对象析构对象pn1时出错时出错(错误)(错误) 改正方

32、案改正方案 修改对象之间修改对象之间赋值运算赋值运算的功能的功能 31 T o0m 对象对象pn1的基本空间的基本空间对象对象pn1的资源空间的资源空间 对象对象pn2的资源空间的资源空间对象对象pn2的基本空间的基本空间 Jr0ye r 系统提供的浅赋值运算系统提供的浅赋值运算 希望希望 主动释放原有资源(避免内存泄漏);主动释放原有资源(避免内存泄漏); 实现深赋值运算,保证各对象的资源独立。实现深赋值运算,保证各对象的资源独立。 32 4 赋值运算赋值运算 四个步骤四个步骤 首先主动释放原有资源(当非自身赋值时);首先主动释放原有资源(当非自身赋值时);

33、 重新构造新的资源空间(根据右值对象资源的尺寸)重新构造新的资源空间(根据右值对象资源的尺寸) ; 复制右值对象的资源空间内容;复制右值对象的资源空间内容; 返回本对象返回本对象(*this)。 T o0m 对象对象pn1的基本空间的基本空间对象对象pn1的资源空间的资源空间 对象对象pn2的资源空间的资源空间对象对象pn2的基本空间的基本空间 Jr0ye r T o0mT o0m 对象对象pn2的资源空间的资源空间 T o0m 33 关于赋值运算符函数关于赋值运算符函数 C+中,运算符是一种特殊的函数中,运算符是一种特殊的函数 被称为被称为运算符函数运算符函数 函数名函数名 operator运算符运算符operator为为C+保留字保留字 例如例如: operator=赋值运算符函数赋值运算符函数 opeartor+加法运算符函数加法运算符函数 operator插入运算符函数插入运算符函数 operator关系运算符关系运算符(小于小于)函数函数 operator=关系运算符关系运算符(等于等于)函数函数 运算符函数的其他特性

温馨提示

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

评论

0/150

提交评论