188

假设我有一个包含私人成员ptrnamepnamernamecrname的课程age。如果我不自己初始化它们会怎样?这是一个例子:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

然后我做:

int main() {
    Example ex;
}

ex中的成员是如何初始化的?指针会发生什么?使用默认构造函数进行0初始化string和? 参考成员呢?还有 const 引用呢?intstring()int()

我想学习它,以便我可以编写更好(无错误)的程序。任何反馈都会有所帮助!

4

8 回答 8

248

代替显式初始化,类中成员的初始化与函数中局部变量的初始化相同。

对于objects,它们的默认构造函数被调用。例如,对于std::string,默认构造函数将其设置为空字符串。如果对象的类没有默认构造函数,如果你没有显式地初始化它,就会出现编译错误。

对于原始类型(指针、整数等),它们没有被初始化——它们包含之前碰巧在该内存位置的任意垃圾。

对于引用(例如std::string&),不初始化它们是非法的,您的编译器会抱怨并拒绝编译此类代码。必须始终初始化引用。

因此,在您的特定情况下,如果它们没有显式初始化:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk
于 2010-06-27T13:29:41.293 回答
30

首先,让我解释一下mem-initializer-list是什么。一个mem-initializer-list是一个逗号分隔的mem-initializer列表,其中每个mem-initializer是一个成员名称(,后跟一个 ,然后是一个表达式列表,然后是一个)表达式列表是成员的构造方式。例如,在

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

用户提供的无参数构造函数的mem-initializer-listname(s_str, s_str + 8), rname(name), crname(name), age(-4). 这个mem-initializer-list意味着name成员由接受两个输入迭代器std::string构造函数初始化,rname成员使用对 的引用进行初始化namecrname成员使用对 的常量引用进行初始化nameage成员使用值进行初始化-4

每个构造函数都有自己的mem-initializer-list,并且成员只能按照规定的顺序进行初始化(基本上是在类中声明成员的顺序)。因此, 的成员Example只能按以下顺序初始化:ptrnamepnamernamecrnameage

当您不指定成员的mem-initializer时,C++ 标准说:

如果实体是类类型的非静态数据成员 ...,则该实体是默认初始化的 (8.5)。...否则,实体未初始化。

在这里,因为name是类类型的非静态数据成员,所以如果namemem-initializer-list中没有指定初始化器,则它是默认初始化的。的所有其他成员Example都没有类类型,因此它们没有被初始化。

当标准说它们没有被初始化时,这意味着它们可以有任何值。因此,因为上面的代码没有初始化pname,它可以是任何东西。

请注意,您仍然必须遵循其他规则,例如必须始终初始化引用的规则。不初始化引用是编译器错误。

于 2010-06-27T14:07:48.807 回答
12

您还可以在声明它们时初始化数据成员:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

我几乎完全使用这种形式,尽管我读过一些人认为它是“糟糕的形式”,也许是因为它是最近才引入的——我认为是在 C++11 中。对我来说这更符合逻辑。

新规则的另一个有用方面是如何初始化本身就是类的数据成员。例如假设这CDynamicString是一个封装字符串处理的类。它有一个构造函数,允许你指定它的初始值CDynamicString(wchat_t* pstrInitialString)。您可以很好地将这个类用作另一个类中的数据成员 - 比如说一个封装 Windows 注册表值的类,在这种情况下,它存储一个邮政地址。要“硬编码”要写入的注册表项名称,请使用大括号:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

请注意,保存实际邮政地址的第二个字符串类没有初始化程序,因此将在创建时调用其默认构造函数 - 可能会自动将其设置为空白字符串。

于 2014-10-29T15:41:16.457 回答
10

如果您的示例类在堆栈上实例化,则未初始化的标量成员的内容是随机且未定义的。

对于全局实例,未初始化的标量成员将被清零。

对于本身是类实例的成员,它们的默认构造函数将被调用,因此您的字符串对象将被初始化。

  • int *ptr;//未初始化的指针(如果是全局指针,则为零)
  • string name;//调用构造函数,初始化为空字符串
  • string *pname;//未初始化的指针(如果是全局指针,则为零)
  • string &rname;//如果初始化失败则编译错误
  • const string &crname;//如果初始化失败则编译错误
  • int age;//标量值,未初始化和随机(如果全局则为零)
于 2010-06-27T13:24:27.527 回答
5

未初始化的非静态成员将包含随机数据。实际上,它们只会具有分配给它们的内存位置的值。

当然,对于对象参数(如string),对象的构造函数可以进行默认初始化。

在您的示例中:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value
于 2010-06-27T13:28:12.697 回答
4

这取决于类的构造方式

回答这个问题需要理解 C++ 语言标准中的一个巨大的 switch case 语句,而且对于普通人来说很难获得直觉。

作为一个简单的例子来说明事情有多困难:

主文件

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

在默认初始化中,您将从:https ://en.cppreference.com/w/cpp/language/default_initialization我们转到“默认初始化的影响是”部分并开始 case 语句:

  • "if T is a non-POD ": no(POD的定义本身就是一个巨大的switch语句)
  • “如果 T 是数组类型”:否
  • “否则,什么都不做”:因此它留下了一个未定义的值

然后,如果有人决定进行值初始化,我们转到https://en.cppreference.com/w/cpp/language/value_initialization “值初始化的影响是”并开始案例陈述:

  • “如果 T 是没有默认构造函数或具有用户提供或删除的默认构造函数的类类型”:不是这种情况。您现在将花费 20 分钟谷歌搜索这些术语:
    • 我们有一个隐式定义的默认构造函数(特别是因为没有定义其他构造函数)
    • 它不是用户提供的(隐式定义)
    • 它没有被删除 ( = delete)
  • “如果 T 是具有既不是用户提供也不是删除的默认构造函数的类类型”:是

这就是为什么我强烈建议您永远不要依赖“隐式”零初始化。除非有强大的性能原因,否则显式初始化所有内容,无论是在构造函数上(如果您定义了一个),还是使用聚合初始化。否则,你会让未来的开发人员非常冒险。

于 2020-03-18T19:09:28.530 回答
2

具有构造函数的成员将调用其默认构造函数进行初始化。

您不能依赖其他类型的内容。

于 2010-06-27T13:28:58.283 回答
0

如果它在堆栈上,则没有自己的构造函数的未初始化成员的内容将是随机且未定义的。即使它是全球性的,依赖它们被归零也是一个坏主意。无论它是否在堆栈上,如果一个成员有自己的构造函数,都会调用它来初始化它。

因此,如果您有 string* pname,则指针将包含随机垃圾。但是对于字符串名称,将调用字符串的默认构造函数,为您提供一个空字符串。对于您的引用类型变量,我不确定,但它可能是对一些随机内存块的引用。

于 2010-06-27T13:32:50.653 回答