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

bod*_*ydo 137 c++ initialization member-initialization

假设我有私人承包商,客人一类ptr,name,pname,rname,crnameage.如果我自己不初始化会怎么样?这是一个例子:

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

    public:
        Example() {}
};
Run Code Online (Sandbox Code Playgroud)

然后我做:

int main() {
    Example ex;
}
Run Code Online (Sandbox Code Playgroud)

如何在ex中初始化成员?指针会发生什么?做stringint获得0 intialized默认构造函数string()int()?参考会员怎么样?const引用怎么样?

我还应该知道什么?

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

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

Tyl*_*nry 181

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

对于对象,将调用其默认构造函数.例如,对于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
Run Code Online (Sandbox Code Playgroud)

  • @wiz我认为他的字面意思是'如果对象没有默认构造函数',就像没有生成的那样,如果类显式定义了除默认值之外的任何构造函数(将不会生成默认的ctor),则会出现这种情况.如果我们过于迂腐,我们可能会混淆不仅仅是帮助,泰勒在回答我之前对此提出了一个很好的观点. (18认同)
  • @ wiz-loz我会说'foo`确实有一个构造函数,它只是隐含的.但这确实是一种语义论证. (7认同)
  • "如果对象的类没有默认构造函数,如果你没有显式初始化它将是编译错误"这是错误的**!如果一个类没有默认构造函数,则会给它一个*默认的默认构造函数*,它是空的. (6认同)
  • +1.值得注意的是,通过严格的标准定义,原始类型的实例以及各种其他事物(任何存储区域)都被视为*对象*. (4认同)
  • 我将"默认构造函数"解释为可以不带参数调用的构造函数.这可以是您自己定义的,也可以是编译器隐式生成的.所以缺乏它意味着既不是你自己定义也不是生成.或者这就是我的看法. (4认同)
  • @stinky:在这种情况下,它不是偶像:考虑这个简单的代码:`class foo {public:int i; }; class bar {public:foo f; 我们没有任何costructor,但你仍然可以实例化`bar`并使用它! (3认同)
  • 是的,但是如果OP阅读了标准,他就不会问这个问题,所以我认为在类类型的一般面向对象编程意义上使用 *object* 并不令人困惑,而不是原始类型。:) (2认同)

Dan*_*ien 26

首先,让我解释一下mem-initializer-list是什么.甲MEM-初始化列表是逗号分隔的列表MEM-初始化 s,其中每个MEM-初始化是其成员的名字,随后(,接着是表达式列表,随后进行).的表达式列表是构件是如何构造的.例如,在

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)
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

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

每个构造函数都有自己的mem-initializer-list,成员只能按照规定的顺序初始化(基本上是在类中声明成员的顺序).因此,的成员Example:只能在顺序进行初始化ptr,name,pname,rname,crname,和age.

如果未指定成员的mem-initializer,则C++标准会说:

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

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

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

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


小智 10

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

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};
Run Code Online (Sandbox Code Playgroud)

我几乎完全使用这种形式,虽然我读过一些人认为它是"糟糕的形式",也许是因为它最近才引入 - 我认为在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;
};
Run Code Online (Sandbox Code Playgroud)

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


Cir*_*四事件 8

这取决于类的构造方式

回答这个问题需要理解 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);
}
Run Code Online (Sandbox Code Playgroud)

在默认初始化中,您可以从: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 “值初始化的效果是”并开始 case 语句:

  • “如果 T 是没有默认构造函数或具有用户提供或删除的默认构造函数的类类型”:情况并非如此。您现在将花 20 分钟谷歌搜索这些术语:
    • 我们有一个隐式定义的默认构造函数(特别是因为没有定义其他构造函数)
    • 它不是用户提供的(隐式定义的)
    • 没有被删除( = delete)
  • “如果 T 是具有默认构造函数的类类型,该构造函数既不是用户提供的也不是删除的”:是
    • “该对象是零初始化的,然后如果它有一个非平凡的默认构造函数,则它是默认初始化的”:没有非平凡的构造函数,只是零初始化。“零初始化”的定义至少很简单并且符合您的期望: https: //en.cppreference.com/w/cpp/language/zero_initialization

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


Pau*_*xon 7

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

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

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

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

  • 这不是随机的!随机是太大的话!如果标量成员是随机的,我们就不需要任何其他随机数生成器.想象一个分析数据"遗留"的程序 - 比如内存中的未删除文件 - 数据远非随机.它甚至都没有定义!通常很难定义,因为通常我们不知道我们的机器做了什么.如果您刚刚取消删除的"随机数据"是您父亲的唯一图像,如果您说它的随机性,您的母亲甚至可能会发现它令人反感...... (2认同)

Wiz*_*ard 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
Run Code Online (Sandbox Code Playgroud)