由于当前的最佳答案是在 2011 年编写的并且仅涉及 C++03,因此我提供了一个更新的答案以考虑 C++11 之后所做的更改。请注意,我正在删除仅在 C++03 或 C++11 之前有效的任何信息以及可以在原始资源中看到的不必要的注释。我尽可能多地引用原始规范,以避免可能导致信息不准确的不必要的重新制定。如果您有兴趣深入研究某个主题,请查阅我提供的原始资源。另外,请注意,我主要关注关于
* 默认初始化
* 未定义的行为
* 零初始化
因为在我看来,这些是理解变量“默认”如何表现所需的最重要方面,正如问题所问的那样。
Default initialization is performed in some cases:
- 当声明具有自动、静态或线程本地存储持续时间的变量时没有初始化器;
- 当具有动态存储持续时间的对象由不带初始化程序的 new 表达式创建时;
- 当构造函数初始化器列表中未提及基类或非静态数据成员并且调用该构造函数时。
这个默认初始化的效果是:
意味着如果未初始化的变量是局部变量(例如,int 仅存在于函数的作用域中),则其值是不确定的(未定义的行为)。 cppreference strongly discourages the usage of uninitialized variables.
附带说明,即使大多数现代编译器在检测到正在使用未初始化的变量时会发出错误(在编译时),但如果您“欺骗”它们,它们通常不会这样做认为您可能正在以某种方式初始化变量,例如:
int main()
{
int myVariable;
myFunction(myVariable); // does not change the variable
cout << myVariable << endl; // compilers might think it is now initialized
}
从 C++14 开始,以下内容成立(注意 std::byte 是在 C++17 中引入的):
使用通过默认初始化任何类型的非类变量获得的不确定值是未定义行为(特别是,它可能是陷阱表示),但以下情况除外:
如果unsigned char 或std::byte 类型的不确定值分配给unsigned char 或std::byte 类型(可能是cv 限定)的另一个变量(变量的值变得不确定,但行为不是未定义的);
如果unsigned char 或std::byte 类型的不确定值用于初始化unsigned char 或std::byte 类型的另一个变量(可能是cv 限定);
-
如果 unsigned char 或 std::byte (C++17 起)类型的不确定值来自
- 条件表达式的第二个或第三个操作数,
- 逗号运算符的右操作数,
- 强制转换或转换为(可能是 cv 限定的)
unsigned char 或 std::byte 的操作数,
- 丢弃值表达式。
有关变量默认初始化及其行为的更多详细信息,请参阅here。
为了更深入地研究不确定的值,2014 年进行了以下更改(如Shafik Yaghmour pointed out here 以及其他有用的资源):
如果没有为对象指定初始化器,则该对象是默认初始化的;如果不执行初始化,则具有自动或动态存储持续时间的对象具有不确定的值。 [注意:具有静态或线程存储持续时间的对象是零初始化的]
到:
如果没有为对象指定初始化程序,则该对象是默认初始化的。当获得具有自动或动态存储持续时间的对象的存储时,该对象具有一个不确定的值,如果没有对该对象执行初始化,则该对象保留一个不确定的值,直到该值被替换。 [注意:具有静态或线程存储持续时间的对象初始化为零]如果评估产生不确定的值,则行为未定义,但以下情况除外:
最后还有zero-initialization的主题,在以下情况下执行:
对于每个具有静态或线程本地存储持续时间且不受常量初始化(C++14 起)的命名变量,在任何其他变量之前初始化。
-
作为非类类型和没有构造函数的值初始化类类型成员的值初始化序列的一部分,包括未提供初始化器的聚合元素的值初始化。
当使用太短的字符串字面量初始化任何字符类型的数组时,数组的其余部分将被初始化为零。
零初始化的效果是:
如果 T 是标量类型,则对象的初始值是显式转换为 T 的整数常量零。
如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,并且所有填充都初始化为零位。构造函数(如果有)将被忽略。
如果 T 是联合类型,则第一个非静态命名数据成员初始化为零,所有填充都初始化为零位。
如果T是数组类型,每个元素都初始化为零
如果 T 是引用类型,则什么也不做。
以下是一些示例:
#include <iostream>
#include <string>
struct Coordinates {
float x, y;
};
class WithDefaultConstructor {
std::string s;
}
class WithCustomConstructor {
int a, b;
public:
WithCustomConstructor() : a(2) {}
}
int main()
{
int a; // Indeterminate value (non-class)
int& b; // Error
std::string myString; // Zero-initialized to indeterminate value
// but then default-initialized to ""
// (class, calls default constructor)
double coordsArray[2]; // Both will be 0.0 (zero-initialization)
Coordinates* pCoords; // Zero-initialized to nullptr
Coordinates coords = Coordinates();
// x: 0.0
// y: 0.0
std::cout << "x: " << coords.x << '\n'
"y: " << coords.y << std::endl;
std::cout << a.a << a.b << a.c << '\n';
WithDefaultConstructor wdc; // Since no constructor is provided,
// calls the default constructor
WithCustomConstructor wcs; // Calls the provided constructor
// a is initialized, while b is
// default-initialized to an indeterminate value
}