我开始使用C ++,来自C和Objective C(以及一些Java)。我认为开始构建我的技能的好地方是从头开始编写一个简单的哈希表,使用链接列表进行冲突。所以我开始为每个班级编写骷髅。
class HashTable
{
public:
...
private:
...
};
class LinkedList
{
public:
...
private:
Node *root;
};
class Node
{
public:
Node *next;
string key;
int value;
Node()
{
...
}
};
关于这一点的奇怪之处在于,对于c ++用户来说,这可能不会让这个代码无效。我会得到一个错误:
error: expected type-specifier before ‘Node’
关于LinkedList类中的根节点。
当我简单地重新排序这些课程时,Node{...}; LinkedList{...}; HashTable{...};
所有的东西都像一个涂油的冰淇淋卡车。
现在,我不是一个质疑C ++设计的人,但这个限制有什么理由吗?如果我没记错的话,Obj。 C的课程基本上变成了桌子,并在飞行中查找。那么这种行为的原因是什么?
答案 0 :(得分:4)
编译器抛出以下错误
error: expected type-specifier before ‘Node’
因为它(编译器)不知道
Node *root;
Node
是什么。 (因为Node
稍后定义。)
两种可能的解决方案:
在Node
课前提出LinkedList
课程的定义(你已经知道了)
通过设置此行,转发在课程Node
之前声明课程LinkedList
class Node;
这告诉编译器存在类Node
。
在阅读PigBen的评论之后,您似乎在质疑这种行为的基本原理。我不是编译人员,但我认为这种行为使解析变得容易。对我来说,它类似于在使用之前提供函数声明。
PS:Nitpick,对于LinkedList
,变量名head
可能比root
更合适。
答案 1 :(得分:4)
这种声明的要求来自两种力量。首先,它简化了编译器设计。由于类型和变量具有相同的标识符结构,因此只要编译器解析标识符,编译器就必须知道它遇到了什么。有两种方法可以做到这一点。一种方法是要求在将其用于其他定义之前声明每个标识符。这意味着代码必须在给出其定义之前转发声明它打算使用的任何名称。这是编写具有其他模糊语法的编译器的一种非常简单的方法。
另一种方法是在多次传递中处理它。每次遇到未声明的标识符时,都会跳过它,编译器会在解析整个文件后尝试解析它。事实证明,C ++的语法使得这很难正确完成。编译器编写者不想不得不去解决这个问题,所以我们有前瞻性声明。
另一个原因是您实际上可能希望拥有前向声明,以便递归结构具有确定性作为语言的内在属性。这有点微妙。假设您编写了一个相互递归的类网络:
class Bar; // forward declaration
class Foo {
Bar myBar;
};
class Bar {
int occupySpace;
Foo myFoo;
};
这显然是不可能的,因为occupySpace
成员将出现在无限嵌套的递归中。要求定义中所有成员的前向声明为此提供特定数量的信息。特别是,它允许编译器提供足够的信息来形成对类的引用,但不允许实例化类(因为它的大小未知)。前向声明使这成为语言语法的一个特征,就像左值如何作为语言语法的特征而不是更微妙的语义或运行时要求一样可分配。
答案 2 :(得分:3)
此行为的原因是历史性的。文件按顺序处理。在遇到标识符的第一次引用时,该标识符需要已经声明。
编译器不会先处理整个文件。
不是重新排序类定义,而是经常可以使用前向声明
class Node;
class List
{
public:
//...
private:
Node *root;
//...
};
//...
答案 3 :(得分:2)
这种限制没有技术上的原因 - 这可以通过编译器在单个类的上下文中执行您明显期望的事实来证明。尽管如此,删除“限制”确实会进一步使编译器复杂化,减慢它们的速度,增加它们的内存使用量,而且至关重要的是 - 不会向后兼容(因为在更局部化的范围(命名空间)中的匹配可能会被选中而不是之前看到的其他符号)
恕我直言,它也使代码更难阅读和理解。能够从上到下阅读并随时理解代码非常有用,并鼓励您更有思想和结构化地表达您的问题解决方案。答案 4 :(得分:1)
反过来考虑一下;如果接受在文件中任何地方声明的类是正确的,为什么不在另一个尚未遇到的文件中声明一个类?
如果你去那么远,那么在你尝试链接程序之前你最终无法给出错误,这可能远离问题实际发生的位置。
答案 5 :(得分:0)
您已声明您的LinkedList
类的类型为Node
,但编译器不知道Node
是什么,因为它尚未声明。
只需在Node
LinkedList
答案 6 :(得分:0)
此样式意味着解析器可以更少次地运行代码。如果你必须识别每个声明的类型,那么再次运行代码,你需要花费额外的时间来解析第二次运行文件。
当然,正如许多人所指出的那样,你可以使用前瞻声明。