Зачем использовать двойное косвенное обращение? или зачем использовать указатели на указатели?

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

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

Ответ 1

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

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

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

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

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

Если вы хотите список биобиблиотек (a lol), вы можете использовать char ******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

Ответ 2

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

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

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

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

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}

Ответ 3

Вот ПРОСТОЙ ответ!!!!

  • позволяет сказать, что у вас есть указатель, что его значение является адресом.
  • но теперь вы хотите изменить этот адрес.
  • вы могли бы, выполнив pointer1 = pointer2, и указатель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 value: %x \n", a);
    printf("\n b value: %x \n", b);
    printf("\n f value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a 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 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 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 value: bf94c204

 b value: bf94c208 

 f value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a 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 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 value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 

Ответ 4

Добавляя к ответу Asha, если вы используете единственный указатель на приведенный ниже пример (например, 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, изменение не относится к коду в другой области видимости.

Ответ 5

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

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

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

2. char ** ch -
'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);
}

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

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

Например:

#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 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 still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

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

Джейсон

Ответ 8

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

int main(int argc, char **argv)

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

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

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

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

Ответ 9

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

Ответ 10

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

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

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

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

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

Ответ 11

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

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- в C

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Вы сохраняете указатель p, который указывает на массив указателей. Каждый указатель указывает на фрагмент данных.

Если sizeof(T) велико, может быть невозможно выделить смежный блок (т.е. используя malloc) sizeof(T) * n bytes.

Ответ 12

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

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Затем создайте массив отсортированных указателей на объекты.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

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

Ответ 13

Ниже приведен очень простой пример С++, который показывает, что если вы хотите использовать функцию для указания указателя на объект, вам нужен указатель на указатель. В противном случае указатель будет продолжать возвращаться к нулевому.

(Ответ на С++, но я считаю, что это то же самое в C.)

(Также для справки: Google ( "pass by value С++" ) = "По умолчанию аргументы в С++ передаются по значению. Когда аргумент передается по значению, значение аргумента копируется в параметр функции". )

Итак, мы хотим установить указатель b равным строке a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

Что происходит в строке Function_1(&a, b);?

  • "Значение" &main::a (адрес) копируется в параметр std::string* Function_1::a. Поэтому Function_1::a является указателем на (т.е. Адрес памяти) строки main::a.

  • "Значение" main::b (адрес в памяти) копируется в параметр std::string* Function_1::b. Поэтому в настоящее время в памяти два этих адреса, оба нулевые указатели. В строке b = a; локальная переменная Function_1::b затем изменяется на равную Function_1::a (= &main::a), но переменная main::b не изменяется. После вызова Function_1, main::b по-прежнему является нулевым указателем.

Что происходит на линии Function_2(&a, &b);?

  • Обработка переменной a одинакова: внутри функции Function_2::a - адрес строки main::a.

  • Но переменная b теперь передается как указатель на указатель. "Значение" &main::b (адрес указателя main::b) копируется в std::string** Function_2::b. Поэтому в Function_2 разыменование этого как *Function_2::b будет доступ и изменение main::b. Таким образом, строка *b = a; фактически устанавливает main::b (адрес), равный Function_2::a (= адрес main::a), который мы хотим.

Если вы хотите использовать функцию для изменения вещи, будь то объект или адрес (указатель), вы должны передать указатель на эту вещь. То, что вы фактически проходите in не может быть изменен (в области вызова), так как создается локальная копия.

(Исключение: если параметр является ссылкой, например std::string& a, но обычно это const. Обычно, если вы вызываете f(x), если x - это объект, который вы можете принять что f не будет изменять x. Но если x является указателем, вы должны предположить, что f может изменить объект, на который указывает x.)

Ответ 14

Почему двойные указатели?

Цель состоит в том, чтобы изменить то, на что указывает ученикA, используя функцию.

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */

Ответ 15

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

В массивах C всегда выделяется память в стеке, поэтому функция не может вернуть (нестатический) массив из-за того, что память выделена в стеке освобождается автоматически, когда выполнение достигает конца текущего блока. Это действительно раздражает, когда вы хотите иметь дело с двумерными массивами (то есть матрицы) и реализовать несколько функций, которые могут изменять и возвращать матрицы. Чтобы достичь этого, вы можете использовать указатель-указатель для реализации матрицы с динамически выделяемая память:

/* Initializes a matrix */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each float-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Вот иллюстрация:

 float**        float*            float
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

Float-pointer-to-float-pointer A указывает на первый элемент A [0] Блок памяти, элементы которого являются указателями типа float. Вы можете представить это указатели с плавающей точкой в виде строк матрицы. Вот причина, почему каждый float-pointer выделяет память для num_cols float-элементов. Кроме того, A [i] указывает на i-ую строку, то есть A [i] указывает на A [i] [0] и это просто первый элемент float блока памяти для i-й строки. Наконец, вы можете получить доступ к элементу в i-й строке и j-й столбец легко с A [i] [j].

Вот полный пример, демонстрирующий использование:

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

/* Initializes a matrix */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each float-pointer (row) allocate memory for num_cols
    // floats
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random float-numbers between -1 and 1 */
void randn_fill_matrix(float** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (float) rand()/RAND_MAX*2.0f-1.0f;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(float** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(float** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    float** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

Ответ 16

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

СЛЕДУЕТ использовать двойные указатели при работе с указателями, которые были изменены в других местах вашего приложения. Вы также можете найти двойные указатели, чтобы быть обязательным, когда вы имеете дело с аппаратным обеспечением, которое возвращает вас и обращается к вам.

Ответ 17

Сравните изменяемое значение переменной с изменяющимся значением указателя:

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

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

СТАРЫЙ (плохо):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NEW (лучше):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    

Ответ 18

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

#include <iostream>
#include <cstring>  // for using strcpy
using namespace std;

void change(char **temp)
{
   strcpy(temp[0],"new");
   strcpy(temp[1],"value");
}

int main()
{
   char **str;
   str = (char **)malloc(sizeof(char *)*3);
   str[0]=(char *)malloc(10);
   str[1]=(char *)malloc(10);
   strcpy(str[0],"old");
   strcpy(str[1],"name");
   char **temp = str;  // always use the temporary variable
   while(*temp!=NULL)
{
    cout<<*temp<<endl;
    temp++;
}
temp = str;   // making it point it to the original head because we have changed the address in while loop above
change(str);
while(*temp!=NULL)
{
   cout<<*temp<<endl;
   temp++;
}

free(temp);
free(str[0]);
free(str[1]);
free(str);

Ответ 19

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

    int* setptr(int *x)
    {
        printf("%u\n",&x);
        x=malloc(sizeof(int));
        *x=1;
        return x;
    }

In the above function setptr we can manipulate x either
1. by taking fn arg as int *x , doing  malloc and setting value of x and return x 
Or
    2. By taking arg as int ** and malloc and then set  **x value to some value.
Note: we cant set any general pointer directly without doing  malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the  pointer and in the case pointer initially points  to NULL or it is defined like int *x where it points  to any random address then we need to assign a valid memory address to pointer 

    1. either we need to allocate memory to it by malloc

    int *x=NULL means its address is 0.
    Now we need to either o following
    1.



    void main()
        {
            int *x;
            x=malloc
            *x=some_val;
        }
        Or
        void main()
        {
            int *x
            Fn(x);
        }

        void Fn(int **x)
        {
            *x=malloc;
            **x=5;
        }
        OR
        int * Fn(int *x)
        {
            x=malloc();
            *x=4;
            Return x;
        }


        2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined.


        OR
        int main()
        {
            int a;
            int *x=&a;
            Fn(x);
            printf("%d",*x);
        }
        void Fn(int *x)
        {
            *x=2;
        }


     in both cases value pointed by x is changed inside fn

    But suppose if we do like


    int main()
    {
        int *x=NULL;
        printf("%u\n",sizeof(x));
        printf("%u\n",&x);
        x=setptr(x);
        //*x=2;
        printf("%d\n",*x);
        return 0;
    }

/* output
4
1
*/

#include<stdio.h>
void setptr(int *x)
{
    printf("inside setptr\n");
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    //x=malloc(sizeof(int));
    *x=1;
    //return x;
}
int main()
{
    int *x=NULL;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    int a;
    x=&a;
    printf("x=%u\n",x);
    printf("&a=%u\n",&a);
    printf("&x=%u\n",&x);
    setptr(x);
    printf("inside main again\n");

    //*x=2;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    printf("*x=%d\n",*x);
    printf("a=%d\n",a);
    return 0;
}

Ответ 20

применение двойного указателя, как показал Бхавук Матур, кажется неправильным. Вот следующий пример является правильным

void func(char **str)
{
     strcpy(str[0],"second");
}

int main(){

    char **str;
    str = (char **)malloc(sizeof(char*)*1); // allocate 1 char* or string
    str[0] = (char *)malloc(sizeof(char)*10);      // allocate 10 character
    strcpy(str[0],"first");            // assign the string
    printf("%s\n",*str);
    func(str);            
    printf("%s\n",*str);           
    free(str[0]); 
    free(str);
}