C++ 第 12 课:局部变量、作用域、变量生命周期

上一课标准答案:

  1. result = 7
  2. 返回值类型是 double;返回值是 45.5
  3. result = false
复制代码
double error = calculateError(80.0, 50.0);

表示调用 calculateError 函数,把 80.0 传给目标值、50.0 传给当前值,函数计算后返回 30.0,最后把这个结果存进 error。


第 12 课:局部变量、作用域、变量生命周期

这一课非常重要。

因为以后你学:

复制代码
函数
指针
引用
类
栈
动态内存
ROS2

都会碰到一个问题:

这个变量到底在哪里能用?什么时候还存在?

先别急着记专业名词,我们从直觉开始。


1. 先看一个奇怪的问题

复制代码
void test()
{
    int a = 5;
}

int main()
{
    std::cout << a << std::endl;

    return 0;
}

你可能觉得:

前面不是已经创建 a = 5 了吗?

为什么 main() 里不能直接用?

因为这个:

复制代码
int a = 5;

是在 test() 的花括号里面创建的。

它只属于:

复制代码
test()

这块区域。


2. 用"房间"理解作用域

把函数想象成不同房间。

复制代码
┌───────────────┐
│   test 房间    │
│               │
│   a = 5       │
│               │
└───────────────┘


┌───────────────┐
│   main 房间    │
│               │
│               │
└───────────────┘

a 放在 test 房间里。

所以:

test 房间里的人能看到 a

但是:

main 房间里看不到 a


3. 这就是"作用域"

专业一点的名字叫:

scope,作用域

意思就是:

一个变量可以被访问的代码范围。

例如:

复制代码
void test()
{
    int a = 5;

    std::cout << a << std::endl;
}

这里可以使用:

复制代码
a

因为还在:

复制代码
{
    ...
}

这个范围里面。


4. 出了花括号,就不行了

复制代码
void test()
{
    int a = 5;
}

std::cout << a << std::endl;

这里:

复制代码
a

已经离开它的作用范围。

所以程序会报错。

你可以先记:

在 {} 里面创建的普通局部变量,一般只能在这对 {} 里面使用。


5. 什么叫局部变量?

例如:

复制代码
void test()
{
    int a = 5;
}

这里的:

复制代码
a

叫:

局部变量 local variable

"局部"就是:

只属于某个局部范围。


6. main() 里的变量也是局部变量

例如:

复制代码
int main()
{
    int age = 20;

    return 0;
}

age 也是局部变量。

它的作用域大致就是:

复制代码
{
    int age = 20;

    return 0;
}

这一块。

不是只有普通函数里的变量才叫局部变量。


7. 不同函数可以有同名变量

看:

复制代码
void test1()
{
    int a = 5;
}

void test2()
{
    int a = 10;
}

这样是可以的。

为什么?

因为它们在不同的"房间"。

复制代码
test1 房间
a = 5


test2 房间
a = 10

它们不是同一个变量。


8. 这点特别重要

看:

复制代码
void functionA()
{
    int angle = 30;
}

void functionB()
{
    int angle = 60;
}

虽然都叫:

复制代码
angle

但实际上是两个不同的变量。

就像:

复制代码
张三家有一个"杯子"
李四家也有一个"杯子"

名字一样,不代表是同一个东西。


9. 参数也是局部变量

还记得:

复制代码
double calculateError(double target, double current)
{
    return target - current;
}

这里:

复制代码
target
current

其实也只在:

复制代码
calculateError

函数内部有效。

它们也属于局部变量。

例如:

复制代码
int main()
{
    std::cout << target << std::endl;
}

这里不能直接使用 target。

因为:

复制代码
target

属于 calculateError()。


10. 调用函数时会发生什么?

例如:

复制代码
double calculateError(double target, double current)
{
    double error = target - current;

    return error;
}

然后:

复制代码
calculateError(50.0, 20.0);

可以粗略想象成:

复制代码
进入 calculateError 房间

target  = 50
current = 20
error   = 30

return 30

离开房间

函数结束后:

复制代码
target
current
error

这些局部变量通常就结束它们的生命周期。


11. 什么叫"生命周期"?

作用域和生命周期不是完全一回事,但初学阶段先这样理解:

作用域:

在哪里能使用它?

生命周期:

它什么时候存在?

例如:

复制代码
void test()
{
    int a = 5;
}

调用:

复制代码
test();

可以粗略理解为:

复制代码
进入 test
↓
创建 a
↓
a = 5
↓
test 执行完
↓
a 的这次生命结束

12. 再次调用 test() 呢?

复制代码
void test()
{
    int a = 5;

    a++;
    std::cout << a << std::endl;
}

int main()
{
    test();
    test();

    return 0;
}

第一次调用:

复制代码
创建新的 a
a = 5
a++
a = 6
输出 6
函数结束

第二次调用:

复制代码
重新创建一个新的 a
a = 5
a++
a = 6
输出 6

所以最终:

复制代码
6
6

不是:

复制代码
6
7

这一点非常重要。


13. 为什么不是 7?

因为普通局部变量:

复制代码
int a = 5;

每次进入函数时都会重新创建。

第一次函数结束以后,那次的 a 已经结束了。

第二次:

又是一个新的 a

所以重新从:

复制代码
5

开始。


14. if 里面也有自己的作用域

看:

复制代码
int main()
{
    if (true)
    {
        int a = 5;

        std::cout << a << std::endl;
    }

    return 0;
}

这里:

复制代码
a

只属于:

复制代码
if
{
    ...
}

这一块。

所以:

复制代码
int main()
{
    if (true)
    {
        int a = 5;
    }

    std::cout << a << std::endl;
}

这里通常会报错。

因为已经出了 a 的作用域。


15. for 循环里的 i 呢?

你之前经常写:

复制代码
for (int i = 0; i < 5; i++)
{
    std::cout << i << std::endl;
}

这个:

复制代码
i

一般只属于这个 for 循环。

所以:

复制代码
for (int i = 0; i < 5; i++)
{
}

结束以后,再写:

复制代码
std::cout << i << std::endl;

通常就不能用了。


16. 为什么这样设计?

因为如果所有变量在整个程序里都一直存在、到处都能用:

复制代码
a
b
angle
speed
state
count
error

程序稍微复杂一点就会乱成一团。

函数内部自己管理自己的变量,反而更安全。

比如:

复制代码
double calculateError(...)
{
    double error;
}

另一个函数也可以有:

复制代码
void checkMotor()
{
    int error;
}

互相不冲突。


17. 机器人程序里为什么尤其重要?

假设以后你写:

复制代码
double calculateJoint1Error()
{
    double error = 10.0;

    return error;
}

另一个函数:

复制代码
double calculateJoint2Error()
{
    double error = 20.0;

    return error;
}

这里两个:

复制代码
error

完全可以同时存在。

因为:

复制代码
Joint1 函数里的 error

和:

复制代码
Joint2 函数里的 error

不是同一个变量。


18. 但是外层变量可以在内层使用

看:

复制代码
int main()
{
    int a = 5;

    if (true)
    {
        std::cout << a << std::endl;
    }

    return 0;
}

这里可以输出:

复制代码
5

为什么?

因为:

复制代码
a

在外层:

复制代码
main

里面创建。

而:

复制代码
if

在它里面。

可以粗略理解成:

复制代码
main 大房间
┌────────────────────┐
│ a = 5              │
│                    │
│   if 小房间         │
│   ┌─────────────┐  │
│   │ 可以看到 a   │  │
│   └─────────────┘  │
└────────────────────┘

19. 但反过来不行

如果变量是在小房间创建:

复制代码
int main()
{
    if (true)
    {
        int a = 5;
    }

    std::cout << a << std::endl;
}

外面看不到。

可以想象:

复制代码
main 大房间
┌────────────────────┐
│                    │
│   if 小房间         │
│   ┌─────────────┐  │
│   │ a = 5       │  │
│   └─────────────┘  │
│                    │
│ 外面看不到 a        │
└────────────────────┘

20. 一个容易混淆的例子

看:

复制代码
int main()
{
    int a = 5;

    if (true)
    {
        int a = 10;

        std::cout << a << std::endl;
    }

    std::cout << a << std::endl;

    return 0;
}

你可能会疑惑:

怎么能有两个 a?

可以。

因为内层又创建了一个新的局部变量:

复制代码
int a = 10;

所以可以想成:

复制代码
外层 a = 5

进入 if
↓
内层又创建 a = 10
↓
内层优先使用自己的 a
↓
输出 10

离开 if
↓
内层 a 消失
↓
重新看到外层 a = 5
↓
输出 5

最终:

复制代码
10
5

21. 这叫"遮蔽"

专业名字叫:

variable shadowing,变量遮蔽

现在不用背英文。

只要知道:

内层创建了一个同名变量,会暂时把外层那个同名变量"挡住"。

但真实工程里:

通常不建议随便写很多同名变量。

因为很容易看晕。


22. 一个机械臂例子

复制代码
#include <iostream>

double calculateError(double target, double current)
{
    double error = target - current;

    return error;
}

int main()
{
    double target = 90.0;
    double current = 60.0;

    double error = calculateError(target, current);

    std::cout << error << std::endl;

    return 0;
}

这里其实存在两套变量。

main() 里面:

复制代码
target
current
error

函数里面:

复制代码
target
current
error

虽然名字一样,但它们属于不同作用域。

调用时只是:

复制代码
main 的 target 值
      ↓
传给函数的 target

main 的 current 值
      ↓
传给函数的 current

不是同一个变量直接"搬进去"。

这个区别以后学值传递、引用、指针时会非常重要。


23. 现在先形成三个概念

局部变量

在某个函数或代码块内部创建的变量。

例如:

复制代码
int a = 5;

作用域

变量:

在哪里可以被使用。


生命周期

变量:

从什么时候创建,到什么时候结束。


24. 本课最核心的一句话

以后看到:

复制代码
{
    int a = 5;
}

你就应该想到:

a 属于这一层 {} 的局部范围,出了这个范围通常就不能再使用。


本课小练习

第 1 题

复制代码
void test()
{
    int a = 5;
}

int main()
{
    std::cout << a << std::endl;
}

这段代码能正常使用 a 吗?为什么?

第 2 题

复制代码
void test()
{
    int a = 5;

    a++;

    std::cout << a << std::endl;
}

int main()
{
    test();
    test();
}

输出:

复制代码
?
?

第 3 题

复制代码
int main()
{
    int a = 3;

    if (true)
    {
        std::cout << a << std::endl;
    }
}

能不能在 if 里面使用 a?

第 4 题

复制代码
int main()
{
    if (true)
    {
        int b = 10;
    }

    std::cout << b << std::endl;
}

这里为什么会有问题?

相关推荐
狗凯之家源码网1 小时前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术1 小时前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
八解毒剂1 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
乐迪信息2 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉
FakeOccupational2 小时前
【电路笔记 信号】DBPSK 波形查找表+脉冲成形(升余弦+根升余弦滤波)
开发语言·笔记
打工仔折腾 AI2 小时前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
ZealSinger2 小时前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox
无名猿3 小时前
shared_ptr 完全指南:引用计数、控制块与开销
c++·性能优化·内存管理·标准库·现代c++