玩命加载中 . . .

7.5-构造函数再探


7.5 构造函数再探

7.5.1 构造函数初始值列表

如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化,然后再在构造函数体内赋值

// 使用初始化列表
Sales_data(const string& s, unsigned n, double p): 
    bookNo(s), units_sold(n), revenus(p * n) {}

// 虽然合法但比较草率,没有使用构造函数初始值
Sales_data(const string& s, unsigned cnt, double price) {
    bookNo = s;
    units_sold = cnt;
    revenue = cnt * price;
}

成员是const、引用,或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始值列表为这些成员提供初值

随着构造函数体一开始执行,初始化就完成了,初始化const或引用类型的成员的唯一机会是通过构造函数初始化列表

class ConstRef {
public: 
    ConstRef(int ii);
private:
    int i;
    const int ci;   // const必须初始化
    int &ri;        // 引用必须初始化
};
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) {}

成员初始化顺序

成员的初始化顺序与它们在类定义中的出现顺序一致,构造函数初始值列表中的前后位置关系不会影响实际的初始化顺序

默认实参和构造函数

如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数

// 定义默认构造函数
Sales_data(string s = ""): bookNo(s) {}

7.5.2 委托构造函数

一个委托构造函数使用它所属类的其他构造函数执行自己的初始化过程,或者说它把自己的一些职责委托给了其他构造函数

class Sales_data {
    // 非委托构造函数使用对应的实参初始化成员
    Sales_data(const string& s, unsigned n, double p): 
        bookNo(s), units_sold(n), revenus(p * n) {}
    // 其余构造函数全都委托给另一个构造函数
    Sales_data(): Sales_data("", 0, 0) {}
    Sales_data(string s): Sales_data(s, 0, 0) {}
    Sales_data(istream& is): Sales_data() {
        read(is, *this);
    }
};

7.5.3 默认构造函数的作用

在实际中,如果定义了其他构造函数,最好也提供一个默认构造函数

使用默认构造函数的正确写法

Sales_data obj;

7.5.4 隐式的类类型转换

能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型转换的规则

string null_book = "9-999";
item.combine(null_book);

这里构造了一个临时的Sales_data对象,bookNo等于null_book,其他成员默认

只允许一步类类型转换

编译器只会自动地执行一步类型转换

item.combine("9-999");  // Err 需要两步类型转换

需要先把9-999转换为string,再转换为Sales_data

可以把其中一步显式地写出来

item.combine(string("9-999"));  // 正确
item.combine(Sales_data("9-999"));  // 正确

抑制构造函数定义的隐式转换

可以通过将构造函数声明为explicit阻止隐式转换

class Sales_data {
    Sales_data() = default;
    Sales_data(const string& s, unsigned n, double p): 
        bookNo(s), units_sold(n), revenus(p * n) {}
    explicit Sales_data(const string& s): bookNo(s) {}
    explicit Sales_data(istream&);
};

这样就不能隐式转换

item.combine(null_book);    // Err
item.combine(cin);          // Err

关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以不用声明explicit
只能在类内声明构造函数时使用explicit,类外不能重复

explicit构造函数只能用于直接初始化

执行拷贝形式的初始化时(使用=)会发生隐式转换,这种只能使用直接初始化而不能使用explicit构造函数

Sales_data item1(null_book);    // 正确,直接初始化
Sales_data item2 = null_book;   // Err,不能将explicit构造函数用于拷贝形式的初始化过程

explicit声明构造函数时,就只能以直接初始化的形式使用,同时也不能在自动转换过程中使用该构造函数

硬要用的话可以用强制类型转换

Sales_data item = static_cast<Sales_data>(null_book);   // 正确

7.5.5 聚合类

  • 所有成员都是public
  • 没有定义任何构造函数
  • 没有类内初始值
  • 没有基类,也没有virtual函数
struct Data {
    int ival;
    string s;
};

Data data1 = {0, "kavin"};

可以使用成员初始化列表初始化聚合类的数据成员,顺序要跟声明的顺序相同

7.5.6 字面值常量类

  • 数据成员必须都是字面值类型
  • 类必须至少有一个constexpr构造函数
  • 数据成员的类内初始值
    • 内置类型:必须是一条常量表达式
    • 类类型:使用成员自己的constexpr构造函数
  • 类必须使用析构函数的默认定义
算数类型、引用、指针是字面值类型

constexpr构造函数体一般应该是空的

class Debug {
public:
    constexpr Debug(bool b = true): hw(b), io(b), other(b) {}
    constexpr Debug(bool h, bool i, bool o):
        hw(h), io(i), other(o) {}
private:
    bool hw;
    bool io;
    bool other;
};

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录