几个问题:
-
您没有向我们展示nodeBT 类型的定义,但您已将aux、root 和parent 声明为指向该类型的指针。
李>
然后您将 aux 分配为指向 BinaryTree,即使它已声明为指向 nodeBT。
1234563您分配给nodeBT->st,它也不属于BinaryTree。
您尝试通过分配nodeBT=root 返回已解析的树。问题在于 C 是一种“按值调用”的语言。这意味着当您的create 函数分配给nodeBT 时,它只是更改其局部变量的值。 create 的调用者没有看到这种变化。所以调用者没有收到根节点。这可能就是您收到“内存不可读”错误的原因;调用者正在访问一些随机内存,而不是包含根节点的内存。
如果您使用称为“递归下降”的标准技术编写解析器,您的代码实际上会更容易理解。方法如下。
让我们编写一个从表达式字符串中解析一个节点的函数。天真地,它应该有这样的签名:
BinaryTree *nodeFromExpression(char const *expression) {
要解析一个节点,我们首先需要得到该节点的info:
char info = expression[0];
接下来,我们需要看看节点是否应该有子节点。
BinaryTree *leftChild = NULL;
BinaryTree *rightChild = NULL;
if (expression[1] == '(') {
如果它应该有孩子,我们需要解析它们。这就是我们将“递归”放在“递归下降”中的地方:我们只需再次调用nodeFromExpression 来解析每个孩子。要解析左孩子,我们需要跳过expression 中的前两个字符,因为它们是当前节点的信息和(:
leftChild = nodeFromExpression(expression + 2);
但是我们跳过多少来解析正确的孩子?我们需要跳过我们在解析左孩子时使用的所有字符……
rightChild = nodeFromExpression(expression + ???
我们不知道那是多少个字符!事实证明,我们需要让nodeFromExpression 不仅返回它解析的节点,还需要一些指示它消耗了多少字符。所以我们需要更改nodeFromExpression 的签名以允许这样做。如果我们在解析时遇到错误怎么办?让我们定义一个结构,nodeFromExpression 可以使用它来返回它解析的节点、它消耗的字符数以及它遇到的错误(如果有的话):
typedef struct {
BinaryTree *node;
char const *error;
int offset;
} ParseResult;
如果error 不为空,则node 为空,offset 是我们发现错误的字符串中的偏移量。否则,offset 刚好超过解析 node 所消耗的最后一个字符。
所以,重新开始,我们将让nodeFromExpression 返回一个ParseResult。它将把整个表达式字符串作为输入,并且它将在该字符串中开始解析的偏移量:
ParseResult nodeFromExpression(char const *expression, int offset) {
现在我们有了报告错误的方法,让我们做一些错误检查:
if (!expression[offset]) {
return (ParseResult){
.error = "end of string where info expected",
.offset = offset
};
}
char info = expression[offset++];
我第一次没有提到这一点,但我们应该在这里处理你的$ NULL 令牌:
if (info == '$') {
return (ParseResult){
.node = NULL,
.offset = offset
};
}
现在我们可以回到解析孩子的问题了。
BinaryTree *leftChild = NULL;
BinaryTree *rightChild = NULL;
if (expression[offset] == '(') {
所以,要解析左孩子,我们只需再次递归调用自己。如果递归调用出错,我们返回相同的结果:
ParseResult leftResult = nodeFromExpression(expression, offset);
if (leftResult->error)
return leftResult;
好的,我们成功解析了左孩子。现在我们需要检查并使用孩子之间的逗号:
offset = leftResult.offset;
if (expression[offset] != ',') {
return (ParseResult){
.error = "comma expected",
.offset = offset
};
}
++offset;
现在我们可以递归调用nodeFromExpression来解析右孩子:
ParseResult rightResult = nodeFromExpression(expression, offset);
如果我们不想泄漏内存,现在的错误情况会稍微复杂一些。我们需要在返回错误之前释放左孩子:
if (rightResult.error) {
free(leftResult.node);
return rightResult;
}
请注意,如果您将NULL 传递给free,则它不会执行任何操作,因此我们不需要明确检查。
现在我们需要检查并消费孩子之后的):
offset = rightResult.offset;
if (expression[offset] != ')') {
free(leftResult.node);
free(rightResult.node);
return (ParseResult){
.error = "right parenthesis expected",
.offset = offset
};
}
++offset;
我们需要设置我们的本地 leftChild 和 rightChild 变量,而 leftResult 和 rightResult 变量仍在范围内:
leftChild = leftResult.node;
rightChild = rightResult.node;
}
如果需要的话,我们已经解析了两个子节点,所以现在我们准备好构造我们需要返回的节点:
BinaryTree *node = (BinaryTree *)calloc(1, sizeof *node);
node->info = info;
node->left = leftChild;
node->right = rightChild;
我们还有最后一件事要做:我们需要设置孩子的father 指针:
if (leftChild) {
leftChild->father = node;
}
if (rightChild) {
rightChild->father = node;
}
最后,我们可以返回一个成功的ParseResult:
return (ParseResult){
.node = node,
.offset = offset
};
}
我已将所有代码放在this gist 中,方便复制粘贴。
更新
如果您的编译器不喜欢(ParseResult){ ... } 语法,您应该寻找更好的编译器。该语法自 1999 年以来一直是标准的(§6.5.2.5 复合文字)。当您正在寻找更好的编译器时,您可以像这样解决它。
首先,添加两个静态函数:
static ParseResult ParseResultMakeWithNode(BinaryTree *node, int offset) {
ParseResult result;
memset(&result, 0, sizeof result);
result.node = node;
result.offset = offset;
return result;
}
static ParseResult ParseResultMakeWithError(char const *error, int offset) {
ParseResult result;
memset(&result, 0, sizeof result);
result.error = error;
result.offset = offset;
return result;
}
然后,将有问题的语法替换为对这些函数的调用。例子:
if (!expression[offset]) {
return ParseResultMakeWithError("end of string where info expected",
offset);
}
if (info == '$') {
return ParseResultMakeWithNode(NULL, offset);
}