Ошибка сегментации при возврате связанного списка узлов

В функции find(T item) я проверяю, являются ли введенные данные строкой, затем я обрабатываю их отдельно, а для других типов данных я обрабатываю их в противном случае, потому что я использую строковую функцию compare() для проверки соответствовать. Программа работает нормально, когда в связанном списке есть совпадающая строка, и возвращает узел и печатает данные узла, но когда совпадающая строка не найдена, тогда вместо возврата пустого узла tmp программы аварийно завершают работу и говорят: «Ошибка сегментации (дамп ядра )». Есть ли способ исправить это?

#include <iostream>
#include <typeinfo>
using namespace std;

template <typename T>
class Node {
public:
    T data;
    Node *next;
    Node *prev;

    Node(){};
};

template <typename T>
class LinkedList {
private:
    Node<T> *head;
    Node<T> *tail;

public:
    LinkedList(){
        head = NULL;
        tail = NULL;
    }

    void insertHead(T item){
        Node<T> *tmp = new Node<T>();
        if (head == NULL){
            head = tmp;
            head->prev = NULL;
            head->data = item;
            head->next = NULL;
            tail = tmp;
        } else {
            tmp->next = head;
            tmp->data = item;
            tmp->prev = NULL;
            head->prev = tmp;
            head = tmp;
        }
    }

    void insertLast(T item){
        Node<T> *tmp = new Node<T>();
        tmp->data = item;
        if (head == NULL){
            head = tmp;
            head->prev = NULL;
            head->next = NULL;
            tail = tmp;
        } else {
            tmp->prev = tail;
            tail->next = tmp;
            tmp->next = NULL;
            tail = tmp;
        }
    }

    Node<T>* find(T item){
        Node<T> *tmp = head;
        if (typeid(item) == typeid(string)){
            string s, d;
            s = item;
            d = tmp->data;
            while(tmp != NULL){
                if (s.compare(d) == 0){
                    return tmp;
                }
                tmp = tmp->next;
                d = tmp->data;
            }
            Node<string> *tmp = new Node<string>();
            tmp->data = "";
            return tmp;
        } else {
            while(tmp != NULL){
                if (tmp->data == item){
                    return tmp;
                }
                tmp = tmp->next;
            }

            tmp = new Node<T>();
            tmp->data = -1;
            return tmp;
        }
    }

    void print(){
        Node<T> *tmp;
        tmp = head;
        while (tmp != NULL){
            cout << tmp->data << "->";
            tmp = tmp->next;
        }
        cout << endl;
    }
};

int main(){

    LinkedList<string> l;
    l.insertHead("Abc");
    l.insertHead("def");
    l.insertHead("ghi jk");

    Node<string>* tmp = new Node<string>();
    tmp = l.find("ghi");
    cout << tmp << endl;

    l.print();
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
81
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Проблема в этом цикле:

while(tmp != NULL){     // tmp is not null, good
    if (s.compare(d) == 0){
         return tmp;
    }
    tmp = tmp->next;    // after this tmp MIGHT be null!
    d = tmp->data;      // possibly dereferencing null-pointer, bad!
}

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

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

s.compare(d) == 0

точно такой же, как s == d, который идентичен item == tmp->data.

Единственная разница будет заключаться в возвращаемом значении, где я предлагаю вернуть nullptr, если элемент не был найден. Что, если список содержит значение "" или -1? Вы не сможете определить значение из списка, кроме «значения по умолчанию».

Проблема:

В строках:

s = item;
d = tmp->data;
while(tmp != NULL){
    if (s.compare(d) == 0){
        return tmp;
    }
    tmp = tmp->next;
    d = tmp->data;
}

Когда tmp->next становится NULL, вы принимаете NULL->data вместо d = tmp->data;, что приводит к ошибке сегментации.

Решение:

Замените код на этот:

s = item;
while(tmp != NULL){
    d = tmp->data;
    if (s.compare(d) == 0){
        return tmp;
    }
    tmp = tmp->next;
}

Дополнительная информация:

  1. Как сказал churill, сравнение строк C++ не похоже на Java, вы можете использовать s == d. Собственно из-за этого и требуется специализация строк.
  2. Я бы также подумал об использовании nullptr вместо NULL.
  3. Вы не должны использовать using namespace std; (Подробнее здесь).
  4. Печать tmp в cout << tmp << endl; печатает адрес tmp, а не значение, которое оно хранит.
  5. tmp->data = -1; return tmp; вызовет ошибку, если вы попытаетесь использовать шаблон с типом, который не может быть представлен в виде целого числа. Вместо этого вам, вероятно, следует использовать return NULL; или return nullptr;.
  6. Node<string>* tmp = new Node<string>(); tmp = l.find("ghi"); использовать дополнительное пространство, которое не освобождается. Вместо этого используйте Node<string>* tmp; tmp = l.find("ghi");.

Полный код:

#include <iostream>
using std::endl;
using std::string;
using std::cout;

template <typename T>
class Node {
public:
    T data;
    Node *next;
    Node *prev;

    Node(){};
};

template <typename T>
class LinkedList {
private:
    Node<T> *head;
    Node<T> *tail;

public:
    LinkedList(){
        head = nullptr;
        tail = nullptr;
    }

    void insertHead(T item){
        Node<T> *tmp = new Node<T>();
        if (head == nullptr){
            head = tmp;
            head->prev = nullptr;
            head->data = item;
            head->next = nullptr;
            tail = tmp;
        } else {
            tmp->next = head;
            tmp->data = item;
            tmp->prev = nullptr;
            head->prev = tmp;
            head = tmp;
        }
    }

    void insertLast(T item){
        Node<T> *tmp = new Node<T>();
        tmp->data = item;
        if (head == nullptr){
            head = tmp;
            head->prev = nullptr;
            head->next = nullptr;
            tail = tmp;
        } else {
            tmp->prev = tail;
            tail->next = tmp;
            tmp->next = nullptr;
            tail = tmp;
        }
    }

    Node<T>* find(T item){
        Node<T> *tmp = head;
        while(tmp != nullptr){
            if (tmp->data == item){
                return tmp;
            }
            tmp = tmp->next;
        }
        return nullptr;
    }

    void print(){
        Node<T> *tmp;
        tmp = head;
        while (tmp != nullptr){
            cout << tmp->data << "->";
            tmp = tmp->next;
        }
        cout << endl;
    }
};

int main(){

    LinkedList<string> l;
    l.insertHead("Abc");
    l.insertHead("def");
    l.insertHead("ghi jk");

    Node<string>* tmp;
    tmp = l.find("ghi");

    cout << ((tmp == nullptr) ? "nullptr":tmp->data) << endl;

    l.print();
}
Ответ принят как подходящий

Для начала функция-член find может вызвать неопределенное поведение в самом начале.

Node<T>* find(T item){
    Node<T> *tmp = head;
    if (typeid(item) == typeid(string)){
        string s, d;
        s = item;
        d = tmp->data;
        ^^^^^^^^^^^^^
        //

потому что вообще head может быть равно nullptr.

В цикле while вы также не проверяете, равен ли указатель tmpnullptr.

tmp = tmp->next;
d = tmp->data;

Создание фиктивного узла

Node<string> *tmp = new Node<string>();
tmp->data = "";
return tmp;

это плохая идея и может привести к утечке памяти. Лучше возвращать нулевой указатель.

Это выделение узла в main

Node<string>* tmp = new Node<string>();
tmp = l.find("ghi");

производит утечку памяти.

Функция может быть объявлена ​​и определена как минимум следующим образом

Node<T> * find( const T &item )
{
    Node <T> *result = head;

    while ( result && result->data != item ) result = result->next;

    return result;
}

Более того, его можно перегрузить следующим образом

template <typename BinaryPredicate>
Node<T> * find( const T &item, BinaryPredicate binary_predicate )
{
    Node <T> *result = head;

    while ( result && not binary_predicate( result->data, item ) ) result = result->next;

    return result;
}

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