[c++] 연결 목록이 노드 내부에 노드를 저장하는 대신 포인터를 사용하는 이유

Java에서 광범위하게 연결 목록을 사용했지만 C ++를 처음 접했습니다. 나는 프로젝트에서 나에게 주어진이 노드 클래스를 잘 사용하고 있었다.

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

하지만 대답이 잘되지 않은 질문이 하나있었습니다. 사용해야하는 이유

Node *m_next;

대신 목록의 다음 노드를 가리 킵니다.

Node m_next;

포인터 버전을 사용하는 것이 더 낫다는 것을 이해합니다. 나는 사실을 주장하지 않을 것이지만 그것이 왜 더 좋은지 모르겠습니다. 포인터가 메모리 할당에 어떻게 더 나은지에 대해 명확하지 않은 답변을 얻었으며 여기에있는 누군가가 나를 더 잘 이해하도록 도울 수 있는지 궁금합니다.



답변

더 나은 것이 아니라 가능한 유일한 방법입니다.

Node 개체를 내부에 저장했다면 무엇 sizeof(Node)일까요? 그것은 것이 sizeof(int) + sizeof(Node)동일 할 것이다, sizeof(int) + (sizeof(int) + sizeof(Node))동일 할 것이다, sizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))무한대 등.

그런 물체는 존재할 수 없습니다. 그건 불가능합니다 .


답변

자바에서

Node m_node

다른 노드에 대한 포인터를 저장합니다. 당신은 그것에 대해 선택권이 없습니다. C ++에서

Node *m_node

같은 의미입니다. 차이점은 C ++에서는 객체에 대한 포인터가 아니라 실제로 객체를 저장할 수 있다는 것입니다. 그래서 포인터를 원한다고 말해야합니다. C ++에서 :

Node m_node

바로 여기에 노드를 저장하는 것을 의미합니다 (목록에서 작동 할 수 없음-결국 재귀 적으로 정의 된 구조로 끝남).


답변

C ++는 Java가 아닙니다. 당신이 쓸 때

Node m_next;

Java에서는 작성과 동일합니다.

Node* m_next;

C ++에서. Java에서 포인터는 암시 적이며 C ++에서는 명시 적입니다. 쓰면

Node m_next;

C ++에서는 Node정의중인 객체 안에 인스턴스를 바로 넣습니다 . 항상 존재하며 생략 할 수 없으며 할당 new할 수없고 제거 할 수도 없습니다. 이 효과는 Java에서 달성 할 수 없으며 Java가 동일한 구문으로 수행하는 것과 완전히 다릅니다.


답변

포인터를 사용하고 그렇지 않으면 코드를 사용합니다.

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

… 것이 없습니다 컴파일러의 크기를 계산할 수 없기 때문에, 컴파일 Node. 이는 자체에 의존하기 때문입니다. 이는 컴파일러가 소비 할 메모리 양을 결정할 수 없음을 의미합니다.


답변

후자 ( Node m_next)는 노드 를 포함 해야합니다. 그것을 가리 키지 않을 것입니다. 그러면 요소가 연결되지 않습니다.


답변

설명하는 접근 방식은 C ++뿐만 아니라 (대부분) 하위 집합 언어 C 와도 호환됩니다 . C 스타일 연결 목록을 개발하는 방법을 배우는 것은 저수준 프로그래밍 기술 (예 : 수동 메모리 관리)을 소개하는 좋은 방법이지만 일반적으로 최신 C ++ 개발을위한 최선의 방법 은 아닙니다 .

아래에서는 C ++에서 항목 목록을 관리하는 방법에 대해 네 가지 변형을 구현했습니다.

  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실제로 “누수”메모리를 피하기 위해 목록을 명시 적으로 삭제해야합니다. 다른 세 가지 방법은 컨테이너가 범위를 벗어날 때 (함수 종료시) 목록과 그 내용을 자동으로 파괴합니다. 요점은 : C ++는 이와 관련하여 매우 “자바와 유사”할 수 있지만, 고수준 도구를 사용하여 프로그램을 개발하기로 선택한 경우에만 가능합니다.


/*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();
}


답변

개요

C ++에서는 객체를 참조하고 할당하는 두 가지 방법이 있지만 Java에는 한 가지 방법 만 있습니다.

이를 설명하기 위해 다음 다이어그램은 객체가 메모리에 저장되는 방식을 보여줍니다.

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 (...)

두 방법의 차이점을 확인하면 첫 번째 방법에서는 주소 항목이 고객 내에 할당되고 두 번째 방법에서는 각 주소를 명시 적으로 만들어야한다는 것을 알 수 있습니다.

경고 : Java는이 두 번째 기술과 같이 메모리에 개체를 할당하지만 구문은 첫 번째 방법과 같으므로 “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가 그의 게시물에서 언급했듯이 언급 할 가치가 있습니다.

때때로 참조 또는 개체 포인터는 중첩 된 항목 ( “UML 구성”이라고도 함)을 나타냅니다.

때로는 참조 또는 개체 포인터가 외부 항목 ( “UML 집계”라고도 함)을 나타냅니다.

그러나 같은 클래스의 중첩 된 항목은 “no-pointer”기술로 적용 할 수 없습니다.