我正在制作Michael J Laszlo的书“Computation Geometry and Computer Graphics in C++”。 以下是模板类原型:
template <class T> class ListNode : public Node {
T _val;
ListNode (T val);
friend class List<T>;
};
template <class T> ListNode <T>::ListNode(T val)
{_val=val;};
template <class T> class List{
private:
ListNode <T> *header;
ListNode <T> *win;
int _length;
public:
List(void);
~List(void);
T insert(T);
T append(T);
List * append(List*);
T prepend(T);
T remove(void);
void val(T); // overloaded function!
T val(void);// overloaded function!
T next(void);
T prev(void);
T first(void);
T last(void);
int length(void);
bool isFirst(void);
bool isLast(void);
bool isHead(void);
};
现在看看他定义List构造函数的方式:
// constructors and destructors
template <class T> list<T>:: List(void): _length(0)
{
header =new ListNode<T>(NULL);
win=header;
}
我的问题:
如何在{...}
之外分配默认长度,其余内部是什么?这背后有某种逻辑推理吗?
因为例如在此之前,他几乎宣称{...}
以外的所有内容,我认为这只是他的风格
答案 0 :(得分:17)
如何在括号外指定默认长度,其余在花括号内?
这是非常普遍和可取的。该构造称为initialization list。例如,这个
template <class T> ListNode <T>::ListNode(T val)
{_val=val;};
可以改写为:
template <class T> ListNode <T>::ListNode(T val)
: _val(val) {};
使用此构造指示编译器对要复制的项使用复制构造函数,而不是使用默认构造函数,后跟赋值运算符。在分配基元的情况下,它几乎不重要,但是对于更复杂的类型,初始化列表可以节省一些CPU周期。
作者没有将header
和win
赋值放入初始化列表的原因是强制执行特定的初始化顺序。 header
的分配必须在分配到win
之前完成。使用初始化列表时,分配顺序不受列表中项目顺序的控制:而是由relative order of declaration of the corresponding members in the class控制。依赖于它对读者来说非常混乱,而且太脆弱而无法继续生产,因此作者正确地决定将两个赋值移动到构造函数的主体中。
答案 1 :(得分:6)
我正在阅读问题的方式,您不是在问初始化列表中为什么_length
被初始化,而是在问为什么header
和win
不是
假设你有
template <class T> List<T>::List(void)
: _length(0)
, header(new ListNode<T>(NULL))
, win(header)
{ }
这会怎么做?这会初始化header
然后将其复制到win
,还是先将header
复制到win
,然后才设置header
?你无法通过查看构造函数定义来判断。您可以通过查看构造函数定义来判断使用普通旧分配时的情况。所以有些人(包括我自己)可能会说代码更容易阅读。
答案 2 :(得分:2)
这是出于效率的原因。该区域 - 在{}之前和之后()被称为初始化列表。您可以在那里初始化变量。编译器而不是默认初始化成员变量将初始化在初始化程序列表中设置的变量。 将它与您在{}内初始化变量的scenerio进行比较。编译器首先初始化所有成员变量然后进入正文{},然后重新初始化成员变量。初始化程序跳过该初始化步骤。始终在初始化列表中初始化。
答案 3 :(得分:2)
首先,在初始化列表中定义值更有效。如果不这样做,除非你有一个聪明的编译器,否则将默认初始化值,并将其分配给。对于某些类型,这不是非常有效(虽然不是你在那里的课程)。
至于为什么他决定以这种方式为这一类做这件事,目前还不清楚。我只能假设它可以在内存不足的情况下捕捉到新的可能投掷 - 尽管在那里他可以为new(nothrow)
做一个可能更有效和更清晰的事情。 / p>
他可能要做的是不要违反C ++初始化要求的顺序 - 类成员按照它们在类定义中声明的顺序初始化,而不是按照在构造函数初始化中指定它们的顺序进行初始化名单。这可能是一个真正的痛苦,虽然大多数编译器现在警告你,如果你这样做(并且还允许你将该警告更改为错误)。在这种情况下,可以在win
之前声明header
,在这种情况下,在初始化列表中执行win(header)
会将win
设置为具有在header
初始化之前header
。虽然,即使在那里,我仍然在初始化列表中初始化了标题,并且只在代码块中初始化win
。
真正令我烦恼的是他对(void)参数列表的使用。这是一个C-ism,看起来很难看。特别是对于默认构造函数和析构函数。