上一课标准答案:
result = 7- 返回值类型是
double;返回值是45.5 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 521. 这叫“遮蔽”
专业名字叫:
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; }这里为什么会有问题?