C/C++陷阱——临时变量的产生和特性

简介: C/C++陷阱——临时变量的产生和特性

C/C++陷阱——临时变量的产生和特性

在学习C++常引用时,有这样一段代码引起了我的注意:

int a = 1;
double& b = a;

当我编译这段代码时,竟然报错了:

按理来说,初始化引用时不能涉及权限的放大(如用const int初始化int&),但是这里只是权限的平移,为什么会错误呢?

我们可以看到报错信息里有这样一句话:非常量限定,这指的又是什么呢?

这就是本次要讨论的重点——临时变量的产生和特性

在C和C++中,临时变量通常指的是临时创建并存储数据的变量,它们在表达式求值或函数调用中起到临时存储值的作用。这些临时变量通常是由编译器自动生成的,无需程序员显式声明或管理

临时变量的产生

临时变量主要在下面这两种情况下产生:

  1. 类型转换

当需要将一个数据类型转换为另一个数据类型时,通常会生成临时变量来保存转换后的值

例如:

int a = 10;
double b = a;

我们都知道,将整型变量a赋值给双精度浮点型变量b时,涉及到了隐式类型转换,但我们可能大多数都忽略了这个过程中就产生了一个临时变量来存储转换后的值,最后变量b接收的就是这个临时变量。因为只有这样,我们才能在类型转换的过程中确保变量a的数据是完整的、合规的、安全的。

又例如:

int a = 1;
int* pt = (int*)a;

我们总不能说,将整型变量a强制转换为整型指针后,整型变量a就变成指针了吧。

  1. 函数调用

当一个函数返回一个值时,通常会生成临时变量来存储函数的返回结果。这个临时变量可以被赋给其他变量或用于进一步的操作。

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接受的也是这个临时变量。

除了上面两种情况外,还有其他的一些情况也会有临时变量的产生,大家了解即可:

  1. 表达式求值:当需要计算一个表达式,特别是包含多个操作数和操作符的复杂表达式时,编程语言通常会生成临时变量来保存中间结果。
  2. 中间计算:在执行复杂计算时,可以使用临时变量来存储中间计算结果,以避免重复计算相同的值。
  3. 循环迭代:在循环结构中,迭代计数器通常被视为临时变量,因为它们在每次迭代中都会被更新。
  4. 条件语句:在条件语句中,如果需要根据条件执行不同的操作,临时变量可能会用于存储条件的结果或中间值。
  5. 数组和容器操作:在对数组、向量、列表等数据结构进行操作时,可能会生成临时变量来存储临时元素或中间结果。
  6. 错误处理:在处理异常或错误时,临时变量可以用于存储错误信息或状态。

临时变量的常性

临时变量有一个很重要的特性:常量性

这一个特性确保了临时变量是不可以被修改,这其中也就包括了权限不能被放大

例如:

int Add(int a, int b)
{
  int sum = a + b;
  return sum;
}
int main()
{
  Add(1, 2)++;
  return 0;
}

就会报错:

这里解释一下左值和右值的概念:

  1. 左值(L-value):左值是可以出现在赋值操作符(例如=)的一侧的表达式,表示一个可以被赋值的内存位置通常是一个变量。左值表示一个标识符或一个引用,它指向内存中的某个位置。例如,如果你有一个变量x,那么x就是一个左值,因为你可以将一个值赋给它,如x = 10。
  2. 右值(R-value):右值是一个表达式的结果值,它可以出现在赋值操作符的右侧。右值通常是计算的结果,它可以是常数、临时变量或函数的返回值。例如,如果你有一个表达式x + y,它的结果是一个右值,因为它代表一个值,但你不能将一个值赋给它。

报错信息显示,函数的返回值不是一个左值,也就是说返回的临时变量是不可被修改的。这也从侧面反映了临时变量的常性。

除此之外,临时变量还有其他一些特性,大家仅作了解即可:

  1. 短暂寿命:临时变量通常在其创建点的作用域内存在,一旦超出该作用域,它们就会被销毁。这使它们成为一种短暂的存储设备。
  2. 匿名:通常,临时变量没有显式的名称,因为它们是在表达式求值或函数调用期间自动创建的。它们只是在内部存储中的值。
  3. 用于中间计算:临时变量通常用于存储中间计算结果或值的转换。它们帮助管理复杂的表达式,确保正确的计算顺序。
  4. 可被编译器优化:现代编译器通常会进行优化,以最小化临时变量的使用,以提高性能。它们可以消除不必要的临时变量,以减少内存开销。
  5. 值语义:临时变量通常采用值语义,这意味着它们存储的是具体的值,而不是引用或指向其他变量。这有助于避免共享状态和副作用。
  6. 用于函数返回:在函数返回值的情况下,临时变量通常用于存储函数的结果,以便将其传递给调用方。
  7. 隐式创建和销毁:编程语言和编译器通常会自动创建和销毁临时变量,程序员无需显式管理它们的生命周期。
  8. 类型与原始值相关:临时变量的类型通常与它们所包含的值的类型相关,以确保类型的一致性。

总结

通过对临时变量的了解,我们就可以解释最开始提到的问题了:

int a = 1;
double& b = a;

当用整形变量a初始化浮点型引用b时,涉及到了隐式类型转换,那么中间就会产生一个double临时变量来临时存储a的值,但由于临时变量具有常性,其权限不能被放大,因此double& b = a;这句就是错误的。我们应该改为**const double& b = a;确保权限不变**。


本篇完。

相关文章
|
2月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
128 59
|
28天前
|
安全 编译器 C++
【C++11】新特性
`C++11`是2011年发布的`C++`重要版本,引入了约140个新特性和600个缺陷修复。其中,列表初始化(List Initialization)提供了一种更统一、更灵活和更安全的初始化方式,支持内置类型和满足特定条件的自定义类型。此外,`C++11`还引入了`auto`关键字用于自动类型推导,简化了复杂类型的声明,提高了代码的可读性和可维护性。`decltype`则用于根据表达式推导类型,增强了编译时类型检查的能力,特别适用于模板和泛型编程。
22 2
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
43 0
|
3月前
|
编译器 C++ 计算机视觉
C++ 11新特性之完美转发
C++ 11新特性之完美转发
57 4
|
3月前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
36 4
|
3月前
|
编译器 C++ 容器
C++ 11新特性之语法甜点2
C++ 11新特性之语法甜点2
33 1