变量其实只不过是程序可操作的存储区的名称。

C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。

一.C++中的变量定义

变量定义是为变量分配存储空间的动作。

  • 作用: 它告诉编译器:“我要创建一个名为 X、类型为 Y 的变量,请在内存中为它留出空间。”
  • 特性:
    • 唯一性: 每个变量在整个程序中只能被定义一次
    • 内存分配: 定义会伴随着内存的分配。
    • 初始化: 定义时可以同时对变量进行初始化(赋值)。

示例:

int a;          // 定义:为 int 类型的变量 a 分配内存,并默认初始化
double pi = 3.14159; // 定义:为 double 类型的变量 pi 分配内存,并初始化为 3.14159

二.C++ 中的变量声明

变量声明是向编译器告知变量的类型和名称,但不分配内存空间。

  • 作用: 它的主要目的是让编译器知道这个变量是存在的,以及它是什么类型的,以便在当前文件中使用它,即使它的实际存储空间是在另一个文件中定义的。
  • 关键字: 在函数体外声明其他文件定义的变量时,通常使用 extern 关键字。

示例:
假设你有一个名为 data.cpp 的文件,其中定义了 count

// data.cpp
int count = 10; // 这是定义,分配了内存

如果你想在另一个文件 main.cpp 中使用 count,你需要先声明它:

// main.cpp
extern int count; // 这是声明,它告诉编译器:
// “存在一个 int 类型的变量 count,但它在别处定义和分配了内存,
// 我在这里要引用它。”

void use_count() {
count++; // 使用 count
}

特殊情况:没有 extern 关键字的声明
在 C++ 中,一个未被初始化的普通声明如果在全局(函数体外)或命名空间范围内,它同时也是一个定义

int value; // 既是声明,也是定义(在全局或命名空间范围)

只有在以下情况中,声明才不是定义:

  1. 使用了 extern 关键字,且没有显式初始化:
    extern int i; // 声明,不是定义
  2. 函数声明(函数声明也属于声明,不分配函数体内存):
    int max(int a, int b); // 声明,不是定义

三.C++中的变量初始化

在 C++ 中,定义变量时如果没有显式初始化,变量的值取决于它的存储类型作用域,可能出现 “未定义值”“默认初始化” 或 “零初始化” 等情况,具体规则如下:

1. 局部变量(自动存储期,auto):值是 “未定义的(undefined)”

  • 场景:在函数内部、代码块({})中定义的非静态局部变量(默认是auto存储类型,可省略auto关键字)。
  • 行为:未初始化时,变量的值是不确定的(垃圾值),它会随机占用内存中该位置之前遗留的二进制数据。
  • 风险:使用这类变量的值会导致未定义行为,可能引发程序崩溃、结果错误等不可预测的问题。
void func() { 
int a; // 局部变量,未初始化
cout << a; // 危险!输出值不确定,属于未定义行为
}

2. 静态变量(static)和全局变量:默认 “零初始化”

  • 场景
    • 全局变量(定义在所有函数 / 类外部);
    • static修饰的局部变量(函数内的静态变量);
    • static修饰的类成员变量(静态成员)。
  • 行为:如果未显式初始化,编译器会自动将其初始化为零值(zero-initialized),具体规则:
    • 数值类型(intdouble等)→ 00.0
    • 指针类型 → nullptr
    • 布尔类型 → false
    • 数组 / 结构体 → 每个成员都按上述规则零初始化。
int g;  // 全局变量,未初始化→默认0
void func() {
static int s; // 静态局部变量,未初始化→默认0
cout << g << " " << s; // 输出 0 0
}

3. 类 / 结构体的成员变量:默认初始化依赖构造函数

  • 场景:类或结构体的非静态成员变量。
  • 行为
    • 如果类没有自定义构造函数,且成员变量未在初始化列表或声明时初始化,则其值不确定(类似局部变量);
    • 如果类有自定义构造函数,但未在构造函数中初始化成员变量,则值不确定
    • 若使用值初始化(如MyClass obj{};),即使没有显式初始化,成员变量也会被零初始化(C++11 及以上)。
struct Test {
int x;
double y;
};

Test t1; // 成员x、y未初始化→值不确定
Test t2{}; // C++11值初始化→x=0,y=0.0

四.全局变量

1. 定义与作用域

  • 定义位置: 定义在所有函数体(包括 main 函数)外部的变量。
  • 作用域: 具有文件作用域。一旦定义,从定义点开始,直到文件末尾,该变量都有效。
  • 多文件注意: 如果需要在其他文件中使用同一个全局变量,需要使用 extern 关键字进行声明。

2. 生命周期

  • 持续时间: 具有静态生命周期。程序启动时创建,程序结束时销毁。
  • 初始化: 如果没有显式初始化,全局变量会被默认初始化为零值(例如,int 初始化为 0,指针初始化为 nullptr)。

3. 存储位置

  • 全局变量通常存储在程序的静态/全局数据区中。

4. 示例

#include <iostream>

// --- 全局变量定义在所有函数外部 ---
int global_counter = 100; // 显式初始化为 100
double global_pi; // 默认初始化为 0.0

void increment_global() {
// 可以在任何函数中访问和修改
global_counter++;
}

int main() {
std::cout << "初始PI: " << global_pi << std::endl; // 0
std::cout << "初始Counter: " << global_counter << std::endl; // 100

increment_global();

std::cout << "修改后的Counter: " << global_counter << std::endl; // 101

return 0;
}

五.局部变量

1. 定义与作用域

  • 定义位置: 定义在函数体内部、代码块 {} 内部、或函数参数列表中的变量。
  • 作用域: 具有块作用域。它们只能在定义它们的函数或代码块内部访问。一旦离开了定义它的 {},变量就变得不可访问。

2. 生命周期

  • 持续时间: 具有自动生命周期。当程序执行流进入定义它们的代码块时创建,当程序执行流离开该代码块时销毁。
  • 初始化: 如果没有显式初始化(也叫未初始化),局部变量将包含不确定的值(通常是内存中已有的垃圾数据)。这是一个常见的错误来源!

3. 存储位置

  • 局部变量通常存储在程序的栈区中。

4. 示例

#include <iostream>

void my_function(int param_a) { // param_a 是一个局部变量(参数)
int local_x; // 未初始化的局部变量,包含垃圾数据!
int local_y = 5; // 显式初始化的局部变量

// 可以在这个函数体内部访问
std::cout << "Local_y: " << local_y << std::endl;
// 不建议打印未初始化的 local_x,但它可以被使用
// std::cout << "Local_x (危险): " << local_x << std::endl;

if (true) {
int block_scope_z = 20; // 块作用域变量
std::cout << "Block_z: " << block_scope_z << std::endl;
} // block_scope_z 在此处销毁

// std::cout << block_scope_z << std::endl; // 错误:z 在此作用域不可见
} // local_y, local_x, param_a 在此处销毁

int main() {
my_function(1);

// std::cout << local_y << std::endl; // 错误:local_y 在 main() 中不可见

return 0;
}

六.静态变量

无论 static 关键字用在哪里,它首先改变的是变量的生命周期

静态生命周期 (Static Lifetime): 变量从程序启动时创建,到程序结束时销毁。

这意味着,即使在函数内部,静态变量也只初始化一次,并且在函数调用结束后,它的值会保留下来。

一、静态局部变量

当在函数内部声明一个局部变量时,使用 static 关键字。

1. 作用与特性

  • 作用域: 保持局部作用域。它只能在定义它的函数内部被访问(和普通局部变量一样)。
  • 生命周期: 变为静态生命周期
    • 只初始化一次:即使函数被多次调用,初始化语句也只在第一次进入函数时执行。
    • 值保持:函数执行结束后,变量的值会保留在内存中,直到程序结束。

      2. 示例

      #include <iostream>

      void counter() {
      // 普通局部变量:每次函数调用时创建,并初始化为 0
      int non_static_count = 0;

      // 静态局部变量:只在程序启动时或第一次调用时初始化为 0
      static int static_count = 0;

      non_static_count++;
      static_count++;

      std::cout << "非静态计数: " << non_static_count
      << ", 静态计数: " << static_count << std::endl;
      }

      int main() {
      std::cout << "第一次调用: ";
      counter(); // non_static_count: 1, static_count: 1

      std::cout << "第二次调用: ";
      counter(); // non_static_count: 1 (重新初始化), static_count: 2 (值保留)

      std::cout << "第三次调用: ";
      counter(); // non_static_count: 1 (重新初始化), static_count: 3 (值保留)

      return 0;
      }

用途: 常用于实现一个函数内共享的、需要保持状态的计数器,或用于存储只需要进行一次的昂贵计算结果。

二、静态全局变量

当在文件(源文件 .cpp)的全局作用域内声明变量时,使用 static 关键字。

注意: 在现代 C++ 中,不推荐使用 static 来限制全局变量的作用域,而应使用匿名命名空间来实现同样的目的。

1. 作用与特性

  • 作用域限制(文件作用域): static 在这里的作用是限制变量的可见性。它将全局变量的作用域限制在定义它的当前源文件(编译单元)内。
  • 链接属性: 具有内部链接。这意味着它不能被其他源文件通过 extern 关键字访问或引用。

2. 示例

假设你有 file1.cppfile2.cpp

在 file1.cpp 中:

// 静态全局变量:只能在 file1.cpp 中使用
static int private_data = 5;

// 普通全局变量:可以在其他文件用 extern 引用
int public_data = 10;

在 file2.cpp 中:

#include <iostream>

// 可以引用 public_data
extern int public_data;

// 尝试引用 private_data 会在链接阶段失败!
// extern int private_data;

void access_data() {
std::cout << "Public Data: " << public_data << std::endl; // 10
// std::cout << "Private Data: " << private_data << std::endl; // 编译/链接错误
}

用途: 创建仅供当前源文件内部使用的全局配置或数据,避免命名冲突。

三、静态成员变量

当在 C++ 类内部声明一个成员变量时,使用 static 关键字。

1. 作用与特性

  • 独有性: 静态成员变量不属于任何特定的对象。它是该类的所有对象共享的一个变量。
  • 内存分配: 即使没有创建任何对象,静态成员也存在,并且只分配一次内存
  • 初始化: 必须在类定义外部进行定义和初始化(通常在 .cpp 文件中)。

2. 示例

#include <iostream>

class Dog {
public:
// 静态成员变量:所有 Dog 对象共享的计数器
static int object_count;

Dog() {
object_count++; // 每创建一只狗,计数器加 1
}
~Dog() {
object_count--; // 每销毁一只狗,计数器减 1
}
};

// 静态成员必须在类定义外部进行定义和初始化
int Dog::object_count = 0;

int main() {
std::cout << "当前狗的数量: " << Dog::object_count << std::endl; // 0

Dog fido; // 创建对象
{
Dog sparky; // 创建对象
std::cout << "当前狗的数量: " << Dog::object_count << std::endl; // 2
} // sparky 被销毁

std::cout << "当前狗的数量: " << Dog::object_count << std::endl; // 1

return 0;
}

用途: 常用于跟踪类实例的数量、存储所有对象共享的配置数据、或实现单例模式。