Logo成贤计协指南

变量和常量

本节将介绍变量和常量的概念、声明和使用方法。

上一节我们了解了数据类型是什么,知道了数据类型用于规定数据的种类和范围。但是在实际编程中,我们需要一个容器来存储这些数据,这个容器就是变量和常量。我们可以把变量和常量想象成带有标签的盒子,用来存放不同类型的数据。

编程语言为我们提供了“变量”和“常量”这两种基本的存储方式。它们就像是程序中的“容器”或“盒子”,每个盒子都有一个标签(名字),用来标识盒子里的内容。

变量是什么

变量是程序运行过程中可以存储数据的容器。变量的最大特点是盒子里的内容可以随时更换,也就是说,变量存储的数据在程序运行期间可以被修改。每个变量都有一个名字(标签),通过这个名字我们可以随时访问盒子里的内容,也可以把新的数据放进去。

从计算机的角度来看,变量其实就是内存中的一块空间。我们给这块空间起一个名字,并指定它能存放什么类型的数据(比如整数、浮点数、字符等)。在程序运行时,我们理论上可以随时读取或修改这块空间里的内容。

常量是什么

常量也是一种数据容器,但它和变量最大的不同是:盒子里的内容一旦放进去就不能再更换。常量的值在程序运行期间始终保持不变。常量适合用来存储那些不会发生变化的数据,比如圆周率、一天的小时数等。

Tips

在一些编程语言(如 Java、Kotlin、Rust、Swift)中,除了“常量”之外,还存在“不可变变量”的概念。不可变变量和常量非常相似,都是盒子里的内容不能被更换,但它们在语义上有一些细微的区别。

  • 常量通常指的是在编译期间就确定了值,整个程序运行过程中都不会改变。
  • 不可变变量则是指在程序运行期间,变量的值一旦初始化就不能再被修改,但它的值可以在运行时确定(比如通过计算得到)。

在 C++ 中,使用 const 关键字声明的变量既可以被称为常量,也可以被称为不可变变量。它们的行为和常量一样,初始化后值不可更改。虽然 C++ 习惯上称之为“常量”,但理解为“不可变变量”也没有问题。

定义变量与常量

变量的定义

定义一个变量很简单。在 C++ 中定义一个变量的格式如下:

<类型名> <变量名> [= <初始值>] [, <变量名> [= <初始值>], ...];
初始赋值对于变量来说是可以省略的,但是通常我们不建议在初始化变量是省略变量初始值,因为这会在变量里留下一个不确定的值,可能导致程序行为不可预测。

例如,你可以通过以下方式定义变量:

int a = 10, b = 20;
int c;
int d = 10, e, f = 20;

变量名、常量名和函数名等被称为“标识符”。C++ 标识符的命名必须遵循以下的规则:

  • 标识符只能由字母、数字和下划线组成,且不能以数字开头。
  • 标识符区分大小写。
  • 标识符不能是 C++ 关键字(或称保留字) 1
  • 同一作用域内,标识符不能重复。
  • 标识符必须在使用前定义。

以下的定义是合法的

int thisIsAVariable, _aVariable, Variable123, var_1, Var_1;

而以下所有标识符都是不合法的

int 123Variable, this-is-a-variable, var@1, var!, V ar;

int a = 1;
int a = 2; // 这也是不合法的,因为 a 已经被定义过

a = 2;
int a; // 这也是不合法的,应该在使用前定义标识符。

使用 auto 关键字自动推断类型

在 C++11 及之后的版本中,可以使用 auto 关键字让编译器自动推断变量的类型。这样可以让代码更加简洁,尤其是在变量类型很复杂或者不容易书写时非常有用。

auto 的基本用法如下:

auto a = 10;        // a 被推断为 int 类型
auto b = 3.14;      // b 被推断为 double 类型
auto c = "hello";   // c 被推断为 const char* 类型

注意,auto 只能用于变量定义时,并且必须初始化,因为编译器需要根据初始值来推断类型。

auto 适合用于类型复杂或不易书写的场景,比如迭代器、模板类型等。使用 auto 可以减少代码冗余,提高可读性,但也要注意不要滥用,避免让代码的类型变得不明确。

变量的赋值

变量定义之后,我们可以通过赋值语句来修改变量的值,也可以通过变量名来访问变量的值。

int a = 10; // 定义一个整型变量 a,并初始化为 10
a = 20; // 修改 a 的值为 20

int b; // 定义一个整型变量 b
b = a + 10; // 修改 b 的值为 a 的值加上 10
cout << b; // 输出 b 的值(使用 b)

int x, y, z; // 定义三个整型变量 x, y, z
x = y = z = 10; // 链式赋值,从右往左赋值,先修改 z 的值为 10,然后修改 y 的值为 z 的值,最后修改 x 的值为 y 的值。从结果而言,x、y、z 的值都是 10。

在赋值语句中,C++ 会先计算赋值符号 = 右边的表达式(右值),然后将结果赋给左边的变量(左值)。如果有链式赋值,C++ 会从右往左依次计算并赋值(可以看上方的例子)。

常量的定义

常量的定义与变量类似,只不过需要加上 const 关键字。

const <数据类型> <常量名> = <初始值> [, <常量名> = <初始值>, ...];

与变量定义唯一不同的是,常量必须要设定初始值,因为常量一旦完成初始化,值就不能再被修改

const int MAX_SIZE = 100, MIN_SIZE = 10;
const double PI = 3.1415926;

PI = 3.14; // ❌ 错误,常量不能被修改
常量也可以使用 const auto 自动推断

还有几种常量的定义方法:#define 宏定义常量 和 C++11 出现的 constexpr 编译期常量。这些常量的定义方法会在后面的章节中提到,此处不在赘述。

变量和常量的作用域

在编程中,“作用域”这个词指的是变量或常量可以被访问的范围。你可以把作用域想象成一个“房间”,在这个房间里你可以看到和使用某些盒子(变量或常量),但出了房间我们就无法在访问和修改这些盒子的内容了。

在 C++ 里,变量和常量的作用域通常由它们定义的位置决定。

举个例子:

int a = 10; // 这是在全局作用域定义的变量,也被称为全局变量

int main() {
    int b = 20; // 这是在 main 函数内部定义的变量,也被称为局部变量

    {
        int c = 20; // 这是在 {} 代码块内部定义的变量,也被称为局部变量
        cout << a << endl; // 可以访问 a
        cout << b << endl; // 可以访问 b
        cout << c << endl; // 可以访问 c
    }

    cout << a << endl; // 可以访问 a
    cout << b << endl; // 可以访问 b

    cout << c << endl; // ❌ 错误,c 只能在上面的 {} 代码块的作用域内部访问
}

全局变量可以在整个程序中访问,函数内部定义的变量只能在该函数内使用,而在代码块 {} 内定义的变量则只能在这个代码块里访问,出了代码块就无法再使用。也就是说,变量只能在它被定义的范围内有效,超出这个范围就无法访问。

变量的作用域也决定了它的生命周期——也就是变量“活着”的时间。局部变量在进入代码块时创建,离开代码块时销毁。全局变量在程序开始时创建,程序结束时销毁。

Footnotes

  1. C++ 常见关键字(或称保留字)包括:auto, break, case, catch, char, class, const, const_cast, continue, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, operator, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_cast, struct, switch, template, this, throw, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq 等等。