📄 [10] constructors, c++ faq lite.htm
字号:
<P><A name=[10.5]></A>
<DIV class=FaqTitle>
<H3>[10.5] 当建立一个 <TT>Fred</TT> 对象数组时,哪个构造函数将被调用?<IMG alt=UPDATED!
src="[10] Constructors, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
changed so it uses new-style headers and the <TT>std::</TT> syntax and reworded
references to STL (on 7/00). <A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.6]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[10.6]:rawtext--></A>.]</EM></SMALL>
<P><TT>Fred</TT> 的<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.4]">默认构造函数</A>(以下讨论除外)。</P>
<P>你无法告诉编译器调用不同的构造函数(以下讨论除外)。如果你的<TT>Fred</TT>类没有<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.4]">默认构造函数</A>,那么试图创建一个<TT>Fred</TT>对象数组将会导致编译时出错。
<P>
<DIV
class=CodeBlock><TT> class Fred {<BR> public:<BR> Fred(int i, int j);<BR> </TT><EM>// ... 假设
<TT>Fred</TT> 类没有<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.4]">默认构造函数</A> ...</EM><TT><BR> };<BR> <BR> int main()<BR> {<BR> Fred a[10]; </TT><EM>// 错误:<TT>Fred</TT>
类没有默认构造函数</EM><TT><BR> Fred* p = new Fred[10]; </TT><EM>// 错误:<TT>Fred</TT>
类没有默认构造函数</EM><TT><BR> } </TT></DIV>
<P>然而,如果你正在创建一个<A
href="http://www.sunistudio.com/cppfaq/class-libraries.html#[32.1]">标准的<TT>std::vector<Fred></TT></A>,而不是
<TT>Fred</TT>对象数组(既然<A
href="http://www.sunistudio.com/cppfaq/containers-and-templates.html#[31.1]">数组是有害的</A>,那么你可能应该这么做),则在
<TT>Fred</TT> 类中不需要默认构造函数。因为你能够给<TT>std::vector</TT>一个用来初始化元素的<TT>Fred</TT> 对象:
<P>
<DIV
class=CodeBlock><TT> #include <vector><BR> <BR> int main()<BR> {<BR> std::vector<Fred> a(10, Fred(5,7));<BR> </TT><EM>// 在<TT>std::vector</TT> 中的
10 个 <TT>Fred</TT>对象将使用 <TT>Fred(5,7)</TT>
来初始化</EM><TT><BR> </TT><EM>// ...</EM><TT><BR> }
</TT></DIV>
<P>虽然应该使用<TT>std::vector</TT>而不是数组,但有有应该使用数组的时候,那样的话,有“数组的显式初始化”语法。它看上去是这样的:
<P>
<DIV
class=CodeBlock><TT> class Fred {<BR> public:<BR> Fred(int i, int j);<BR> </TT><EM>// ... 假设<TT>Fred</TT>类没有<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.4]">默认构造函数</A>...</EM><TT><BR> };<BR> <BR> int main()<BR> {<BR> Fred a[10] = {<BR> Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7),<BR> Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7)<BR> };<BR> <BR> </TT><EM>// 10
个 <TT>Fred</TT>对象将使用 <TT>Fred(5,7)</TT>
来初始化.</EM><TT><BR> </TT><EM>// ...</EM><TT><BR> }
</TT></DIV>
<P>当然你不必每个项都做<TT>Fred(5,7)</TT>—你可以放任何你想要的数字,甚至是参数或其他变量。重点是,这种语法是(a)可行的,但(b)不如<TT>std::vector</TT>语法漂亮。记住这个:<A
href="http://www.sunistudio.com/cppfaq/containers-and-templates.html#[31.1]">数组是有害</A>的—除非由于编译原因而使用数组,否则应该用<TT>std::vector</TT>
取代。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.6]></A>
<DIV class=FaqTitle>
<H3>[10.6] 构造函数应该用“初始化列表”还是“赋值”?<IMG alt=UPDATED!
src="[10] Constructors, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
rewrote (on 4/01). <A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.7]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[10.7]:rawtext--></A>.]</EM></SMALL>
<P>初始化列表。事实上,构造函数应该在初始化列表中初始化<I>所有</I>成员对象。
<P>例如,构造函数用初始化列表<TT>Fred::Fred() : x_(</TT><EM>whatever</EM><TT>) { }</TT>来初始化成员对象
<TT>x_</TT>。这样做最普通的好处是提高性能。如,<EM>whatever</EM>表达式和成员变量 <TT>x_
</TT>相同,<EM>whatever</EM>表达式的结果直接由内部的<TT>x_</TT>来构造——编译器不会产生对象的两个拷贝。即使类型不同,使用初始化列表时编译器通常也能够做得比使用赋值更好。
<P>建立构造函数的另一种(错误的)方法是通过赋值,如:<TT>Fred::Fred() { x_ = </TT><EM>whatever</EM><TT>; }</TT>。在这种情况下,<EM>whatever</EM>表达式导致一个分离的,临时的对象被建立,并且该临时对象被传递给<TT>x_</TT>对象的赋值操作。然后该临时对象会在
;处被析构。这样是效率低下的。
<P>这好像还不是太坏,但这里还有一个在构造函数中使用赋值的效率低下之源:成员对象会被以默认构造函数完整的构造,例如,可能分配一些缺省数量的内存或打开一些缺省的文件。但如果
<EM>whatever</EM>表达式和/或赋值操作导致对象关闭那个文件和/或释放那块内存,这些工作是做无用功(举例来说,如默认构造函数没有分配一个足够大的内存池或它打开了错误的文件)。
<P>结论:其他条件相等的情况下,使用初始化列表的代码会快于使用赋值的代码。
<P>注意:如果<TT>x_</TT>的类型是诸如<TT>int</TT>或者<TT>char*</TT>
或者<TT>float</TT>之类的内建类型,那么性能是没有区别的。但即使在这些情况下,我个人的偏好是为了匀称,仍然使用初始化列表而不是赋值来设置这些数据成员。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.7]></A>
<DIV class=FaqTitle>
<H3>[10.7] 可以在构造函数中使用 <TT>this</TT> 指针吗?<IMG alt=UPDATED!
src="[10] Constructors, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
rewrote because of a suggestion from <A href="mailto:PRapp@smartronix.com">Perry
Rapp</A> (on 4/01). <A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.8]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[10.8]:rawtext--></A>.]</EM></SMALL>
<P>某些人认为不应该在构造函数中使用<TT>this</TT>指针,因为这时<TT>this</TT>对象还没有完全形成。然后,只要你小心,是可以在构造函数(在函数体甚至在<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.6]">初始化列表</A>中)使用<TT>this</TT>的。
<P>以下是始终可行的:构造函数的函数体(或构造函数所调用的函数)能可靠地访问基类中声明的数据成员和/或构造函数所属类声明的数据成员。这是因为所有这些数据成员被保证在构造函数函数体开始执行时已经被完整的建立。
<P>以下是始终不可行的:构造函数的函数体(或构造函数所调用的函数)不能向下调用被派生类重新的虚函数。如果你的目的是得到派生类重写的函数,那么<A
href="http://www.sunistudio.com/cppfaq/strange-inheritance.html#[23.3]">你将无功而返</A>。注意,无论你如何调用虚成员函数:显式使用<TT>this</TT>指针(如,<TT>this->method()</TT>),隐式的使用<TT>this</TT>指针(如,<TT>method()</TT>),或甚至在<TT>this</TT>对象上调用其他函数来调用该虚成员函数,你都不会得到派生类的重写函数。这是底线:即使调用者正在构建一个派生类的对象,在基类的构造函数执行期间,<A
href="http://www.sunistudio.com/cppfaq/strange-inheritance.html#[23.3]">对象还不是一个派生类的对象</A>。
<P>以下是有时可行的:如果传递 <TT>this </TT>对象的任何一个数据成员给另一个数据成员的<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.6]">初始化程序</A>,你必须确保该数据成员已经被初始化。好消息是你能使用一些不依赖于你所使用的编译器的显著的语言规则,来确定那个数据成员是否已经(或者还没有)被初始化。坏消息是你必须知道这些语言规则(例如,基类子对象首先被初始化(如果有多重和/或虚继承,则查询这个次序!),然后类中定义的数据成员根据在类中声明的次序被初始化)。如果你不知道这些规则,则不要从<TT>this</TT>对象传递任何数据成员(不论是否显式的使用了<TT>this</TT>关键字)给任何其他数据成员的<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.6]">初始化程序</A>!如果你知道这些规则,则需要小心。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.8]></A>
<DIV class=FaqTitle>
<H3>[10.8] 什么是“命名的构造函数用法(Named Constructor Idiom)”? <IMG alt=UPDATED!
src="[10] Constructors, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
fixed a typo (<TT>Fred</TT> <I>vs.</I> <TT>Point</TT>) in the prose thanks to <A
href="mailto:lecates@star.cosmic.org">Roy LeCates</A> (on 7/00). <A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.9]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[10.9]:rawtext--></A>.]</EM></SMALL>
<P>为你的类的用户提供的一种更直觉的和/或更安全的构造操作技巧。
<P>问题在于构造函数总是有和类相同的名字。因此,区分类的不同的构造函数是通过参数列表。但如果有许多构造函数,它们之间的区别有时就会很敏感并且有错误倾向。
<P>使用命名的构造函数用法(Named Constructor
Idiom),在<TT>private:</TT>节和<TT>protected:</TT>节中声明所有类的构造函数,并提供返回一个对象的<TT>public</TT>
<TT>static</TT> 方法。这些方法由此称为“命名的构造函数(Named
Constructors)”。一般,每种不同的构造对象的方法都有一个这样的静态方法。
<P>例如,假设我们正在建立一个描绘X-Y平面的<TT>Point</TT>类。通常有两种方法指定一个二维空间坐标:矩形坐标(X+Y),极坐标(Radius+Angle)(半径+角度)。(不必担心已经忘了这些;重点不在于坐标系统的析解;重点在于有几种方法来创建一个<TT>Point</TT>对象。)不幸的是,这两种坐标系统的参数是相同的:两个
<TT>float</TT>。这将在重载构造函数中导致一个重载不明确的错误:
<P>
<DIV
class=CodeBlock><TT> class Point {<BR> public:<BR> Point(float x, float y); </TT><EM>// 矩形坐标</EM><TT><BR> Point(float r, float a); </TT><EM>// 极坐标 (半径和角度)</EM><TT><BR> </TT><EM>// 错误:重载不明确:<TT>Point::Point(float,float)</TT></EM><TT><BR> };<BR> <BR> int main()<BR> {<BR> Point p = Point(5.7, 1.2); </TT><EM>// 不明确:哪个坐标系统?</EM><TT><BR> }
</TT></DIV>
<P>解决这个不明确错误的一种方法是使用命名的构造函数用法(Named Constructor Idiom):
<P>
<DIV
class=CodeBlock><TT> #include <cmath> </TT><EM>// To get <TT>sin()</TT> and <TT>cos()</TT></EM><TT><BR> <BR> class Point {<BR> public:<BR> static Point rectangular(float x, float y); </TT><EM>// 矩形坐标</EM><TT><BR> static Point polar(float radius, float angle); </TT><EM>// 极坐标</EM><TT><BR> </TT><EM>// 这些 <TT>static</TT> 方法称为“命名的构造函数(named constructors)”</EM><TT><BR> </TT><EM>// ...</EM><TT><BR> private:<BR> Point(float x, float y); </TT><EM>// 矩形坐标</EM><TT><BR> float x_, y_;<BR> };<BR> <BR> inline Point::Point(float x, float y)<BR> : x_(x), y_(y) { }<BR> <BR> inline Point Point::rectangular(float x, float y)<BR> { return Point(x, y); }<BR> <BR> inline Point Point::polar(float radius, float angle)<BR> { return Point(radius*cos(angle), radius*sin(angle)); }
</TT></DIV>
<P>现在,<TT>Point</TT>的用户有了一个清晰的和明确的语法在任何一个坐标系统中创建<TT>Point</TT>对象:
<P>
<DIV
class=CodeBlock><TT> int main()<BR> {<BR> Point p1 = Point::rectangular(5.7, 1.2); </TT><EM>// 显然是矩形坐标</EM><TT><BR> Point p2 = Point::polar(5.7, 1.2); </TT><EM>// 显然是极坐标</EM><TT><BR> }
</TT></DIV>
<P>如果期望<TT>Point</TT>有派生类,则确保你的构造函数在<TT>protected:</TT>节中。
<P>命名的构造函数用法也能用于<A
href="http://www.sunistudio.com/cppfaq/freestore-mgmt.html#[16.20]">总是通过<TT>new</TT>来创建对象</A>。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -