Как на самом деле работают двойные указатели (struct tree_st **root)?

Нам предстоит создать бинарное дерево с моделями автомобилей и модифицировать его по-разному. Основной проблемой для меня является использование двойного указателя (struct tree_st **root).

Почему я не могу просто использовать стандартный одиночный указатель?

Вот код, если вам нужно больше деталей того, о чем я говорю:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_SIZE 20+1

typedef struct auto_st   //auto as in "car"
{
    char marka[MAX_SIZE];  //marka as in "car model"
    int kubikaza;  // "cubage"
    int godiste;  // "year of production"
    struct auto_st *left, *right;

}AUTO;

FILE *safe_fopen(char *filename, char *mode, int exit_code);
void init(AUTO **root);
AUTO *create_new(char marka[], int kubikaza, int godiste);
void add_location(AUTO *new, AUTO **root);
void read_file(FILE *in, AUTO **root);
void write_one(FILE *out, AUTO *root);
void write_all(FILE *out, AUTO *root);
void find(AUTO *root, char marka[]);
AUTO *newest(AUTO *root, int max_kub);

int main(int arg_num, char *args[])
{
    AUTO *root;
    if(arg_num!=4)
    {
        printf("WRONG NUMBER OF ARGUMENTS!\n");
        exit(1);
    }

    int max_kub = atoi(args[1]);
    char *in_filename = args[2];
    char *out_filename = args[3];

    FILE *in = safe_fopen(in_filename,"r",10);
    FILE *out = safe_fopen(out_filename,"w",11);

    init(&root);
    read_file(in,&root);
    write_all(out,root);

    AUTO *best = newest(root, max_kub);
    if(best==NULL)
    {
        printf("CAR DOESN'T EXIST!\n");
    }
    else
    {
        write_one(out,best);
    }

    find(root,"Ferrari");

    fclose(in);
    fclose(out);
    return EXIT_SUCCESS;
}

FILE *safe_fopen(char *filename, char *mode, int exit_code)
{
    FILE *pf = fopen(filename,mode);
    if(pf==NULL)
    {
        printf("CAN'T OPEN FILE %s\n", filename);
        exit(exit_code);
    }
    return pf;
}

void init(AUTO **root)
{
    *root = NULL;
}

AUTO *create_new(char marka[], int kubikaza, int godiste)
{
    AUTO *new = (AUTO*)malloc(sizeof(AUTO));
    if(new == NULL)
    {
        printf("NOT ENOUGH RAM!!\n");
        exit(5);
    }

    strcpy(new->marka,marka);
    new->kubikaza = kubikaza;
    new->godiste = godiste;

    new->left = NULL;
    new->right = NULL;

    return new;
}

void add_location(AUTO *new, AUTO **root)
{
    if(*root==NULL)
    {
        *root = new;
    }
    else if(strcmp((*root)->marka,new->marka)==1)
    {
        add_location(new,&((*root)->left));
    }
    else if(strcmp((*root)->marka,new->marka)==-1)
    {
        add_location(new,&((*root)->right));
    }
}

void read_file(FILE *in, AUTO **root)
{
    char tmarka[MAX_SIZE];
    int tkubikaza;
    int tgodiste;

    while(fscanf(in,"%s %d %d",tmarka, &tkubikaza, &tgodiste)!=EOF)
    {
        AUTO *new = create_new(tmarka, tkubikaza, tgodiste);
        add_location(new,root);
    }
}

void write_one(FILE *out, AUTO *root)
{
    fprintf(out,"%s %d %d\n",root->marka, root->kubikaza, root->godiste);
}

void write_all(FILE *out, AUTO *root)
{
    if(root!=NULL)
    {
        write_all(out,root->left);
        write_one(out,root);
        write_all(out,root->right);
    }
}

void find(AUTO *root, char tmarka[])
{
    if(root!=NULL)
    {
        if(strcmp(root->marka,tmarka)==0)
        {
            printf("%s %d %d\n",root->marka, root->kubikaza, root->godiste);
        }
        else if(strcmp(root->marka,tmarka)==1)
        {
            find(root->left, tmarka);
        }
        else if(strcmp(root->marka,tmarka)==-1)
        {
            find(root->right, tmarka);
        }
    }
}

AUTO *newest(AUTO *root, int max_kub)
{
    if(root==NULL)
    {
        return NULL;
    }

    AUTO *best = NULL;
    if(root->kubikaza <= max_kub)
    {
        best = root;
    }

    AUTO *left = newest(root->left, max_kub);
    if(left!=NULL)
    {
        if(best==NULL || left->godiste > best->godiste)
        {
            best = left;
        }
    }

    AUTO *right = newest(root->right, max_kub);
    if(right!=NULL)
    {
        if(best==NULL || right->godiste > best->godiste)
        {
            best = right;
        }
    }

    return best;
}

person Muge    schedule 23.12.2016    source источник


Ответы (2)


Если явно не определено как ссылка, все параметры в C/C++ передаются по значению, включая указатели. Когда вы меняете содержимое указателя, вы не меняете сам указатель, который на самом деле является просто адресом памяти. Если вам нужно иметь возможность изменить указатель, вам нужно либо передать его по ссылке, либо как двойной указатель.

В вашем коде:

void init(AUTO **root);

init значение указателя на NULL, тем самым изменив его. Отправка AUTO *root не позволила бы этого.

void add_location(AUTO *new, AUTO **root)

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

void read_file(FILE *in, AUTO **root);

read_file изменяет указатель, создавая новый экземпляр.

person Mikel F    schedule 23.12.2016
comment
Большое тебе спасибо! Это мне очень помогло! - person Muge; 23.12.2016

Указатель есть указатель, и не более того. Это указывает на что-то другое.

Возьмем, к примеру

int int_value = 1;
int *ptr_to_int = &int_value;

Переменная ptr_to_int указывает на место, где существует int_value.

Теперь, чтобы иметь «двойной указатель»:

int **ptr_to_ptr_to_int = &ptr_to_int;

Переменная ptr_to_ptr_to_int указывает на то, где существует ptr_to_int.

Более «графически» приведенное выше можно рассматривать как что-то вроде

+-------------------+      +------------+      +-----------+
| ptr_to_ptr_to_int | ---> | ptr_to_int | ---> | int_value |
+-------------------+      +------------+      +-----------+

Существует три основных способа использования «двойных указателей», оба из которых связаны с массивами.

  • Во-первых, иметь динамический массив указателей на объекты (в основном структуры).
  • Во-вторых, иметь динамический массив динамических массивов объектов (он же динамический "двухмерный массив").
  • Чтобы эмулировать, перейдите по ссылке для указателя.

В случае, который вы показываете, когда функции init и read_file принимают "двойной указатель", это последний случай для эмуляции передачи по ссылке.

person Some programmer dude    schedule 23.12.2016
comment
@ IMug3tsu Замените тип int в моем примере на структуру. Фактический тип не имеет значения, это (как и вы, и я говорим) переменная, которая указывает на указатель, указывающий на что-то. - person Some programmer dude; 23.12.2016
comment
Я понимаю. Большое спасибо! Эта информация заставила меня осознать много разных вещей, которые я не мог полностью понять. - person Muge; 23.12.2016