具有初始值的类构造

时间:2011-08-26 16:38:28

标签: c++

我是C ++的新手,以及课程的全部理念 - 我还在读一本书来尝试和学习。我正在阅读的这本书说,当我构建一个类时,我可以通过这样做来指定默认值:

class foo {
public:
   foo(char c, int i);
private:
   char exampleChar;
   int exampleInt;
};

foo::foo(char c, int i):
exampleChar(c),
exampleInt(i)
{}

这段代码(对我来说)看起来非常混乱,并且不遵循我在其他语言中习惯的规则。 我的问题是,做上述内容与此之间有什么区别(下面,我个人认为看起来更干净)?

foo::foo(char c, int i) {
   exampleChar = c;
   exampleInt = i;
}

我正在考虑的一些事情是:如果大规模地进行性能/效率问题 - 还是完全一样?

8 个答案:

答案 0 :(得分:10)

第一种方式,通过执行: exampleChar(c), exampleInt(i)称为初始化列表。

如果以第二种方式进行,则两个变量默认构造首先 1 然后为它们分配一个值。 (当输入构造函数的实际主体时,默认构造的任何尚未由初始化程序列表初始化的内容。)这是浪费时间,因为您只是覆盖了值。对于像intchar这样的小类型,这不是什么大问题,但是当这些成员变量是需要大量循环来构建的大型类型时,你肯定想要使用初始化列表。 / p>

第二种方式不会浪费时间给它们一个默认值然后覆盖它 - 它会将它们的值直接设置为你给它的值(或者如果成员是一个对象则调用正确的构造函数)。

你可以通过这样做看出我们的意思:

class MyClass {
public:
    int _i; // our data

    // default constructor
    MyClass() : _i(0) { cout << "default constructor"; }

    // constructor that takes an int
    MyClass(int i) : _i(i) { cout << "int constructor"; }

    // assignment operator
    void operator=(int i) { _i = i; cout << "assignment operator"; }
};

class OtherClass {
public:
    MyClass c;

    OtherClass() {
        c = 54;
    }
};

OtherClass oc;

你会看到

default constructor
assignment operator

已打印。这是两个函数调用,对于其他类,可能很昂贵。

如果您将OtherClass的构造函数更改为

OtherClass() : c(54) {   }

你会看到

int constructor

已打印。只有一个电话与两个电话相比。这是最有效的方式。

初始化程序列表也是必须的

  1. 具有没有默认构造函数的类型。您必须在初始化列表中调用正确的构造函数。

  2. 有一个const成员要提供一些价值(而不仅仅是默认值

  3. 有一个参考会员。您必须在这些列表上使用初始化列表。

  4. tl; dr:这样做是因为它至少与其他方式一样快但从不慢,有时甚至更快。

    1 对于像intchar这样的内置类型,它们实际上根本没有构建;他们只是拥有他们以前遇到过的任何记忆的价值。

答案 1 :(得分:5)

不同之处在于编译器将始终在第一个用户定义的构造函数语句之前初始化所有成员(按声明顺序)。在charint的情况下,它们都是基本类型,“初始化”实际上意味着“没有初始化”。但是,如果您的成员具有执行某些实际工作的构造函数,则将提前调用此构造函数 - 如果您执行

foo::foo() {
    myComplexMember = MyComplexClass(42);
}

编译器在调用代码之前已经调用了MyComplexClass默认构造函数,这会浪费资源(如果无法访问默认ctor,则编译错误。)

通过使用初始化列表,您可以自定义默认初始化并避免无所事事。显然,这是要走的路。

答案 2 :(得分:5)

你可以做的事情就是你不可能做到的事情。

  1. 如果其中一个成员没有默认构造函数。这是你在建筑工程中发起会员的唯一方法。 (同样适用于基类)

  2. 您可以为const成员分配值。

  3. 您可以在构造函数开始运行之前确保类的已定义状态。

  4. 如果成员是引用,则需要在初始化列表中初始化它。因为引用是不可变的,并且只能在开头初始化一次(如const)

答案 3 :(得分:1)

嗯,这是一个典型的常见问题解答:http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6

在您的情况下,使用charint没有区别。

一般规则:尽可能使用初始化列表,极少数情况下您可以更喜欢分配,例如提高可读性:

MyClass::MyClass
{
  a = b = c = d = e = f = 0;
}

优于

class MyClass::MyClass : a(0), b(0), c(0), d(0), e(0), f(0) { }

答案 4 :(得分:1)

如果成员有非平凡的构造函数,则在下面的代码中首先调用默认构造函数,然后执行赋值,而在上面的代码中,它们只会被初始化一次。是的,可能存在性能问题。

还有一个实际问题:如果它们是const,引用,或者没有默认构造函数,则不能使用下面的版本。

答案 5 :(得分:1)

这两个选项之间存在微妙但重要的区别。您在顶部拥有的内容称为成员初始化列表。创建对象时,此列表中的成员将初始化为您在括号中放置的任何内容。

在构造函数体中执行赋值时,值首先已初始化,然后已分配。我将在下面发布一个简短的例子。

示例1:成员初始化

class foo 
{
public:
   foo(char c, int i);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i):
    exampleChar(c),
    exampleInt(i),
    exampleBar()     //Here, a bar is being default constructed
{
}

示例2:构造函数分配

class foo 
{
public:
   foo(char c, int i, Bar b);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i, Bar b):
    //exampleChar(c),
    //exampleInt(i),
    //exampleBar()  
{
    exampleChar = c;
    exampleInt = i;
    exampleBar = someOtherBar;  //Here, a bar is being assigned
}

这是有趣的地方。请注意,正在分配exampleBar。在幕后,Bar实际上是第一个默认构造,即使你没有指定。此外,如果你的Bar比一个简单的结构更复杂,你需要确保实现赋值运算符,以便以这种方式初始化它。更进一步,为了从成员初始化列表中初始化Bar来自另一个Bar,您必须实现复制构造函数!

示例3:在成员init

中使用的复制构造函数
class foo 
{
public:
   foo(char c, int i, Bar b);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i, Bar b):
    //exampleChar(c),
    //exampleInt(i),
    exampleBar(b)     //Here, a bar is being constructed using the copy constructor of Bar
{
    exampleChar = c;
    exampleInt = i;
}

答案 6 :(得分:0)

我会养成使用初始化列表的习惯。当有人将char更改为首先调用默认构造函数的某个对象时,以及const值的const正确性时,它们不会遇到问题!

答案 7 :(得分:0)

foo::foo(char c, int i):exampleChar(c),exampleInt(i){}

此构造在C ++中称为成员初始化列表

将您的会员exampleChar初始化为值c&amp; exampleInti


构造函数中的Initializing和Assignment有什么区别? &安培;
有什么好处?

使用初始化程序列表初始化成员并在构造函数体内为其指定值之间存在差异。

通过初始化列表初始化字段时,构造函数将被调用一次。

如果使用赋值,则首先使用默认构造函数初始化字段,然后使用实际值重新分配(通过赋值运算符)。

正如您所看到的,还有额外的创作和开销。后者中的赋值,对于用户定义的类可能相当大。

对于整数数据类型(您使用它)或POD类成员,没有实际开销。