リンクリストノードの実装スキル-構造体の魔法の使用
執筆者の能力には制限があります。閲覧中にエラーが発生した場合は、私に連絡してエラーを指摘し、後で間違った知識を読者に知られないようにしてください。
でたらめ
C言語は非常に単純な構文しか提供しませんが、C言語プログラマーがCを使用して多くの驚くべき高度な機能を実現することには影響しません。
この記事では、C言語で非常に一般的なリンクリストノードを実装する手法を紹介します。
C言語でいくつかの本を読んだことがあり、関連する紹介を見たことがあるかもしれませんが、あまり気にしないので、ここで詳しく学習します。
次に、リンクリストノードの実装について説明しますが、がっかりしないでください。その実装は、思ったほど簡単ではないかもしれません。
ノード定義
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Next;
} LIST_ENTRY, *PLIST_ENTRY;
LIST_ENTRYは、二重リンクリストのノードを表します。次は、次のノードへのポインターです。
しかし、上記のノードでは、ノードを表すことができることを除いて、追加の情報を含めることができないため、それを使用することはできません。
さて、ここでは、生徒を表すリンクリストを作成することを想定しています。まず、生徒の構造を定義しましょう。
typedef struct _STUDENT {
char name[64];
int age;
} STUDENT, *PSTUDENT;
ここでは、LIST_ENTRYを使用するかどうかを示すためにのみ使用し、学生管理システムの構築方法を説明しないため、手元の学生を表す構造を記述します。これは非常に単純です。
STUDENT構造がリンクリストのノードになるためには、それらをマージする必要があります。その後、STUDENT構造は次のようになります。
typedef struct _STUDENT {
LIST_ENTRY list_entry;
char name[64];
int age;
} STUDENT, *PSTUDENT;
LIST_ENTRY構造をSTUDENT構造の最初にネストしたため、その後の実装がはるかに簡単になることに注意してください。他の位置に配置することは確かに可能ですが、問題が複雑になります。
使用する
これで構造が定義されたので、この構造の使用方法と、この記事で表現したい構造の賢さを見てみましょう。
繰り返しになりますが、この記事はこの構造の使用法の美しさを説明するものであり、完全なリンクリストを実装することを意図したものではないため、最も基本的なバージョンのみを示します。
#define GET_STUDENT(address, type, field) ((type *)( \
(char *)(address) - \
(char *)(&((type *)0)->field)))
PLIST_ENTRY list_header = NULL; // 链表头
// 在链表的尾部添加一个新的节点
int add_student(char* name, int age) {
// create a student with the given parameters
PSTUDENT student = malloc(sizeof(STUDENT));
if (student == NULL)
return -1;
memset(student, 0, sizeof(STUDENT));
strcpy(student->name, name);
student->age = age;
if (list_header == NULL) {
list_header = &student->list_entry;
} else {
PLIST_ENTRY p = list_header;
while (p->Next) {
p = p->Next;
}
p->Next = &student->list_entry;
// student->list_entry.Next is NULL
}
}
int main() {
// 添加两个节点
add_student("student abc", 22);
add_student("student ijk", 25);
// 遍历整个链表
请注意这里!!!!
/////////////////////////////////////////////////////
for (p = list_header; p != NULL; p = p->Next) {
// get the student struct
PSTUDENT student = GET_STUDENT(p, STUDENT, list_entry);
// PSTUDENT student = (PSTUDENT)(((char*)p - (char*)(&((PSTUDENT)0)->list_entry)));
printf("student name: %s, student age: %d\n", student->name, student->age);
}
/////////////////////////////////////////////////////
// 省略释放内存的代码
return 0;
}
解析中
これまでに工夫を凝らしてきたのであれば、次の部分を見るのに時間を浪費する必要はありません。
上記のコードの
ポイントは何ですか?ポイントはマクロGET_STUDENTです。
デバッグを容易にするために、42行のマクロ展開フォームを提供して、デバッグを容易にします。
- 最初に注意する点は、リンクリストの各ノードのタイプがLIST_ENTRYではなくSTUDENTであることです。
- ただし、STUDENT構造の最初のフィールドはLIST_ENTRYであることに注意してください。これは、GET_STUDETNが適切に機能するための前提条件です。
- では、なぜこれが機能するのでしょうか?
まず、デバッグするために42行目にブレークポイントを追加すると、次の結果が得られます。
このときのpのアドレスは、学生のアドレスと同じであることに注意してください。これは、LIST_ENTRYがSTUDENT構造の最初の位置に配置され、新しいノードをリストに追加するときに、STUDETN構造を追加するためです。この場合、STUDENT構造体のポインターをLIST_ENTRYのポインターに割り当てることができます。
ここでは、同じ学生構造の内部レイアウトを見ています。
ここでは、最初に生徒のメモリアドレスがであることがわかります
0x00000000600049fb0
。この値は上の図に示されているものと同じです。これは、私のコンピューターが64ビットマシンであるため、アドレスが8バイトを占めるため、
これらのバイトを詳細に解析します:意味
私たちの学生の最初のフィールドはLIST_ENTRYで、LIST_ENTRY次は、リスト内の次のノードへのポインタだけが含まれていますので、間違いなく前者8つの文字があるので(1)。10a00400 06000000
現在のバイトの次のノードの最初のアドレスを表します。これは小さなセグメントであるため、最初の画像に示されているアドレスバイトシーケンスとは正反対であり、最上位ビットが最後にあることに注意してください。
(2)解析されたバイト値は、最後から3番目のバイトメモリです。 student-> nameを保存するために使用
(3)最後の2バイトは、student-> ageを表し、その値は小さなセグメントコード16です
。4。次に、ループを続行して、リンクリストの2番目のノードを見つけ、メモリを確認します。レイアウト:
右、我々は上記の言ったことを確認してください。第2のノードのアドレス
0x0000000060004a010
と一致し、10a00400 06000000
合意、我々はlist_entry-最初のノードを知っているので>次は0バイトで、その最初の二つを指して現在のノードであります、現在のノードの次が空なので、次のフィールドは(2)(3)の要約と同じなので、ここでは説明しません。
要約すると、LIST_ENTRYとSTUDENTの構造は、C言語構造のメモリレイアウトの特性を巧みに使用し、STUDENT構造をLIST_ENTRYのリンクリストに入れます。その利点は何ですか?
これにより、LIST_ENTRYを使用して定義したリンクリストに任意の構造を配置できます。相互に接続できるようにリンクリストに個別に配置する必要がある構造ごとに関連フィールドを定義する必要はありません。そうすると、リンクリストに関連するロジックと同等になります。実際に情報を格納するための構造から抽出すると、リンクリストの操作方法を記述する際に、リンクリストに格納されている実際の学生のタイプにほとんど注意を払うことができません。
どんなアイデアでも大歓迎です。
終わり…