Реализация указателей / списков C++

Write a class ListNode which has the following properties:

  • int value;
  • ListNode *next;

Provide the following functions:

  • ListNode(int v, ListNode *l)
  • int getValue();
  • ListNode* getNext();
  • void insert(int i);
  • bool listcontains(int j);

Write a program which asks the user to enter some integers and stores them as ListNodes, and then asks for a number which it should seek in the list.

Вот мой код:

#include <iostream>
using namespace std;

class ListNode
{
    private:
        struct Node
        {
            int value;
            Node *next;
        } *lnFirst;

    public:
        ListNode();
        int Length();       
        void DisplayList();     
        void Insert( int num );
        bool Contains( int num );       
        int GetValue( int num );        
};

ListNode::ListNode()
{
    lnFirst = NULL;
}

int ListNode::Length()
{
    Node *lnTemp;
    int intCount = 0;
    for( lnTemp=lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next )
    {
        intCount++;
    }
    return intCount;
}

void ListNode::DisplayList()
{
    Node *lnTemp;
    for( lnTemp = lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next )
        cout<<endl<<lnTemp->value;
}

void ListNode::Insert(int num)
{
    Node *lnCurrent, *lnNew;

    if ( lnFirst == NULL )
    {
        lnFirst = new Node;
        lnFirst->value = num;
        lnFirst->next = NULL;
    }
    else
    {
        lnCurrent = lnFirst;
        while( lnCurrent->next != NULL )
            lnCurrent = lnCurrent->next;

        lnNew = new Node;
        lnNew->value = num;
        lnNew->next = NULL;
        lnCurrent->next = lnNew;
    }
}

bool ListNode::Contains(int num)
{
    bool boolDoesContain = false;
    Node *lnTemp,*lnCurrent;
    lnCurrent = lnFirst;
    lnTemp = lnCurrent;
    while( lnCurrent!=NULL )
    {
        if ( lnCurrent->value == num )
        {
            boolDoesContain = true;
            return boolDoesContain;
        }
        lnTemp = lnCurrent;
        lnCurrent = lnCurrent->next;
    }
    return boolDoesContain;
}

int ListNode::GetValue(int num)
{
    Node *lnTemp;
    int intCount = 1;
    for( lnTemp=lnFirst; lnTemp != NULL; lnTemp = lnTemp->next )
    {
        if (intCount == num)
        {
            return lnTemp->value;
        }
        intCount++;
    }    
}

int main()
{   
    cout << "Input integers below. Input the integer -1 to stop inputting.\n\n";
    
    ListNode lnList;
    int intNode = 1, intInput = 0;
    while (intInput != -1) {
        cout << "Please input integer number " << intNode << ": "; cin >> intInput;
        intNode++;
        if (intInput != -1) { lnList.Insert(intInput); }
    }   
    
    lnList.DisplayList();
    cout << "\n\n";
    
    int intListLength = lnList.Length();    
    cout << "Which value do you wish to recall? (Between 1 and " << intListLength << "): "; cin >> intNode;    
    if ( intNode >= 1 && intNode <= intListLength ) {
        cout << "Value at position " << intNode << " is " << lnList.GetValue(intNode) << ".";                
    } else {
        cout << "No such position in the list. Positions run from 1 to " << intListLength << ". You asked for " << intNode << ".";
    }
    
    cout << "\n\nCheck if the following value is in the list: "; cin >> intNode;
    bool IsThere = lnList.Contains(intNode);
    if (IsThere) {
        cout << intNode << " is in the list.";
    } else {
        cout << intNode << " is not in the list.";
    }
    
    cout << "\n\n";
    system("pause");
    return 0;  
}

Где мы можем это улучшить?

что сказал раскрутка. ваш getValue, например, имеет параметр, который принимает индекс. так что ваш ListNode на самом деле является списком, а не самим узлом.

Johannes Schaub - litb 27.11.2008 16:58

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

Collin 21.03.2012 16:54

Вопрос был задан в ноябре 2008 года ... Я не думаю, что SO к тому времени даже вышла из частной / публичной беты. Не говоря уже о Трилогии или SE2.0! :-)

Rob Burke 23.03.2012 19:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
647
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Я думаю, что вы чрезмерно усложняете моделирование узлов списка. Узел списка класса ListNode ЭТО, что видно из его названия. Тогда ему не потребуется вложенная структура для хранения информации о списках, что очень сбивает с толку.

Собственно, неправильное только название. Класс - это список, или то, как он выглядит из реализации. Так что вместо ListNode лучше будет звучать имя LinkedList. Вложенная структура представляет собой узел. Также было бы непросто иметь метод Insert () для каждого узла в списке.

Dan C. 27.11.2008 17:00

@Dan: вообще-то я так подумал, когда читал вопрос. ListNode не должен иметь метода вставки, он должен иметь insertAfter или что-то еще. Для односвязного списка первый узел может служить «самим списком», поэтому вам совсем не обязательно нужен класс LinkedList.

Steve Jessop 27.11.2008 19:37

Я думаю, вы неправильно поняли запрошенный дизайн. Класс ListNode должен быть узлом, а не списком, содержащим узлы.

Я бы посоветовал вам не помещать несколько команд в одну строку, например:

cout << "Please input integer number " << intNode << ": "; cin >> intInput;

Это просто сбивает с толку.

Часть задания гласила:

Provide the following functions:

  • ListNode(int v, ListNode *l)
  • int getValue();
  • ListNode* getNext();
  • void insert(int i);
  • bool listcontains(int j);

Вы не предоставляли ни одной из этих функций.

Поскольку у некоторых других есть указатель, вы реализовали List вместо ListNode, поэтому подписи ваших функций отличаются.

Но вы также не должны бездумно нарушать правила кодирования задания. У вас есть фон C#? В соглашениях о кодировании C++ обычно указываются имена методов в нижнем регистре.

Ответ принят как подходящий

Что говорят раскрученные и ckarmann. Вот подсказка, я реализую listcontains для вас, чтобы вы поняли, как может означать назначение:

class ListNode {
private:
    int value;
    ListNode * next;

public:
    bool listcontains(int v) { 
        // does this node contain the value?
        if (value == v) return true; 

        // was this the last node?
        if (next == 0) return false;

        // return whether nodes after us contain the value 
        return next->listcontains(v);
    }
};

Итак, у вас есть только заголовок списка, который по очереди ссылается на следующий узел. Хвост будет иметь next == 0;

@litb, думаю, теперь я тебя понимаю. Но как создать экземпляр узла с помощью этого класса? Я не могу осмыслить это.

Rob Burke 27.11.2008 17:55

Роб, это просто ListNode * head = new ListNode (42); это создаст головной узел со значением 42 и значением по умолчанию next с 0. тогда вы можете сделать head-> insert (11); чтобы вставить значение 11.

Johannes Schaub - litb 27.11.2008 18:01

он вызовет next-> insert (11); до тех пор, пока next не станет 0, вы назначите новый ListNode (11) этому следующему указателю. это все, чтобы что-то вставить :)

Johannes Schaub - litb 27.11.2008 18:03

обратите внимание, когда вы хотите удалить список, вы делаете «удалить голову;» . добавить «удалить далее»; в ваш деструктор. удаление головы приведет к удалению следующего указателя. это, в свою очередь, вызовет, который указывает на вызываемые объекты dtor (деструктор) и так далее. Не забывайте об этом, иначе вы потеряете память.

Johannes Schaub - litb 27.11.2008 18:08

(поскольку в назначении указано, что ваш конструктор должен выглядеть как ListNode (int v, ListNode * j), вам придется явно передать «0» (как в new ListNode (42, 0)). Тогда не может быть по умолчанию 0)

Johannes Schaub - litb 27.11.2008 18:11

Я знаю, что это вряд ли будет проблемой для этого игрушечного примера, но я не уверен, что я бы посоветовал студенту написать код обработки связанного списка, когда рекурсивно O (n) глубоко в C++. Может быть, компилятор выполняет оптимизацию хвостового вызова, а может и нет ...

Steve Jessop 27.11.2008 19:42

Безопасное разрушение связанного списка, состоящего только из узловых объектов, без объекта-контейнера LinkedList и без взрыва стека для больших списков, потенциально само по себе является вопросом назначения.

Steve Jessop 27.11.2008 19:44

В более общем плане стиля: объявляйте указатели ближе к тому месту, где вы их определяете, и сохраняйте их область как можно меньше. Хотя технически с вашим кодом ничего не может пойти не так, как надо, всегда это позволяет избежать ошибок в гораздо более крупных / старых кодовых базах, по моему опыту.

например вместо

Node *lnTemp;
int intCount = 0;
for( lnTemp=lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next )
{
}

написать

int intCount = 0;
for(Node* lnTemp=lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next )
{
}

или, аналогично, вместо

Node *lnTemp,*lnCurrent;
lnCurrent = lnFirst;
lnTemp = lnCurrent;

написать

Node* lnCurrent = lnFirst;
Node* lnTemp = lnCurrent;

Еще одно улучшение, в коде списка, вы не должны перемещаться по всему списку, чтобы получить длину, вы можете вести счетчик количества элементов, обновляющих его при вставках / удалениях, и возвращать его.

Более подробный отзыв внизу этого поста, но для начала просто несколько встроенных комментариев и изменений в коде:

struct Node // Why doesn't this have a constructor initializing the members?
{
        int value;
        Node *next;
} *lnFirst; 


ListNode::ListNode() : lnFirst(NULL) {} // Use initializer lists instead of initializing members in the ctor body. It's cleaner, more efficient and may avoid some nasty bugs (because otherwise the member gets default-initialized *first*, and then assigned to in the body)

int ListNode::Length()
{
    int intCount = 0;
    for( Node* lnTemp=lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next ) // Create the loop iteration variable lnTemp here, in the loop, not at the start of the function
    {
        intCount++;
    }
    return intCount;
}

void ListNode::DisplayList()
{
    for(Node* lnTemp = lnFirst ; lnTemp != NULL ; lnTemp = lnTemp->next ) // And again, initialize the loop variable in the loop
        cout<<endl<<lnTemp->value; // Not a huge deal, but endl flushes the stream as well as inserting a newline. That can be needlessly slow. So you might want to just use "\n" in cases where you don't need the flushing behavior.
}

void ListNode::Insert(int num)
{
//    Node *lnCurrent, *lnNew; // Very subjective, but I prefer not declaring multiple variables on the same line, because the syntax if they're pointers can be surprising (You got it right, but a lot of people would write Node* lnCurrent, lnView, which would make lnView not a pointer. I find it clearer to just give ecah variable a separate line:
    if ( lnFirst == NULL )
    {
//        lnFirst = new Node;
//        lnFirst->value = num;
//        lnFirst->next = NULL;
        lnFirst = new Node(num); // Make a constructor which initializes next to NULL, and sets value = num. Just like you would in other languages. ;)
    }
    else
    {
        Node* lnCurrent = lnFirst; // Don't declare variables until you need them. Both to improve readability, and because C++ distinguishes between initialization and assignment, so in some cases, default-initialization followed by assigment may not be the same as just initializing with the desired value.
        while( lnCurrent->next != NULL )
                lnCurrent = lnCurrent->next;

        Node* lnNew = new Node(num); // Again, let a constructor do the work.
        lnCurrent->next = lnNew;
    }
}

bool ListNode::Contains(int num)
{
    bool boolDoesContain = false;
//    Node *lnTemp,*lnCurrent; // Again, don't initialize variables at the start of the function if they're not needed
    Node* lnCurrent = lnFirst;
//    lnTemp = lnCurrent;
    while( lnCurrent!=NULL )
    {
        if ( lnCurrent->value == num )
        {
//                boolDoesContain = true;
//                return boolDoesContain;
                  return true; // Just return directly, and remove the boolDoesContain variable. Alternatively, set boolDoesContain to true, and then break out of the loop without returning, so you have a single exit point from the function. Both approaches have their merits, but setting a variable you don't need, and then returning is silly. ;)
        }
//        Node* lnTemp = lnCurrent; // you don't actually use lnTemp for anything, it seems
        lnCurrent = lnCurrent->next;
    }
//    return boolDoesContain;
      return false; // just return false. If you get this far, it must be because you haven't found a match, so boolDoesContain can only be false anyway.
}

int ListNode::GetValue(int num)
{
//    Node *lnTemp;
    int intCount = 1; // Wouldn't most people expect this indexing to be zero-based?
    for( Node* lnTemp=lnFirst; lnTemp != NULL; lnTemp = lnTemp->next )
    {
        if (intCount == num)
        {
            return lnTemp->value;
        }
        intCount++;
    }    
}

А теперь пара общих замечаний. (Я проигнорирую, неправильно ли вы поняли задание, и сосредоточусь на опубликованном вами коде) Во-первых, венгерские обозначения: не надо. Сначала вызовите указатели узлов, temp и т. д. Без префикса ln. Вызовите свою переменную типа bool doesContain без ненужного префикса bool. Во-вторых, как я пытался сделать в отредактированном коде, создавайте переменные только тогда, когда они вам нужны. Раньше C требовал, чтобы переменные объявлялись в верхней части блока, но C++ никогда этого не делал. В-третьих, вам не нужно возвращать 0 в конце основной функции. Main - это особый случай, когда, если он достигает конца функции, он автоматически возвращает 0.

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

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

Следующая большая проблема, что, если я скопирую список?

int main(){
ListNode list;
list.Insert(1);
list.Insert(2);
list.Insert(3);
}
ListNode list2 = list;

Ваш код взрывается. Оба списка теперь указывают на узлы такой же вместо того, чтобы делать копии узлов. Добавление узла в один список также приведет к его отображению в другом. И прежде чем заявить, что «это особенность, а не ошибка»;), подумайте, что происходит, когда один из списков удаляется Теперь.

Предположим, что list2 удаляется первым. С помощью только что определенного деструктора он удаляет три узла и возвращается. Теперь перечислите точки на удаленные узлы. Доступ к ним является неопределенным поведением и, скорее всего, приведет к сбою. Допустим, мы не получаем к ним доступ, вместо этого мы просто быстро удаляем и этот список.

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

Итак, чтобы исправить это, ваш класс ListNode должен реализовать две дополнительные функции, конструктор копирования и оператор присваивания:

ListNode(const ListNode& other);
ListNode& operator==(const ListNode& other);

Эти двое должны гарантировать, что когда все данные являются скопировано от «другого». Для каждого узла в 'other' вы должны выделить новый узел в текущем списке, а не просто указывать оба списка на один и тот же узел. (Это означает, что классу узла, скорее всего, понадобится как минимум конструктор копирования).

Это основной способ управления памятью, и это важно понимать, потому что в противном случае вы облажаетесь с воля. ;)

Другие вопросы по теме