Зачем использовать двойное косвенное обращение?или зачем использовать указатели на указатели? - PullRequest
232 голосов
/ 07 апреля 2011

Когда следует использовать двойное косвенное в C? Кто-нибудь может объяснить на примере?

Что я знаю, так это то, что двойная косвенность является указателем на указатель. Зачем мне указатель на указатель?

Ответы [ 16 ]

445 голосов
/ 07 апреля 2011

Если вы хотите иметь список символов (слово), вы можете использовать char *word

Если вы хотите список слов (предложение), вы можете использовать char **sentence

Если вы хотите список предложений (монолог), вы можете использовать char ***monologue

Если вам нужен список монологов (биография), вы можете использовать char ****biography

Если вам нужен список биографий (био-библиотека), вы можете использовать char *****biolibrary

Если вам нужен список биобиблиотек (a lol), вы можете использовать char ******lol

... ...

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


Пример использования с очень-очень-очень скучным lol

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

Выход:

total words in my lol: 243
160 голосов
/ 07 апреля 2011

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

Проще говоря, Используйте **, если вы хотите сохранить (ИЛИ сохранить изменение) Распределение памяти или Назначение даже вне вызова функции. (Итак, передайте такую ​​функцию с двойным указателем Arg.)

Это может быть не очень хороший пример, но покажет вам основное использование:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
77 голосов
/ 08 июня 2015

Вот простой ответ !!!!

  • Допустим, у вас есть указатель на то, что его значение является адресом.
  • , но теперь вы хотите изменить этот адрес.
  • вы могли бы, сделав указатель1 = указатель2, а указатель1 теперь будет иметь адрес указателя 2.
  • НО!если вы хотите, чтобы функция делала это для вас, и вы хотите, чтобы результат сохранялся после выполнения функции, вам нужно проделать дополнительную работу, вам нужен новый указатель 3, чтобы просто указать на указатель 1, и передать указатель 3 в функцию.

  • вот забавный пример (сначала посмотрите на вывод ниже, чтобы понять!):

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}
  • ивот вывод:
 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 
42 голосов
/ 24 сентября 2014

Добавление к ответа Аши , если вы используете единственный указатель на приведенный ниже пример (например, alloc1 ()), вы потеряете ссылку на память, выделенную внутри функции.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

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

21 голосов
/ 04 августа 2014

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

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

typedef struct node
{
    struct node * next;
    ....
} node;

Теперь вы хотите реализовать функцию remove_if, которая принимает критерий удаления rm в качестве одного из аргументов и пересекает связанный список: если запись удовлетворяет критерию (что-то вроде rm(entry)==true)его узел будет удален из списка.В конце remove_if возвращает заголовок (который может отличаться от исходного заголовка) связанного списка.

Вы можете написать

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

в качестве цикла for.Сообщение гласит: без двойных указателей, вам нужно сохранить переменную prev, чтобы перестроить указатели и обрабатывать два разных случая.

Но с двойными указателями вы можетена самом деле пишите

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

Вам не нужно prev сейчас, потому что вы можете напрямую изменить то, что prev->next указывало на .

Чтобы прояснить ситуацию,давайте немного последуем за кодом.Во время удаления:

  1. , если entry == *head: это будет *head (==*curr) = *head->next - head, теперь указывает на указатель нового узла заголовка.Вы делаете это путем непосредственного изменения содержимого head на новый указатель.
  2. , если entry != *head: аналогично, *curr - это то, на что prev->next указывает, а теперь указывает на entry->next.

Независимо от того, в каком случае вы можете реорганизовать указатели единым образом с двойными указателями.

21 голосов
/ 18 июня 2013

1. Основная концепция -

Когда вы заявляете следующее: -

1. char * ch - (вызываемый символьный указатель)
- ch содержит адрес одного символа.
- (* ch) будет разыменовывать значение символа.

2. символ ** ч -
'ch' содержит адрес массива указателей символов. (как в 1)
«* ch» содержит адрес одного символа. (Обратите внимание, что он отличается от 1 из-за разницы в объявлении).
(** ch) будет разыменовывать точное значение символа.

Добавление дополнительных указателей расширяет размерность типа данных, от символа к строке, до массива строк и т. Д. ... Вы можете связать его с 1d, 2d, 3d матрицей.

Итак, использование указателя зависит от того, как вы его объявили.

Вот простой код ..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Другое применение двойных указателей -
(это также относится к передаче по ссылке)

Предположим, вы хотите обновить персонажа из функции. Если вы попробуете следующее: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

Выход будет АА. Это не работает, так как вы "передали по значению" в функцию.

Правильный способ сделать это будет -

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Теперь расширьте это требование для обновления строки вместо символа.
Для этого вам необходимо получить параметр в функции в виде двойного указателя.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

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

15 голосов
/ 07 апреля 2011

Указатели на указатели также пригодятся в качестве «дескрипторов» памяти, где вы хотите передать «дескриптор» между функциями в переместимую память.Это в основном означает, что функция может изменять память, на которую указывает указатель внутри переменной handle, и каждая функция или объект, использующий дескриптор, будут правильно указывать на вновь перемещенную (или выделенную) память.Библиотеки любят делать это с «непрозрачными» типами данных, то есть с типами данных, если вам не нужно беспокоиться о том, что они делают с указанным объемом памяти, вы просто передаете «дескриптор» междуфункции библиотеки для выполнения некоторых операций с этой памятью ... библиотечные функции могут распределять и перераспределять внутреннюю память без необходимости явно беспокоиться о процессе управления памятью или о том, куда указывает указатель.

Например:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

Надеюсь, это поможет,

Джейсон

6 голосов
/ 08 июля 2017

Простой пример, который вы, наверное, видели много раз до

int main(int argc, char **argv)

Во втором параметре он есть: указатель на указатель на символ.

Обратите внимание, что запись указателя (char* c) и запись массива (char c[]) являются взаимозаменяемыми в аргументах функции. Таким образом, вы также можете написать char *argv[]. Другими словами char *argv[] и char **argv являются взаимозаменяемыми.

То, что представлено выше, на самом деле представляет собой массив последовательностей символов (аргументы командной строки, которые передаются программе при запуске).

См. Также этот ответ для получения более подробной информации о подписи функции выше.

5 голосов
/ 07 апреля 2011

Например, вы можете захотеть убедиться, что когда вы освобождаете память чего-либо, впоследствии вы устанавливаете указатель на ноль.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Когда вы вызываете эту функцию, вы вызываете ее с адресом указателя

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Теперь myMemory имеет значение NULL, и любая попытка его повторного использования будет совершенно очевидно неправильной.

5 голосов
/ 07 апреля 2011

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

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