IT狗工作室

C++ 构造函数的初始化(B篇)

2019-09-19  本文已影响0人  铁甲万能狗

前文:C++ 构造函数的奇葩问题

对象中的对象

在前文中的类Person对象用string作为数据成员(因为string本身就是一个类类型的数据类型)。 这种构造技术称为"组合(composition)"。
组合既不是非常规的也不是C ++特定的:在C中,struct或union字段通常用于合成其他数据类型。 在C ++中,它需要一些特殊的考虑,因为它们的初始化有时会受到限制,如以下几节所述。

组合和(const)对象:(const)成员初始化器
除非另有说明,否则类的对象数据成员由其默认构造函数初始化。 使用默认构造函数可能并不总是初始化对象的最佳方式,甚至可能不可能:类可能根本不定义默认构造函数。
我们之前前文定义了Person的构造函数

Person::Person(string const &pname, string const &paddr, size_t const &age)
{
    d_address = paddr;
    d_name = pname;
    d_age = age;
}

这个构造函数中发生了什么? 在构造函数的内部,需要对Person实例中的字符串对象的赋值。 由于赋值在构造函数体中使用,因此它们的左侧对象必须存在。 但是当对象已经存在,必须调用构造函数。 然后,Person的构造函数的主体立即撤消这些对象的初始化。这不仅效率低下,而且有时完全不可能。假设类接口提到了一个字符串const数据成员:其值一经初始化根本不应该改变(比如出生日期,它通常不会发生很大变化,因此是字符串const数据成员的一个很好的字符串类型).

构造函数的数据成员初始化

构造函数的主体允许分配数据成员。 数据成员的初始化发生在此之前.

//带参数的构造函数的实现
Person::Person(string const &pname, string const &paddr, size_t age)
    : d_name(pname),
      d_address(paddr),
      d_age(age)
{
}

在此示例中,数据成员初始化使用( )。 也可以使用花括号而不是括号。 例如d_name也可以这样初始化:

d_name{ pname },

当对象在类中组合时,总是发生成员初始化:如果在成员初始化列表中没有提到构造函数,则调用对象的默认构造函数。 请注意这仅适用于对象。

上面的示例显示了数据成员d_name接受参数pname的初始化。 当使用成员初始值设定项时,并且成员初始值设定项中使用的左标识符始终是初始化的数据成员,而括号内的标识符被解释为参数

初始化类类型的数据成员的顺序由组合类接口中定义的这些成员的顺序定义的。 如果构造函数中的初始化顺序与类接口中的顺序不同,则编译器会抱怨,并重新排序初始化以匹配类接口的顺序。

应尽可能经常使用成员初始化器。 如图所示,例如初始化const数据成员,或初始化缺少默认构造函数的类的对象)。但不使用成员初始器也会导致代码效率低下,因为没有显式指定成员初始值,类对象始终会自动调用各数据成员的默认构造函数。 这些数据成员的默认构造构在造函数体中重新分配显然效率低下。

当然,有时可以使用默认构造函数,但在这些情况下,可以省略显式成员初始值设定项。

根据经验:如果将值赋给构造函数体中的数据成员,则有利于避免数据成员重新分配。

引用成员初始值设定项

除了使用成员初始值设定项来初始化组合对象(无论是否为const对象),还有另一种情况,即必须使用成员初始值设定项。考虑以下情况。

我们假设有一个教务系统的学生管理模块StudentMgr类,需要读取Tearcher类的信息
Teacher对象可以在构造时传递给StudentMgr对象。直接传递一个对象(即按值传递)可能不是一个好主意,因为必须将该对象复制到tc参数中,然后可以使用StudentMgr类的数据成员保存该Teacher对象,使StudentMgr中其他成员函数或者外部可访问。

备注:严格来说,引用不是一种数据类型,类型涉及特定的内存块尺寸的分配和对应的变量值,而引用就是对它指向的数据类型新建了一个快捷方式而已。

委托构造函数

在C ++ 11中引入的构造函数能够调用同一个类的另一个构造函数非常有用。 这个功能名为委托构造函数
下面示例是表示维度的Dimensional类,其中第四个函数的原型从语意上更有代表性,其余三个不同程度上的特殊化。

除了第四个构造函数外,前面三个分别不同程度上存在代码冗余。第一个和第二个,第二个和第三个,我们这里可以使用委托构造函数的技术解决这个问题。

#include <iostream>

class Dimensional
{
    int d_x,d_y,d_z;

public:
    Dimensional(): d_x(0),d_y(0),d_z(0){}

    Dimensional(int x): d_x(x),d_y(0),d_z(0){}

    Dimensional(int x, int y) : d_x(x), d_y(y),d_z(0){}

    Dimensional(int x, int y, int z) : d_x(x), d_y(y), d_z(z) {}

    void display(){
        std::cout << "x=" << d_x << std::endl;
        std::cout << "y=" << d_y << std::endl;
        std::cout << "z=" << d_z << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    Dimensional d{3};
    d.display();
    return 0;
}

C ++的委托构造函数提供了一个优雅的解决方案来处理这个问题,允许我们通过将构造函数放在其他构造函数的初始化列表中来调用它。 以下程序演示了如何完成:

class Dimensional
{
    int d_x, d_y, d_z;

public:
    Dimensional() : d_x(0), d_y(0), d_z(0) {}
    
    //委托构造函数
    Dimensional(int x) : Dimensional()
    {
        d_x = x;
    }

    //委托构造函数
    Dimensional(int x, int y) : Dimensional(x, y, 0) {}

    Dimensional(int x, int y, int z) : d_x(x), d_y(y), d_z(z) {}

    void display()
    {
        std::cout << "x=" << d_x << std::endl;
        std::cout << "y=" << d_y << std::endl;
        std::cout << "z=" << d_z << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    Dimensional d{3, 7};
    d.display();
    return 0;
}
上一篇下一篇

猜你喜欢

热点阅读