如果不明确地执行C ++类成员,它是如何初始化的?

时间:2010-06-27 13:21:07

标签: c++ initialization member-initialization

假设我有一个包含私人网站ptrnamepnamernamecrnameage的课程。如果我自己不初始化会怎么样?这是一个例子:

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

    public:
        Example() {}
};

然后我这样做:

int main() {
    Example ex;
}

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

我还应该知道什么?

有谁知道涵盖这些案例的教程?也许在一些书中?我可以在大学的图书馆访问很多C ++书籍。

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

8 个答案:

答案 0 :(得分:165)

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

对于对象,会调用其默认构造函数。例如,对于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

答案 1 :(得分:26)

首先,让我解释一下 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-list name(s_str, s_str + 8), rname(name), crname(name), age(-4)。此 mem-initializer-list 表示name成员已由the std::string constructor that takes two input iterators初始化,rname成员初始化为namecrname成员初始化为name的const引用,age成员初始化为值-4

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

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

  

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

这里,因为age是类类型的非静态数据成员,所以如果在 mem-initializer-list 中没有指定name的初始化程序,则默认初始化它。 <{1}}的所有其他成员都没有类类型,因此它们没有初始化。

当标准表明它们未初始化时,这意味着它们可以具有任何值。因此,因为上面的代码没有初始化name,所以它可以是任何东西。

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

答案 2 :(得分:9)

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

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注册表值的类,在这种情况下它存储了一个邮政地址。要硬编码&#39;这写入的注册表项名称使用大括号:

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;
};

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

答案 3 :(得分:7)

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

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

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

  • int *ptr; //未初始化的指针(如果是全局的话,则为零)
  • string name; //构造函数调用,用空字符串
  • 初始化
  • string *pname; //未初始化的指针(如果是全局的话,则为零)
  • string &rname; //如果您无法初始化
  • ,则会出现编译错误
  • const string &crname; //如果您无法初始化
  • ,则会出现编译错误
  • int age; //标量值,未初始化和随机(或全局归零)

答案 4 :(得分:4)

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

当然对于对象参数(如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

答案 5 :(得分:2)

这取决于类的构造方式

为解决这个问题而来的是理解C ++语言标准中的一个巨大的switch case语句,而这仅仅是凡人难以理解的。

举一个简单的例子,说明事情有多困难:

main.cpp

#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语句:

  • “如果T是non-POD”:否(POD的定义本身就是一个巨大的switch语句)
  • “如果T是数组类型”:否
  • “否则,什么也没做”:因此它带有未定义的值

然后,如果有人决定进行值初始化,我们转到https://en.cppreference.com/w/cpp/language/value_initialization“值初始化的作用是”并启动case语句:

  • “如果T是没有默认构造函数或具有用户提供或删除的默认构造函数的类类型”:情况并非如此。现在,您将花费20分钟来搜索以下条款:
    • 我们有一个隐式定义的默认构造函数(特别是因为未定义其他构造函数)
    • 它不是用户提供的(隐式定义的)
    • 它没有被删除(= delete
  • “如果T是具有既不由用户提供也未删除的默认构造函数的类类型”:是

这就是为什么我强烈建议您不要依赖于“隐式”零初始化的原因。除非有很强的性能原因,否则在构造函数(如果已定义)上或使用聚合初始化显式初始化所有内容。否则,对于未来的开发人员来说,事情变得非常危险。

答案 6 :(得分:1)

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

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

答案 7 :(得分:0)

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

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