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


272

Коли слід застосовувати подвійну непрямість у С? Хтось може пояснити прикладом?

Що я знаю, це те, що подвійне непряме є вказівником на вказівник. Навіщо мені потрібен вказівник на покажчик?


49
Будь обережний; фраза "подвійний вказівник" також відноситься до типу double*.
Кіт Томпсон

Візьміть на замітку: відповідь на це питання відрізняється для C і C ++ - не додайте тег c + до цього дуже старого питання.
BЈович

Відповіді:


479

Якщо ви хочете мати список символів (слово), ви можете використовувати 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);
}

Вихід:

всього слів у моєму лол: 243

6
Просто хотів зазначити, що а arr[a][b][c]не є ***arr. Покажчик покажчиків використовує посилання на посилання, при цьому arr[a][b][c]він зберігається як звичайний масив у основному порядку рядків.
MCCCS

170

Однією з причин є те, що ви хочете змінити значення вказівника, переданого на функцію як аргумент функції, для цього вам потрібен вказівник на покажчик.

Простими словами, Використовуйте, **коли ви хочете зберегти (АБО зберегти зміни) пам'яті-розподілу або призначення, навіть поза функцією виклику функції. (Отже, передайте таку функцію з подвійним аргументом вказівника.)

Це може бути не дуже вдалим прикладом, але покаже вам основне використання:

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

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

14
що було б інакше, якби виділити, void allocate(int *p)і ти це назвав allocate(p)?
ア レ ッ ク ス

@AlexanderSupertramp Так. Код буде за замовчуванням. Будь ласка, дивіться відповідь Сільвіу.
Абхішек

@Asha Яка різниця між розподілити (p) і розподілити (& p)?
user2979872

1
@Asha - Чи не можемо ми просто повернути вказівник? Якщо ми маємо це робити недійсним, то який практичний варіант використання цього сценарію?
Shabirmean

91
  • Скажімо, у вас є вказівник. Його значення - адреса.
  • але тепер ви хочете змінити цю адресу.
  • ти міг. роблячи pointer1 = pointer2, ви даєте pointer1 адресу pointer2.
  • але! якщо ви робите це в межах функції, і хочете, щоб результат зберігався і після того, як функція виконана, вам потрібно виконати додаткову роботу. вам потрібен новий pointer3, щоб просто вказати на pointer1. передайте покажчик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!!!. 

4
Це чудова відповідь і справді допомогла мені уявити мету та корисність подвійного вказівника.
Джастін

1
@Justin Ви перевірили мою відповідь вище цієї? її прибиральник :)
Брайан Джозеф Спінос

10
Чудова відповідь, просто не вистачає пояснення, що <code> void cant_change (int * x, int * z) </code> не вдається, тому що його параметри - це лише нові локальні масштабні покажчики, які ініціалізуються аналогічно покажчикам a і f (тому вони не є те саме, що і a і f).
Педро Рейс

1
Простий? Дійсно? ;)
алк

1
ця відповідь справді пояснює одне з найпоширеніших вказівників на використання вказівників, дякую!
tonyjosi

48

Якщо додати відповідь Аша , якщо ви будете використовувати один вказівник на приклад нижче (наприклад, 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, зміна не стосується коду в іншій області застосування.


1
Що станеться, якщо р - статичний цілочисельний покажчик? Помилка сегментації.
kapilddit

free(p)не вистачає, вам потрібно , if(p) free(*p)а також
Шицзин Lv

@ShijingLv: Ні. *pОцінює значення intхолдингу 10, передаючи це intбезкоштовно () `- погана ідея.
алк

Розподіл, здійснене в alloc1()результаті, втілює пам'ять. Значення вказівника, яке передається безкоштовно, втрачається поверненням із функції.
алк

Не потрібно (!)
алк

23

Сьогодні я побачив дуже гарний приклад із цієї публікації в блозі , як я підсумовую нижче.

Уявіть, у вас є структура для вузлів у пов'язаному списку, яка, ймовірно, є

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. if entry != *head: аналогічно - *currце те , на що prev->nextвказували, і тепер вказує на entry->next.

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


22

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);
}

Вихід буде AA. Це не працює, оскільки ви "пройшли значення" у функції.

Правильний спосіб це було б -

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);
}

У цьому прикладі метод очікує подвійний покажчик як параметр для оновлення значення рядка.


#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } Але ви можете це зробити і без використання подвійного вказівника.
кумар

" char ** ch - 'ch" містить адресу масиву символьних вказівників. "Ні, він містить адресу 1-го елемента масиву charвказівників. Вказівник на масив char*має бути введений, наприклад, так: char(*(*p)[42])визначає pяк вказівник на масив з 42 вказівника на char.
алк

Останній фрагмент повністю розбитий. Для початку: Ось *str = ... strдереференційований неініціалізований виклик невизначеної поведінки.
алк

Це malloc(sizeof(char) * 10);не виділяє місця для 10 покажчиків на, charа charлише для 10 ..
алк

Цей цикл for(i=0;i<10;i++) { str = ... пропускає використовувати індекс i.
алк

17

Покажчики на покажчики також корисні як «ручки» для пам’яті, де ви хочете пройти навколо «ручки» між функціями для повторної локалізації пам’яті. Це в основному означає, що функція може змінювати пам'ять, на яку вказує вказівник всередині змінної ручки, і кожна функція або об'єкт, який використовує ручку, буде правильно вказувати на щойно переміщену (або виділену) пам'ять. Бібліотеки люблять робити це з "непрозорими" типами даних, тобто типами даних, якщо вам не доведеться турбуватися про те, що вони роблять із вказаною пам'яттю, ви просто проходите навколо "ручки" між функції бібліотеки для виконання деяких операцій над цією пам'яттю ...

Наприклад:

#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;
}

Сподіваюся, це допомагає,

Джейсон


Що є причиною того, що тип ручки не підписаний char **? Невже ** може нічого не працювати?
Коннор Кларк

5
unsigned charвикористовується спеціально, тому що ми зберігаємо вказівник на двійкові дані, які будуть представлені у вигляді необроблених байтів. Використання voidвимагає відтворення в якийсь момент і, як правило, не настільки читабельне, як у намірі того, що робиться.
Джейсон

7

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

int main(int argc, char **argv)

У другому параметрі у вас є: вказівник на покажчик на char.

Зауважте, що позначення вказівника ( char* c) і позначення масиву ( char c[]) є взаємозамінними в аргументах функції. Тож ви могли також писати char *argv[]. Іншими словами char *argv[]і char **argvє взаємозамінними.

Наведене вище є насправді масивом послідовностей символів (аргументи командного рядка, які надаються програмі при запуску).

Дивіться також цю відповідь для отримання більш детальної інформації про вищевказану функцію підпису.


2
"Позначення вказівника ( char* c) і позначення масиву ( char c[]) є взаємозамінними" (і мають однакове точне значення) в аргументах функції . Вони різні, однак поза аргументами функції.
pmg

6

Струни - чудовий приклад використання подвійних покажчиків. Сама рядок є вказівником, тому щоразу, коли вам потрібно вказати на рядок, вам знадобиться подвійний вказівник.


5

Наприклад, ви можете переконатися, що, звільняючи пам'ять про щось, ви встановите вказівник на нуль після цього.

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

Коли ви викликаєте цю функцію, ви б викликали її за адресою вказівника

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

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


1
це має бути if(*memory)іfree(*memory);
Аша

1
Хороший момент, втрата сигналу між мозку та клавіатурою. Я відредагував це, щоб мати трохи більше сенсу.
Джефф Фостер

Чому ми не можемо зробити наступне ... void safeFree (void * memory) {if (memory) {free (memory); пам'ять = NULL; }}
Peter_pk

@Peter_pk Призначення пам'яті null не допоможе, тому що ви передали вказівник за значенням, а не за посиланням (отже, приклад вказівника на покажчик).
Джефф Фостер

2

Наприклад, якщо ви хочете випадковий доступ до неперервних даних.

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

- в С

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байтів.


1
Не потрібно (!)
алк

2

Одне, для чого я їх постійно використовую, - це коли у мене є масив об’єктів, і мені потрібно виконувати пошук (двійковий пошук) по різних полях.
Я зберігаю оригінальний масив ...

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);

Ви можете зробити стільки відсортованих масивів вказівників, скільки вам потрібно, а потім скористатись двійковим пошуком у відсортованому масиві вказівників для доступу до потрібного вам об’єкта за наявними вами даними. Оригінальний масив об'єктів може залишатися несортованим, але кожен масив вказівників буде відсортований за вказаним полем.


2

Чому подвійні покажчики?

Мета - змінити те, на що вказує studentA, використовуючи функцію.

#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!)
 */

1
Не потрібно (!)
алк

2

Далі наводиться дуже простий приклад C ++, який показує, що якщо ви хочете використовувати функцію для встановлення вказівника на вказівку на об’єкт, вам потрібен вказівник на покажчик . В іншому випадку вказівник продовжить повертати до нуля .

(Відповідь C ++, але я вважаю, що це однаково в C.)

(Також для довідки: Google ("передай значення c ++") = "За замовчуванням аргументи в C ++ передаються за значенням. Коли аргумент передається за значенням, значення аргументу копіюється в параметр функції.")

Отже ми хочемо встановити покажчик 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), що ми хочемо.

Якщо ви хочете використовувати функцію для зміни речі, будь то об'єкт або адреса (покажчик), вам потрібно передати вказівник на цю річ. Те, що ви фактично передаєте, не може бути змінено (в області виклику), оскільки робиться локальна копія.

(Виняток - якщо параметр є посиланням, наприклад std::string& a. Але зазвичай це такі const. Як правило, якщо ви телефонуєте f(x), якщо xце об'єкт, ви повинні мати можливість припустити, що f він не змінюватиметься x. Але якщо xвказівник, то слід припустимо, що f може змінити об'єкт, на який вказує x.)


Код C ++ для відповіді на запитання C - не найкраща ідея.
алк

1

Трохи запізнюємось на вечірку, але, сподіваємось, це комусь допоможе.

У масивах C завжди виділяють пам'ять на стеку, тому функція не може повернути (нестатичний) масив через те, що пам'ять, виділена на стек, автоматично звільняється, коли виконання досягає кінця поточного блоку. Це дійсно дратує, коли ви хочете мати справу з двовимірними масивами (тобто матрицями) та реалізувати кілька функцій, які можуть змінювати та повертати матриці. Для цього ви можете використовувати вказівник на вказівник для реалізації матриці з динамічно розподіленою пам'яттю:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-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;
} 

Ось ілюстрація:

double**       double*           double
             -------------       ---------------------------------------------------------
   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] |
             -------------       ---------------------------------------------------------

Подвійний вказівник на подвійний вказівник A вказує на перший елемент A [0] блоку пам'яті, елементи якого є самими подвійними вказівниками. Ви можете уявити ці подвійні покажчики як рядки матриці. Саме тому кожен подвійний покажчик виділяє пам'ять для елементів num_cols типу double. Крім того, A [i] вказує на i-й ряд, тобто A [i] вказує на A [i] [0], і це лише перший подвійний елемент блоку пам'яті для i-го рядка. Нарешті, ви можете легко отримати доступ до елемента i-го рядка та j-го стовпця за допомогою A [i] [j].

Ось повний приклад, який демонструє використання:

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

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    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 double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** 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(double** 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;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

0

Я сьогодні використовував подвійні покажчики, поки я щось програмував для роботи, тому можу відповісти, чому нам довелося їх використовувати (це перший раз, коли я насправді мав використовувати подвійні вказівники). Нам довелося мати справу з кодуванням кадрів у реальному часі, що містяться в буферах, які є членами деяких структур. У кодері нам довелося використовувати вказівник на одну з цих структур. Проблема полягала в тому, що наш покажчик змінювався, щоб вказувати на інші структури з іншої нитки. Для того, щоб використовувати поточну структуру в кодері, мені довелося скористатися подвійним покажчиком, щоб вказати на покажчик, який змінювався в іншій потоці. Спочатку було не очевидно, щонайменше для нас, що ми повинні використовувати цей підхід. У процесі було надруковано багато адрес :)).

Ви повинні використовувати подвійні покажчики, коли працюєте над покажчиками, які змінюються в інших місцях вашої програми. Ви також можете знайти подвійні вказівники як необхідні, коли ви маєте справу з обладнанням, яке повертається та звертається до вас.


0

Порівняйте модифікуюче значення змінної та модифікуючого значення вказівника :

#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);
  ...
}    

НОВО (краще):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.