web-dev-qa-db-ja.com

リンクリストがノード内にノードを保存する代わりにポインターを使用する理由

以前はJavaで広範囲にリンクリストを使用していましたが、C++を初めて使用します。私はプロジェクトで私に与えられたこのノードクラスをうまく使っていました

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

しかし、よく答えられなかった質問が1つありました。使用する必要がある理由

Node *m_next;

代わりにリスト内の次のノードを指す

Node m_next;

ポインターバージョンを使用する方が良いことを理解しています。私は事実を議論するつもりはありませんが、なぜそれが良いのか分かりません。ポインタがメモリ割り当てにどのように優れているかについて、あまり明確な答えは得られませんでした。

119
m0meni

それだけでなく、それが唯一の可能な方法です。

Nodeobjectを内部に格納した場合、sizeof(Node)はどうなりますか? sizeof(int) + sizeof(Node)になり、sizeof(int) + (sizeof(int) + sizeof(Node))に等しくなり、sizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))に等しくなります。

そのようなオブジェクトは存在できません。 不可能です。

217
emlai

Javaで

Node m_node

別のノードへのポインタを保存します。あなたはそれについて選択の余地はありません。 C++で

Node *m_node

同じことを意味します。違いは、C++では、オブジェクトへのポインタではなく、オブジェクトを実際に格納できることです。そのため、ポインタが必要だと言わなければなりません。 C++の場合:

Node m_node

ここにノードを保存することを意味します(そして、それは明らかにリストに対して機能しません-再帰的に定義された構造になります)。

178
pm100

C++はJavaではありません。書くとき

Node m_next;

javaでは、それは書くことと同じです

Node* m_next;

c ++で。 Javaでは、ポインターは暗黙的であり、C++では明示的です。書くなら

Node m_next;

c ++では、Nodeのインスタンスを、定義しているオブジェクト内に配置します。常に存在し、省略できず、newで割り当てることも、削除することもできません。この効果をJavaで実現することは不可能であり、同じ構文でJavaが行うこととはまったく異なります。

38
cmaster

ポインターを使用します。それ以外の場合はコード:

class Node
{
   //etc
   Node m_next; //non-pointer
};

…コンパイラはNodeのサイズを計算できないため、notをコンパイルします。これは、それが自分自身に依存しているためです。つまり、コンパイラが消費するメモリ量を決定できないことを意味します。

27
Nawaz

後者 (Node m_next含むノードにする必要があります。それを指していないでしょう。そして、要素のリンクはありません。

13
wallyk

説明するアプローチは、C++と互換性があるだけでなく、 (大部分)サブセット言語C と互換性があります。 Cスタイルのリンクリストの開発を学ぶことは、低レベルのプログラミング手法(手動メモリ管理など)を紹介する良い方法ですが、一般的にはnot最新のC++開発のベストプラクティス。

以下に、C++でアイテムのリストを管理する方法に関する4つのバリエーションを実装しました。

  1. raw_pointer_demoは、あなたと同じアプローチを使用します。生のポインタを使用する場合は、手動のメモリ管理が必要です。ここでのC++の使用は、syntactic-sugarのみであり、使用されるアプローチはC言語と互換性があります。
  2. shared_pointer_demoリスト管理は依然として手動で行われますが、メモリ管理は 自動 (生のポインタを使用しません)です。これは、おそらくJavaで経験したことと非常に似ています。
  3. std_list_demoは、標準ライブラリ list コンテナを使用します。これは、独自のライブラリをローリングするのではなく、既存のライブラリに依存する場合に、物事がどれほど容易になるかを示しています。
  4. std_vector_demoは、標準ライブラリ vector コンテナを使用します。これにより、単一の連続したメモリ割り当てでリストストレージが管理されます。つまり、個々の要素へのポインターはありません。特定のかなり極端な場合、これは非常に非効率になる可能性があります。ただし、典型的なケースでは、 C++でのリスト管理の推奨ベストプラクティスです です。

注意:これらのすべてのうち、raw_pointer_demoは、実際に「リーク」メモリを回避するためにリストを明示的に破棄する必要があります。他の3つのメソッドは、(関数の終了時に)コンテナーがスコープ外になると、リストとその内容を自動的に破棄します。要点:C++は、この点で非常に「Javaに似た」能力を備えていますが、自由に高レベルのツールを使用してプログラムを開発することを選択した場合に限ります。


/*BINFMTCXX: -Wall -Werror -std=c++11
*/

#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <memory>
using std::cerr;

/** Brief   Create a list, show it, then destroy it */
void raw_pointer_demo()
{
    cerr << "\n" << "raw_pointer_demo()..." << "\n";

    struct Node
    {
        Node(int data, Node *next) : data(data), next(next) {}
        int data;
        Node *next;
    };

    Node * items = 0;
    items = new Node(1,items);
    items = new Node(7,items);
    items = new Node(3,items);
    items = new Node(9,items);

    for (Node *i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr << "\n";

    // Erase the entire list
    while (items) {
        Node *temp = items;
        items = items->next;
        delete temp;
    }
}

raw_pointer_demo()...
9, 3, 7, 1

/** Brief   Create a list, show it, then destroy it */
void shared_pointer_demo()
{
    cerr << "\n" << "shared_pointer_demo()..." << "\n";

    struct Node; // Forward declaration of 'Node' required for typedef
    typedef std::shared_ptr<Node> Node_reference;

    struct Node
    {
        Node(int data, std::shared_ptr<Node> next ) : data(data), next(next) {}
        int data;
        Node_reference next;
    };

    Node_reference items = 0;
    items.reset( new Node(1,items) );
    items.reset( new Node(7,items) );
    items.reset( new Node(3,items) );
    items.reset( new Node(9,items) );

    for (Node_reference i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr<<"\n";

    // Erase the entire list
    while (items)
        items = items->next;
}

shared_pointer_demo()...
9, 3, 7, 1

/** Brief   Show the contents of a standard container */
template< typename C >
void show(std::string const & msg, C const & container)
{
    cerr << msg;
    bool first = true;
    for ( int i : container )
        cerr << (first?" ":", ") << i, first = false;
    cerr<<"\n";
}

/** Brief  Create a list, manipulate it, then destroy it */
void std_list_demo()
{
    cerr << "\n" << "std_list_demo()..." << "\n";

    // Initial list of integers
    std::list<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find( items.begin(), items.end(), 3), 8);
    show("B: ", items);

    // Sort the list
    items.sort();
    show( "C: ", items);

    // Erase '7'
    items.erase(std::find(items.begin(), items.end(), 7));
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_list_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

/** brief  Create a list, manipulate it, then destroy it */
void std_vector_demo()
{
    cerr << "\n" << "std_vector_demo()..." << "\n";

    // Initial list of integers
    std::vector<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find(items.begin(), items.end(), 3), 8);
    show( "B: ", items );

    // Sort the list
    sort(items.begin(), items.end());
    show("C: ", items);

    // Erase '7'
    items.erase( std::find( items.begin(), items.end(), 7 ) );
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_vector_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

int main()
{
    raw_pointer_demo();
    shared_pointer_demo();
    std_list_demo();
    std_vector_demo();
}
9
nobar

概要

C++でオブジェクトを参照および割り当てるには2つの方法がありますが、Javaでは1つの方法しかありません。

これを説明するために、次の図は、オブジェクトがメモリに格納される方法を示しています。

1.1ポインターのないC++アイテム

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int          Code;
    char[50]     FirstName;
    char[50]     LastName;
    // "Address" IS NOT A pointer !!!
    AddressClass Address;
};

int main(...)
{
   CustomerClass MyCustomer();
     MyCustomer.Code = 1;
     strcpy(MyCustomer.FirstName, "John");
     strcpy(MyCustomer.LastName, "Doe");
     MyCustomer.Address.Code = 2;
     strcpy(MyCustomer.Address.Street, "Blue River");
     strcpy(MyCustomer.Address.Number, "2231 A");

   return 0;
} // int main (...)

.......................................
..+---------------------------------+..
..|          AddressClass           |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: Street            |..
..| [+] char[10]: Number            |..
..| [+] char[50]: POBox             |..
..| [+] char[50]: City              |..
..| [+] char[50]: State             |..
..| [+] char[50]: Country           |..
..+---------------------------------+..
.......................................
..+---------------------------------+..
..|          CustomerClass          |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: FirstName         |..
..| [+] char[50]: LastName          |..
..+---------------------------------+..
..| [+] AddressClass: Address       |..
..| +-----------------------------+ |..
..| | [+] int:      Code          | |..
..| | [+] char[50]: Street        | |..
..| | [+] char[10]: Number        | |..
..| | [+] char[50]: POBox         | |..
..| | [+] char[50]: City          | |..
..| | [+] char[50]: State         | |..
..| | [+] char[50]: Country       | |..
..| +-----------------------------+ |..
..+---------------------------------+..
.......................................

警告:この例で使用されるC++構文は、Javaの構文に似ています。ただし、メモリの割り当ては異なります。

1.2ポインターを使用するC++アイテム

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int           Code;
    char[50]      FirstName;
    char[50]      LastName;
    // "Address" IS A pointer !!!
    AddressClass* Address;
};

.......................................
..+-----------------------------+......
..|        AddressClass         +<--+..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: Street        |...|..
..| [+] char[10]: Number        |...|..
..| [+] char[50]: POBox         |...|..
..| [+] char[50]: City          |...|..
..| [+] char[50]: State         |...|..
..| [+] char[50]: Country       |...|..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|         CustomerClass       |...|..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: FirstName     |...|..
..| [+] char[50]: LastName      |...|..
..| [+] AddressClass*: Address  +---+..
..+-----------------------------+......
.......................................

int main(...)
{
   CustomerClass* MyCustomer = new CustomerClass();
     MyCustomer->Code = 1;
     strcpy(MyCustomer->FirstName, "John");
     strcpy(MyCustomer->LastName, "Doe");

     AddressClass* MyCustomer->Address = new AddressClass();
     MyCustomer->Address->Code = 2;
     strcpy(MyCustomer->Address->Street, "Blue River");
     strcpy(MyCustomer->Address->Number, "2231 A");

     free MyCustomer->Address();
     free MyCustomer();

   return 0;
} // int main (...)

両方の方法の違いを確認すると、最初の方法では住所アイテムが顧客内に割り当てられ、2番目の方法では各住所を明示的に作成する必要があることがわかります。

警告: Javaこの2番目の手法のようにメモリ内のオブジェクトを割り当てますが、構文は最初の方法に似ており、「C++」の初心者にはわかりにくいかもしれません。

実装

したがって、リストの例は次の例のようになります。

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

.......................................
..+-----------------------------+......
..|            Node             |......
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................v..
...................................[X].
.......................................

概要

リンクリストには可変量のアイテムがあるため、メモリは必要に応じて割り当てられ、利用可能な場合に割り当てられます。

更新:

@haccksが彼の投稿でコメントしたように、言及する価値もあります。

時々、参照またはオブジェクトポインターは、ネストされたアイテム(別名「U.M.L. Composition」)を示します。

また、参照またはオブジェクトポインターは、外部アイテム(別名「U.M.L.集約」)を示す場合があります。

ただし、同じクラスのネストされたアイテムは、「ポインタなし」の手法では適用できません。

8
umlcat

サイドノートでは、クラスまたは構造体の最初のメンバーが次のポインターである場合(したがって、次を意味する仮想関数またはクラスの他の機能はクラスまたは構造体の最初のメンバーではない)、次のポインタだけで「ベース」クラスまたは構造を使用し、追加、前に挿入、前から取得などの基本的なリンクリスト操作に共通のコードを使用できます。これは、CまたはC++が、クラスまたは構造の最初のメンバーのアドレスがクラスまたは構造のアドレスと同じであることを保証するためです。ベースノードクラスまたは構造体には、基本的なリンクリスト関数で使用される次のポインタのみがあり、必要に応じてベースノードタイプと「派生」ノードタイプ間の変換に型キャストが使用されます。サイドノート-C++では、ベースノードクラスに次のポインターしかない場合、派生クラスに仮想関数を含めることはできません。

7
rcgldr

リンクリストでポインタを使用する方が良いのはなぜですか?

その理由は、Nodeオブジェクトを作成するとき、コンパイラはそのオブジェクトとオブジェクトのサイズを計算するためにメモリを割り当てる必要があるためです。
任意の型へのポインターのサイズはコンパイラーに既知であるため、したがって自己参照ポインターを使用してオブジェクトのサイズを計算できます。

代わりに_Node m_node_が使用される場合、コンパイラはNodeのサイズを認識せず、sizeof(Node)を計算する無限再帰に留まります。常に覚えておいてください: クラスには、独自のタイプのメンバーを含めることはできません

6
haccks

これはC++

int main (..)
{
    MyClass myObject;

    // or

    MyClass * myObjectPointer = new MyClass();

    ..
}

Javaのこれと同等です

public static void main (..)
{
    MyClass myObjectReference = new MyClass();
}

どちらもデフォルトのコンストラクタを使用してMyClassの新しいオブジェクトを作成します。

5
Khaled.K