Neden çift dolaylı kullanılır? veya İşaretçilere neden işaretçiler kullanılır?


272

C'de çift dolaylı aktarma ne zaman kullanılmalıdır? Herkes bir örnekle açıklayabilir mi?

Bildiğim şey, bir çift dolaylamanın bir işaretçiye bir işaretçi olduğudur. Neden bir işaretçiye bir işaretçiye ihtiyacım var?


49
Dikkatli ol; "çift işaretçi" ifadesi de türü belirtir double*.
Keith Thompson

Bir not alın: bu sorunun cevabı C ve C ++ için farklıdır - bu çok eski soruya c + etiketi eklemeyin.
BЈовић

Yanıtlar:


479

Bir karakter listesi (bir kelime) almak istiyorsanız, char *word

Kelimelerin bir listesini (cümle) istiyorsanız, char **sentence

Cümle listesi (bir monolog) istiyorsanız, kullanabilirsiniz char ***monologue

Monologların (biyografi) bir listesini istiyorsanız, char ****biography

Bir biyografi listesi (bir biyo kütüphane) istiyorsanız, char *****biolibrary

Biyo-kütüphanelerin bir listesini istiyorsanız (a ?? lol), char ******lol

... ...

evet, bunların en iyi veri yapıları olmayabileceğini biliyorum


Çok çok çok sıkıcı bir lol ile kullanım örneği

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

Çıktı:

lolimdeki toplam kelime sayısı: 243

6
Sadece a'nın a arr[a][b][c]olmadığını belirtmek istedim ***arr. İşaretçi işaretçisi referansların referanslarını kullanır arr[a][b][c], sıra ana sırada sıralı bir dizi olarak saklanır.
MCCCS

170

Bunun bir nedeni, bir işleve geçirilen işaretçinin değerini işlev bağımsız değişkeni olarak değiştirmek istemenizdir, bunu yapmak için işaretçiye işaretçi gerekir.

Basit bir ifadeyle, bir işlev çağrısının dışında bile Bellek Ayırma veya Atamayı korumak (VEYA değişikliği tutmak) istediğinizde kullanın **. (Yani, böyle bir işlevi çift pointer arg ile geçirin.)

Bu çok iyi bir örnek olmayabilir, ancak size temel kullanımı gösterecektir:

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

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

14
tahsis olsaydı ne olurdu void allocate(int *p)ve onu şöyle çağırdın allocate(p)?
レ ッ ク ス

@AlexanderSupertramp Evet. Kod segfault olur. Lütfen Silviu'nun Cevabına bakınız.
Abhishek

@Asha tahsis (p) ve tahsis (& p) arasındaki fark nedir?
user2979872

1
@Asha - İşaretçiyi geri getiremez miyiz? Bunu geçersiz kılmalıyız, o zaman bu senaryonun pratik kullanım durumu nedir?
Shabirmean

91
  • Diyelim ki bir işaretçiniz var. Değeri bir adrestir.
  • ama şimdi bu adresi değiştirmek istiyorsunuz.
  • yapabilirdiniz. yaparak pointer1 = pointer2, pointer1'e pointer2 adresini verirsiniz.
  • fakat! bunu bir işlev içinde yaparsanız ve işlev tamamlandıktan sonra sonucun devam etmesini istiyorsanız, fazladan bir iş yapmanız gerekir. yalnızca pointer1'i işaret etmek için yeni bir pointer3'e ihtiyacınız var. pointer3 işlevine iletir.

  • işte bir örnek. anlamak için önce aşağıdaki çıktıya bakın.

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

İşte çıktı: ( önce bunu okuyun )

 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
Bu harika bir cevap ve çift işaretçinin amacını ve kullanışlılığını görselleştirmeme gerçekten yardımcı oldu.
Justin

1
@ Justin, bunun üstündeki cevabımı kontrol ettin mi? onun temizleyici :)
Brian Joseph Spinos

10
Harika yanıt, sadece <code> void cant_change (int * x, int * z) </code> 'un başarısız olduğunu açıklamaktan yoksun çünkü parametreleri' a ve f işaretçileri gibi başlatılmış yeni yerel kapsam işaretçileridir (bu yüzden a ve f ile aynı).
Pedro Reis

1
Basit? Gerçekten mi? ;)
alk

1
Bu cevap gerçekten işaretçiler işaretçi en yaygın kullanımlarından birini açıklar, teşekkürler!
tonyjosi

48

Ekleme Asha'nın Eğer örnek, aşağıda gösterilen tek işaretçi kullanırsanız, yanıt (örn alloc1 ()) Eğer işlevi içinde ayrılan belleğe başvuru kaybedecektir.

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

Bunun gibi olmasının nedeni alloc1, işaretçideki değerin iletilmesidir. Bu nedenle, mallociçindeki çağrı sonucuna yeniden atandığında alloc1, değişiklik farklı bir kapsamdaki kodla ilgili değildir.


1
P statik tamsayı işaretçisi ise ne olur? Segmentasyon hatası alınıyor.
kapilddit

free(p)yetmez yapmanız gerekenler if(p) free(*p)de
Shijing Lv

@ShijingLv: Hayır . 10 değerini tutarak *pdeğerlendirir int, intbunu free () `ye geçirmek kötü bir fikirdir.
alk

Yapılan ayırmada alloc1()bir bellek sızıntısı ortaya çıkar. Serbest bırakılacak işaretçi değeri işlevden dönülerek kaybolur.
alk

(!) Gerek C malloc'un sonucu döküm için
alk

23

Aşağıda, aşağıda özetlediğim gibi, bu blog yayınından çok güzel bir örnek gördüm .

Bağlantılı bir listedeki düğümler için bir yapınız olduğunu düşünün.

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

Şimdi remove_ifbir kaldırma ölçütünü rmbağımsız değişkenlerden biri olarak kabul eden ve bağlantılı listeyi geçen bir işlev uygulamak istiyorsunuz : bir girdi ölçütü karşılarsa (benzer bir şey rm(entry)==true), düğümü listeden kaldırılır. Sonunda, remove_ifbağlı listenin başını (orijinal başından farklı olabilir) döndürür.

Yazabilirsiniz

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

senin kadar fordöngü. İleti, çift ​​işaretçiler olmadanprev , işaretçileri yeniden düzenlemek ve iki farklı durumu ele almak için bir değişken tutmanız gerekir .

Ancak çift işaretçilerle, aslında

// 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Şimdi bir şeyeprev->next ihtiyacınız yok çünkü işaret ettiğiniz şeyi doğrudan değiştirebilirsiniz .

İşleri daha net hale getirmek için kodu biraz izleyelim. Kaldırma işlemi sırasında:

  1. if entry == *head: olacak *head (==*curr) = *head->next- headşimdi yeni başlık düğümünün işaretçisini gösteriyor. Bunu doğrudan headiçeriğini yeni bir işaretçiye değiştirerek yaparsınız .
  2. eğer entry != *head: benzer şekilde, işaret eden ve şimdi işaret eden *currşeydir .prev->nextentry->next

Bu durumda, işaretçileri çift işaretçilerle birleştirilmiş bir şekilde yeniden düzenleyebilirsiniz.


22

1. Temel Kavram -

Aşağıdaki gibi beyan ettiğinizde: -

1. char * ch - (karakter işaretçisi olarak adlandırılır)
- ch, tek bir karakterin adresini içerir.
- (* ch) karakterin değerine bağlı olacaktır.

2. char ** ch -
'ch', bir karakter işaretçisi dizisinin adresini içerir. (1'deki gibi)
'* ch' tek bir karakterin adresini içerir. (Beyandaki farktan dolayı 1'den farklı olduğunu unutmayın).
(** ch), karakterin tam değerine bağlı olacaktır.

Daha fazla işaretçi eklemek, bir veri tipinin boyutunu karakterden dizeye, dizelerden oluşan diziye kadar genişletir ... 1d, 2d, 3d matrisle ilişkilendirebilirsiniz.

Dolayısıyla, işaretçinin kullanımı onu nasıl bildirdiğinize bağlıdır.

İşte basit bir kod ..

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. Çift İşaretçilerin Başka Bir Uygulaması -
(bu aynı zamanda referans ile geçişi de kapsayacaktır)

Bir fonksiyondan bir karakteri güncellemek istediğinizi varsayalım. Aşağıdakileri denerseniz: -

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

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

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

Çıkış AA olacaktır. Bu, işlev için "Değere Göre Geçti" olarak çalışmaz.

Bunu yapmanın doğru yolu -

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

Şimdi karakter yerine bir dizeyi güncellemek için bu gereksinimi uzatın.
Bunun için işlevdeki parametreyi çift işaretçi olarak almanız gerekir.

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

Bu örnekte, yöntem, bir dizenin değerini güncellemek için parametre olarak bir çift işaretçi bekler.


#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; } Ancak çift işaretçiyi kullanmadan da yapabilirsiniz.
kumar

" char ** ch - 'ch', bir karakter işaretçisi dizisinin adresini içerir . " Hayır, bir charişaretçi dizisinin 1. öğesinin adresini içerir . Bir dizinin işaretçisi char*örneğin şöyle yazılabilir: 42 işaretçi dizisinin işaretçisi olarak char(*(*p)[42])tanımlar . pchar
alk

Son pasaj tamamen kırılmış. Yeni başlayanlar için: Burada *str = ... strtanımlanmamış davranışı çağıran kayıttan çıkarılmış, başlatılmamış.
alk

Bu malloc(sizeof(char) * 10);10 işaretçiye yer ayırmaz, sadece char10'a yer char
alk

Bu döngü for(i=0;i<10;i++) { str = ... dizini kullanmak için özlüyor i.
alk

17

İşaretçilerden işaretleyicilere, yeniden konumlandırılabilir belleğe işlevler arasında bir "tutamacın" etrafından geçmek istediğiniz bellekte "tutamaçlar" olarak da kullanışlı olurlar. Bu, temel olarak işlevin tanıtıcı değişkenin içindeki işaretçiyle gösterilen belleği değiştirebileceği anlamına gelir ve tanıtıcıyı kullanan her işlev veya nesne yeni taşınan (veya ayrılan) belleği düzgün bir şekilde gösterecektir. Kütüphaneler bunu "opak" veri türleriyle yapmayı severler, yani veri türleri, işaret edilen bellekle ne yaptıklarından endişelenmenize gerek kalmadıysa, o bellekte bazı işlemleri gerçekleştirmek için kütüphanenin fonksiyonları ...

Örneğin:

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

Bu yardımcı olur umarım,

Jason


Tutamak türünün imzasız karakterin ** olmasının nedeni nedir? Hiçlik de aynı şekilde işe yarar mı?
Connor Clark

5
unsigned charözellikle kullanılır, çünkü ham bayt olarak temsil edilecek ikili verilere bir işaretçi depolarız. Kullanmak voidbir noktada alçı gerektirir ve genellikle ne yapıldığının amacı kadar okunabilir değildir.
Jason

7

Muhtemelen daha önce birçok kez gördüğünüz basit bir örnek

int main(int argc, char **argv)

İkinci parametrede: işaretçi - işaretçi.

İşaretçi gösteriminin ( char* c) ve dizi gösteriminin ( char c[]) işlev bağımsız değişkenlerinde değiştirilebilir olduğunu unutmayın. Böylece yazabilirsiniz char *argv[]. Başka bir deyişle char *argv[]ve char **argvdeğiştirilebilir.

Yukarıdakilerin gösterdiği şey aslında bir dizi karakter dizisidir (başlangıçta bir programa verilen komut satırı bağımsız değişkenleri).

Ayrıca bkz bu cevabı yukarıdaki fonksiyon imza ile ilgili daha fazla ayrıntı için.


2
char* cchar c[]işlev argümanlarında "işaretçi gösterimi ( ) ve dizi gösterimi ( ) değiştirilebilir" (ve aynı anlama sahiptir) . Ancak, işlev argümanlarının dışında farklıdırlar.
pmg

6

Dizeler, çift işaretçilerin kullanımına mükemmel bir örnektir. Dizenin kendisi bir işaretçi olduğundan, bir dizeyi işaretlemeniz gerektiğinde çift işaretçiye ihtiyacınız olacaktır.


5

Örneğin, bir şeyin belleğini boşalttığınızda işaretçiyi daha sonra null değerine ayarladığınızdan emin olmak isteyebilirsiniz.

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

Bu işlevi çağırdığınızda, bir işaretçi adresiyle çağırırsınız

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

şimdi myMemory NULL olarak ayarlanmıştır ve yeniden kullanma girişimleri çok yanlış olacaktır.


1
olmalı if(*memory)vefree(*memory);
Asha

1
İyi nokta, beyin ve klavye arasındaki sinyal kaybı. Biraz daha mantıklı olacak şekilde düzenledim.
Jeff Foster

Neden aşağıdakileri yapamıyoruz ... void safeFree (void * memory) {if (memory) {free (memory); bellek = NULL; }}
Peter_pk

@Peter_pk null değerine bellek atamak, bir işaretçiyi referansla değil değere göre geçtiğiniz için işe yaramaz (dolayısıyla bir işaretçiye işaretçi örneği).
Jeff Foster

2

Örneğin bitişik olmayan verilere rasgele erişim istiyorsanız.

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

- C cinsinden

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

Bir işaretçi depolarsınız p dizisini gösteren . Her işaretçi bir veri parçasını gösterir.

Eğer sizeof(T)büyük bunun (yani malloc kullanarak) bitişik bir bloğun yerleştirilmesi mümkün olmayabilir sizeof(T) * nbayt.


1
(!) Gerek C malloc'un sonucu döküm için
alk

2

Onları sürekli olarak kullandığım bir şey, bir dizi nesneye sahip olduğum ve farklı alanlar tarafından aramaları (ikili arama) gerçekleştirmem gerektiğinde.
Orijinal diziyi saklıyorum ...

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

Ardından nesnelere sıralı işaretçiler dizisi yapın.

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

İhtiyacınız olan sayıda sıralanmış işaretçi dizisi yapabilir, ardından ihtiyacınız olan nesneye sahip olduğunuz verilere erişmek için sıralanmış işaretçi dizisinde ikili bir arama kullanabilirsiniz. Orijinal nesne dizisi ayrılmadan kalabilir, ancak her işaretçi dizisi belirtilen alanlarına göre sıralanır.


2

Neden çift işaretçiler?

Amaç, bir işlevi kullanarak studentA'nın işaret ettiği şeyi değiştirmektir.

#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
(!) Gerek C malloc'un sonucu döküm için
alk

2

Aşağıda, bir nesneyi gösterecek bir işaretçi ayarlamak için bir işlev kullanmak istiyorsanız, bir işaretçiye bir işaretçiye ihtiyacınız olduğunu gösteren çok basit bir C ++ örneğidir . Aksi takdirde, işaretçi null değerine geri dönmeye devam eder .

(C ++ yanıtı, ancak C'de aynı olduğuna inanıyorum.)

(Ayrıca, başvuru için: Google ("c ++ değerine göre geç") = "Varsayılan olarak, C ++ içindeki bağımsız değişkenler değere göre iletilir. Bir bağımsız değişken değere göre iletildiğinde, bağımsız değişkenin değeri işlevin parametresine kopyalanır.")

Bu yüzden işaretçiyi bdizeye eşit ayarlamak istiyoruz 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

Hatta ne olur Function_1(&a, b);?

  • &main::aBir adresin "değeri" parametreye kopyalanır std::string* Function_1::a. Bu nedenle Function_1::a, dizenin işaretçisi (yani bellek adresi) main::a.

  • main::b(Bellekteki bir adres) öğesinin "değeri" parametreye kopyalanır std::string* Function_1::b. Bu nedenle, bellekte bu adreslerden 2 tanesi var, her ikisi de boş gösterici. Satırda b = a;, yerel değişken Function_1::bdaha sonra eşittir Function_1::a(= &main::a), ancak değişken main::bdeğişmez. Çağrısının ardından Function_1, main::byine bir boş göstericidir.

Hatta ne olur Function_2(&a, &b);?

  • aDeğişkenin işlenmesi aynıdır: fonksiyon Function_2::aiçinde, dizenin adresidir main::a.

  • Ancak değişken bşimdi bir işaretçiye işaretçi olarak aktarılıyor. Öğesinin "değeri" &main::b( işaretçinin adresi main::b ) içine kopyalanır std::string** Function_2::b. Bu nedenle, Function_2 içinde *Function_2::berişim ve değişiklik olarak bu kayıttan çıkarılması main::b. Yani çizgi *b = a;aslında main::b(adres) Function_2::a(= adres ) 'e eşit main::aolanı istiyoruz.

Bir şeyi değiştirmek için bir işlev kullanmak istiyorsanız, nesne veya adres (işaretçi) olsun, o öğeye bir işaretçi iletmeniz gerekir. Yerel bir kopya yapıldığı için, gerçekte ilettiğiniz şey değiştirilemez (arama kapsamında).

(Parametre referans gibi, eğer bir istisna oluşturur std::string& a. Ama genelde bunlar const. Genellikle, ararsanız f(x)eğer xbir nesnedir sen varsaymak gerekir f olmayacaktır değiştirmek x. Ama eğer xbir gösterici, o zaman gerekir varsaymak f olabilir işaret ettiği nesneyi değiştirmek x.)


Bir C sorusunu cevaplamak için C ++ kodu en iyi fikir değildir.
alk

1

Partiye biraz geç, ama umarım bu birine yardım eder.

C dizilerinde her zaman yığına bellek ayırır, bu nedenle bir işlev (statik olmayan) bir dizi döndüremez, çünkü yürütme geçerli bloğun sonuna ulaştığında yığına ayrılan belleğin otomatik olarak boşalması sağlanır. İki boyutlu dizilerle (yani matrislerle) uğraşmak ve matrisleri değiştirebilen ve geri döndürebilen birkaç işlevi uygulamak istediğinizde bu gerçekten sinir bozucu. Bunu başarmak için, dinamik olarak ayrılmış belleğe sahip bir matris uygulamak için bir işaretçi-işaretçi kullanabilirsiniz:

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

İşte bir örnek:

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

Çift-işaretçi-çift-işaretçi A, öğeleri çift işaretçi olan bir bellek bloğunun ilk elemanını A [0] işaret eder. Bu çift işaretçileri matrisin satırları olarak hayal edebilirsiniz. Her çift işaretçinin double türündeki num_cols öğeleri için bellek ayırmasının nedeni budur. Ayrıca A [i] i-sıraya işaret eder, yani A [i] A [i] [0] 'ya işaret eder ve bu, i-sıraya ait bellek bloğunun sadece ilk çift elemanıdır. Son olarak, i [d] ve j dördüncü sütundaki öğeye A [i] [j] ile kolayca erişebilirsiniz.

Kullanımı gösteren tam bir örnek:

#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

İş için bir şey programlarken bugün çift işaretçiler kullandım, bu yüzden onları neden kullanmak zorunda olduğumuzu cevaplayabilirim (aslında ilk kez çift işaretçiler kullanmak zorunda kaldım). Bazı yapıların üyesi olan tamponlarda bulunan karelerin gerçek zamanlı kodlanmasıyla uğraşmak zorunda kaldık. Kodlayıcıda bu yapılardan birine bir işaretçi kullanmak zorunda kaldık. Sorun, işaretçimizin başka bir iş parçacığından diğer yapılara işaret edecek şekilde değiştirilmesiydi. Kodlayıcıdaki mevcut yapıyı kullanmak için, başka bir iş parçacığında değiştirilen işaretçiye işaret etmek için bir çift işaretçi kullanmak zorunda kaldım. İlk başta, en azından bizim için bu yaklaşımı benimsememiz gerektiği belli değildi. Bu süreçte çok sayıda adres basıldı :)).

Uygulamanızın diğer yerlerinde değiştirilen işaretçiler üzerinde çalışırken çift işaretçiler KULLANMALISINIZ. Ayrıca, size geri dönen ve adreslenen donanımlarla uğraşırken çift işaretçiler de bulunmalıdır.


0

Modifiye karşılaştır değişkeninin değerini değiştirerek karşılık işaretçi değerini :

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

Bu, işaretçi çağrılan işlevle (tekli bağlantılı listede kullanılır) değiştirildiğinde işaretçi değerini döndürmeme engel oldu.

ESKİ (kötü):

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

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

YENİ (daha iyi):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.