Mengapa menggunakan tipuan ganda? atau Mengapa menggunakan pointer ke pointer?


272

Kapan tipuan ganda digunakan dalam C? Adakah yang bisa menjelaskan dengan contoh?

Yang saya tahu adalah bahwa tipuan ganda adalah pointer ke pointer. Mengapa saya membutuhkan pointer ke pointer?


49
Hati-hati; frasa "penunjuk ganda" juga merujuk pada jenisnya double*.
Keith Thompson

1
Catat: jawaban untuk pertanyaan ini berbeda untuk C dan C ++ - jangan tambahkan tag c + untuk pertanyaan yang sangat lama ini.
BЈовић

Jawaban:


479

Jika Anda ingin memiliki daftar karakter (kata), Anda dapat menggunakan char *word

Jika Anda ingin daftar kata-kata (kalimat), Anda dapat menggunakan char **sentence

Jika Anda ingin daftar kalimat (sebuah monolog), Anda dapat menggunakannya char ***monologue

Jika Anda ingin daftar monolog (biografi), Anda dapat menggunakannya char ****biography

Jika Anda ingin daftar biografi (bio-library), Anda dapat menggunakannya char *****biolibrary

Jika Anda ingin daftar bio-libraries (a lol), Anda dapat menggunakan char ******lol

... ...

ya, saya tahu ini mungkin bukan struktur data terbaik


Contoh penggunaan dengan lol yang sangat sangat sangat membosankan

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

Keluaran:

total kata dalam lol saya: 243

7
Hanya ingin menunjukkan bahwa a arr[a][b][c]bukan a ***arr. Pointer pointer menggunakan referensi referensi, sementara arr[a][b][c]disimpan sebagai array biasa dalam urutan utama baris.
MCCCS

170

Salah satu alasannya adalah Anda ingin mengubah nilai pointer yang diteruskan ke fungsi sebagai argumen fungsi, untuk melakukan ini Anda memerlukan pointer ke pointer.

Dengan kata sederhana, Gunakan **ketika Anda ingin menyimpan (ATAU mempertahankan perubahan) Alokasi Memori atau Penugasan bahkan di luar panggilan fungsi. (Jadi, Lewati fungsi tersebut dengan arg pointer ganda.)

Ini mungkin bukan contoh yang sangat baik, tetapi akan menunjukkan kepada Anda penggunaan dasar:

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

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

14
apa yang akan berbeda jika mengalokasikan itu void allocate(int *p)dan Anda menyebutnya sebagai allocate(p)?
ア レ ッ ク ス

@AlexanderSupertramp Ya. Kode tersebut akan segfault. Silakan lihat Jawaban Silviu.
Abhishek

@ Asha apa perbedaan antara mengalokasikan (p) dan mengalokasikan (& p)?
user2979872

1
@ Asha - Bisakah kita mengembalikan pointer? Jika kita harus menjaganya agar tetap kosong, apa gunanya skenario ini?
Shabirmean

91
  • Katakanlah Anda memiliki pointer. Nilainya adalah alamat.
  • tetapi sekarang Anda ingin mengubah alamat itu.
  • Anda bisa. dengan melakukan pointer1 = pointer2, Anda memberi pointer1 alamat pointer2.
  • tapi! jika Anda melakukan itu dalam suatu fungsi, dan Anda ingin hasilnya bertahan setelah fungsi selesai, Anda perlu melakukan beberapa pekerjaan tambahan. Anda memerlukan pointer3 baru hanya untuk menunjuk ke pointer1. lewat pointer3 ke fungsi.

  • ini sebuah contoh. lihat output di bawah ini terlebih dahulu, untuk mengerti.

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

Inilah hasilnya: ( baca ini dulu )

 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
Ini adalah jawaban yang bagus dan benar-benar membantu saya memvisualisasikan tujuan dan kegunaan pointer ganda.
Justin

1
@ Justin apakah Anda memeriksa jawaban saya di atas yang ini? lebih bersih :)
Brian Joseph Spinos

10
Jawaban yang bagus, hanya kurang menjelaskan bahwa <code> void cant_change (int * x, int * z) </code> gagal karena parameternya hanyalah pointer lingkup lokal baru yang diinisialisasi juga pointer a dan f (jadi mereka tidak sama dengan a dan f).
Pedro Reis

1
Sederhana? Betulkah? ;)
alk

1
jawaban ini benar-benar menjelaskan salah satu penggunaan pointer ke pointer yang paling umum, terima kasih!
tonyjosi

49

Menambah respons Asha , jika Anda menggunakan satu penunjuk ke contoh di bawah (mis. Alokasi1 ()) Anda akan kehilangan referensi ke memori yang dialokasikan di dalam fungsi.

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

Alasan itu terjadi seperti ini adalah bahwa di alloc1 pointer dilewatkan oleh nilai. Jadi, ketika dipindahkan ke hasil mallocpanggilan di dalam alloc1, perubahan tidak berkaitan dengan kode dalam cakupan yang berbeda.


1
Apa yang terjadi jika p adalah pointer integer statis? Mendapatkan kesalahan Segmentasi.
kapilddit

free(p)tidak cukup, Anda perlu if(p) free(*p)juga
Shijing Lv

@ShijingLv: Tidak. *pMengevaluasi nilai intholding dari 10, meneruskannya intke free () `adalah ide yang buruk.
alk

Alokasi dilakukan dalam alloc1()memperkenalkan kebocoran memori. Nilai pointer yang akan dilewati gratis hilang dengan kembali dari fungsi.
alk

Tidak (!) Perlu membuang hasil malloc di C.
alk

23

Saya melihat contoh yang sangat baik hari ini, dari posting blog ini , seperti yang saya rangkum di bawah ini.

Bayangkan Anda memiliki struktur untuk node dalam daftar yang ditautkan, yang mungkin adalah

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

Sekarang Anda ingin mengimplementasikan remove_iffungsi, yang menerima kriteria penghapusan rmsebagai salah satu argumen dan melintasi daftar yang ditautkan: jika sebuah entri memenuhi kriteria (seperti rm(entry)==true), simpulnya akan dihapus dari daftar. Pada akhirnya, remove_ifmengembalikan kepala (yang mungkin berbeda dari kepala asli) dari daftar tertaut.

Anda bisa menulis

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

sebagai forloop Anda . Pesannya adalah, tanpa pointer ganda, Anda harus mempertahankan prevvariabel untuk mengatur ulang pointer , dan menangani dua kasus yang berbeda.

Tetapi dengan double pointer, Anda sebenarnya bisa menulis

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

Anda tidak memerlukan yang prevsekarang karena Anda dapat langsung memodifikasi apa yang prev->nextditunjuk .

Agar lebih jelas, ikuti sedikit kode. Selama penghapusan:

  1. if entry == *head: itu akan *head (==*curr) = *head->next- headsekarang menunjuk ke pointer dari node heading baru. Anda melakukan ini dengan mengubah secara langsunghead konten ke pointer baru.
  2. jika entry != *head: sama, *curradalah apa yang prev->nextmenunjuk, dan sekarang menunjuk ke entry->next.

Dalam kasus apa pun, Anda dapat mengatur ulang pointer dengan cara yang sama dengan pointer ganda.


22

1. Konsep Dasar -

Ketika Anda menyatakan sebagai berikut: -

1. char * ch - (disebut penunjuk karakter)
- ch berisi alamat karakter tunggal.
- (* ch) akan merujuk pada nilai karakter ..

2. char ** ch -
'ch' berisi alamat dari Array of character pointer. (seperti pada 1)
'* ch' berisi alamat karakter tunggal. (Perhatikan bahwa ini berbeda dari 1, karena perbedaan dalam deklarasi).
(** ch) akan merujuk pada nilai karakter yang tepat ..

Menambahkan lebih banyak pointer memperluas dimensi tipe data, dari karakter ke string, ke array string, dan seterusnya ... Anda dapat menghubungkannya dengan 1d, 2d, 3d matrix ..

Jadi, penggunaan pointer tergantung pada bagaimana Anda mendeklarasikannya.

Ini kode sederhana ..

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. Aplikasi Dua Pointer Lain -
(ini juga mencakup referensi lewat referensi)

Misalkan Anda ingin memperbarui karakter dari suatu fungsi. Jika Anda mencoba yang berikut: -

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

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

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

Outputnya adalah AA. Ini tidak berfungsi, karena Anda telah "Melewati Nilai" ke fungsi.

Cara yang benar untuk melakukan itu adalah -

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

Sekarang perluas persyaratan ini untuk memperbarui string alih-alih karakter.
Untuk ini, Anda perlu menerima parameter dalam fungsi sebagai penunjuk ganda.

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

Dalam contoh ini, metode mengharapkan penunjuk ganda sebagai parameter untuk memperbarui nilai string.


#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; } Tetapi Anda dapat melakukannya tanpa menggunakan pointer ganda juga.
kumar

" char ** ch - 'ch' berisi alamat Array of pointer karakter. " Tidak, itu berisi alamat elemen pertama dari array charpointer. Pointer ke array char*akan diketik misalnya seperti ini: char(*(*p)[42])mendefinisikan psebagai pointer ke array dari 42 pointer ke char.
alk

Cuplikan terakhir benar-benar rusak. Sebagai permulaan: Berikut *str = ... strini adalah dereferenced uninitialised meminta perilaku tidak terdefinisi.
alk

Ini malloc(sizeof(char) * 10);tidak mengalokasikan ruang untuk 10 pointer ke chartetapi charhanya untuk 10 ..
alk

Loop ini tidak for(i=0;i<10;i++) { str = ... dapat menggunakan indeks i.
alk

17

Pointer ke pointer juga berguna sebagai "pegangan" ke memori di mana Anda ingin menyampaikan "pegangan" antara fungsi ke memori yang dapat dilokasikan kembali. Itu pada dasarnya berarti bahwa fungsi tersebut dapat mengubah memori yang sedang diarahkan oleh pointer di dalam variabel pegangan, dan setiap fungsi atau objek yang menggunakan pegangan akan menunjuk ke memori yang baru dipindahkan (atau dialokasikan). Perpustakaan suka melakukan ini dengan tipe data "buram", yaitu tipe data yang Anda tidak perlu khawatir tentang apa yang mereka lakukan dengan memori yang ditunjuk lakukan, Anda cukup membagikan "pegangan" antara fungsi perpustakaan untuk melakukan beberapa operasi pada memori itu ...

Misalnya:

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

Semoga ini membantu,

Jason


Apa alasan tipe pegangan menjadi unsigned char **? Apakah batal ** berfungsi sama baiknya?
Connor Clark

5
unsigned charsecara khusus digunakan karena kami menyimpan pointer ke data biner yang akan direpresentasikan sebagai byte mentah. Menggunakan voidakan membutuhkan gips di beberapa titik, dan umumnya tidak dapat dibaca dengan maksud apa yang sedang dilakukan.
Jason

7

Contoh sederhana yang mungkin pernah Anda lihat sebelumnya

int main(int argc, char **argv)

Pada parameter kedua Anda memilikinya: pointer ke pointer ke char.

Perhatikan bahwa notasi pointer ( char* c) dan notasi array ( char c[]) dapat dipertukarkan dalam argumen fungsi. Jadi Anda juga bisa menulis char *argv[]. Dengan kata lain char *argv[]dan char **argvdapat dipertukarkan.

Apa yang diwakili di atas sebenarnya adalah array dari urutan karakter (argumen baris perintah yang diberikan kepada suatu program saat startup).

Lihat juga jawaban ini untuk detail lebih lanjut tentang tanda tangan fungsi di atas.


2
"notasi pointer ( char* c) dan notasi array ( char c[]) dapat dipertukarkan" (dan memiliki arti yang persis sama) dalam argumen fungsi . Mereka berbeda namun di luar argumen fungsi.
sore

6

String adalah contoh yang bagus dari penggunaan pointer ganda. String itu sendiri adalah sebuah pointer, jadi setiap kali Anda perlu menunjuk ke sebuah string, Anda akan membutuhkan pointer ganda.


5

Misalnya, Anda mungkin ingin memastikan bahwa ketika Anda membebaskan memori dari sesuatu yang Anda tetapkan pointer ke nol setelahnya.

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

Saat Anda memanggil fungsi ini, Anda akan memanggilnya dengan alamat penunjuk

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

Sekarang myMemorydiatur ke NULL dan segala upaya untuk menggunakannya kembali akan menjadi sangat salah.


1
seharusnya if(*memory)danfree(*memory);
Asha

1
Poin bagusnya, kehilangan sinyal antara otak dan keyboard. Saya telah mengeditnya agar lebih masuk akal.
Jeff Foster

Mengapa kita tidak dapat melakukan hal berikut ... void safeFree (void * memory) {if (memory) {free (memory); memory = NULL; }}
Peter_pk

@ Peter_pk Menetapkan memori ke nol tidak akan membantu karena Anda telah melewati sebuah pointer dengan nilai, bukan dengan referensi (maka contoh dari sebuah pointer ke sebuah pointer).
Jeff Foster

2

Misalnya jika Anda ingin akses acak ke data yang tidak bersebelahan.

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

- dalam C

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

Anda menyimpan sebuah pointer p yang menunjuk ke array pointer. Setiap pointer menunjuk ke sepotong data.

Jika sizeof(T)besar, tidak mungkin untuk mengalokasikan blok yang berdekatan (yaitu menggunakan malloc) sizeof(T) * nbyte.


1
Tidak (!) Perlu membuang hasil malloc di C.
alk

2

Satu hal yang saya gunakan untuk terus-menerus adalah ketika saya memiliki array objek dan saya perlu melakukan pencarian (pencarian biner) pada mereka dengan bidang yang berbeda.
Saya menyimpan array asli ...

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

Kemudian buatlah array pointer yang diurutkan ke objek.

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

Anda dapat membuat sebanyak banyak array pointer yang diurutkan yang Anda butuhkan, kemudian gunakan pencarian biner pada array pointer yang diurutkan untuk mengakses objek yang Anda butuhkan oleh data yang Anda miliki. Array objek asli dapat tetap tidak disortir, tetapi setiap array pointer akan diurutkan berdasarkan bidang yang ditentukan.


2

Mengapa pointer ganda?

Tujuannya adalah untuk mengubah apa yang ditunjuk oleh studentA, menggunakan suatu fungsi.

#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
Tidak (!) Perlu membuang hasil malloc di C.
alk

2

Berikut ini adalah contoh C ++ yang sangat sederhana yang menunjukkan bahwa jika Anda ingin menggunakan fungsi untuk mengatur penunjuk ke titik ke objek, Anda memerlukan penunjuk ke penunjuk . Jika tidak, pointer akan tetap kembali ke nol .

(Jawaban C ++, tapi saya percaya itu sama dalam C.)

(Juga, untuk referensi: Google ("lulus dengan nilai c ++") = "Secara default, argumen dalam C ++ dilewatkan oleh nilai. Ketika sebuah argumen dilewatkan oleh nilai, nilai argumen disalin ke dalam parameter fungsi.")

Jadi kami ingin mengatur pointer bsama dengan string 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

Apa yang terjadi di telepon Function_1(&a, b);?

  • "Nilai" dari &main::a(alamat) disalin ke dalam parameter std::string* Function_1::a. Oleh karena itu Function_1::aadalah penunjuk ke (yaitu alamat memori) string main::a.

  • "Nilai" dari main::b(alamat dalam memori) disalin ke dalam parameter std::string* Function_1::b. Oleh karena itu sekarang ada 2 alamat ini dalam memori, keduanya null pointer. Pada baris tersebut b = a;, variabel lokal Function_1::bkemudian diubah menjadi sama Function_1::a(= &main::a), tetapi variabelnya main::btidak berubah. Setelah panggilan ke Function_1, main::bmasih pointer nol.

Apa yang terjadi di telepon Function_2(&a, &b);?

  • Perlakuan avariabel adalah sama: di dalam fungsi, Function_2::aadalah alamat string main::a.

  • Tetapi variabel bsekarang diteruskan sebagai pointer ke pointer. "Nilai" dari &main::b( alamat penunjuk main::b ) disalin ke std::string** Function_2::b. Oleh karena itu dalam Function_2, dereferencing ini sebagai *Function_2::bakan mengakses dan memodifikasi main::b. Jadi garis *b = a;sebenarnya pengaturan main::b(alamat) sama dengan Function_2::a(= alamat main::a) yang kita inginkan.

Jika Anda ingin menggunakan fungsi untuk memodifikasi sesuatu, baik itu objek atau alamat (pointer), Anda harus meneruskan pointer ke hal itu. Hal yang benar - benar Anda lewati tidak dapat dimodifikasi (dalam lingkup panggilan) karena salinan lokal dibuat.

(Pengecualian adalah jika parameter adalah referensi, seperti std::string& a. Tapi biasanya ini const. Umumnya, jika Anda memanggil f(x), jika xadalah objek Anda harus dapat berasumsi bahwa f tidak akan memodifikasi x. Tetapi jika xadalah pointer, maka Anda harus menganggap bahwa f mungkin memodifikasi objek yang ditunjuk oleh x.)


Kode C ++ untuk menjawab pertanyaan C bukanlah ide terbaik.
alk

1

Sedikit terlambat ke pesta, tapi mudah-mudahan ini akan membantu seseorang.

Dalam array C selalu mengalokasikan memori pada stack, sehingga suatu fungsi tidak dapat mengembalikan array (non-statis) karena fakta bahwa memori yang dialokasikan pada stack dibebaskan secara otomatis ketika eksekusi mencapai akhir blok saat ini. Itu benar-benar menjengkelkan ketika Anda ingin berurusan dengan array dua dimensi (yaitu matriks) dan mengimplementasikan beberapa fungsi yang dapat mengubah dan mengembalikan matriks. Untuk mencapai ini, Anda bisa menggunakan pointer-to-pointer untuk mengimplementasikan matriks dengan memori yang dialokasikan secara dinamis:

/* 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;
} 

Inilah ilustrasi:

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

Double-pointer-to-double-pointer A menunjuk ke elemen pertama A [0] dari blok memori yang elemen-elemennya adalah double-pointer itu sendiri. Anda bisa membayangkan pointer ganda ini sebagai baris dari matriks. Itulah alasan mengapa setiap pointer ganda mengalokasikan memori untuk elemen num_cols bertipe double. Selanjutnya A [i] menunjuk ke baris ke-i, yaitu A [i] menunjuk ke [[]] [0] dan itu hanya elemen-ganda pertama dari blok memori untuk baris ke-i. Akhirnya, Anda dapat mengakses elemen di baris ke-i dan kolom ke-ke-mudah dengan A [i] [j].

Berikut ini adalah contoh lengkap yang menunjukkan penggunaan:

#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

Saya telah menggunakan pointer ganda hari ini ketika saya sedang memprogram sesuatu untuk pekerjaan, jadi saya bisa menjawab mengapa kita harus menggunakannya (ini pertama kali saya benar-benar harus menggunakan pointer ganda). Kami harus berurusan dengan pengkodean frame yang terkandung dalam buffer yang merupakan anggota dari beberapa struktur. Dalam encoder kami harus menggunakan pointer ke salah satu struktur itu. Masalahnya adalah bahwa pointer kita sedang diubah untuk menunjuk ke struktur lain dari utas lainnya. Untuk menggunakan struktur saat ini di encoder, saya harus menggunakan pointer ganda, untuk menunjuk ke pointer yang sedang dimodifikasi di thread lain. Awalnya tidak jelas, setidaknya bagi kami, bahwa kami harus mengambil pendekatan ini. Banyak alamat yang dicetak dalam proses :)).

Anda HARUS menggunakan pointer ganda ketika Anda bekerja pada pointer yang diubah di tempat lain aplikasi Anda. Anda mungkin juga menemukan pointer ganda menjadi suatu keharusan ketika Anda berurusan dengan perangkat keras yang kembali dan alamat kepada Anda.


0

Bandingkan nilai pengubah variabel dengan nilai pengubah pointer :

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

Ini membantu saya untuk menghindari mengembalikan nilai pointer ketika pointer diubah oleh fungsi yang dipanggil (digunakan dalam daftar tertaut tunggal).

LAMA (buruk):

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

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

BARU (lebih baik):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.