美文网首页IT狗工作室
C++ 构造函数的初始化(B篇)

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

作者: 铁甲万能狗 | 来源:发表于2019-09-19 22:57 被阅读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数据成员的一个很好的字符串类型).

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

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

  • C ++定义了成员初始化器语法,允许我们指定数据成员在构造时初始化的方式。
  • 成员初始值设定项被指定为构造函数参数列表后面冒号构造函数体左大括号之间构造函数规范列表(constructor specifications list),如下所示:
//带参数的构造函数的实现
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 },

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

  • 基本数据类型的数据成员不会自动初始化。
  • 成员初始化也可以用于基本数据成员,如int和double。

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

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

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

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

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

引用成员初始值设定项

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

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

  • 按值传递如下列情况:

      StudentMgr::StudentMgr(Tearcher tc){
          d_teacher=tc;
      }
    
  • 如果使用指向Teacher对象的指针,则可以避免复制指令,如下所示:

    StudentMgr::StudentMgr(Teacher* tc){
        d_teacher=tc;
    }
    

    这样的结构是可以的,但是迫使我们使用- >字段选择器操作符,而不 是“.”访问操作符。 从概念上讲,人们倾向于将Teacher对象视为对象,而不是指向对象的指针。 在C中,这可能是首选方法,但在C ++中我们可以使用“&”引用运算符。

  • 按引用传递
    可以将Teacher参数定义为StudentMgr的构造函数的引用参数,而不是使用值或指针参数。 接下来,在StudentMgr类中使用Teacher引用的数据成员。但是无法使用赋值操作符初始化引用变量,因此以下代码不正确

    StudentMgr::StudentMgr(Teacher &tc){
        d_teacher=tc;
    }
    

    上个示例中的d_teacher=tc之所以不正确,是因为它不是初始化,而是将参数Teacher对象的引用分配给d_teacher.对引用变量的赋值仅仅是多了对Teacher对象的引用副本。当我们尝试初始化的数据成员是对某类型的引用,我们应该是成员初始化列表的方式。

    StudentMgr::StudentMgr(Teacher &tc):d_teacher(tc);
    {}
    

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

委托构造函数

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

  • Dimensional()
  • Dimensional(int x)
  • Dimensional(int x, int y)
  • Dimensional(int x, int y, int z)

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

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

相关文章

  • C++:面向对象基础

    构造函数 C++中有三种构造函数:默认构造函数,有参构造函数,拷贝构造函数 类对象的初始化 括号法//默认构造函数...

  • [C++之旅] 11 初始化列表

    [C++之旅] 11 初始化列表 初始化列表的特性 初始化列表先于构造函数执行 初始化列表只能用于构造函数 初始化...

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

    前文:C++ 构造函数的奇葩问题 对象中的对象 在前文中的类Person对象用string作为数据成员(因为str...

  • C++中的构造函数 & 拷贝构造函数 & 赋值运算符重载

    C++中的构造函数 & 析构函数 什么是构造函数?   一种特殊的方法, 在创建实例的时候初始化对象; 构造函数没...

  • 4.2C++虚析构函数

    为什么构造函数不能声明虚函数 在C++中,构造函数用于在创建对象时进行初始化工作,不能声明为虚函数。因为在执行构造...

  • 直接初始化与复制初始化

    C++ Primer:直接初始化不一定要调用复制构造函数,而复制初始化一定要调用复制构造函数。 一、通常的误解 为...

  • 条款04:确定对象被使用前已被初始化

    请记住: 为内置对象进行手工初始化,C++不保证初始化。 构造函数最好使用 成员初值列 ,而不要在构造函数内使用赋...

  • C++对象的初始化方式

    对象初始化可以分为默认初始化、直接初始化、拷贝初始化以及值初始化。C++的类默认提供了六种函数:构造函数、析构函数...

  • C#构造函数中this和base的使用

    C#构造函数中this和base的使用 结论 构造函数的作用是,给对象成员进行初始化。 然后,构造函数this和b...

  • 第六章 初始化与清除

    简介:C++的安全性:初始化和清除。这两个概念是简化库的使用的关键所在。 6.1 用构造函数确保初始化 构造函数的...

网友评论

    本文标题:C++ 构造函数的初始化(B篇)

    本文链接:https://www.haomeiwen.com/subject/mlsructx.html