英文原文:
https://www.jacksondunstan.com/articles/5543
今天我们继续这个系列,介绍变量以及它们是如何初始化的。对于 C# 开发人员来说,这是另一个具有惊人复杂性的基本主题。
声明
C# 开发人员应该非常熟悉变量声明的基本形式:
int x;
C# 就像在 C# 中一样,我们声明变量的类型、变量的名称,并以分号结尾。我们也可以在一个语句中声明多个变量:
int x, y, z;
与 C# 一样,这些变量还没有值。考虑尝试读取这样一个变量的值:
int x;
int y = x;
在 C# 中,这将导致第二行出现编译器错误。编译器知道 x 没有值,因此无法读取并分配给 y。在 C++ 中,这被称为“未定义行为”。当编译器遇到未定义的行为时,它可以自由地为整个可执行文件生成任意代码。它可能会或可能不会产生警告或错误来警告这一点,这意味着它可能会默默地产生一个可执行文件,该可执行文件不执行作者认为它应该执行的操作。永远不要调用未定义的行为非常重要,并且已经编写了工具来帮助避免它。
这种未定义的行为确实有一个目的:速度。考虑一下:
int localPlayerHealth;
foreach (Player p in players)
{
if (p.IsLocal)
{
localPlayerHealth = p.Health;
break;
}
}
Debug.Log(localPlayerHealth);
我们知道一个玩家必须是本地玩家,因为我们的游戏就是这样设计的,所以在循环之前不初始化 localPlayerHealth 是安全的。在这种情况下,将其初始化为 0 会很浪费,但是 C# 编译器不了解我们的游戏设计,因此它不能证明我们总能找到本地玩家并强制我们进行初始化。
在 C++ 中,我们可以自由地跳过这个初始化并承担未定义行为的风险,如果事实证明在 player 数组中确实没有本地player。或者,我们可以复制 C# 方法并仅初始化变量以确保安全。
初始化
C++ 提供了很多初始化变量的方法。我们已经在上面看到了一个值被复制的例子:
int x = y;
还有一些 C# 中没有的其他方法:
int x{}; // x is filled with zeroes, so x == 0
int x{123};
int x(123);
存在更多类型的初始化,但特定于某些类型,例如数组和类。我们将在本系列的后面部分介绍这些内容。
在一个语句中声明多个变量时,所有这些初始化策略都可以组合使用:
int a, b = 123, c{}, d{456}, e(789);
这会产生以下值:
类型推论
在 C# 中,我们可以使用 var 来避免需要指定变量的类型。同样,C++ 有 auto 关键字:
auto x = 123;
auto x{123};
auto x(123);
也类似于 C#,我们只能在有初始化程序时使用 auto。不允许出现以下情况:
auto x;
auto x{};
重要的是要记住 x 是强类型的,就好像 int 被显式指定一样。这里发生的只是编译器正在确定变量应该是什么类型,而不是我们手动输入它。
另一种不太常见的方法是使用 decltype 运算符。这解析为其参数的类型:
int x;
decltype(x) y = 123; // y is an int
最后,从C++17开始,寄存器关键字已经被废弃:
register int x = 123;
它用于请求将变量放入 CPU 寄存器而不是 RAM 中,例如堆栈中。编译器早就忽略了这个请求,所以现在最好避免这个关键字。
标识符
命名 C++ 标识符的规则类似于 C# 的规则。它们必须以字母、下划线或任何非数字 Unicode 字符开头。之后,它们可以包含任何 Unicode 字符,除了一些非常奇怪的字符。
此外,我们可以选择的名称有一些限制:
没有等效于 C# 的“逐字标识符”(例如 int @for)来解决关键字限制。
指针
与 C# 一样,至少在启用“不安全”功能时,C++ 具有指针类型。语法甚至类似:
int* x;
int * x;
int *x;
* 的位置很灵活,就像在 C# 中一样。但是,在一行中声明多个变量在 C++ 中是不同的。考虑这个声明:
int* x, y, z;
y 的类型因语言而异,因为 * 仅附加到 C++ 中的一个变量:
要将所有三个变量都变成 C++ 中的指针,请为每个变量添加一个 *:
int *x, *y, *z;
或者省略 * 以便只有一些是指针:
int *x, y, *z; // x and z are int*, y is int
我们将在本系列的后面更深入地介绍如何实际使用指针。
引用
C++ 有两种引用:“左值”和“右值”。就像指针一样,这些是另一种类型的注释:
// lvalue references
int& x;
int & x;
int &x;
// rvalue references
int&& x;
int && x;
int &&x;
当每条语句声明多个变量时,同样的规则在这里适用:& 或 && 只附加到一个变量:
int &x, y; // x is an int&, y is an int
总而言之,这意味着我们可以在每个语句中声明多个变量,并且每个变量都可以在声明的类型上拥有自己的修饰符:
int a, *b, &c, &&d;
变量获得以下类型:
我们将在本系列的后面部分深入探讨左值引用和右值引用如何工作的细节。现在,重要的是要知道它们就像不可为空的指针。这意味着我们必须在声明它们时对其进行初始化。上述所有行都将无法编译,因为我们没有编译。所以让我们更正一下:
int x = 123;
int& y = x;
int&& z = 456;
在这里,我们将 y 作为“对 int 的左值引用”。我们将它初始化为一个左值,它本质上是任何有名字的东西。 x 有一个名字并且是正确的类型:int。结果是 y 现在引用 x。
z 是“对 int 的右值引用”。右值本质上是没有名称的任何东西。我们将它初始化为 456,它没有名字但有正确的类型:int。这意味着 z 现在引用 456。
把它放在一起,我们最终会在需要时声明和初始化多个变量,如下所示:
int x = 123;
int a, *b, &c = x, &&d = 456;
总结
在高层次上,C++中的变量与C#相似。但在细节上,有非常重要的区别。不初始化变量所产生的未定义行为,指针和引用字符只适用于多个声明中的一个变量,各种新的初始化语法,以及lvalue和rvalue引用的存在,都使得即使在变量这个基本类别中也有相当不同的情况。
在本系列的后面,我们将在讨论类、数组、函数指针、lambda 和各种其他奇特的主题时扩展这个主题。敬请关注!