上一课标准答案result 7返回值类型是double返回值是45.5result falsedouble 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房间里看不到a3. 这就是“作用域”专业一点的名字叫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; }这里为什么会有问题