Операции с указателями в связанных списках - PullRequest
0 голосов
/ 24 января 2019

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

int firstvalue= 5
int * p1;
p1 = &firstvalue;  // p1 = address of firstvalue

Что произойдет, если мы определим операцию, подобную следующей, в связанном списке? Означает ли *current=*list, что значение, на которое указывает ток, равно значению, указанному в списке? И что это значит, если мы определим ecur=current?

int function(struct list_t **list){
    struct list_t *ecur=NULL;
    struct list_t *current=*list;
    ecur=current;
}

Обновление: Что это делает *list=remove(*list, param1, param2)? И почему это так?

remove - это функция, которая возвращает измененный список list.

Обновление 2: Почему нам нужно определить указатель на указатель, чтобы изменить список? *list является указателем на указатель?

Ответы [ 5 ]

0 голосов
/ 25 января 2019

Почему нам нужно определить указатель на указатель, чтобы изменить список

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

// C pointer exercise: sort arguments
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>

struct list
{
    char *arg;
    struct list *next;
};

int main(int argc, char *argv[])
{
    // pointer to base, running pointer and pointer to pointer
    struct list *base = NULL, *p, **pp;

    for (int i = 1; i < argc; ++i)
    {
        struct list *new_entry = malloc(sizeof(struct list));
        if (new_entry)
        {
            new_entry->arg = argv[i];

            // find where to insert new entry
            for (pp = &base; *pp; pp = &(*pp)->next)
                if (strcasecmp(new_entry->arg, (*pp)->arg) < 0)
                    break;

            // insertion in a simply linked list
            new_entry->next = *pp;
            *pp = new_entry;
        }
    }

    // display and cleanup
    for (p = base; p;)
    {
        struct list * tmp = p->next;
        puts(p->arg);
        free(p);
        p = tmp;
    }

    return 0;
}
0 голосов
/ 24 января 2019

Почему нам нужно определить указатель на указатель, чтобы изменить список?* Перечисляет ли указатель на указатель?

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

void swap( int a, int b )
{
  int tmp = a;
  a = b;
  b = tmp;
}

void foo( void )
{
  int x = 1;
  int y = 2;

  swap( x, y );
}

a - это другой объект в памяти, чем x, а b - это другой объект в памяти, чем y, поэтому поменяйте местами a и bне влияет на x и y.Чтобы поменять местами значения x и y, необходимо передать им указатели :

void swap( int *a, int *b )
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

void foo( void )
{
  int x = 1;
  int y = 2;

  swap( &x, &y );
}

выражение *a - этотакой же как x, поэтому запись в *a такая же, как запись в x.То же самое для *b и y.

Таким образом, для записи функции в параметр необходимо передать указатель на этот параметр:

void foo ( T *arg )
{
  *arg = new_value(); // writes a new value to the thing arg points to
}

void bar( void )
{
  T var;
  foo( &var );        // write a new value to var
}

Это верно для любой не массив типа T.Давайте заменим T типом указателя P *:

void foo( P **arg )
{
  *arg = new_value(); // write a new *pointer* value to the thing arg points to
}

void bar( void )
{
  P *var;
  foo( &var );        // write a new pointer value to var
}

Семантика точно такая же - все, что изменилось - это тип.

Если функция может изменитьсяобъект list * (скажем, указывающий его на новый заголовок списка), затем вы должны передать указатель на этот list * объект:

void add_node( struct list_t **list, struct list_t *node )
{
  if ( !*list || (node->value < (*list)->value) ) // make node new head of list
    *list = node;
  else
    // add node somewhere else in the list
}

int main( void )
{
  struct list_t *list = NULL;
  ...
  struct list_t *node = newNode( value );
  add_node( &list, node );
  ...
}
0 голосов
/ 24 января 2019

Переменная list является указателем на указатель на структуру list_t.Если мы (просто в качестве примера) предположим, что структура размещена по адресу 2000 и что безымянный указатель находится по адресу 1000, он будет выглядеть следующим образом:

enter image description here

Тогда у вас есть инициализация, которая добавляет две новые переменные.Оба в качестве указателя на структуру list_t.

struct list_t *ecur=NULL;
struct list_t *current=*list;

Таким образом, изображение теперь становится:

enter image description here

Обратите внимание, что current получилто же значение, что и «некоторый указатель» в середине, потому что это *list, который был назначен на current.

Тогда у вас есть присвоение:

ecur=current;

, что означаетчто ecur получает то же значение, что и current и дает картинку:

enter image description here

Обновление: что делает *list=remove(*list, param1, param2)?

Изменяет значение «некоторого указателя» в середине изображения.Это, например, необходимо, если функция remove удаляет первый элемент в связанном списке.

0 голосов
/ 24 января 2019

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

int function(struct list_t **list)
{
    struct list_t *current = *list;
    struct list_t *ecur    = current;
}

Если мы вызываем эту функцию с элементом foo, мы, по сути, получаем это:

struct list_t foo      = { .data = "foo" };
struct list_t *bar     = &foo;
struct list_t **list   = &bar;
struct list_t *current = *list;
struct list_t *ecur    = current;

У нас есть пять объявлений и пять заданий. Для лучшей читаемости я все напишу без объявлений:

foo     = { .data = "foo" };
bar     = &foo;
list    = &bar;
current = *list;
ecur    = current;

Теперь давайте пройдемся по нему:

  1. foo является структурой. Содержит вышеуказанное поле данных.
  2. bar - указатель на структуру. Содержит адрес foo
  3. list - указатель на указатель на структуру. Содержит адрес bar
  4. current - указатель на структуру. Содержит содержимое list, которое является адресом foo
  5. ecur - указатель на структуру. Он идентичен current и содержит адрес bar

В конце мы можем упростить весь пример до этого:

struct list_t foo   = { .data = "foo" };
struct list_t *ecur = &foo;

Что все это значит?

  • list: поскольку list является указателем на указатель, вы можете изменить bar, чтобы он указывал на что-то совершенно другое, отменив ссылку на него (*list = ...)
  • current / ecur: это то, на что изначально указывал bar. Отменив ссылки, вы можете изменить само поле данных ((*ecur).data = "banana" или лучше ecur->data)

Я надеюсь, что смогу прояснить ситуацию и не усугубить ситуацию;)

0 голосов
/ 24 января 2019
TYPE *p = ptype /*variable of type: TYPE * */;

не является назначением . Это инициализация , которая для auto -матики (= on-the-stack) p может быть переписана как:

TYPE *p;
p = ptype;

(не TYPE *p; *p=ptype; /*would be a type error*/)

С точки зрения вашего примера:

struct list_t *current=*list;

устанавливает, куда current будет указывать (то же, что и то, на что указывает *list (*list также является указателем, потому что list является дважды косвенным указателем)), ничего не делая с каким бы то ни было текущим указать на (*current) после инициализации.

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

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