Данные массивов LinkedList в C ++

0
class Node
{
private:
    Node* pNext;
    char* name;
public:
    Node();
    ~Node();
    Node* getNext();
    char* getname();
    void setNext(Node* pNode);
    void setname(char* pname);
}

void linkedlist:: insert(Node* pNode){
    Node *current;
    pNode->setNext(NULL);

    if(!pHead)
    pHead=pNode;
    else{
        current=pHead;
        while(current->getNext()!=NULL)
        current=current->getNext();
        current->setNext(pNode);
    }
}


void main(){
    linkedlist list;
    Node *temp=NULL;
    char i[200]={0,};
    temp=new Node();
    strcpy(i, "Anna");
    temp->setname(i);
    list.insert(temp);
    temp=new Node();
    strcpy(i, "Jane");
    temp->setname(i);
    list.insert(temp);
    temp=new Node();
    strcpy(i, "Peter");
    temp->setname(i);
    list.insert(temp);
    temp=new Node();
    strcpy(i, "Brooth");
    temp->setname(i);
    list.insert(temp);
    temp=new Node();
    strcpy(i, "Tim");
    temp->setname(i);
    list.insert(temp);
}

почему результат не Anna-> Jane → Peter-> Brooth-> Tim И почему результат Tim-> Tim-> Tim-> Tim-> Tim Что мне делать? Всякий раз, когда char я [200] изменяет все измененные данные узла. Зачем? он работал хорошо, когда я использую данные int. Это происходит только тогда, когда я использую массив char.

  • 0
    Нет, он не insert , поэтому указатель где-то есть. Hovewer - проблема в setname вероятно, и вы не предоставили код для этого. На заметку: используйте string , а не char* . Это C ++.
  • 0
    @ PawełStawarz Видел, удалил мой комментарий уже ...
Теги:
arrays
char

4 ответа

-2
Лучший ответ

Когда я начинаю писать это, "решение" уже принято OP.

Однако это решение, использующее std::string для уменьшения сложности управления памятью и косвенной привязки с помощью указателей, на мой взгляд не является оптимальным способом узнать об управлении памятью и косвенности с помощью указателей. Другими словами, чтобы узнать о выполнении X, на мой взгляд, не рекомендуется использовать готовые абстракции, которые обрабатывают X-making для вас. Или даже более короткий, соответствующий "трудный" материал, который std::string обрабатывает автоматически, - это то, о чем OP явно не знает.

Так.

Улучшение потенциала для вопроса.

  1. Опубликованный код НЕ РЕАЛЬНЫЙ КОД. Это очень плохо, потому что он часто может тратить время на все (мы не телепаты). Это гарантировало не реальный код, потому что по крайней мере одна отсутствующая точка с запятой, так что она не будет компилироваться ни с каким компилятором.

  2. Опубликованный код является неполным. Большая часть класса linked_list отсутствует. В частности, его конструктор важен для оценки правильности или отсутствия логики.

  3. Опубликованный код является нестандартным, в данном случае специфичным для Microsoft. В стандарте C и C++ void main недействителен, он должен быть int main. Например, компилятор g++ отказывается компилировать void main файл void main в данном коде.

Пожалуйста, для будущих вопросов отправляйте реальный код, который является полным (но минимальным, как вы можете это сделать), и стандартным C++ (насколько это возможно).

Почему имена кажутся одинаковыми, и как их исправить.

Все узлы указывают на один и тот же буфер имен.

Чтобы исправить это, не используя std::string, вам нужно убедиться, что каждый узел указывает на уникальный буфер имен.

И один из способов сделать это - динамически распределять каждый буфер узла, используя new[].

Как создать дубликат строки.

Принцип DRY: не повторяйте себя.

В соответствии с этим принципом плохая идея использовать new[] непосредственно в каждом месте, куда вы хотите добавить строку в список, а также плохая идея скопировать строку в новый буфер в каждом таком месте. Причина в том, что все это дублирование кода легко может привести к небольшим ошибкам, что добавляет времени, используемого для поиска и исправления ошибок. Хуже того, если дублирование кода осуществляется с помощью удобного метода копирования и модификации, тогда ошибки могут быть дублированы, и из-за закона Мерфи это произойдет - часто.

Вместо этого вызов new[] и копирование строки должны быть централизованы, например, в функции, называемой duplicate, поскольку это (повторяющаяся строка, а не дублирующаяся ошибка) - это то, что она делает и что она производит:

#include <string.h>     // strcpy, strlen
#include <stddef.h>     // ptrdiff_t

typedef ptrdiff_t Size;

auto duplicate( char const* const s )
    -> char*
{
    Size const buffer_size = 1 + strlen( s );
    return strcpy( new char[buffer_size], s );
}

Тип ptrdiff_t - это тип выражения разности указателей. Поэтому он достаточно велик, чтобы обрабатывать любой реальный размер, а в 32-битной и 64-битной системах - подписанный эквивалент unsigned size_t. Он использовал выше, а не size_t как правило, хорошую практику, чтобы избежать целых чисел без знака для чисел, поскольку неявное преобразование до неподписанного типа может дать очень неожиданные результаты, что приводит к ошибкам.

duplicate функция по существу является вариантом Do It Yourself C++ старого * nix C strdup. Я включил реализацию, чтобы вы могли видеть, как она работает. Чтобы быть реалистичной реализацией DIY, вы можете попробовать это для себя - также должны быть реализованы strlen и strcpy, возможно, с более современными именами без искажений.

Почему в стандартную библиотеку C не входит strdup? Я не знаю, это большая тайна. Но для программирования C++ хорошо, что он отсутствует, поскольку функция C использовала malloc и free, а в C++ очень сильное соглашение - использовать new[] и delete[], чтобы можно было случайно использовать неправильная функция освобождения...

Управление временем жизни динамического объекта по уникальной собственности.

Для каждого выполненного new[] должно быть выполнено только одно выполненное delete[]. Этого трудно достичь. Когда нет соответствующего delete[] программа теряет память, а при наличии двух или более delete[] для одного и того же объекта программа имеет Undefined Behavior.

Общее решение C++ - использовать специальный класс, конструктор которого либо принимает новый new[] указатель new[] ed, либо выполняет сам new[], а деструктор которого выполняет delete[].

Этот объект - который может быть Node - тогда говорят, владеть динамически выделяемый объект, время жизни он управляет.

Стандартная библиотека предоставляет несколько общих и многоразовых классов объектов владельца, называемых интеллектуальными указателями, включая std::unique_ptr и std::shared_ptr, но здесь нужно сделать все это, поэтому...

Например, каждый конструктор узла может просто вызвать duplicate, чтобы дать узлу собственный отдельный дубликат указанной строки. new[] в конструкторе (выполненный по вызову для duplicate) затем должен быть сопоставлен соответствующим delete[] в деструкторе, чтобы не утечка памяти. Первая попытка определения класса Node может выглядеть следующим образом:

// 1st attempt:
struct Node
{
    Node*           p_next;
    char const*     name;

    ~Node()
    { delete[] name; }

    Node( char const s[] )
        : p_next( nullptr )
        , name( duplicate( s ) )
    {}
};

Но это потенциальная проблема, а именно, что произойдет, если name узла переназначено?

Затем, если код, выполняющий присваивание, сначала выполняет delete[], или копирует существующий указатель, указатель на буфер будет потерян, и невозможно будет освободить этот буфер (нет информации о том, где он), и память будет просочиться.

Эта проблема может быть решена путем либо делая name константа (добавление const), или делают его private, с функцией газопоглощающей, также известной как аксессор.

Но есть и противоположная проблема, а именно: что произойдет, если узел скопирован?

Затем (с приведенным выше определением) у вас будет два или более узла с одним и тем же именем буфера, каждый из которых возьмет на себя ответственность за вызов delete[], что приведет к двум или более вызовам delete[] для одного и того же объекта с результатом Undefined Behavior,

Самый простой способ взять на себя ответственность за копирование - это запретить его. Копирование можно запретить, объявив private конструктор копирования и операторы присваивания копий. Или можно наследовать от класса, который делает это, рискуя каким-то компилятором, производя глупое предупреждение о его неспособности создать оператор присваивания копии для класса. Также обратите внимание, что объявление члена const запрещает копирование, а не копирование. Поэтому, чтобы взять на себя ответственность, необходимо объявить хотя бы один конструктор копирования, который известен как правило 3: если вам нужен деструктор, конструктор копирования или оператор присваивания, то (например, чтобы взять на себя ответственность за копирование), вам, вероятно, понадобится все три.

// 2nd. attempt, conforming to the rule of 3:
class Node
{
private:
    char const*     name_;

    Node( Node const& );                    // Copy constructor, no such.
    Node& operator=( Node const& );         // Copy assignment, no such.

public:
    Node*           p_next_;

    auto name() const
        -> char const*
    { return name_; }

    ~Node()                                 // Destructor.
    { delete[] name_; }

    Node( char const s[] )
        : name_( duplicate( s ) )
        , p_next_( nullptr )
    {}
};

Теперь нет технической проблемы, но есть проблема проектирования и проблемы использования, а именно, что один логический атрибут, имя, получает доступ через функцию-член, а другой логический атрибут, следующий указатель, обращается непосредственно в качестве элемента данных. Программист, использующий редактор без автозаполнения, может немного раздражаться, забыв ввести круглые скобки вызова функции для доступа к имени или ввести скобки, где это не должно быть для доступа следующего указателя.

Одно из решений состоит в том, чтобы сделать оба члена private и предоставить как функцию setter, так и функцию getter для следующего указателя. Но в то время как это обеспечивает единую нотацию, в целом много многословия. Защита имени члена при некоторой многословии служит для предотвращения неправильного использования, возможно неправильного использования с UB, что важно. Аналогичная защита следующего указателя будет только для равномерного обозначения. И Node - такая вещь низкого уровня, что на самом деле это не имеет смысла.

Поэтому лучше представить инкапсуляцию управления жизненным циклом динамически выделенной строки в выделенном классе владельца (класс объектов владельца), например, например...

class String_value
{
private:
    char const*     chars_;

    String_value( String_value const& );            // Copy constr., no such.
    String_value& operator=( String_value const& ); // Copy assign, no such.

public:
    auto pointer() const
        -> char const*
    { return chars_; }

    ~String_value()
    { delete[] chars_; }

    String_value( char const* const s )
        : chars_( ::duplicate( s ) )
    {}
};

// 3rd attempt: using a dedicated owner class.
struct Node
{
    String_value    name;
    Node*           p_next;

    Node( char const s[] )
        : name_( s )
        , p_next_( nullptr )
    {}
};

Это определение обеспечивает как некоторую меру безопасности для управления жизненным циклом, так и удобство, например, написание только new Node( "Anna" ). :-)

Связанный список как класс коллекции.

Хотя каждый узел управляет временем жизни динамически выделенной строки, кто или что управляет временем жизни узла?

Естественным владельцем является выделенный объект списка. Можно легко связать управление списками, напрямую обрабатывая узлы, но выделенный объект списка может добавить некоторую меру безопасности, централизуя управление жизненным циклом и другие аспекты. В вопросительном коде есть класс связанного списка, но ни определение класса, ни его конструктор не отображаются, поэтому неясно, каковы его обязанности.

Одно отличие по сравнению с узлами заключается в том, что объект списка будет владеть (управлять временем жизни) целым набором объектов. Это обычно известно как класс коллекции. Например, стандартными библиотечными "контейнерами" являются классы коллекции.

На выбор есть множество вариантов дизайна. В частности, должен ли класс списка открывать узлы как таковые (хорошо для обучения и наименьшего количества кода лесов) или должен быть непрозрачным контейнером значений, где он просто аспект реализации, который эти значения - здесь строки - хранятся в список узлов. Стандартная библиотека std::list действует как непрозрачный контейнер, а класс ниже бесстыдно раскрывает узлы как таковые:

class Linked_list
{
private:
    Node    head_;
    Node*   p_last_;

    Linked_list( Linked_list const& );              // Copy constr., no such.
    Linked_list& operator=( Linked_list const& );   // Copy assign., no such.

public:
    auto first_node() const
        -> Node const*
    { return head_.p_next; }

    void append( Node* const p_node )
    {
        p_last_->p_next = p_node;
        p_last_ = p_node;
    }

    ~Linked_list()
    {
        while( Node* const p_doomed = head_.p_next )
        {
            head_.p_next = p_doomed->p_next;
            delete p_doomed;
        }
    }

    Linked_list()
        : head_( "" )
        , p_last_( &head_ )
    {}
};

Заголовок ndoe находится здесь непосредственно в объекте списка. Он не является частью логической последовательности значений, но является частью связанной последовательности узлов. Как часть связанного списка, он упрощает вставку и удаление, например, здесь можно поддерживать указатель на последний узел, даже если последовательность значений пуста.

Тонкое наблюдение за оптимизацией: узлу заголовка, как правило, не нужно иметь значение, поэтому, упорядочивая вещи "просто так", можно использовать только часть ссылки узла как узел заголовка. Это более или менее эквивалентно схеме, содержащей указатель на первый узел вместо узла заголовка и имеющий указатель на указатель на последний узел вместо указателя на последний узел. Ладно, глаза, возможно, застекляются, так что теперь, как использовать все это.

Как использовать все это.

Основная программа перекодирована для использования вышеуказанных функций:

#include <iostream>
using namespace std;

int main()
{
    Linked_list list;
    list.append( new Node( "Anna" ) );
    list.append( new Node( "Jane") );
    list.append( new Node( "Peter") );
    list.append( new Node( "Brooth") );
    list.append( new Node( "Tim") );

    for( Node const* p = list.first_node();  p != nullptr;  p = p->p_next )
    {
        cout << p->name.pointer() << endl;
    }
}
  • 1
    @ downvoter: почему ты понизил голос?
4

Причина, по которой все они говорят "Тим", состоит в том, что вы устанавливаете name в Node для указания буфера char i[200] в main каждом вызове имени setname.

Из-за этого каждый раз, когда вы strcpy вы заменяете строку во всех узлах. Поскольку "Тим" - это фамилия, которую вы установили, это то, на что все они указывают!

Лучше и больше C++, решение будет состоять в том, чтобы вместо этого сохранить name как std::string:

class Node
{
private:
  Node* pNext;
  std::string name;
public:
  Node();
  ~Node();
  Node* getNext();
  const std::string &getname() const
  {
    return name
  }

  void setNext(Node* pNode);
  void setname(consts std::string &pname)
  {
    name = pname;
  }
}
  • 2
    «Лучшим решением C ++ было бы хранить имя как std :: string» ... и вообще не использовать связанные списки.
  • 0
    Так как OP явно выполняет упражнение, чтобы узнать о динамическом распределении и освобождении, а также обрабатывать указатели, не рекомендуется предлагать здесь std::string . Для общего обучения и даже для профессионального кода (в целом) это хороший совет. Однако, для того, чтобы узнать о том, как выполнять свою работу, его использовать нехорошо.
1

Я думаю, что проблема заключается в том, что функция-член-функция-член, реализация которой вы не показывали, просто присваивает имя имени элемента pname. Поскольку pname всегда совпадает с адресом верхнего элемента массива i, то все имена элементов данных каждого узла содержат один и тот же адрес, а последнее значение, которое хранится по этому адресу, является строковым литералом "Tim".

0

В настоящее время все ваши узлы получают доступ к одной и той же памяти (i) для своего имени. Все они меняют i, и все они отображают i. Поэтому все, что хранится в i, будет использоваться для всех ваших узлов. Чтобы создать уникальные значения для каждого из ваших узлов, вам необходимо иметь отдельный набор памяти для каждого параметра имени узла. Если вы используете std :: string, класс string будет обрабатывать это распределение памяти для вас. Если вы просто используете своих собственных членов char *, вам решать, где и как вы обрабатываете выделение памяти, либо динамически, создавая новую память (и, надеюсь, удаляя ее позже), либо статически выделяя отдельный массив для каждого узла, Создайте. Учитывая, что узлы динамически распределяются сами по себе, и вы, вероятно, хотите, чтобы ваш список мог расти во время выполнения, вы, скорее всего, хотите динамически распределять свою память для каждого имени. Вы должны серьезно подумать о том, чтобы разрешить std :: string дескриптор для вас, или использовать интеллектуальный указатель из std, если нужно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню