いくつかの問題:
type の定義は示していませんが、、 、およびをその型へのポインタとしてnodeBT
宣言しています。aux
root
parent
次に、 aを指すように宣言されていても、aux
a を指すように代入します。BinaryTree
nodeBT
に割り当てますがaux->dr
、これは の一部ではないため、意図した場所にBinaryTree
入力したとは限りません。に割り当てますが、それはどちらの一部でもありません。nodeBT
BinaryTree
nodeBT->st
BinaryTree
を代入して、解析されたツリーを返そうとしますnodeBT=root
。問題は、C が「値による呼び出し」言語であることです。これは、create
関数が に代入するときnodeBT
、ローカル変数の値のみを変更していることを意味します。の呼び出し元にcreate
は、その変更が表示されません。したがって、呼び出し元はルート ノードを受け取りません。これがおそらく、「メモリを読み取れません」というエラーが発生する理由です。呼び出し元は、ルート ノードを含むメモリではなく、ランダム メモリにアクセスしています。
「再帰降下」と呼ばれる標準的な手法を使用してパーサーを作成すると、コードは実際にははるかに理解しやすくなります。方法は次のとおりです。
式の文字列から 1 つのノードを解析する関数を書きましょう。単純に、次のような署名が必要です。
BinaryTree *nodeFromExpression(char const *expression) {
ノードを解析するには、まずノードの を取得する必要がありますinfo
:
char info = expression[0];
次に、ノードに子が必要かどうかを確認する必要があります。
BinaryTree *leftChild = NULL;
BinaryTree *rightChild = NULL;
if (expression[1] == '(') {
子が必要な場合は、それらを解析する必要があります。ここで、「再帰的」を「再帰的降下」に入れnodeFromExpression
ます。各子を解析するために再度呼び出すだけです。左の子を解析するには、 の最初の 2 文字をスキップする必要があります。これは、現在のノードのexpression
情報と であるためです。(
leftChild = nodeFromExpression(expression + 2);
しかし、適切な子を解析するためにどれだけスキップするのでしょうか? 左の子の解析中に使用したすべての文字をスキップする必要があります…</p>
rightChild = nodeFromExpression(expression + ???
それが何文字だったのかわかりません!nodeFromExpression
解析したノードだけでなく、消費した文字数の指標も返さなければならないことがわかりました。したがって、それを許可するには、 の署名を変更する必要がありnodeFromExpression
ます。解析中にエラーが発生した場合はどうなるでしょうか。nodeFromExpression
解析したノード、消費した文字数、発生したエラー (エラーがあった場合) を返すために使用できる構造を定義しましょう。
typedef struct {
BinaryTree *node;
char const *error;
int offset;
} ParseResult;
error
ifが null でない場合はnode
null でありoffset
、エラーが見つかった文字列内のオフセットであると言えます。それ以外の場合offset
は、解析に消費された最後の文字を過ぎたところnode
です。
したがって、最初からやり直して、nodeFromExpression
return aを作成します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;
OK、左の子を正常に解析しました。ここで、子の間のコンマを確認して消費する必要があります。
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;
}
free
を渡しても何もしないことに注意してNULL
ください。そのため、明示的に確認する必要はありません。
)
次に、子の後にチェックして消費する必要があります。
offset = rightResult.offset;
if (expression[offset] != ')') {
free(leftResult.node);
free(rightResult.node);
return (ParseResult){
.error = "right parenthesis expected",
.offset = offset
};
}
++offset;
and変数がまだスコープ内にある間に、ローカル変数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
最後にもう 1 つ、子のポインターを設定する必要があります。
if (leftChild) {
leftChild->father = node;
}
if (rightChild) {
rightChild->father = node;
}
最後に、成功した を返すことができますParseResult
:
return (ParseResult){
.node = node,
.offset = offset
};
}
簡単にコピーアンドペーストできるように、すべてのコードをこの要点に入れました。
アップデート
コンパイラが構文を気に入らない場合は(ParseResult){ ... }
、より優れたコンパイラを探す必要があります。この構文は 1999 年から標準になっています (§6.5.2.5 複合リテラル)。より優れたコンパイラを探している間は、次のように回避できます。
まず、2 つの静的関数を追加します。
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);
}