Вы не предложили много информации для go, но я полагаю, что это новость для сообщества. Из того, что вы предоставляете, ясно, что, как минимум, у вас есть Doubly-Linked-List , где у вас есть строка в качестве члена данных. Также очевидно, что вы объявили оба указателя head
и tail
как глобальные переменные (не очень хорошая практика, поскольку вы должны передавать любую информацию, требуемую вашей функцией, в качестве параметра, но для этого учебное упражнение обеспечивает минимальное упрощение)
Из того, что вы описываете, вы хотите, чтобы ваша функция del
выполняла, вы хотите выполнить итерации по тестированию связанного списка, содержит ли член name
подстроку key
и если это так, вы хотите удалить этот узел из списка.
У вас есть две основные проблемы:
- вы используете неправильную строковую функцию для проверки подстроки в вашем
name
член. strcmp()
найдет только те узлы, где полный name
соответствует вашему key
. Вместо этого вы хотите strstr()
, где вы можете сопоставить key
в любом месте в пределах name
; - вы чрезмерно усложняете удаление узла, пытаясь использовать указатель только для текущего узла, чтобы итерируйте по списку вместо использования указателя и адреса для текущего узла. См. Линус о понимании указателей
Чтобы исправить проблему с кулаком, вам просто нужно изменить strcmp()
на strstr()
для соответствия key
в любом месте name
, например
...
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
...
Чтобы решить вторую проблему, вам нужно переработать функцию del
. Для этого вы должны понять, чем итерация при удалении нескольких узлов отличается от итерации, чтобы найти один узел для удаления. При циклическом поиске отдельного узла для удаления вы просто переходите к следующему узлу в списке на каждой итерации до тех пор, пока узел не будет найден, а затем удаляете этот узел.
Вы не можете сделать это при потенциальном удалении нескольких узлы из вашего списка. Почему? Когда вы удаляете узел с соответствующей подстрокой, следующий узел в списке занимает его место. Вы не можете просто перейти к следующему узлу после этого удаления, потому что узел, который является текущим узлом после удаления первого, также может содержать подстроку, которую вы хотите найти. Если вы просто слепо переходите к следующему узлу, а текущий после удаления также содержит подстроку, вы пропустите удаление этого узла.
Что это означает с точки зрения кода, вам нужно добавить предложение else
ниже вашего if
, например
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
...
else
...
Согласно вашему предложению if
, следующий узел заменяет текущий после удаления, поэтому вы НЕ продвигаетесь снова к следующему узлу в вашем списке. Таким образом, новый текущий узел будет проверен на соответствие подстроки на следующей итерации. Вы переходите к следующему узлу в соответствии с предложением else
, если key
не совпадает.
При выполнении итерации с указателем на текущий узел вместе с адресом текущего узла, вам не нужно обрабатывать особые случаи. Вы всегда будете устанавливать контент по текущему адресу на следующий узел в вашем списке. (head
не изменяется, потому что вы установили структуру по этому адресу на следующую при удалении) Единственная проверка, которая вам нужна, - это проверка, удаляете ли вы узел tail
. В этом случае вам нужно обновить узел tail
, чтобы он указывал на предыдущий узел, так как вы будете удалять то, на что в данный момент указывает tail
. В противном случае все, что вам нужно сделать, это обновить указатель ->prev
узла, который вы переместили в текущий адрес, чтобы он указывал на предыдущий узел в списке.
Учитывая это, ваша функция del
уменьшает to:
void del (char key[])
{
if (head == NULL) { /* check empty */
puts ("list-empty");
return;
}
node_t **ppnode = &head, /* address of current node */
*curr = head; /* pointer to current node */
while (curr) {
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
*ppnode = curr->next; /* fill address w/next node */
if (curr != tail) /* if not tail */
(*ppnode)->prev = curr->prev; /* set ->prev to prev node */
else /* otherwise */
tail = curr->prev; /* update tail pointer */
free (curr); /* free node */
curr = *ppnode; /* set new current node */
}
else { /* node to keep */
ppnode = &curr->next; /* set address to addres of next */
curr = curr->next; /* advance to next node */
}
}
}
Не зная больше о вашем коде, все, что мы можем сделать, это написать короткий пример, который добавляет ваши строки как узлы в список (используя фиксированный name
для упрощения). Короткий пример как:
int main (void) {
add ("Andy"); /* add nodes to list */
add ("Barry");
add ("Matilda");
prnfwd(); /* print forward and reverse */
prnrev();
putchar ('\n');
del ("y"); /* delete nodes containing substring "y" */
prnfwd(); /* print forward and reverse */
prnrev();
del_list(); /* free allocated memory */
}
Это добавляет узлы, перебирает список в обоих направлениях, вызывает del ("y");
, чтобы удалить все узлы, где строка содержит подстроку "y"
(обратите внимание, что она отличается от символа 'y'
), а затем перебирает снова список в обоих направлениях, выводящий то, что осталось до освобождения всей памяти в списке.
Пример использования / Вывод
Результат, полученный в примере строки, будет:
$ ./bin/lldglobaldelkey
Andy Barry Matilda
Matilda Barry Andy
Matilda
Matilda
Полная реализация примера:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXNM 64
typedef struct node_t {
char name[MAXNM];
struct node_t *prev, *next;
} node_t;
node_t *head, *tail;
/** add node at end of list, update tail to end */
node_t *add (const char *s)
{
node_t *node = malloc (sizeof *node); /* allocate node */
if (!node) /* validate allocation */
return NULL;
strcpy (node->name, s); /* initialize new node */
node->prev = node->next = NULL;
if (!head) /* if 1st node, node is head/tail */
head = tail = node;
else { /* otherwise */
node->prev = tail; /* set prev to tail */
tail->next = node; /* add at end, update tail pointer */
tail = node;
}
return node; /* return new node */
}
/* print list forward */
void prnfwd (void)
{
if (!head) { /* check empty */
puts ("list-empty");
return;
}
for (node_t *n = head; n; n = n->next) /* iterate over nodes - forward */
printf (" %s", n->name);
putchar ('\n');
}
/* print list reverse */
void prnrev (void)
{
if (!head) { /* check empty */
puts ("list-empty");
return;
}
for (node_t *n = tail; n; n = n->prev) /* iterate over nodes - reverse */
printf (" %s", n->name);
putchar ('\n');
}
/** delete all nodes in list */
void del_list (void)
{
node_t *n = head;
if (!head) { /* check empty */
puts ("list-empty");
return;
}
while (n) { /* iterate over nodes - forward */
node_t *victim = n; /* save ptr to node to delete */
n = n->next; /* advance to next */
free (victim); /* delete node */
}
head = tail = NULL; /* set pointers NULL */
}
void del (char key[])
{
if (head == NULL) { /* check empty */
puts ("list-empty");
return;
}
node_t **ppnode = &head, /* address of current node */
*curr = head; /* pointer to current node */
while (curr) {
if (strstr(curr->name, key) != NULL) { /* strstr, not strcmp, to find key */
*ppnode = curr->next; /* fill address w/next node */
if (curr != tail) /* if not tail */
(*ppnode)->prev = curr->prev; /* set ->prev to prev node */
else /* otherwise */
tail = curr->prev; /* update tail pointer */
free (curr); /* free node */
curr = *ppnode; /* set new current node */
}
else { /* node to keep */
ppnode = &curr->next; /* set address to addres of next */
curr = curr->next; /* advance to next node */
}
}
}
int main (void) {
add ("Andy"); /* add nodes to list */
add ("Barry");
add ("Matilda");
prnfwd(); /* print forward and reverse */
prnrev();
putchar ('\n');
del ("y"); /* delete nodes containing substring "y" */
prnfwd(); /* print forward and reverse */
prnrev();
del_list(); /* free allocated memory */
}
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически выделяет память , у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, поэтому (2) это может быть освобожден , когда он больше не нужен.
Обязательно используйте программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами или за пределами вашего выделенного блока, попытайтесь прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через нее.
$ valgrind ./bin/lldglobaldelkey
==10704== Memcheck, a memory error detector
==10704== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10704== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10704== Command: ./bin/lldglobaldelkey
==10704==
Andy Barry Matilda
Matilda Barry Andy
Matilda
Matilda
==10704==
==10704== HEAP SUMMARY:
==10704== in use at exit: 0 bytes in 0 blocks
==10704== total heap usage: 4 allocs, 4 frees, 1,264 bytes allocated
==10704==
==10704== All heap blocks were freed -- no leaks are possible
==10704==
==10704== For counts of detected and suppressed errors, rerun with: -v
==10704== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Надеюсь это близко к тому, что ваша реализация. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.