C++ 变量
变量(variable)是程序里存储数据的容器。每个变量都有名字、类型、值、地址。这一章我们看 C++ 变量的声明、初始化、作用域,以及现代 C++ 的两个重要新特性:auto 类型推导和 constexpr 编译期常量。
一、声明与初始化
C++ 声明变量要先告诉编译器类型(这是和 Python/JS 最大的不同)。声明方式有四种:
#include <iostream>
using namespace std;
int main() {
// 1. 先声明后赋值
int age;
age = 20;
// 2. 声明同时初始化(推荐)
int score = 95;
// 3. C++11 列表初始化(花括号,最安全)
int count{0};
double pi{3.14};
// 4. 小括号初始化(构造函数风格)
int year(2024);
cout << age << " " << score << " " << count << endl;
return 0;
}
// 输出: 20 95 0四种初始化方式对比:
- 等号初始化(
int x = 5):最经典,直接好懂。 - 列表初始化(花括号语法):C++11 引入,最严格——会禁止"窄化转换"(如把 double 字面量赋给 int 会报错,因为损失精度)。生产代码推荐。
- 小括号初始化(
int x(5)):构造函数风格,主要用于对象。 - 先声明后赋值:不推荐——未初始化的局部变量值未定义,容易出 bug。
二、变量的作用域
作用域(scope)是变量"可见"的代码区域。C++ 有三种主要作用域:
#include <iostream>
using namespace std;
int globalVar = 100; // 全局变量:函数外声明,程序结束才销毁
int main() {
int localVar = 10; // 局部变量:函数内声明,函数结束销毁
cout << globalVar << " " << localVar << endl; // 100 10
{ // 进入一个新"块作用域"
int blockVar = 5;
cout << blockVar << endl; // 5
// 这里能访问 globalVar, localVar, blockVar
}
// 离开块,blockVar 销毁
// cout << blockVar << endl; // 编译错误:blockVar 未声明
return 0;
}- 全局作用域:函数外声明的变量,整个文件都能访问,程序退出时销毁。
- 局部作用域:函数内声明的变量,只在函数内有效,函数返回时销毁。
- 块作用域:花括号内声明的变量,出花括号就销毁。
作用域越小越好——这是工程上的铁律。作用域越小,bug 越少,代码越好懂。
三、变量遮蔽(shadowing)
当内层作用域的变量和外层同名时,内层的会"遮蔽"外层的。这是常见 bug 来源,要小心:
#include <iostream>
using namespace std;
int x = 1; // 全局 x
int main() {
int x = 2; // 局部 x 遮蔽了全局 x
cout << x << endl; // 2(局部 x)
{
int x = 3; // 内层 x 遮蔽了外层 x
cout << x << endl; // 3
}
cout << x << endl; // 2(局部 x)
cout << ::x << endl; // 1(用 :: 显式访问全局 x)
return 0;
}编译器一般会警告变量遮蔽。建议开 -Wshadow 选项让它变成警告,避免不小心遮蔽。
四、static:静态变量
static 关键字用在局部变量上,意味着"只初始化一次,函数退出后依然保留"。常用于实现计数器、缓存:
#include <iostream>
using namespace std;
int counter() {
static int count = 0; // 静态局部变量:只初始化一次
count++;
return count;
}
int main() {
cout << counter() << endl; // 1
cout << counter() << endl; // 2
cout << counter() << endl; // 3
cout << counter() << endl; // 4
return 0;
}每次调用 counter(),count 不会被重新初始化为 0,而是保留上次的值。这种"函数内但生命周期是整个程序"的变量,适合延迟初始化(只在第一次调用时构造)。
五、const 与 constexpr 常量
常量是"不能被修改的变量"。C++ 有两种:
#include <iostream>
using namespace std;
const double PI = 3.14159; // 运行时常量
constexpr int SIZE = 1024; // 编译期常量(C++11)
int main() {
const int MAX = 100;
// MAX = 200; // 编译错误:const 不能重新赋值
int x = 10;
const int& ref = x; // const 引用:不能通过 ref 修改 x
cout << PI << " " << SIZE << " " << MAX << endl;
return 0;
}- const:运行时常量。值在运行时确定,但不能修改。
- constexpr(C++11):编译期常量。值在编译时就必须能算出来,性能更好(可以用于数组大小、模板参数)。
规则:能用 constexpr 就用 constexpr。它能编译期计算,可以做更多优化。
六、auto:类型推导(C++11)
这是现代 C++ 最重要的特性之一。auto 让编译器根据初始值自动推导变量类型,大大简化代码:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
// auto 让编译器根据初始值自动推导类型(C++11)
auto i = 42; // int
auto d = 3.14; // double
auto s = "hi"; // const char*
auto str = string("hi"); // string
auto v = vector<int>{1, 2, 3};
// 常见用法:简化迭代器/复杂类型书写
vector<int> nums = {1, 2, 3, 4, 5};
for (auto it = nums.begin(); it != nums.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 输出: 1 2 3 4 5
return 0;
}auto 的典型场景:
- 类型名太长:如 STL 迭代器
vector<int>::const_iterator,写auto一秒搞定。 - 不想关心具体类型:比如 lambda 表达式的类型没法手写。
- 重构友好:把
int改long时,所有auto都自动适应。
但要注意:auto 不该滥用。当类型对理解代码很重要时(比如函数返回值类型影响行为),还是写明白更好。
七、未初始化变量:坑!
C++ 局部变量默认不会初始化为 0!这是从 C 继承下来的"性能优先"设计。但容易出 bug:
#include <iostream>
using namespace std;
int main() {
int x; // 未初始化:值未定义(可能是任意值)
cout << x << endl; // 危险!可能输出任意数字
int y{}; // 列表初始化:默认 0
cout << y << endl; // 0
int z = int(); // 值初始化:0
cout << z << endl; // 0
return 0;
}建议:声明变量时一定给初始值。哪怕给个 0 也比不初始化强。更稳妥的写法是用列表初始化语法,它会自动赋 0。
小结
这一章你掌握了:变量声明的四种方式(推荐列表初始化)、三种作用域(全局/局部/块)、变量遮蔽、static 静态变量、const/constexpr 常量、auto 类型推导。下一篇我们看 C++ 的数据类型——这些变量到底能装什么样的数据。
← 上一篇 C++ 基础语法
下一篇 C++ 数据类型 →