- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
12.4 有关返回对象的说明
当成员函数或独立的函数返回对象时,有几种返回方式可供选择。可以返回指向对象的引用、指向对象的 const 引用或 const 对象。到目前为止,介绍了前两种方式,但没有介绍最后一种方式,现在是复习这些方式的好时机。
12.4.1 返回指向 const 对象的引用
使用 const 引用的常见原因是旨在提高效率,但对于何时可以采用这种方式存在一些限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高其效率。例如,假设要编写函数 Max(),它返回两个 Vector 对象中较大的一个,其中 Vector 是第 11 章开发的一个类。该函数将以下面的方式被使用:
下面两种实现都是可行的:
这里有三点需要说明。首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本所做的工作更少,效率更高。其次,引用指向的对象应该在调用函数执行时存在。在这个例子中,引用指向 force1 或 force2,它们都是在调用函数中定义的,因此满足这种条件。第三,v1 和 v2 都被声明为 const 引用,因此返回类型必须为 const,这样才匹配。
12.4.2 返回指向非 const 对象的引用
两种常见的返回非 const 对象情形是,重载赋值运算符以及重载与 cout 一起使用的<<运算符。前者这样做旨在提高效率,而后者必须这样做。
operator=() 的返回值用于连续赋值:
在上述代码中,s2.operator=() 的返回值被赋给 s3。为此,返回 String 对象或 String 对象的引用都是可行的,但与 Vector 示例中一样,通过使用引用,可避免该函数调用 String 的复制构造函数来创建一个新的 String 对象。在这个例子中,返回类型不是 const,因为方法 operator=() 返回一个指向 s2 的引用,可以对其进行修改。
Operator<<() 的返回值用于串接输出:
在上述代码中,operator<<(cout, s1)的返回值成为一个用于显示字符串“is coming!”的对象。返回类型必须是 ostream &,而不能仅仅是 ostream。如果使用返回类型 ostream,将要求调用 ostream 类的复制构造函数,而 ostream 没有公有的复制构造函数。幸运的是,返回一个指向 cout 的引用不会带来任何问题,因为 cout 已经在调用函数的作用域内。
12.4.3 返回对象
如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。请看下述示例,它再次使用了 Vector 类:
返回的不是 force1,也不是 force2,force1 和 force2 在这个过程中应该保持不变。因此,返回值不能是指向在调用函数中已经存在的对象的引用。相反,在 Vector::operator+( ) 中计算得到的两个矢量的和被存储在一个新的临时对象中,该函数也不应返回指向该临时对象的引用,而应该返回实际的 Vector 对象,而不是引用:
在这种情况下,存在调用复制构造函数来创建被返回的对象的开销,然而这是无法避免的。
在上述示例中,构造函数调用 Vector(x + b.x,y + b.y)创建一个方法 operator+() 能够访问的对象;而返回语句引发的对复制构造函数的隐式调用创建一个调用程序能够访问的对象。
12.4.4 返回 const 对象
前面的 Vector::operator+( ) 定义有一个奇异的属性,它旨在让您能够以下面这样的方式使用它:
然而,这种定义也允许您这样使用它:
这提出了三个问题。为何编写这样的语句?这些语句为何可行?这些语句有何功能?
首先,没有要编写这种语句的合理理由,但并非所有代码都是合理的。即使是程序员也会犯错。例如,为 Vector 类定义 operator==() 时,您可能错误地输入这样的代码:
而不是:
另外,程序员通常很有创意,这可能导致错误。
其次,这种代码之所以可行,是因为复制构造函数将创建一个临时对象来表示返回值。因此,在前面的代码中,表达式 force1 + force2 的结果为一个临时对象。在语句 1 中,该临时对象被赋给 net;在语句 2 和 3 中,net 被赋给该临时对象。
第三,使用完临时对象后,将把它丢弃。例如,对于语句 2,程序计算 force1 和 force2 之和,将结果复制到临时返回对象中,再用 net 的内容覆盖临时对象的内容,然后将该临时对象丢弃。原来的矢量全都保持不变。语句 3 显示临时对象的长度,然后将其删除。
如果您担心这种行为可能引发的误用和滥用,有一种简单的解决方案:将返回类型声明为 const Vector。例如,如果 Vector::operator+() 的返回类型被声明为 const Vector,则语句 1 仍然合法,但语句 2 和语句 3 将是非法的。
总之,如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类(如 ostream 类)的对象,它必须返回一个指向这种对象的引用。最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论