まず、構造体の宣言と必要と思われるポインターtypedefに関して、これを行うにはいくつかの方法があります。以下はCまたはC++で動作します。
// declare NodePtr as a pointer to Node, currently an incomplete type
// C and C++ both allow you to declare a pointer to damn-near anything
// so long as there is an understanding of what it *will* be, in this
// case, a structure called Node.
typedef struct Node *NodePtr;
// Now declare the structure type itself
struct Node
{
int x;
NodePtr next;
};
そうは言っても、私は正直にこれを行うことをお勧めしません。ほとんどのエンジニアは、「これはポインタです!」と叫ぶ、明確で構文が見える定義を望んでいます。あなたは違うかもしれません。私は、個人的にこれを単に好むでしょう:
struct Node
{
int x;
struct Node *next; // omit the 'struct' for C++-only usage
};
あなた、そして同様に重要な、あなたのコードを読んでいる他のエンジニアが、ノードへのポインタとしてのあなたの使用法を理解しているNodePtr
限り、そしてあなたの状況で最もうまくいくものを選んでください。ポインタ型の宣言は一部の人にとっては宗教に近いので、それを覚えておいてください。それらのアスタリスクを見ることを好む人もいれば(私は1人です)、そうでない人もいます(あなたのように聞こえます= P)。
注:edポインター型を使用すると、潜在的なエラーを回避するのに役立つ場所が1つあります。それは、複数の変数宣言です。このことを考慮:typedef
Node* a, b; // declares one Node* (a), and one Node (b)
これをtypedef struct Node *NodePtr;
許可する:
NodePtr a, b; // declares two Node*; both (a) and (b)
Cでコードを書くのに十分な時間を費やすと、前者が戻ってきて、間違いを犯さないように学ぶことができますが、それでもたまに発生する可能性があります。
ロードループ
リストをつなぎ合わせるためのロードループに関しては、リストを正しく接続していません。率直に言って、それを行う方法は無数にあります。そのうちの1つは以下の方法です。これには、「余分なノード」をクリーンアップする必要はありません。if (head){} else{}
また、同じ条件を回避するためにブロック構造も必要ありません。私たちが実際にやろうとしていることを考えてみましょう。ノードを作成し、それらのアドレスを適切なポインターに割り当てます。
NodePtr head = NULL; // always the head of the list.
NodePtr* ptr = &head; // will always point to the next pointer to assign.
int n;
while (cin >> n)
{
*ptr = new Node;
(*ptr)->x = n;
ptr = &(*ptr)->next;
}
// note this always terminates the load with a NULL tail.
(*ptr)->next = NULL;
使い方
- ヘッドポインタをNULLに初期化します
- ヘッドポインターを指すようにノードポインターポインター(はい、ポインターへのポインター)を初期化します。このポインタ間ポインタは、次の動的に割り当てられたノードのアドレスを受け取るターゲットポインタのアドレスを常に保持します。最初は、それがヘッドポインタになります。上記のコードでは、このポインターからポインターは変数です:
ptr
。
- whileループを開始します。
ptr
読み取られた値ごとに、新しいノードを割り当て、それを(したがって)が指すポインターに保存します*ptr
。最初の反復では、これはhead
ポインターのアドレスを保持するため、head
変数は新しいノード割り当てを取得します。以降のすべての反復では、最後に挿入されたノードnext
のポインターのアドレスが含まれます。ちなみに、この新しいターゲットポインタのアドレスを保存することは、次の割り当てサイクルに移る前にループで行われる最後のことです。
- ループが完了すると、最後に挿入されたノードの
next
ポインターをNULLに設定して、リンクリストが適切に終了するようにする必要があります。これは必須です。そのポインタへのポインタ(これまでずっと使用してきたものと同じもの)があると便利なので、「指す」ポインタをNULLに設定します。リストが終了し、ロードが完了しました。Brain Food:ロードループがノードをロードしなかった場合、どのポインターを指しますか?回答:&head
、これはNULL
、リストが空の場合に必要なもの(ヘッドポインタ)です。
設計
これが、ループの3回の完全な反復を通じてどのように機能するかをよりよく説明するのに役立つことを願っています。
初期構成
head ===> NULL;
ptr --^
1回の反復後:
head ===> node(1)
next
ptr ------^
2回の反復後
head ===> node(1)
next ===> node(2)
next
ptr ----------------^
3回の反復後
head ===> node(1)
next ===> node(2)
next ===> node(3)
next
ptr --------------------------^
3回の反復で停止した場合、最終的な終了割り当て(*ptr = NULL;
)は次のようになります。
head ===> node(1)
next ===> node(2)
next ===> node(3)
next ===> NULL;
ptr --------------------------^
最初の反復が終了すると、変更されないことに注意してくださいhead
(常に最初のノードを指します)。またptr
、入力される次のポインターのアドレスを常に保持していることにも注意してください。これは、最初の反復後(ヘッドポインターのアドレスとして開始された場合)、常に最後に追加されたノードのnext
ポインターのアドレスになります。
それがあなたにいくつかのアイデアを与えることを願っています。head
これらの2つのポインター(ポインターとptr
ポインター)を独自の構造にペアリングし、適切な管理機能を使用すると、教科書のキューが定義されることに注意してください。ここで、一方の端は挿入専用(ptr
)で、もう一方の端は抽出用(head
)であり、コンテナーはランダムアクセスを許可しません。のような標準ライブラリコンテナアダプタでは、最近そのようなことはあまり必要ありませんがstd::queue<>
、ポインタからポインタへの概念をうまく利用するための興味深い冒険を提供します。
完全な作業サンプル
このサンプルでは、キューに20個の要素をロードして印刷し、キューをクリーンアップして終了します。必要に応じて使用法に適応します(ヒント:受信データのソースを変更するなど)
#include <iostream>
using namespace std;
// declare NodePtr as a pointer to Node, currently an incomplete type
// C and C++ both allow you to declare a pointer to damn-near anything
// so long as there is an understanding of what it *will* be, in this
// case, a structure called Node.
typedef struct Node *NodePtr;
// Now declare the structure type itself
struct Node
{
int x;
NodePtr next;
};
int main()
{
// load our list with 20 elements
NodePtr head = NULL;
NodePtr* ptr = &head;
for (int n=1;n<=20;++n)
{
*ptr = new Node;
(*ptr)->x = n;
ptr = &(*ptr)->next;
}
// terminate the list.
*ptr = NULL;
// walk the list, printing each element
NodePtr p = head;
while (p)
{
cout << p->x << ' ';
p = p->next;
}
cout << endl;
// free the list
while (head)
{
NodePtr victim = head;
head = head->next;
delete victim;
}
return 0;
}
出力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20