Исключение при удалении элемента из связанного списка

Я пытаюсь удалить слово (значение char*) из связанного списка, но получаю исключение, когда сравниваю свое слово со словом узла списка ссылок.

EXC_BAD_ACCESS (код=1, адрес=0xa00000002)

Я не уверен, почему это происходит, я был бы признателен за любое решение проблемы.

LinkedList* DeleteWordElement(LinkedList* head, char* value){

    LinkedList *previous=head, *current=head->next;
    if (head == NULL)
        return head;
    if (head->data == value) 
    {
        LinkedList *temp = head;
        head = head->next;
        free(temp);
        return head;
    }
    while (previous!=NULL)
    {
        if (previous->data == value) // Exception
            break;
        current = previous;
        previous = previous->next;
    }
    if (previous != NULL)
        current->next = previous->next;
    free(previous);
    return head;
}

*текущий=голова->следующий; это нужно делать после того, как вы убедитесь, что голова не NULL

dreamcrash 25.12.2020 11:05
Стоит ли изучать 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
1
126
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Прежде чем делать:

LinkedList *previous=head, *current=head->next;

вам нужно проверить, является ли headNULL. Тогда вы должны использовать strcmp вместо ==, чтобы сравнить C-строку value:

strcmp сравнивает фактическое содержимое C-строки, используя == между две C-строки спрашивают, указывают ли эти два указателя на один и тот же символ позиция. (источник)

Наконец, вам не нужно держать указатель previous, достаточно указателя current->next:

LinkedList* DeleteWordElement(LinkedList* head, char* value){
    if (head == NULL)
        return head;

    if (strcmp(value, head->data) == 0){
       LinkedList *next = head->next;
       free(head);
       return next;
    }
    LinkedList *current=head;
    while (current->next != NULL){
        if (strcmp(value, current->next->data) == 0){
           LinkedList *next = current->next;
           current->next = current->next->next;
           free(next);
           break;
        }
        current = current->next;
    }
    return head;
}

Код можно сократить, если использовать рекурсивную версию, хотя и за счет производительности:

LinkedList* DeleteWordElement(LinkedList* current, char* value){
    if (current != NULL){
       if (strcmp(value, current->data) == 0){
          LinkedList *result = current->next;
          free(current);
          return result;
       }
       current->next = DeleteWordElement(current->next, data);
    }
    return current;
}

Как уже было указано в другом ответе и в разделе комментариев, выражение

current=head->next

вызовет неопределенное поведение, когда head == NULL.

Другая проблема заключается в том, что выражение

head->data == value

не сравнивает фактическое содержимое строки, а вместо этого сравнивает сами указатели (т.е. адреса памяти). Это не то, что вам нужно, потому что указатели, вероятно, всегда будут разными, даже если содержимое строки идентично. Чтобы сравнить фактическое содержимое строки, вы должны вместо этого использовать функцию strcmp.

Другой ответ уже содержит решение проблемы. Однако я хотел бы предоставить альтернативное решение, которое короче и эффективнее для компьютера, но, возможно, его сложнее понять программисту, поскольку оно использует двойные указатели (т.е. указатели на указатели):

LinkedList* DeleteWordElement( LinkedList *head, char* value )
{
    LinkedList **pp = &head, *p;

    while ( (p=*pp) != NULL )
    {
        if ( strcmp( p->data, value ) == 0 )
        {
            //found node, so unlink and remove it
            *pp = p->next;
            free( p );
            break;
        }

        pp = &p->next;
    }

    return head;
}

Как видите, для этого решения требуется только один оператор if, в отличие от кода в вашем ответе, который требует 4 оператора if, и другого ответа, для которого требуется 3 оператора if. Эти дополнительные операторы if не нужны при использовании двойных указателей, потому что один и тот же код может обрабатывать все случаи. Поэтому вам не нужно вводить дополнительные пути кода для каждого отдельного случая. Как следствие, это решение также не требует дублирования кода.

Также стоит упомянуть, что сигнатура функции

LinkedList* DeleteWordElement(LinkedList* head, char* value)

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

Для этого вы можете изменить сигнатуру функции на следующую:

void DeleteWordElement( LinkedList** pp_head, char* value )

Поскольку адрес указателя является указателем на указатель (то есть двойным указателем), теперь вы должны использовать ** вместо только *.

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

bool DeleteWordElement( LinkedList** pp_head, char* value )

Теперь можно сделать функцию return true, когда значение было найдено, иначе return false указать, что значение не найдено. Обратите внимание, что вы должны #include <stdbool.h> иметь доступ к bool, true и false.

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

bool DeleteWordElement( LinkedList **pp_head, char* value )
{
    LinkedList **pp = pp_head, *p;

    while ( (p=*pp) != NULL )
    {
        if ( strcmp( p->data, value ) == 0 )
        {
            //found node, so unlink and remove it, and then return true
            *pp = p->next;
            free( p );
            return true;
        }

        pp = &p->next;
    }

    //return false because we did not find any matching node in the list
    return false;
}

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