C/C++陷阱——临时变量的产生和特性
在学习C++常引用时,有这样一段代码引起了我的注意:
int a = 1;
double& b = a;
当我编译这段代码时,竟然报错了:
按理来说,初始化引用时不能涉及权限的放大(如用const int
初始化int&
),但是这里只是权限的平移,为什么会错误呢?
我们可以看到报错信息里有这样一句话:非常量限定
,这指的又是什么呢?
这就是本次要讨论的重点——临时变量的产生和特性:
在C和C++中,临时变量通常指的是临时创建并存储数据的变量,它们在表达式求值或函数调用中起到临时存储值的作用。这些临时变量通常是由编译器自动生成的,无需程序员显式声明或管理。
临时变量的产生
临时变量主要在下面这两种情况下产生:
- 类型转换
当需要将一个数据类型转换为另一个数据类型时,通常会生成临时变量来保存转换后的值
例如:
int a = 10;
double b = a;
我们都知道,将整型变量a
赋值给双精度浮点型变量b
时,涉及到了隐式类型转换,但我们可能大多数都忽略了这个过程中就产生了一个临时变量来存储转换后的值,最后变量b
接收的就是这个临时变量。因为只有这样,我们才能在类型转换的过程中确保变量a
的数据是完整的、合规的、安全的。
又例如:
int a = 1;
int* pt = (int*)a;
我们总不能说,将整型变量a
强制转换为整型指针后,整型变量a
就变成指针了吧。
- 函数调用
当一个函数返回一个值时,通常会生成临时变量来存储函数的返回结果。这个临时变量可以被赋给其他变量或用于进一步的操作。
int Add(int a, int b)
{int sum = a + b;return sum;
}int main()
{int ret = Add(1, 2);return 0;
}
Add
函数就是一个简单的两数相加的函数。那么小伙伴觉得这个函数的返回值是什么?是变量sum
吗?当然不是,应该清楚局部变量出了其所在函数的作用域后就会被销毁,因此当Add
函数被调用完后,sum
所代表的值就是一个随机数了。因此实际上,编译器一般都会生成一个临时变量来存储函数返回的结果,最后ret
接受的也是这个临时变量。
除了上面两种情况外,还有其他的一些情况也会有临时变量的产生,大家了解即可:
- 表达式求值:当需要计算一个表达式,特别是包含多个操作数和操作符的复杂表达式时,编程语言通常会生成临时变量来保存中间结果。
- 中间计算:在执行复杂计算时,可以使用临时变量来存储中间计算结果,以避免重复计算相同的值。
- 循环迭代:在循环结构中,迭代计数器通常被视为临时变量,因为它们在每次迭代中都会被更新。
- 条件语句:在条件语句中,如果需要根据条件执行不同的操作,临时变量可能会用于存储条件的结果或中间值。
- 数组和容器操作:在对数组、向量、列表等数据结构进行操作时,可能会生成临时变量来存储临时元素或中间结果。
- 错误处理:在处理异常或错误时,临时变量可以用于存储错误信息或状态。
临时变量的常性
临时变量有一个很重要的特性:常量性
这一个特性确保了临时变量是不可以被修改,这其中也就包括了权限不能被放大。
例如:
int Add(int a, int b)
{int sum = a + b;return sum;
}int main()
{Add(1, 2)++;return 0;
}
就会报错:
这里解释一下左值和右值的概念:
- 左值(L-value):左值是可以出现在赋值操作符(例如=)的一侧的表达式,表示一个可以被赋值的内存位置,通常是一个变量。左值表示一个标识符或一个引用,它指向内存中的某个位置。例如,如果你有一个变量x,那么x就是一个左值,因为你可以将一个值赋给它,如x = 10。
- 右值(R-value):右值是一个表达式的结果值,它可以出现在赋值操作符的右侧。右值通常是计算的结果,它可以是常数、临时变量或函数的返回值。例如,如果你有一个表达式x + y,它的结果是一个右值,因为它代表一个值,但你不能将一个值赋给它。
报错信息显示,函数的返回值不是一个左值,也就是说返回的临时变量是不可被修改的。这也从侧面反映了临时变量的常性。
除此之外,临时变量还有其他一些特性,大家仅作了解即可:
- 短暂寿命:临时变量通常在其创建点的作用域内存在,一旦超出该作用域,它们就会被销毁。这使它们成为一种短暂的存储设备。
- 匿名:通常,临时变量没有显式的名称,因为它们是在表达式求值或函数调用期间自动创建的。它们只是在内部存储中的值。
- 用于中间计算:临时变量通常用于存储中间计算结果或值的转换。它们帮助管理复杂的表达式,确保正确的计算顺序。
- 可被编译器优化:现代编译器通常会进行优化,以最小化临时变量的使用,以提高性能。它们可以消除不必要的临时变量,以减少内存开销。
- 值语义:临时变量通常采用值语义,这意味着它们存储的是具体的值,而不是引用或指向其他变量。这有助于避免共享状态和副作用。
- 用于函数返回:在函数返回值的情况下,临时变量通常用于存储函数的结果,以便将其传递给调用方。
- 隐式创建和销毁:编程语言和编译器通常会自动创建和销毁临时变量,程序员无需显式管理它们的生命周期。
- 类型与原始值相关:临时变量的类型通常与它们所包含的值的类型相关,以确保类型的一致性。
总结
通过对临时变量的了解,我们就可以解释最开始提到的问题了:
int a = 1;
double& b = a;
当用整形变量a
初始化浮点型引用b
时,涉及到了隐式类型转换,那么中间就会产生一个double
型临时变量来临时存储a
的值,但由于临时变量具有常性,其权限不能被放大,因此double& b = a;
这句就是错误的。我们应该改为**const double& b = a;
确保权限不变**。
本篇完。