Реализация указателей / списков C ++ - PullRequest
0 голосов
/ 27 ноября 2008

Напишите класс ListNode, который имеет следующие свойства:

  • int value;
  • ListNode * следующий;

Предоставляет следующие функции:

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

Напишите программу, которая просит пользователя ввести некоторые целые числа и сохраняет их как ListNodes, а затем запрашивает номер, который должен искать в списке.

Вот мой код:

#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;  
}

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

Ответы [ 7 ]

3 голосов
/ 27 ноября 2008

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

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

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;
3 голосов
/ 27 ноября 2008

То, что раскручивают и 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;

3 голосов
/ 27 ноября 2008

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

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

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

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

2 голосов
/ 27 ноября 2008

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

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++;
    }    
}

Теперь пара общих комментариев. (Я собираюсь игнорировать, неправильно ли вы поняли назначение, и сосредоточиться на опубликованном вами коде) Во-первых, венгерская запись: не надо. Сначала вызывайте указатели вашего узла, временные и любые другие, без префикса 'ln'. Вызовите вашу переменную bool doContain без ненужного префикса 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);

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

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

2 голосов
/ 27 ноября 2008

Я думаю, что вы чрезмерно проектируете моделирование узла списка. Класс ListNode IS A узел списка, что видно из его названия. В этом случае не требуется вложенная структура для хранения информации о списке, что очень запутанно.

1 голос
/ 27 ноября 2008

Часть задания читается:

Предоставляет следующие функции:

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

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

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

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

0 голосов
/ 27 ноября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...