- 类通常应定义一个默认构造函数
为了例示需要默认构造函数的情况,假定有一个 NoDefault 类,它没有定义自己的默认构造函数,却有一个接受一个 string 实参的构造函数。
定义这样的一个类没有错,这样编译会编译成功:
class NoDefault { public: void output() { cout << units_sold << endl; } NoDefault(const std::string &book) : isbn(book), units_sold(0), revenue(0.0) { cout << "first constructor" << endl; } /*NoDefault() : units_sold(0), revenue(0.0) { cout << "second constructor" << endl; }*/ private: std::string isbn; unsigned units_sold; double revenue; }; int main() { system("pause"); return 0; }
但如果定义一个对象就会报错:
因为该类定义了一个构造函数,因此编译器将不合成默认构造函数。NoDefault 没有默认构造函数,这意味着:
(1)具有 NoDefault 成员的每个类的每个构造函数,必须通过传递一个初始的 string 值给 NoDefault 构造函数来显式地初始化 NoDefault 成员。
如果NoDefault有默认构造函数:
class NoDefault { public: NoDefault(const std::string book) : isbn(book), units_sold(0), revenue(0.0) { cout << "NoDefault Constructor" << endl; } NoDefault() : units_sold(0), revenue(0.0) { cout << "NoDefault default constructor" << endl; } private: std::string isbn; unsigned units_sold; double revenue; }; class Sales_item { public: double avg_price() const; bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } void output() { cout << units_sold << endl; } Sales_item(const std::string &book) : isbn(book), units_sold(0), revenue(0.0) { cout << "Sales_item constructor" << endl; } Sales_item() : units_sold(0), revenue(0.0) { cout << "Sales_item default constructor" << endl; } private: std::string isbn; NoDefault ND; unsigned units_sold; double revenue; }; int main() { Sales_item si; system("pause"); return 0; }
输出结果:
如果NoDefault没有默认构造函数:
输出结果:
(2)编译器将不会为具有 NoDefault 类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式地定义,并且默认构造函数必须显式地初始化其 NoDefault 成员(如上例)指望编译器为Sales_item合成默认构造函数会报错:
class NoDefault { public: NoDefault(const std::string book) : isbn(book), units_sold(0), revenue(0.0) { cout << "NoDefault Constructor" << endl; } private: std::string isbn; unsigned units_sold; double revenue; }; class Sales_item { public: double avg_price() const; bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } void output() { cout << units_sold << endl; } private: std::string isbn; NoDefault ND; unsigned units_sold; double revenue; }; int main() { Sales_item si; system("pause"); return 0; }
报错:
如果没有NoDefault作为Sales_item的成员,则会编译成功。
(3)NoDefault 类型的静态分配数组必须为每个元素提供一个显式的初始化式
输出结果:
如果NoDefault没有默认构造函数,则会报错:
这种数组也无法给每个NoDefault类型的成员传递初始值。
(4)NoDefault 类型的静态分配数组必须为每个元素提供一个显式的初始化式。
否则仍然会报错:
(5)如果有一个保存 NoDefault 对象的容器,例如 vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数
实际上,如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。
使用默认构造函数定义一个对象的正确方法:
// ok: defines a class object ... Sales_item myobj;
如果定义成这样:
Sales_item myobj(); // ok: but defines a function, not an object
myobj 的定义被编译器解释为一个函数的声明,该函数不接受参数并返回一个 Sales_item 类型的对象。
这样定义也是正确的:
// ok: create an unnamed, empty Sales_itemand use to initialize myobj Sales_item myobj = Sales_item();
我们创建并初始化一个 Sales_item 对象,然后用它来按值初始化 myobj。编译器通过运行 Sales_item 的默认构造函数来按值初始化一个 Sales_item。
- 隐式类类型转换
C++ 语言定义了内置类型之间的几个自动转换。也可以定义如何将其他类型的对象隐式转换为我们的类类型,或将我们的类类型的对象隐式转换为其他类型。为了定义到类类型的隐式转换,需要定义合适的构造函数。可以用单个实参来调用的构造函数,定义了从形参类型到该类类型的一个隐式转换。
让我们再看看定义了两个构造函数的 Sales_item 版本:
class Sales_item { public: // default argument for book is the empty string Sales_item(const std::string &book = ""): isbn(book), units_sold(0), revenue(0.0) { } Sales_item(std::istream &is); // as before };
这个类中有一个成员函数
bool same_isbn(const Sales_item &rhs) const//const 成员不能改变其所操作的对象的数据成员。 { return isbn == rhs.isbn; }
当像下面这样使用这个成员函数时,这里的每个构造函数都定义了一个隐式转换:
string null_book = "9-999-99999-9"; // ok: builds a Sales_itemwith 0 units_soldand revenue from // and isbn equal to null_book item.same_isbn(null_book);
因此,在期待一个 Sales_item 类型对象的地方,可以使用一个 string 或一个 istream。
如果没有这两个构造函数,就会报错: