C++的临时变量
 它们是被神所遗弃的孩子,没有人见过它们,更没有人知道它们的名字.它们命中注定徘徊于命运边缘高耸的悬崖和幽深的深渊之间,
用自己短暂的生命抚平了生与死之间的缝隙.譬如朝露,却与阳光无缘.是该为它们立一座丰碑的时候了,墓铭志上写着:我来了,我走了,我快乐过.

许多人对临时变量的理解仅仅限于:
 string temp;
 其实,从C++的观点来看,这根本就不是临时变量,而是局部变量.
 
 C++的临时变量是编译器在需要的时候自动生成的临时性变量,它们并不在代码中出现.但是它们在编译器生成的二进制编码中是存在的,
 也创建和销毁.在C++语言中,临时变量的问题格外的重要,因为每个用户自定义类型的临时变量都要出发用户自定义的构造函数和析构函数(如果用户提供了)
 
 又是该死的编译器!又该有人抱怨编译器总在自己背后干着偷偷摸摸的事情了.但是如果离开了编译器的这些工作,我们可能寸步难行.
 
 如果X是一个用户自定义的类型,有默认构造函数,拷贝构造函数,赋值运算函数,析构函数(这也是类的4个基本函数),那么请考虑以下代码:
 X get(X arg)
 {
  return arg;
 }
 
 X a;
 X b = get(a);
 即使是这么简单的代码也是很难实现的

让我们分析一下代码执行过程中发生了什么?
 首先我要告诉你一个秘密:对于一个函数来说,无论是传入一个对象还是传出一个对象其实都是不可能的.
 让一个函数传入或传出一个内置的数据类型,例如int,是很容易的,但是对于用户自定义类型得对象却非常的困难,因为编译器总得找地方为这些对象
 写上构造函数和析构函数,不是在函数内,就是在函数外,除非你用指针或引用跳过这些困难
 
 那么怎么办?在这里,编译器必须玩一些必要的小花招,嗯,其中的关键恰恰就是临时变量
 
 对于以对象为形参的函数:
  void foo(X x0)
 {
 }

X xx;
 foo(xx);

编译器一般按照以下两种转换方式中的一种进行转换
 1.在函数外提供临时变量
  void foo(X& x0)   //修改foo的声明为引用
 {
 }
 X xx;        //声明xx
 X::X(xx);      //调用xx的默认构造函数
 X __temp0;     //声明临时变量__temp0
 X::X(__temp0, xx); //调用__temp0的拷贝构造函数
 foo(__temp0);    //调用foo
 X::~X(__temp0);   //调用__temp0的析构函数
 X::~X(xx);     //调用xx的析构函数

2.在函数内提供临时变量
  void foo(X& x0)   //修改foo的声明为引用
 {
  X __temp0;     //声明临时变量__temp0
  X::X(__temp0, x0); //调用__temp0的拷贝构造函数
  X::~X(__temp0);   //调用__temp0的析构函数 
 }
 X xx;        //声明xx
 X::X(xx);      //调用xx的默认构造函数
 foo(xx);      //调用foo
 X::~X(xx);     //调用xx的析构函数
 
 无论是在函数的内部声明临时变量还是在函数的外部声明临时变量,其实都是差不多的,这里的含义是说既然参数要以传值的
 语意传入函数,也就是实参xx其实并不能修改,那么我们就用一个一摸一样临时变量来移花接木,完成这个传值的语意
 但是这样做也不是没有代价,编译器要修改函数的声明,把对象改为对象的引用,同时修改所有函数调用的地方,代价确实巨大啊,
 但是这只是编译器不高兴而已,程序员和程序执行效率却没有影响

对于以对象为返回值的函数:
  X foo()
 {
  X xx;
  return xx;
 }
 
 X yy = foo();

编译器一般按照以下方式进行转换
  void foo(X& __temp0) //修改foo的声明为引用
 {
  X xx;        //声明xx
  X::X(xx);      //调用xx的默认构造函数

__temp0::X::X(xx); //调用__temp0的拷贝构造函数
  X::~X(xx);     //调用xx的析构函数
 }
 
 X yy;         //声明yy
 X __temp0;      //声明临时变量__temp0
 foo(__temp0);     //调用foo
 X::X(yy, __temp0);  //调用yy的拷贝构造函数
 X::~X(__temp0);    //调用__temp0的析构函数
 X::~X(yy);      //调用yy的析构函数
 
 既然我们已经声明了yy,为什么还要紧接着声明__temp0,其实这里完全可以把yy和临时变量合一
 优化后,上面的代码看起来象这个样子:
 void foo(X& __temp0) //修改foo的声明为引用
 {
  X xx;        //声明xx
  X::X(xx);      //调用xx的默认构造函数

__temp0::X::X(xx); //调用__temp0的拷贝构造函数
  X::~X(xx);     //调用xx的析构函数
 }
 
 X yy;         //声明yy
 foo(yy);       //调用foo
 X::~X(yy);      //调用yy的析构函数
 
 嗯,怎么说呢,这算是一种优化算法吧,其实这各个技巧已经非常普遍了,并拥有一个专门的名称Named Return Value(NRV)优化
 NRV优化如今被视为标准C++编译器的一个义不容辞的优化操作(虽然其需求其实超出了正式标准之外)

除了以类为参数以外,如果参数的类型是const T&类型,这也可能导致临时变量
  void fun(const string& str)
  const char* name = "wgs";
 fun(name);
  嗯,还记得在const文档中的论述吗?对于这种特殊的参数类型,编译器是很乐意为你做自动转换的工作的,代价嘛,就是一个临时变量,
  不过如果是你自己去做,大概就只能声明一个局部变量了
 
 为什么函数和临时变量这么有缘,其实根本的原因在于对象传值的语意,这一个也是为什么C++中鼓励传对象地址的原因
 
 和函数的情况类似的,还有一大类情况是临时变量的乐土,那就是表达式
 string s,t;
 printf("%s", s + t);
 这里s+t的结果该放在什么地方呢?只能是临时变量中.
 
 这个printf语句带来了新的问题,那就是"临时变量的生命期"是如何的?
 对于函数的情况,我们已经看到了,临时变量在完成交换内容的使命后都是尽量早的被析构了,那么对于表达式呢?
 如果在s+t计算后析构,那么print函数打印的就是一个非法内容了,因此C++给出的规则是:
 临时变量应该在导致临时变量创建的"完整表达式"求值过程的最后一个步骤被析构
 什么又是"完整表达式"?简单的说,就是不是表达式的子表达式
 这条规则听起来很简单,但具体实现起来就非常的麻烦了,例如:
 X foo(int n)
 if (foo(1) || foo(2) || foo(3) )
 其中X中有operator int()转换,所以可以用在if语句中
 这里的foo(1)将产生一个临时变量1,如果这部分为false,foo(2)将继续产生一个临时变量,如果这部分也为false,foo(3)...
 一个临时变量的参数居然是和运行时相关的,更要命的是你要记住你到底产生了几个临时变量并在这个表达式结束的时候进行析构以小心的维护对象构造和析构的一致
 我猜想,这里会展开成一段复杂的代码,并加入更多的if判断才能搞定,呵呵,好在我不是做编译器的
 
 上面的规则其实还有两条例外:
 string s,t;
 string v = 1 ? s + t : s - t;
 这里完整表达式是?语句,但是在完整表达式结束以后临时变量还不能立即销毁,而必须在变量v赋值完成后才能销毁,这就是例外规则1:
 凡含有表达式执行结果的临时变量,应该存留到对象的初始化操作完成后销毁
 
 string s,t;
 string& v = s + t;
 这里s+t产生的临时变量即使在变量v的赋值完成后也不能销毁,否则这个引用就没用了,这就是例外规则2:
 如果一个临时变量被绑定到一个引用,这个临时变量应该留到这个临时变量和这个引用那个先超出变量的作用域后才销毁
 
 这篇文章可能有些深奥了,毕竟大多数内容来自于<<Inside The C++ Object Model>>
 那么就留下一条忠告:
 在stl中,以下的代码是错误的
 string getName();
 char* pTemp = getName().c_str();
 getName返回的就是一个临时变量,在把它内部的char指针赋值给pTemp后析构了,这时pTemp就是一个非法地址
 确实如C++发明者Bjarne Stroustrup所说,这种情况一般发生在不同类型的相互转换上
 
 在Qt中,类似的代码是这样的
 QString getName();
 char* pTemp = getName().toAscii().data();
 这时pTemp是非法地址
 
 希望大家不要犯类似的错误

c++ 临时变量的更多相关文章

  1. C++11引用临时变量的终极解析

    工作中遇到一个引用临时变量的问题,经过两天的学习,私以为:不仅弄明白了这个问题,还有些自己的独到见解. 这里使用一个简单的例子来把自己的学习过程和理解献给大家,如果有什么问题请不吝指正.   **** ...

  2. python 临时变量使用心得

    在函数里面的临时变量也可以定义为一个函数名.变量名,这样就可以通过对象来访问这个变量了,函数使用完之后不会消除.因为函数也是对象,python里面一切皆为对象.

  3. 临时变量不能作为非const类型引用形参的实参

    摘要:     非const 引用形参只能与完全同类型的非const对象关联.      具体含义为:(1)不能用const类型的对象传递给非const引用形参:                  ( ...

  4. C++临时变量的生命周期

    C++ 中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量.主要的用途主要有两类: 1) 函数的返回值, 如: string proc() { return string(" ...

  5. Mysql 临时变量的 定义 和 赋值 Set 和 Into 赋值; Swith Mysql版本 Case When的用法

    一:临时变量的定义和赋值 DECLARE spot SMALLINT; -- 分隔符的位置 DECLARE tempId VARCHAR(64); -- 循环 需要用到的临时的Cid DECLARE ...

  6. [转] C++临时变量的生命周期

    http://www.cnblogs.com/catch/p/3251937.html C++中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量. 主要的用途主要有两类: 1) 函数的 ...

  7. linq中的临时变量

    有一个字符串数组: string[]arrStr={"123","234","345","456"}; 现在想得到该数组 ...

  8. JavaScript两个变量交换值(不使用临时变量)

    概要  本文主要描述,如何不使用中间值,将两个变量的值进行交换. 一.普通做法 var a = 1, b = 2, tmp; tmp = a; a = b; b = tmp;  普通的做法就是声明多一 ...

  9. 重构手法之Replace Temp with Query(以查询取代临时变量)

    返回总目录 6.4Replace Temp with Query(以查询取代临时变量) 概要 你的程序以一个临时变量保存某一表达式的运算结果. 将这个表达式提炼到一个独立函数中.将这个临时变量的所有引 ...

随机推荐

  1. 【jQuery UI 1.8 The User Interface Library for jQuery】.学习笔记.9.Progressbar控件

    Progressbar控件用来显示任意进程的完成百分比. 默认安装启用 配置选项 控件暴露的事件API progressbar暴露的独一无二的方法 一些现实生活的例子 当前版本中,我们或系统必须明确进 ...

  2. Nagios监控Oralce

    一.本文说明: 本文是监控本地的Oracle,其实监控远端的Oracle也是跟下面的步骤差不多的. 二.安装Nagios.Nagios插件.NRPE软件: 安装步骤可以参考<Linux下Nagi ...

  3. win7里边使用telnet命令提示telnet不是内部或外部命令

    Win7默认没有安装telnet功能,所以你直接用telnet命令是用不了的: 你可以去“控制面板”-->“程序”(在左下角)--->“打开或关闭Windows功能”,勾上“telnet客 ...

  4. Andorid 编程 系统环境安装

    内网环境下安装: 1.配置源 :找到公司内部整理的源文件中的内容,将其内容拷贝到系统 源文件 中,并注释掉所有外网链接(如果公司支持内部环境配置,通常会有一个内部源文件)  2.安装jdk, ecli ...

  5. GUI_Delay函数

    GUI_Delay()函数 使用GUI_Delay()函数时,对于其延时时间不确定,明明设置为最小值1,延时时间 仍旧太长,不能达到需求.遂决定研究明白其实现机理. uC/OS-II使用OSTimeD ...

  6. eclipse编辑jsp快捷键保存时特别卡的解决方法

    今天eclipse用着用着的时候,每次编辑jsp页面快捷键保存的时候要等半天才保存好,特别的卡.搞的很蛋疼.上网搜了下有解决办法 Window -> Preference -> Gener ...

  7. Java提高篇---Map总结

    一.Map概述 首先先看Map的结构示意图 Map:"键值"对映射的抽象接口.该映射不包括重复的键,一个键对应一个值. SortedMap:有序的键值对接口,继承Map接口. Na ...

  8. 2016年11月12日 星期六 --出埃及记 Exodus 20:3

    2016年11月12日 星期六 --出埃及记 Exodus 20:3 "You shall have no other gods before me.除了我以外,你不可有别的 神.

  9. HDU 5640 King's Cake

    King's Cake Problem Description It is the king's birthday before the military parade . The ministers ...

  10. 三、java中的流程控制

    流程控制:1.分类:顺序结构.选择结构.循环结构.2.顺序结构:依次执行.3.选择结构:if.if...else.if...else if...else:三目运算符(表达式?为true的执行语句:为f ...