在 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 次”。
注意
优先使用局部变量:在满足需求的前提下,我们应该优先使用局部变量。局部变量只在当前函数内部有效,各个函数之间互不干扰,代码更安全也好维护。
尽量少用全局变量:如果很多函数都能随意修改同一个全局变量,一旦数据出错就会很难排查。凡是能通过函数参数传递数据的场景,我们都推荐使用局部变量配合传参来完成。
避免内外层变量同名:虽然语法允许局部变量与全局变量同名,但容易让人看花眼,平时写代码时小伙伴们尽量给不同含义的变量起不同的名字。
