c语言中,什么都是通过传值来实现的,c++继承了这一传统并将它作为默认方式。除非明确指定,函数的形参总是通过
“实参的拷贝”来初始化的,函数的调用者得到的也是函数返回值的拷贝。
正如我在本书的导言中所指出的,“通过值来传递一个对象”的具体含义是由这个对象的类的拷贝构造函数定义的。这使
得传值成为一种非常昂贵的操作。例如,看下面这个(只是假想的)类的结构:

代码

class person {
public:
person(); // 为简化,省略参数
//
~person();
...
private:
string name, address;
};
class student: public person {
public:
student(); // 为简化,省略参数
//
~student();
...
private:
string schoolname, schooladdress;
};

现在定义一个简单的函数returnstudent,它取一个student参数(通过值)然后立即返回它(也通过值)。定义完后,调用这
个函数:
student returnstudent(student s) { return s; }
student plato; // plato(柏拉图)在
// socrates (苏格拉底)门下学习
returnstudent(plato); // 调用returnstudent
这个看起来无关痛痒的函数调用过程,其内部究竟发生了些什么呢?
简单地说就是:首先,调用了student的拷贝构造函数用以将s初始化为plato;然后再次调用student的拷贝构造函数用以将函
数返回值对象初始化为s;接着,s的析构函数被调用;最后,returnstudent返回值对象的析构函数被调用。所以,这个什么
也没做的函数的成本是两个student的拷贝构造函数加上两个student析构函数。
但没完,还有!student对象中有两个string对象,所以每次构造一个student对象时必须也要构造两个string对象。student对象
还是从person对象继承而来的,所以每次构造一个student对象时也必须构造一个person对象。一个person对象内部有另外两
个string对象,所以每个person的构造也必然伴随另两个string的构造。所以,通过值来传递一个student对象最终导致调用了
一个student拷贝构造函数,一个person拷贝构造函数,四个string拷贝构造函数。当student对象被摧毁时,每个构造函数对
应一个析构函数的调用。所以,通过值来传递一个student对象的最终开销是六个构造函数和六个析构函数。因为
returnstudent函数使用了两次传值(一次对参数,一次对返回值),这个函数总共调用了十二个构造函数和十二个析构函
数!
在c++编译器的设计者眼里,这是最糟糕的情况。编译器可以用来消除一些对拷贝构造函数的调用(c++标准——见条款
50—— 描述了具体在哪些条件下编译器可以执行这类的优化工作,条款m20给出了例子)。一些编译器也这样做了。但在不
是所有编译器都普遍这么做的情况下,一定要对通过值来传递对象所造成的开销有所警惕。
为避免这种潜在的昂贵的开销,就不要通过值来传递对象,而要通过引用:
const student& returnstudent(const student& s)
{ return s; }
这会非常高效:没有构造函数或析构函数被调用,因为没有新的对象被创建。
通过引用来传递参数还有另外一个优点:它避免了所谓的“切割问题(slicing problem)”。当一个派生类的对象作为基类
对象被传递时,它(派生类对象)的作为派生类所具有的行为特性会被“切割”掉,从而变成了一个简单的基类对象。这
往往不是你所想要的。例如,假设设计这么一套实现图形窗口系统的类:

class window {
public:
string name() const; // 返回窗口名
virtual void display() const; // 绘制窗口内容
};
class windowwithscrollbars: public window {
public:
virtual void display() const;
};

每个window对象都有一个名字,可以通过name函数得到;每个窗口都可以被显示,着可以通过调用display函数实现。
display声明为virtual意味着一个简单的window基类对象被显示的方式往往和价格昂贵的windowwithscrollbars对象被显示的方
式不同(见条款36,37,m33)。
现在假设写一个函数来打印窗口的名字然后显示这个窗口。下面是一个用错误的方法写出来的函数:
// 一个受“切割问题”困扰的函数
void printnameanddisplay(window w)
{
cout << w.name();
w.display();
}
想象当用一个windowwithscrollbars对象来调用这个函数时将发生什么:
windowwithscrollbars wwsb;
printnameanddisplay(wwsb);
参数w将会作为一个windows对象而被创建(它是通过值来传递的,记得吗?),所有wwsb 所具有的作为
windowwithscrollbars对象的行为特性都被“切割”掉了。printnameanddisplay内部,w的行为就象是一个类window的对象
(因为它本身就是一个window的对象),而不管当初传到函数的对象类型是什么。尤其是,printnameanddisplay内部对
display的调用总是window::display,而不是windowwithscrollbars::display。
解决切割问题的方法是通过引用来传递w:
// 一个不受“切割问题”困扰的函数
void printnameanddisplay(const window& w)
{
cout << w.name();
w.display();
}
现在w的行为就和传到函数的真实类型一致了。为了强调w虽然通过引用传递但在函数内部不能修改,就要采纳条款21的建
议将它声明为const。
传递引用是个很好的做法,但它会导致自身的复杂性,最大的一个问题就是别名问题,这在条款17进行了讨论。另外,更
重要的是,有时不能用引用来传递对象,参见条款23。最后要说的是,引用几乎都是通过指针来实现的,所以通过引用传
递对象实际上是传递指针。因此,如果是一个很小的对象——例如int—— 传值实际上会比传引用更高效。

转载于:https://www.cnblogs.com/lancidie/archive/2010/10/28/1863424.html

条款22: 尽量用“传引用”而不用“传值”相关推荐

  1. effective c++20 尽量以传引用代替传参,pass-by-reference-to-const 代替pass-by-value

    原则:尽量以pass-by-reference-to-const 替换pass-by-value,前者通常情况效率更好,并且可以避免切割问题:但该原则并不适用stl的迭代器,函数对象以及内置类型,对于 ...

  2. C++中的参数传递方式:传值、传地址、传引用总结

    指针:指针是一个变量,只不过这个变量中存储的是一个地址,指向内存中的一个单元. 引用:引用和原变量是同一个东西,只不过是原变量的一个别名. int a = 10; 定义一个整型变量aint *p = ...

  3. php中什么时候用传值,php中传值与传引用的区别。什么时候传值什么时候传引用?...

    java中的this与super的区别 java中的this与super的区别 1. 子类的构造函数如果要引用super的话,必须把super放在函数的首位 代码如下: class Base { Ba ...

  4. 传值和传引用(米斯特吴22)

    一.传值:string number boolean(删除或添加数据,只改变自身,其他不变) 传引用:array object(一旦删除或添加一个地方的数据,其他的也会随之改变) 二.传引用(user ...

  5. JAVA中是传值还是传引用?回答KEZHANG问题

    首先,推荐对Java有一定理解的同仁一本书<Practical Java>.在<Practical Java>中也有一个章节介绍Java中关于传值和传引用的问题,堪称经典. & ...

  6. 语言深入:java中究竟是传值还是传引用

    http://hi.baidu.com/hugoxian/item/5212a65bb1546aded48bace1 首先,推荐对Java有一定理解的同仁一本书<Practical Java&g ...

  7. 5个php实例,细致说明传值与传引用的区别

    传值:是把实参的值赋值给行参 ,那么对行参的修改,不会影响实参的值 传引用 :真正的以地址的方式传递参数传递以后,行参和实参都是同一个对象,只是他们名字不同而已对行参的修改将影响实参的值 说明: 传值 ...

  8. [转载] java中的经典问题:传值与传引用

    参考链接: 有关Java中数组分配的有趣事实 参数传递的秘密 知道方法参数如何传递吗? 记得刚开始学编程那会儿,老师教导,所谓参数,有形式参数和实际参数之分,参数列表中写的那些东西都叫形式参数,在实际 ...

  9. go语言接收html传值,Go语言参数传递是传值还是传引用

    本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持. 对于了 ...

最新文章

  1. MySQL权限管理-安全与效率的折中
  2. 一些有意思的VR设备介绍
  3. 关于for循环与setTimeout的延迟
  4. 避免每个类中都初始化日志类
  5. 叉乘点乘混合运算公式_初中数学学不会?公式这样记,让你做题效率翻倍!
  6. Acwing 1089. 烽火传递
  7. 04.Android之动画问题
  8. 买mac电脑的各个使用阶段:
  9. eclipse package包 java类 有问号 无法读取的问题解决
  10. Choerodon猪齿鱼敏捷管理实践(一)——需求管理
  11. 软件测试中测试用例的简单案例
  12. 【转载】气象数据相关资源
  13. 蓝桥杯单片机(九)DS18B20温度测量(四位小数和负数显示)
  14. 表不存在,但是可以查询、删除(没有返回结果,一直hang住)
  15. 那些年被欺骗的感情---分布式实现限流操作(上)
  16. 控制kobuki 运行一个矩形: 类的形式写ROS节点程序
  17. hibernate 根据方言生成sql
  18. oracle练习题-emp表
  19. MQTT移植到stm32开发板——使用TencentOS tiny操作系统
  20. php薄饼,即将消失的汕头美食:糖葱薄饼

热门文章

  1. MyBatis复习笔记6:MyBatis缓存机制
  2. 计算机组成考试题及答案,计算机组成测试题一参考答案
  3. miui通知栏要点两下_MIUI免费主题分享,半透明通知栏很好看,另附壁纸!
  4. 运行时异常 检查时异常
  5. Docker学习笔记_安装ActiveMQ
  6. 【分布式共识三】拜占庭将军问题----书面协议
  7. 排序(一)归并、快排、优先队列等(图文具体解释)
  8. Remove Duplicates from Sorted Array II -- LeetCode
  9. 【DAY23】JVM与反射的学习笔记
  10. win7中Android开发环境搭建超详细(百度)