🔷 第六章 函数
📄 一句话概括

作用域 = 一个变量的"地盘",也就是"在哪些代码里叫这个名字能叫得应"。C++ 里主要有三层:全局(整个文件可见)、局部(只在本函数里)、块内(只在那一对 {} 里)。内外同名时,里面的说了算——这叫就近遮蔽。

📝 本节和前面几节的关系

6.3.2/6.3.3 一直在说"每层调用都新开一套变量",这一节换个角度把这件事讲透——名字的可视范围。旧知识也来帮忙:2.1.5 讲函数体时就说过"花括号是作用域边界",这一节把它展开成完整规则;6.2.2 的"形参是函数自己的盒子"是同一个道理。

一、变量的"地盘"有多大(三层)

作用域 在哪定义 谁能看见 活多久
全局 所有函数外面 从定义那行起,到文件结束(谁都能用) 程序从头活到尾
局部 函数体里 只在本函数内 函数被调用时出生,函数一结束就消失
块内 {} 里(if / for / while 的花括号) 只在这个 {} 内 出这个花括号就消失

记忆法:作用域像小区的门禁——全局是"大门口,谁都能进",局部是"本单元楼",块内是"家里的一间屋"。

二、就近遮蔽(最容易踩的规则)

#include <iostream>
using namespace std;

int score = 90;                  // 全局

int main() {
    cout << score << endl;       // 90(看见全局的)
    int score = 60;              // 局部:把全局那个"挡住"了
    cout << score << endl;       // 60(看见局部的)
    {
        int score = 30;          // 块内:又把外面的挡住
        cout << score << endl;   // 30
    }
    cout << score << endl;       // 60(回到局部那个)
    return 0;
}

规则一句话:从里往外找,找到第一个就停。里面的同名变量会把外面的遮住,但外面那个变量的值本身一点没变(和 6.2.2"复印件不影响原件"是同一种感觉)。

编译器通常会提示:局部变量遮蔽了全局变量时,多数编译器会给出 shadow 警告。见到这个警告就取个别的名字,别硬扛。

三、动手 Demo:三层变量的现场

#include <iostream>
using namespace std;

int count = 100;                     // 全局

void show() {
    cout << "show 里能看到 count = " << count << endl;     // 100(全局)
    int count = 5;                   // 局部(遮蔽)
    cout << "show 里遮蔽后 count = " << count << endl;     // 5
}

int main() {
    cout << "main 里 count = " << count << endl;           // 100
    show();
    for (int i = 0; i < 1; i++) {
        int count = 1;               // 块内(再遮蔽)
        cout << "循环块里 count = " << count << endl;       // 1
    }
    cout << "回到 main,count = " << count << endl;        // 100
    return 0;
}

看到什么算成功:

main 里 count = 100
show 里能看到 count = 100
show 里遮蔽后 count = 5
循环块里 count = 1
回到 main,count = 100

重点看第一行和最后一行:不管中间怎么遮蔽,main 里看到的始终是全局那个 100。遮蔽只改变"看得见哪一个",不改变"别的变量有没有被改"。

30 秒实验:在 main 的最前面加一句 count = 200;(改的是全局那个),然后运行——show 里第一个输出也变成 200 了。这就是全局变量"人人可改"的威力,也是它的危险所在(谁都能改,改坏了难追责)。

四、全局变量的代价(为什么尽量不用)

好处 代价
随处可用,省得传参 谁都能改——一处改坏,全程序遭殃,还很难定位
生命周期长 常驻内存,程序结束才释放
—— 名字容易撞(各处命名都要躲着它)

实用建议三条: 1. 能不用全局就不用。数据靠"参数传进去 + 返回值带出来"(第六章的正道)。 2. 非要用就加 const(2.2.5):const double PI = 3.14159;——只读的全局变量是安全的,它其实就是"常量"。 3. 命名上打个标记(如 g_count),提醒自己"这是全局的"。

五、常见误区

  1. "函数里定义的变量,函数外面也能用"——不能。局部变量一出函数就消失,外面既看不见也摸不到。
  2. "同名就是同一个变量"——只有同一作用域里的同名才是同一个;跨作用域的同名是两个不同的变量。
  3. "在 if 里定义的变量,if 结束后还能用"——不能。for (int i = 0; ...) 里的 i 出了循环就没了(5.2.1 讲过的"案发现场")。
  4. "全局变量比参数方便"——短期方便,长期灾难。参数和返回值才是函数之间通信的正规通道。
自测三问(点开对答案) 1. 作用域的三层分别是什么?——全局(整个文件)、局部(本函数)、块内(那一对花括号里)。 2. 什么是就近遮蔽?——内外同名时,从里往外找,第一个找到的就是生效的那个。 3. 为什么不推荐用全局变量?——谁都能改,改动难追溯;优先用参数和返回值传递数据。

相关笔记