HOWTO · C++

C++의 연결 목록 복사 생성자

C++를 사용하여 연결 목록 데이터 구조에서 복사 생성자를 사용하는 방법에 대한 간단한 프로그래밍 자습서입니다.

이 페이지의 내용

이 간단한 기사는 깊은 복사 생성자가 있는 연결 목록의 C++ 기반 구현에 관한 것입니다.

C++의 복사 생성자

동일한 클래스의 다른 객체를 사용하여 객체를 초기화하기 위해 복사 생성자가 호출됩니다. 복사 생성자는 다음과 같은 여러 상황에서 호출됩니다.

  • 다른 개체를 사용하여 개체를 복사하는 경우.
  • 객체가 함수에서 값으로 반환될 때.
  • 객체가 함수에 값으로 전달될 때.

모든 클래스에는 항상 기본 복사 생성자가 있지만 전체 복사를 수행하지 않고 얕은 복사를 수행합니다. 포인터의 주소만 얕은 복사로 복사되고 두 개체(예: 호출자 및 전달된 개체)가 동일한 메모리를 가리킵니다.

반대로 전체 복사에서는 데이터 구성원의 값이 대상 개체 데이터 구성원에 복사됩니다.

C++의 연결 리스트

연결 목록 데이터 구조는 동적으로 성장할 수 있는 데이터 노드 형태로 데이터를 저장합니다. 연속 메모리가 필요하지 않으므로 메모리의 어느 위치에나 효율적으로 저장할 수 있습니다.

C++의 연결 목록 구현

연결 리스트를 구현해 봅시다. 먼저 데이터 노드를 가져오기 위한 클래스를 만들어야 합니다.

template <class T>
class Node {
 public:
  T info;
  Node<T>* next;

  Node() { next = 0; }
  Node(T val) {
    info = val;
    next = 0;
  }
};

Node 클래스에는 두 개의 멤버가 있습니다. 하나는 데이터(즉, info)를 저장하기 위한 것이고 다른 하나는 다음 노드의 주소를 저장하기 위한 클래스 자체에 대한 포인터입니다. 클래스는 모든 데이터 유형의 목록을 생성할 수 있도록 템플릿화됩니다.

위의 구현은 두 개의 생성자를 제공합니다. 첫 번째는 기본 생성자이고 다른 하나는 매개변수화됩니다.

연결 목록의 템플릿 클래스를 살펴보겠습니다.

template <class T>
class LSLL {
 private:
  Node<T>* head;

 public:
  LSLL() { head = 0; }
};

LSLL 클래스에는 Node 클래스에 대한 포인터가 하나만 있으며 연결 목록의 첫 번째 노드 주소를 보유하는 데 사용됩니다.

사용자에게 하나의 연결 목록 개체를 다른 개체에 깊이 복사하는 기능을 제공하려면 아래와 같이 사용자 정의 복사 생성자 구현을 제공해야 합니다.

LSLL(LSLL& PassedObj) {
  if (PassedObj.head == NULL) {
    head = NULL;
  } else {
    // copy all nodes of PassedObj to the caller object
    // attach first node to the head of the caller object
    Node<T>* newNode = new Node<T>();
    newNode->info = PassedObj.head->info;
    newNode->next = NULL;
    head = newNode;

    // Now deep-copy all the remaining nodes of the Passed linked list object
    Node<T>* PassedItr = PassedObj.head->next;
    Node<T>* CallerItr = head;
    while (PassedItr != NULL) {
      CallerItr->next = new Node<T>();
      CallerItr->next->info = PassedItr->info;
      CallerItr->next->next = NULL;
      CallerItr = CallerItr->next;  // move to newly added node
      PassedItr = PassedItr->next;  // move one node further
    }
  }
}

위의 코드 세그먼트는 전달된 객체의 첫 번째 노드의 전체 복사본을 생성하고 이를 호출자 목록 객체의 head에 첨부합니다. 그 후, 전달된 객체의 나머지 모든 노드는 깊이 복사되어 호출자 연결 목록 노드에 연결됩니다.

전체 구현에 대한 인지적 관점을 살펴보겠습니다.

#include <iostream>
using namespace std;

template <class T>
class Node {
 public:
  T info;
  Node<T>* next;

  Node() { next = NULL; }
  Node(T val) {
    info = val;
    next = NULL;
  }
};

template <class T>
class LSLL {
 private:
  Node<T>* head;

 public:
  LSLL() { head = NULL; }
  LSLL(LSLL& PassedObj) {
    if (PassedObj.head == NULL) {
      head = NULL;
    } else {
      // copy all nodes of PassedObj to the caller object
      // attach first node to the head of the caller object
      Node<T>* newNode = new Node<T>();
      newNode->info = PassedObj.head->info;
      newNode->next = NULL;
      head = newNode;

      // Now deep-copy all the remaining nodes of the Passed linked list object
      Node<T>* PassedItr = PassedObj.head->next;
      Node<T>* CallerItr = head;
      while (PassedItr != NULL) {
        CallerItr->next = new Node<T>();
        CallerItr->next->info = PassedItr->info;
        CallerItr->next->next = NULL;
        CallerItr = CallerItr->next;  // move to newly added node
        PassedItr = PassedItr->next;  // move one node further
      }
    }
  }
  void insertAtHead(T val) {
    Node<T>* x = new Node<T>(val);
    x->next = head;
    head = x;
  }
  void displayAll() {
    Node<T>* x = head;
    {
      while (x != 0) {
        cout << x->info << endl;
        x = x->next;
      }
    }
  }
  int isEmpty() {
    if (head == 0) return 1;
    return 0;
  }
  void insetAtTail(T val) {
    Node<T>* x = head;
    if (isEmpty()) {
      insertAtHead(val);
      return;
    }

    while (x->next != 0) {
      x = x->next;
    }
    x->next = new Node<T>(val);
  }
  void insertAfter(T key, T val) {
    if (isEmpty()) return;

    Node<T>* x = head;
    while (x != 0 && x->info == key) x = x->next;

    if (!x) return;

    Node<T>* temp = new Node<T>(val);

    temp->next = x->next;
    x->next = x;
  }
};
int main() {
  LSLL<int> list;
  list.insertAtHead(200);
  list.insertAtHead(100);
  list.insetAtTail(300);
  list.displayAll();

  LSLL<int> list2(list);
  cout << "List2: " << endl;
  list2.displayAll();
  return 0;
}

메인 드라이버 코드는 먼저 list 객체를 생성하고 여기에 3개의 노드를 삽입하고 displayAll 함수를 호출합니다. 그런 다음 새 연결 목록 개체 list2를 만들고 list를 인수로 사용하여 매개 변수화된 복사 생성자를 호출합니다.

list2 개체는 호출자 개체이고 list는 전달된 개체입니다.

출력:

100
200
300
List2:
100
200
300