c++ primer 学习笔记:类之构造函数
2013-08-24 19:41
337 查看
构造函数
构造函数是特殊的成员函数,只要创建类类型的新对象,都要执行构造函数。构造函数的工作是保证每个对象的数据成员具有合适的初始值。构造函数的名字与类的名字相同,并且不能指定返回类型。像其他任何函数一样,它们可以没有形参,也可以定义多个形参。构造函数可以被重载
可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。我们如何才能知道应该定义哪个或多少个构造函数?一般而言,不同的构造函数允许用户指定不同的方式来初始化数据成员。实参决定使用哪个构造函数
用于初始化一个对象的实参类型决定使用哪个构造函数。用于const 对象的构造函数
构造函数不能声明为constclass Sales_item { public: Sales_item() const; // error };const 构造函数是不必要的。创建类类型的 const 对象时,运行一个普通构造函数来初始化该 const 对象。构造函数的工作是初始化对象。不管对象是否为 const,都用一个构造函数来初始化化该对象。
构造函数初始化式
与任何其他函数一样,构造函数具有名字、形参表和函数体。与其他函数不同的是,构造函数也可以包含一个构造函数初始化列表:// recommended way to write constructors using a constructor initializer Sales_item::Sales_item(const string &book): isbn(book), units_sold(0), revenue(0.0) { }构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。这个构造函数将isbn 成员初始化为 book形参的值,将units_sold 和 revenue 初始化为 0。与任意的成员函数一样,构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中而不是声明中指定。
构造函数初始化列表是许多相当有经验的 C++ 程序员都没有掌握的一个特性。
在本节中编写的两个Sales_item 构造函数版本具有同样的效果:无论是在构造函数初始化列表中初始化成员,还是在构造函数函数体中对它们赋值,最终结果是相同的。构造
函数执行结束后,三个数据成员保存同样的值。不同之外在于,使用构造函数初始化列表的版本初始化数据成员,没有定义初始化列表的构造函数版本在构造函数函数体中对数
据成员赋值。这个区别的重要性取决于数据成员的类型。
有时需要构造函数初始化列表
如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。在这种情况下,为了初始化数据成员,必须提供初始化式。
有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; }; // no explicit constructor initializer: error ri is uninitialized ConstRef::ConstRef(int ii) { // assignments: i = ii; // ok ci = ii; // error: cannot assign to a const ri = i; // assigns to ri which was not bound to an object }记住,可以初始化const 对象或引用类型的对象,但不能对它们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化 const 或引用类型数据成员的唯一机会是构
造函数初始化列表中。编写该构造函数的正确方式为
// ok: explicitly initialize reference and const members ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
在许多类中,初始化和赋值严格来讲都是低效率的:数据成员可能已经被直接初始化了,还要对它进行初始化和赋值。比较率问题更重要的是,某些数据成员必须要初始化,这是一个事实。必须对任何const 或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次,这不会令人惊讶。毕竟,给一个成员两个初始值意味着什么?也许更令人惊讶的是,构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。第一个成员首先被初始化,然后是第二个,依次类推。
初始化的次序常常无关紧要。然而,如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
默认构造函数
只要定义一个对象时没有提供初始化式,就使用默认构造函数。为所有形参提供默认实参的构造函数也定义了默认构造函数。合成的默认构造函数
一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。这条规则的根据是,如果一个类在某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
合成的默认构造函数(synthesized default constructor)使用与变量初始化相同的规则来初始化成员。具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。
如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。
此外,每个构造函数应该为每个内置或复合类型的成员提供初始化式。没有初始化内置或复合类型成员的构造函数,将使那些成员处于未定义的状态。除了作为赋值的目标之外,以任何方式使用一个未定义的成员都是错误的。如果每个构造函数将每个成员设置为明确的已知状态,则成员函数可以区分空对象和具有实际值的对象。
类通常应定义一个默认构造函数
在某些情况下,默认构造函数是由编译器隐式应用的。如果类没有默认构造函数,则该类就不能用在这些环境中。为了例示需要默认构造函数的情况,假定有一个NoDefault类,它没有定义自己的默认构造函数,却有一个接受一个string 实参的构造函数。因为该类定义了一个构造函数,因此编译器将不合成默认构造函数。NoDefault 没有默认构造函数,意味着:
1.具有NoDefault 成员的每个类的每个构造函数,必须通过传递一个初始的 string 值给 NoDefault 构造函数来显式地初始化 NoDefault 成员。
2.编译器将不会为具有NoDefault 类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式地定义,并且默认构造函数必须显式地初始化其
NoDefault 成员。
3.NoDefault 类型不能用作动态分配数组的元素类型。
4.NoDefault 类型的静态分配数组必须为每个元素提供一个显式的初始化式。
5.如果有一个保存NoDefault 对象的容器,例如 vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。
实际上,如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。
抑制由构造函数定义的隐式转换
可以通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数:class Sales_item { public: // default argument for book is the empty string explicit Sales_item(const std::string &book = ""): isbn(book), units_sold(0), revenue(0.0) { } explicit Sales_item(std::istream &is); // as before };explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它:
// error: explicit allowed only on constructor declaration in class header explicit Sales_item::Sales_item(istream& is) { is >> *this; // uses Sales_iteminput operator to read the members }当构造函数被声明explicit 时,编译器将不使用它作为转换操作符。
为转换而显式地使用构造函数
只要显式地按下面这样做,就可以用显式的构造函数来生成转换:// ok: builds a Sales_itemwith 0 units_soldand revenue from // and isbn equal to null_book item.same_isbn(Sales_item(null_book));在这段代码中,从null_book 创建一个 Sales_item。尽管构造函数为显式的,但这个用法是允许的。显式使用构造函数只是中止了隐式地使用构造函数。任何构造函数都可以用来显式地创建临时对象。
通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit 可以避免错误,并且当转换有用时,用户可以显式地构造对象。
相关文章推荐
- 《C++ Primer Plus》10.3 类的构造函数和析构函数 学习笔记
- C++ Primer Plus学习笔记之拷贝构造函数
- C++ Primer Plus学习笔记之STL函数对象
- 2012/1/9 《C++ Primer Plus》第三章:处理数据 学习笔记
- C++ primer plus 学习笔记
- 2012/1/25 《C++ Primer Plus》第十一章:使用类 学习笔记
- C++学习笔记1:构造函数和析构函数
- C++ Primer plus 学习笔记之第九章内存模型和名称空间(1)
- C++ Primer plus第6版第9章学习笔记
- 《C++ Primer Plus》14.4 类模板 学习笔记
- C++学习笔记一、构造函数和析构函数的概念
- 《C++ Primer Plus》第15章 友元、异常和其他 学习笔记
- C++学习笔记:类、构造函数
- C++ Primer 学习笔记_88_用于大型程序的工具 -错误处理[续1]
- 《C++ Primer Plus(第六版)》(1)(第二章 开始学习C++ 笔记和答案)
- c++学习笔记4,派生类的构造函数与析构函数的调用顺序(一)
- C++ primer 学习笔记(4)
- C++ Primer Plus第九章学习笔记
- C++学习笔记18,C++11中的初始化列表构造函数(二)
- C++ primer第二次阅读学习笔记(第16章:模板与泛型编程) .