C++ 全局变量与局部变量

在 C++ 中,每一个变量都有自己的 “作用域”,也就是该变量在程序中有效的范围。如果超出了它的作用域去访问,编译器就会报错提示找不到这个变量名。

根据定义位置的不同,C++ 中的变量主要分为 2 种:一种是定义在函数或代码块内部的 “局部变量”,另一种是定义在所有函数外部的 “全局变量”。

C++ 局部变量

在 C++ 中,定义在函数内部或代码块内部的变量统称为 “局部变量”。局部变量只能在它所在的局部范围内使用,一旦执行离开这个范围,变量就会失效并被自动销毁。

1. 函数内的局部变量

定义在函数体内部的变量,以及函数小括号里的形参,都只在该函数内部有效,其他函数无法直接访问它们。由于不同函数拥有各自独立的作用域,我们在不同函数中定义同名的局部变量是完全合法的,它们之间互不干扰。

示例 1:函数内的局部变量

#include <iostream>

void showYear() {
    int year = 2030;    // showYear 内的局部变量
    std::cout << "showYear 中的 year:" << year << '\n';
}

int main() {
    int year = 2026;    // main 内的局部变量
    showYear();
    std::cout << "main 中的 year:" << year << '\n';
    return 0;
}

运行结果如下。

showYear 中的 year:2030
main 中的 year:2026

分析:

在这个例子中,showYear() 里的 year 和 main() 里的 year 虽然名字相同,但分别处于两个不同的函数中,属于两个各自独立的局部变量。showYear() 访问的是自己内部的 2030,main() 访问的则是自己内部的 2026,互不影响。

需要注意的是,同一个函数内部不能重复定义两个同名变量。同时,如果某个局部变量只定义在 showYear() 内部,我们在 main() 中直接访问它就会编译报错:

void showYear() {
    int a = 10;    // 局部变量
}

int main() {
    std::cout << a << '\n';
    return 0;
}

2. 代码块内的局部变量

在函数内部,我们经常会使用 if 语句、for 循环等流程控制结构。这些语句的一对花括号 {} 会形成一个更小的作用域,通常称为 “代码块作用域”。

你可以把一对花括号 {} 看作一道围墙:变量定义在哪一对花括号里面,就只能在哪一对花括号内部使用。一旦离开对应的右花括号 },这个变量就失效了。

示例 2:代码块内的局部变量

#include <iostream>

int main() {
    int score = 80;

    if (score >= 60) {
        int bonus = 5;
        std::cout << "加上奖励分:" << score + bonus << '\n';
    }

    for (int i = 0; i < 3; i++) {
        std::cout << "循环变量 i:" << i << '\n';
    }

    return 0;
}

运行结果如下。

加上奖励分:85
循环变量 i:0
循环变量 i:1
循环变量 i:2

分析:

int score = 80; 定义在 main() 函数的最外层花括号内,因此在里面的 if 代码块中依然可以正常读取 score。

而 bonus 定义在 if 的花括号内,循环变量 i 定义在 for 循环中,它们仅在各自的代码块内有效。如果像下面这样在 for 循环外面继续访问 i,编译器就会直接报错:

for (int i = 0; i < 3; i++) {
    std::cout << i << '\n';
}

std::cout << i << '\n';

C++ 全局变量

在 C++ 中,如果我们把变量定义在所有函数的外面,那么它就属于 “全局变量”。

1. 定义与访问全局变量

全局变量的作用范围是从定义它的位置开始,一直到整个源文件结束。也就是说,排在它后面的任何函数都可以直接读取或修改它的值。因此在实际写代码时,我们通常把全局变量统一放在文件开头的 #include 头文件下方。

示例 3:跨函数读写全局变量

#include <iostream>

int points = 100;    // 定义全局变量

void addPoints() {
    points += 15;    // 修改全局变量
}

int main() {
    std::cout << "初始积分:" << points << '\n';
    addPoints();
    std::cout << "增加后积分:" << points << '\n';
    return 0;
}

运行结果如下。

初始积分:100
增加后积分:115

分析:

在这个例子中,points 定义在所有函数外面,因此 addPoints() 和 main() 访问的是同一个全局变量。当我们调用 addPoints() 执行 points += 15; 后,points 的值变成了 115,回到 main() 再次打印时输出的就是 115。

通过下面这段代码,我们可以直观看出全局变量与局部变量在定义位置上的区别:

#include <iostream>

int a = 10;        // 全局变量

void fn() {
    int b = 20;    // 局部变量
}

int main() {
    int c = 30;    // 局部变量
    return 0;
}

2. 全局变量的默认初始化

在前面学习变量时我们知道,普通的局部变量如果在定义时没有赋初值,里面保存的是不确定的随机垃圾值,直接读取会导致未定义行为。而全局变量如果在定义时没有显式赋初值,程序会自动将数值类型的全局变量默认初始化为 0。

示例 4:未赋初值的全局变量

#include <iostream>

int totalCount;      // 默认初始化为 0
double unitPrice;    // 默认初始化为 0.0

int main() {
    std::cout << "totalCount = " << totalCount << '\n';
    std::cout << "unitPrice = " << unitPrice << '\n';
    return 0;
}

运行结果如下。

totalCount = 0
unitPrice = 0

分析:

从运行结果可以看出,全局变量 totalCount 和 unitPrice 虽然没有写出初始值,但程序启动时已经自动将它们初始化为了 0。不过在实际开发中,无论是全局变量还是局部变量,我们都建议大家在定义时明确写出初始值,这样代码的意图更加清晰。

全局变量与局部变量同名

如果在所有函数外面已经定义了一个全局变量,而在某个函数内部又定义了一个同名的局部变量,会发生什么呢?

在 C++ 中,这种情况是允许的,并且遵循 “就近原则”:在该函数内部,局部变量会暂时遮蔽外面的同名全局变量。如果我们确实需要在函数内部访问被遮蔽的全局变量,可以在变量名前面加上全局作用域符 ::。

示例 5:全局变量与局部变量同名

#include <iostream>

int year = 2026;        // 全局变量

void testYear() {
    int year = 2030;    // 同名局部变量
    std::cout << "局部 year:" << year << '\n';
    std::cout << "全局 ::year:" << ::year << '\n';
}

int main() {
    testYear();
    std::cout << "main 中的 year:" << year << '\n';
    return 0;
}

运行结果如下。

局部 year:2030
全局 ::year:2026
main 中的 year:2026

分析:

在 testYear() 内部,直接写 year 会优先找到离它最近的局部变量 2030。而 ::year 用到了我们在命名空间中学过的作用域符 :::当双冒号左边留空时,表示直接去全局作用域查找,因此输出了全局变量的值 2026。

等 testYear() 执行完毕回到 main() 后,由于 main() 内部没有定义局部变量 year,直接访问的依然是全局变量 2026。这也说明在子函数里定义同名局部变量,并不会修改外面全局变量本身的值。

静态局部变量(static)

前面我们说过,普通局部变量在函数执行结束时就会被销毁,下次再调用这个函数时又会重新创建并初始化。如果我们希望一个局部变量在函数结束后 “不要销毁”,而是保留上一次留下来的值供下次调用继续使用,可以在定义局部变量时加上 static 关键字,将其声明为 “静态局部变量”。

语法:

static 数据类型 变量名 = 初始值;

说明:

static 静态局部变量具有以下 2 个特点:

  • 生命周期延续到程序结束:所谓 “生命周期”,指的就是变量从创建到销毁的存活时间。静态局部变量只在函数首次被调用时初始化一次,函数执行完毕后它不会被销毁,下次再进入该函数时会直接沿用上一次的值。
  • 作用域仍限于当前函数内部:虽然它在整个程序运行期间一直存在,但依然只有定义它的那个函数能访问它,其他函数看不见它。

示例 6:使用 static 统计调用次数

#include <iostream>

void countCall() {
    static int count = 0;    // 仅首次调用时初始化
    count++;
    std::cout << "函数被调用了 " << count << " 次" << '\n';
}

int main() {
    countCall();
    countCall();
    countCall();
    return 0;
}

运行结果如下。

函数被调用了 1 次
函数被调用了 2 次
函数被调用了 3 次

分析:

static int count = 0; 表示将 count 声明为静态局部变量,连续 3 次调用的具体变化过程如下:

  • 第 1 次调用 countCall():count 初始化为 0,执行 count++ 后变成 1。函数结束时 count 不会销毁。
  • 第 2 次调用 countCall():程序自动跳过初始化语句,直接在原来 1 的基础上执行 count++ 变成 2。
  • 第 3 次调用 countCall():继续在 2 的基础上累加到 3,因此输出 3 次。

对于这个例子来说,如果我们把前面的 static 关键字去掉,每次调用 countCall() 时都会重新把 count 初始化为 0 再加到 1,最终 3 次都会输出 “函数被调用了 1 次”。

注意

优先使用局部变量:在满足需求的前提下,我们应该优先使用局部变量。局部变量只在当前函数内部有效,各个函数之间互不干扰,代码更安全也好维护。

尽量少用全局变量:如果很多函数都能随意修改同一个全局变量,一旦数据出错就会很难排查。凡是能通过函数参数传递数据的场景,我们都推荐使用局部变量配合传参来完成。

避免内外层变量同名:虽然语法允许局部变量与全局变量同名,但容易让人看花眼,平时写代码时小伙伴们尽量给不同含义的变量起不同的名字。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号