c++ 临时变量
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++ 临时变量的更多相关文章
- C++11引用临时变量的终极解析
工作中遇到一个引用临时变量的问题,经过两天的学习,私以为:不仅弄明白了这个问题,还有些自己的独到见解. 这里使用一个简单的例子来把自己的学习过程和理解献给大家,如果有什么问题请不吝指正. **** ...
- python 临时变量使用心得
在函数里面的临时变量也可以定义为一个函数名.变量名,这样就可以通过对象来访问这个变量了,函数使用完之后不会消除.因为函数也是对象,python里面一切皆为对象.
- 临时变量不能作为非const类型引用形参的实参
摘要: 非const 引用形参只能与完全同类型的非const对象关联. 具体含义为:(1)不能用const类型的对象传递给非const引用形参: ( ...
- C++临时变量的生命周期
C++ 中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量.主要的用途主要有两类: 1) 函数的返回值, 如: string proc() { return string(" ...
- Mysql 临时变量的 定义 和 赋值 Set 和 Into 赋值; Swith Mysql版本 Case When的用法
一:临时变量的定义和赋值 DECLARE spot SMALLINT; -- 分隔符的位置 DECLARE tempId VARCHAR(64); -- 循环 需要用到的临时的Cid DECLARE ...
- [转] C++临时变量的生命周期
http://www.cnblogs.com/catch/p/3251937.html C++中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量. 主要的用途主要有两类: 1) 函数的 ...
- linq中的临时变量
有一个字符串数组: string[]arrStr={"123","234","345","456"}; 现在想得到该数组 ...
- JavaScript两个变量交换值(不使用临时变量)
概要 本文主要描述,如何不使用中间值,将两个变量的值进行交换. 一.普通做法 var a = 1, b = 2, tmp; tmp = a; a = b; b = tmp; 普通的做法就是声明多一 ...
- 重构手法之Replace Temp with Query(以查询取代临时变量)
返回总目录 6.4Replace Temp with Query(以查询取代临时变量) 概要 你的程序以一个临时变量保存某一表达式的运算结果. 将这个表达式提炼到一个独立函数中.将这个临时变量的所有引 ...
随机推荐
- MEANIO
sudo npm install -g bower sudo npm install -g meanio sudo bower cache clean --allow-root sudo mean i ...
- PHP数组的一些常用函数
[数组排序]sort()低到高,rsort()高到低.保持键值对应关系使用 asort()和arsort().对键排序ksort()和krsort().随机排序 shuffle(). [数组key相关 ...
- PHP无限级分类-递归(不推荐)
[http://www.helloweba.com/view-blog-204.html] 在一些复杂的系统中,要求对信息栏目进行无限级的分类,以增强系统的灵活性.那么PHP是如何实现无限级分类的呢? ...
- 关于sql server 2008过期导致 MSSQLSERVER服务就无法启动,手动启动就报告错误代码17051。
1.基本现象:MSSQLSERVER服务就无法启动,手动启动就报告17051错误. 2.解决办法: 第一步:进入SQL2008配置工具中的安装中心, 第二步:再进入维护界面,选择版本升级, 第三步:进 ...
- DOM元素尺寸和位置
一.获取元素 CSS大小 1.通过style 内联获取元素的大小 var box = document.getElementById('box'); //获取元素 box.style.width; / ...
- php中的curl】php中curl的详细解说
本文我来给大家详细介绍下cURL的简单的使用方法,下文我将会给大家详细介绍cURL的高级应用, cURL可以使用URL的语法模拟浏览器来传输数据, FTP, FTPS, HTTP, HTTPS, GO ...
- java 编程时候的性能调优
一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...
- 请求php返回json生成自定义对象
php代码 public function convert_array(){ $arr = array( '0'=>array('name'=>'zc','height'=>173) ...
- Python 中translate()与replace()区别
translate函数和replace函数一样,用于替换字符串中的某个部分,但是和replace不同,translate只处理单个字符,而且可以同时进行多个替换.在使用translate函数转换之前, ...
- hiho 第117周 二分图多重匹配,网络流解决
描述 学校的秋季运动会即将开始,为了决定参赛人员,各个班又开始忙碌起来. 小Hi和小Ho作为班上的班干部,统计分配比赛选手的重任也自然交到了他们手上. 已知小Hi和小Ho所在的班级一共有N名学生(包含 ...