1. コード
#include<iostream>
using namespace std;
template<class T>
struct ListNode
{
T _data;
ListNode* next;
ListNode(const T& data = T())
{
_data = data;
next = nullptr;
}
~ListNode()
{
next = nullptr;
}
};
template<class T>
class List
{
typedef ListNode<T> Node; // 重命名
public:
List()
{
_head = nullptr;
}
void PushFront(const T& data = T()) // 每次对头结点判空处理
{
Node* newnode = new Node(data);
if (_head == nullptr)
_head = newnode;
else
{
Node* cur = _head;
_head = newnode;
_head->next = cur;
}
}
void PopFront()
{
if (_head == nullptr)
return;
Node* del = _head;
_head = _head->next;
delete del;
}
void PushBack(const T& data = T())
{
Node* newnode = new Node(data);
if (_head == nullptr)
{
_head = newnode;
return;
}
Node* cur = _head;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
void PopBack()
{
Node* pre = _head;
if (pre == nullptr)
return;
else if (pre->next == nullptr)
{
_head = nullptr;
delete pre;
}
else
{
Node* cur = _head->next;
while (cur->next)
{
cur = cur->next;
pre = pre->next;
}
pre->next = nullptr;
delete cur;
}
}
private:
Node* _head;
};
void Test()
{
List<int> l;
l.PushBack(0);
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
l.PopBack();
l.PopBack();
l.PopBack();
l.PopBack();
l.PopBack();
l.PopBack();
l.PopBack();
}
void Test1()
{
List<int> l;
l.PushFront(0);
l.PushFront(1);
l.PushFront(2);
l.PushFront(3);
l.PushFront(4);
l.PopFront();
l.PopFront();
l.PopFront();
l.PopFront();
l.PopFront();
l.PopFront();
l.PopFront();
}
2. まとめ
先頭ノードのある単連鎖リストと先頭ノードのない単連鎖リスト
1. 2 つの違い:
1. 先頭ノードのない単連鎖リストの操作は、最初のノードに対する他のノードの操作とは異なります。特別な処理により、プログラムが複雑になり、バグが発生する可能性が高くなるため、通常、ヘッド ノードは単方向リンク リストの開始ノードの前に接続されます。
2. 先頭ノードを持つ単一リンク リストの場合、最初に先頭ノードを指すアドレスを返す必要があるため、2 次元ポインタを使用する必要があります。そうしないと、メモリ アクセスの失敗または例外が発生します。
3. 先頭ノードと非先頭ノードの初期化、挿入、削除、出力操作が異なる 出力連結リストデータをたどる際、先頭ノードの判定条件は while(head->next!=NULLノードはwhile(head!=NULL)ですが、最初はヘッドポインタを設定できますが、1で述べたように、ノードが1つしかないなどの特殊な場合に問題が発生します。
2. 非リーディング ノードを初期化する方法が 2 つあるのに、リーディング ノードを初期化する方法が 1 つしかないのはなぜですか? Node *head が
ヘッド ノードなしで宣言されると、C コンパイラが自動的にそれを NULL に初期化するため、InitList(head) を呼び出す必要はありません; つまり、ヘッド ノードなしの初期化は疑似操作です。リーディング ノードの初期化により、ヒープ内のメモリのセクションが開かれ、ヘッド ポインター変数が指すアドレス (つまり、ヘッドの値) を変更する必要があります。したがって、ヘッドの値を変更するには、ヘッド変数 (つまり、2 次元ポインター) のアドレスを渡す必要があります。CreatList(head); を直接呼び出すことは、 head 変数の値を渡すことと同じです. 関数は head のコピーを変更し、 head の値を実際に変更することはできません. 注: ここでは、理解しやすいように、ヘッド ポインターを (保持するアドレスに関係なく) 変数と見なすことができます。3. 実際には、本質的には値の受け渡しとアドレスの受け渡しの問題ですが、ポインター自体によって保存されるアドレスにより、このプロセスが少し複雑になります。関数呼び出しでポインター変数のポインティング (値) を変更する必要がある場合は、ポインター変数のアドレス (アドレス) を渡す必要があります。
また、関数の仮パラメータがポインタの場合、パラメータが左辺でない限り(つまり、右辺値演算でない限り)、2次元ポインタ(仮パラメータ)を1に簡略化できます。 -次元ポインター。テール プラグの簡略化されたバージョンで、先頭ノードが上にあります。
名前空間 std を使用する#include<iostream>
;template<class T>
struct ListNode
{ T _data; ListNode* 次; ListNode(const T& データ = T()) { _data = データ; 次 = nullptr; } ~ListNode() { 次 = nullptr; } };
template<class T>
class List
{ typedef ListNode<T> ノード; public: List() { _head = nullptr; } void PushFront(const T& data = T()) { ノード* newnode = 新しいノード(データ); if (_head == nullptr) _head = newnode; else { ノード* cur = _head; _head = 新しいノード; _head->next = cur; } } void PopFront() { if (_head == nullptr) return; ノード* del = _head;
_head = _head->next;
デルを削除します。
}
void PushBack(const T& data = T())
{ ノード* newnode = 新しいノード(データ); if (_head == nullptr) { _head = newnode; 戻る; } Node* cur = _head; while (cur->next) { cur = cur->next; } cur->next = newnode; } void PopBack() { Node* pre = _head; if (pre == nullptr) return; else if (pre->next == nullptr)
{ _head = nullptr; 前を削除します。 } else { ノード* cur = _head->next; while (cur->next) { cur = cur->next; pre = pre->next; } pre->next = nullptr; cur を削除します。 } }プライベート: Node* _head; };
void Test()
{ List<int> l; l.PushBack(0); l.PushBack(1); l.PushBack(2); l.PushBack(3); l.PushBack(4); l.PopBack(); l.PopBack(); l.PopBack(); l.PopBack(); l.PopBack(); l.PopBack(); l.PopBack(); }void Test1()
{ List<int> l; l.PushFront(0); l.PushFront(1); l.PushFront(2); l.PushFront(3); l.PushFront(4); l.PopFront(); l.PopFront(); l.PopFront(); l.PopFront(); l.PopFront(); l.PopFront(); l.PopFront(); }