您的位置:首页 > 编程语言 > C语言/C++

c++ primer阅读笔记-12章-3

2014-04-06 21:36 197 查看

构造函数

1、构造函数不能声明为 const,const 构造函数是不必要的。创建类类型的 const 对象时,运行一个普通构造函数来初始化该 const 对象。构造函数的工作是初始化对象。不管对象是否为 const,都用一个构造函数来初始化化该对象。
2、与任何其他函数一样,构造函数具有名字、形参表和函数体。与其他函数不同的是,构造函数也可以包含一个构造函数初始化列表。构造函数初始化只在构造函数的定义中而不是声明中指定。

3、造函数初始化列表难以理解的一个原因在于,省略初始化列表在构造函数的函数体内对数据成员赋值是合法的。例如,可以将接受一个 string 的 Sales_item 构造函数编写为:
// legal but sloppier way to write the constructor:
// no constructor initializer
Sales_item::Sales_item(const string &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}

这个构造函数给类 Sales_item 的成员赋值,但没有进行显式初始化(指的是用成员初始化列表?)。不管是否有显式的初始化式,在执行构造函数之前,要初始化 isbn 成员。这个构造函数隐式使用默认的 string 构造函数来初始化 isbn。执行构造函数的函数体时,isbn 成员已经有值了。该值被构造函数函数体中的赋值所覆盖。

从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。(类的类类型数据成员总是在执行构造函数之前就已经调用其默认构造函数进行初始化;非类类型数据成员在初始化列表中初始化和在构造函数函数体中赋值效果是一样的,但有个例外,const数据成员和引用类型的数据成员,必须在声明阶段就初始化其值。
参考:http://blog.csdn.net/u011779269/article/details/23083363)

4、在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为
0(???)。
5、使用构造函数初始化列表的版本初始化数据成员,没有定义初始化列表的构造函数版本在构造函数函数体中对数据成员赋值。这个区别的重要性取决于数据成员的类型。

6、如果没有为类类型成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。在这种情况下,为了初始化数据成员,必须提供初始化式。

7、有些成员(没有默认构造函数的类类型的成员(?),以及 const 或引用类型的成员)必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。
8、因为内置类型的成员不进行隐式初始化,所以对这些成员是进行初始化还是赋值似乎都无关紧要。除了两个例外(const 类型和引用数据类型),对非类类型的数据成员进行赋值或使用初始化式在结果和性能上都是等价的。
9、可以初始化 const 对象或引用类型的对象,但不能对它们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中。必须对任何 const 或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
10、成员被初始化的次序就是定义成员的次序。
11、只要定义一个对象时没有提供初始化式,就使用默认构造函数。为所有形参提供默认实参的构造函数也定义为默认构造函数。

12、一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。这条规则的根据是,如果一个类在某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制(???)。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
13、合成的默认构造函数(synthesized default constructor)使用与变量初始化相同的规则来初始化成员。具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。(???)如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员(参考:/article/11222960.html)。
14、每个构造函数应该为每个内置或复合类型的成员提供初始化式。没有初始化内置或复合类型成员的构造函数,将使那些数据成员处于未定义的状态。除了作为赋值的目标之外,以任何方式使用一个未定义的数据成员都是错误的。如果每个构造函数将每个数据成员设置为明确的已知状态,则成员函数可以区分空对象(数据成员)和具有实际值的对象(数据成员)。
15、类通常应定义一个默认构造函数。在某些情况下,默认构造函数是由编译器隐式应用的。如果类没有默认构造函数,则该类就不能用在这些环境中。为了例示需要默认构造函数的情况,假定有一个 NoDefault 类,它没有定义自己的默认构造函数,却有一个接受一个 string 实参的构造函数。因为该类定义了一个构造函数,因此编译器将不合成默认构造函数。NoDefault
没有默认构造函数,意味着:
1)具有 NoDefault 成员的每个类的每个构造函数,必须通过传递一个初始的 string 值给 NoDefault 构造函数来显式地初始化 NoDefault 成员。

2)编译器将不会为具有 NoDefault 类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式地定义,并且默认构造函数必须显式地初始化其 NoDefault 成员。

3)NoDefault 类型不能用作动态分配数组的元素类型(???)。

4)NoDefault 类型的静态分配数组必须为每个元素提供一个显式的初始化式。

5)如果有一个保存 NoDefault 对象的容器,例如 vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。

16、让我们再看看定义了两个构造函数的 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
};


这里的每个构造函数都定义了一个隐式转换。因此,在期待一个 Sales_item 类型对象的地方,可以使用一个 string 或一个 istream:

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);


这段程序使用一个 string 类型对象作为实参传给 Sales_item 的 same_isbn 函数。该函数期待一个 Sales_item 对象作为实参。编译器使用接受一个 string 的 Sales_item 构造函数从 null_book 生成一个新的 Sales_item 对象。新生成的(临时的)Sales_item 被传递给 same_isbn。

这个行为是否我们想要的,依赖于我们认为用户将如何使用这个转换。在这种情况下,它可能是一个好主意。book 中的 string 可能代表一个不存在的 ISBN,对 same_isbn 的调用可以检测 item 中的 Sales_item 是否表示一个空的 Sales_item。另一方面,用户也许在 null_book 上错误地调用了 same_isbn。
更成问题的是从 istream 到 Sales_item 的转换:

// ok: uses the Sales_item istream constructor to build an object
// to pass to same_isbn
item.same_isbn(cin);


这段代码将 cin 隐式转换为 Sales_item。这个转换执行接受一个 istream 的 Sales_item 构造函数。该构造函数通过读标准输入来创建一个(临时的)Sales_item 对象。然后该对象被传递给 same_isbn。

这个 Sales_item 对象是一个临时对象(第 7.3.2 节)。一旦 same_isbn 结束,就不能再访问它。实际上,我们构造了一个在测试完成后被丢弃的对象。这个行为几乎肯定是一个错误。
17、可以通过将构造函数声明为 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 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它。

18、为转换而显式地使用构造函数。通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为 explicit 可以避免错误,并且当转换有用时,用户可以显式地构造对象。

19、尽管大多数数据成员可以通过运行适当的构造函数进行初始化,但是直接初始化简单的非抽象类的数据成员仍是可能的。对于没有定义构造函数并且其全体数据成员均为 public 的类,可以采用与初始化数组元素相同的方式初始化其成员:

struct Data {
int ival;
char *ptr;
};
// val1.ival = 0; val1.ptr = 0
Data val1 = { 0, 0 };

// val2.ival = 1024;
// val2.ptr = "Anna Livia Plurabelle"
Data val2 = { 1024, "Anna Livia Plurabelle" };
根据数据成员的声明次序来使用初始化式。

这种形式的初始化从 C 继承而来,支持与 C 程序兼容。显式初始化类类型对象的成员有三个重大的缺点。

1)要求类的全体数据成员都是 public。
2)将初始化每个对象的每个成员的负担放在程序员身上。这样的初始化是乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。

3)如果增加或删除一个成员,必须找到所有的初始化并正确更新。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: