İşaretçiden işaretçiye açıklama


142

Bir işaretçi bir işaretçi nasıl çalışır hakkında bu öğretici takip ediyordu .

İlgili pasajı alıntılayayım:


    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;

Şimdi ayarlayabiliriz

    int **ipp = &ip1;

ve ippişaret ettiği ip1için hangi puan i. *ippolduğu ip1ve **ippolduğu iBöyle, bizim tanıdık kutu ve ok gösterimi ile durumu tasvir edebilir, ya da 5.:

resim açıklamasını buraya girin

O zaman deriz ki

    *ipp = ip2;

bir kopyasını içerecek şekilde ipp(yani ip1) işaret eden işaretçiyi değiştirdik ip2, böylece ( ip1) şimdi şuna işaret ediyor j:

resim açıklamasını buraya girin


Sorum şu: İkinci resimde neden ipphala işaret ediyor ip1ama değil ip2?


6
lütfen, ipptanımlarken türünü ekleyin , böylece sorunuz tamamlandı ;-)
zmo

1
Çünkü ippişaret ip1öylesine *ipp = ip2aynıdırip1 = ip2
Grijesh Chauhan

1
Lütfen sivri tipten uzak yıldızları yazmayı bırakabilir miyiz? özellikle tür bildirimleri dışında tamamen farklı bir şey ifade ettiğinden int **ippçok daha az sezgiseldir . int** ipp**ipp
Darkhogg

4
@Darkhogg int **ippbenim için oldukça sezgisel görünüyor. Bu demek oluyor ki, ben **ippbir int. Hangisi doğru.
ithisa

2
tipi: @ user54609 anlamları ve birbirinden kişisel sezgi, bu düşünün ippolduğunu int**, bu yüzden sadece yazma int**yerine büyülü " impKQUEUE int'dir" anlayışlar .
Manu343726

Yanıtlar:


143

İşaret benzetmesi hakkında bir saniye unutun. İşaretçinin gerçekten içerdiği şey bir bellek adresidir. &Operatör "adresinin" - bu bir nesnenin bellekte adresini döndürür yani. *Operatör o bellek adresinde nesnesi döndüren bir işaretçi, bir adres içeren bir işaretçi verilen yani belirtir size nesne sağlar. Bunu yaptığında Yani *ipp = ip2, ne yaptığınızı çok *ipptutulan adreste nesneyi almak içinde ipphangi ip1ardından atamak ip1saklanan değerin ip2adresi, j.

Basitçe
& -> Adres
*- -> Değer


14
& ve * hiç bu kadar kolay olmamıştı
Ray

7
Ana karışıklık kaynağının, değişkenin gerçekte belirli bir veri türüne işaretçi olduğunu göstermek için değişken bildirimi sırasında kullanılan * operatörünün belirsizliğinden kaynaklandığına inanıyorum. Ancak, öte yandan, ifadelerde, bir işaretçi (dereferencing operatörü) ile gösterilen değişkenin içeriğine erişmek için de kullanılır.
Lucas A.

43

Çünkü işaret ettiğiniz ippdeğeri değerine değil değiştirdiniz ipp. Yani, ipphala ip1(değerini ipp) gösteriyor, ip1değeri artık değeriyle aynı ip2, her ikisi dej .

Bu:

*ipp = ip2;

aynıdır:

ip1 = ip2;

11
Değerinde arasındaki farkı işaret olabilir int *ip1 = &ive *ipp = ip2;siz kaldırırsanız, yani intilk ifadeden sonra atamaları çok benzer, ancak *iki durumda çok farklı bir şey yapıyor.
Crowman

22

C etiketindeki çoğu başlangıç ​​sorusu gibi, bu soru ilk ilkelere dönülerek cevaplanabilir:

  • İşaretçi bir tür değerdir.
  • Değişken bir değer içerir.
  • &Operatör bir işaretçi bir değişken döner.
  • *Operatör bir değişken bir işaretçi döner.

(Teknik olarak "değişken" yerine "lvalue" demeliyim, ancak değiştirilebilir depolama konumlarını "değişkenler" olarak tanımlamanın daha açık olduğunu hissediyorum.)

Yani değişkenlerimiz var:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

Değişken bir işaretçi ip1 içerir . &Operatör döner ibir işaretçisi ve bu işaretçi değeri atanmıştır ip1. Yani bir işaretçi ip1 içeriri .

Değişken bir işaretçi ip2 içerir . &Operatör döner jbir işaretçisi ve bu işaretçi atanır ip2. Yani bir işaretçi ip2 içerirj .

int **ipp = &ip1;

Değişken ippbir işaretçi içerir. &Bulunan operatör değişken döner ip1bir işaretçisi ve bu işaretçi değeri atanmıştır ipp. Yani ippbir işaretçi içerir ip1.

Şimdiye kadar hikayeyi özetleyelim:

  • i 5 içerir
  • j 6 içerir
  • ip1"işaretçi i" içeriyor
  • ip2"işaretçi j" içeriyor
  • ipp"işaretçi ip1" içeriyor

Şimdi diyoruz

*ipp = ip2;

*Operatör bir değişkene bir işaretçi geri döner. Biz ipp"işaretçisi olan ip1ve değişkene dönüştüren " değerini alırız . Hangi değişken?ip1 Tabii ki!

Bu yüzden bu sadece söylemenin başka bir yolu

ip1 = ip2;

Böylece değerini alırız ip2. Bu ne? msgstr "işaretçi j". Biz bu işaretçi değeri atamak ip1yüzden, ip1"işaretçi için şimdij "

Sadece bir şeyi değiştirdik ip1:

  • i 5 içerir
  • j 6 içerir
  • ip1"işaretçi j" içeriyor
  • ip2"işaretçi j" içeriyor
  • ipp"işaretçi ip1" içeriyor

Neden ipphala işaret ediyor ip1, değil ip2?

Değişken atandığında değişir. Ödevleri sayın; Değişkenlerde atamalardan daha fazla değişiklik olamaz! Sen hiç atayarak başlar i, j, ip1, ip2ve ipp. Sonra atarsınız ki *ipp, gördüğümüz gibi "ata" ile aynı anlama gelir ip1. ippİkinci kez atamadığınız için değişmedi!

Değiştirmek ippistiyorsanız, aslında aşağıdakilere atamanız gerekir ipp:

ipp = &ip2;

Örneğin.


21

umarım bu kod parçası yardımcı olabilir.

#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;
    int** ipp = &ip1;
    printf("address of value i: %p\n", &i);
    printf("address of value j: %p\n", &j);
    printf("value ip1: %p\n", ip1);
    printf("value ip2: %p\n", ip2);
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
    *ipp = ip2;
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
}

çıktılar:

resim açıklamasını buraya girin


12

Benim kişisel görüşüm, okları bu şekilde gösteren veya işaretçileri anlamayı zorlaştıran resimlerdir. Bazı soyut, gizemli varlıklar gibi görünmelerini sağlıyor. Onlar değil.

Bilgisayarınızdaki diğer her şey gibi, işaretçiler de sayılardır . "İşaretçi" adı, "adres içeren bir değişken" demenin şık bir yoludur.

Bu nedenle, bir bilgisayarın gerçekte nasıl çalıştığını açıklayarak işleri karıştırmama izin verin.

Bizde int, adı ive 5 değeri var. Bu, bellekte saklanır. Bellekte saklanan her şey gibi, bir adrese ihtiyacı vardır, yoksa bulamazdık. Diyelim ki i0x12345678 adresi ve arkadaşıj 6 değeri ile hemen hemen bitiyor. İnt'in 4 bayt ve işaretçilerin 4 bayt olduğu 32 bit CPU varsayarsak, değişkenler fiziksel bellekte şu şekilde saklanır:

Address     Data           Meaning
0x12345678  00 00 00 05    // The variable i
0x1234567C  00 00 00 06    // The variable j

Şimdi bu değişkenlere işaret etmek istiyoruz. İnt int* ip1ve için bir işaretçi oluşturuyoruz int* ip2. Bilgisayardaki her şey gibi, bu işaretçi değişkenleri de bellekte bir yere ayrılır. Diyelim ki, hemen sonra, bellekteki bir sonraki bitişik adreslere ulaşacaklar j. İşaretçileri önceden tahsis edilen değişkenlerin adreslerini içerecek şekilde ayarladık: ip1=&i;("i'nin adresini ip1'e kopyala") ve ip2=&j. Çizgiler arasında ne olur:

Address     Data           Meaning
0x12345680  12 34 56 78    // The variable ip1(equal to address of i)
0x12345684  12 34 56 7C    // The variable ip2(equal to address of j)

Yani elimizde sadece 4 baytlık rakamlar içeren bellek parçaları vardı. Görünürde hiçbir yerde mistik veya büyülü ok yoktur.

Aslında, sadece bir bellek dökümü bakarak, 0x12345680 adresinde bir intveya olup olmadığını söyleyemeyiz int*. Fark, programımızın bu adreste depolanan içeriği nasıl kullanmayı seçmesidir. (Programımızın görevi aslında CPU'ya bu sayılarla ne yapacağını söylemektir.)

Sonra başka bir dolaylılık düzeyi ekliyoruz int** ipp = &ip1;. Yine, sadece bir yığın bellek alıyoruz:

Address     Data           Meaning
0x12345688  12 34 56 80    // The variable ipp

Desen tanıdık geliyor. Yine bir sayı içeren 4 baytlık başka bir yığın.

Şimdi, yukarıdaki kurgusal küçük RAM'in bir bellek dökümü olsaydı, bu işaretçilerin nereye işaret ettiğini elle kontrol edebiliriz. ippDeğişkenin adresinde nelerin depolandığına bakarız ve 0x12345680 içeriğini buluruz. Hangi tabii ki ip1saklandığı adres . Bu adrese gidebilir, oradaki içeriği kontrol edebilir ve adresini bulabilirizi ve sonunda o adrese gidip 5 sayısını bulabiliriz.

Eğer ipp içeriğini *ippalırsak, işaretçi değişkeninin adresini alırız ip1. Yazarak *ipp=ip2ip2'yi ip1'e kopyalarız ip1=ip2. Her iki durumda da

Address     Data           Meaning
0x12345680  12 34 56 7C    // The variable ip1
0x12345684  12 34 56 7C    // The variable ip2

(Bu örnekler büyük bir endian CPU için verilmiştir)


5
Ne demek istediğimi anlasam da, işaretçileri soyut, gizemli varlıklar olarak düşünmenin değeri var. İşaretçilerin herhangi bir özel uygulaması sadece rakamlardır, ancak çizdiğiniz uygulama stratejisi bir uygulamanın bir gereği değildir , sadece ortak bir stratejidir. İşaretçilerin int ile aynı boyutta olması gerekmez, işaretçilerin düz sanal bellek modelinde adres olması gerekmez vb. bunlar sadece uygulama detaylarıdır.
Eric Lippert

@EricLippert Sanırım gerçek bellek adresleri veya veri blokları kullanmadan bu örneği daha soyut hale getirebiliriz. location, value, variableKonumun 1,2,3,4,5ve değerin olduğu gibi bir şey belirten bir tablo olsaydı A,1,B,C,3, karşılık gelen işaretçi fikri, doğası gereği kafa karıştırıcı olan oklar kullanılmadan kolayca açıklanabilirdi. Hangi uygulama seçilirse seçilsin, bir yerde bir değer vardır ve bu, oklarla modelleme yaparken gizlenen bulmacanın bir parçasıdır.
MirroredFate

@EricLippert Deneyimlerime göre, işaretçileri anlamada sorun yaşayan C programcılarının çoğu soyut, yapay modeller ile beslenenlerdir. Soyutlama yararlı değildir , çünkü bugün C dilinin tüm amacı donanıma yakın olmasıdır. C öğreniyor ancak donanıma yakın kod yazmak istemiyorsanız zamanınızı boşa harcıyorsunuz demektir . Bilgisayarların nasıl çalıştığını bilmek istemiyorsanız, ancak yüksek düzeyde programlama yapmak istiyorsanız Java vb. Çok daha iyi bir seçimdir.
Lundin

@EricLippert Ve evet, işaretçilerin adreslere mutlaka karşılık gelmediği çeşitli işaretçi uygulamaları mevcut olabilir. Ancak ok çizmek, bunların nasıl çalıştığını anlamanıza yardımcı olmaz. Bir noktada soyut düşünceden ayrılmalı ve donanım seviyesine inmelisiniz, aksi takdirde C kullanmamalısınız. Tamamen soyut yüksek seviyeli programlama için tasarlanmış çok daha uygun, modern diller vardır.
Lundin

@Lundin: Ben de ok diyagramlarının büyük bir hayranı değilim; veri olarak ok kavramı aldatıcıdır. Bunu soyut olarak ama oklar olmadan düşünmeyi tercih ederim. &Bir değişkeni operatör sana o değişkeni temsil eden bir sikke verir. Bu *madeni para üzerindeki operatör, değişkeni size geri verir. Ok gerekmez!
Eric Lippert

8

Ödevlere dikkat edin:

ipp = &ip1;

Sonuçlar ippişaret edecek ip1.

böylece için ippnoktaya kadar ip2, benzer şekilde değiştirmelisiniz,

ipp = &ip2;

ki bunu açıkça yapmıyoruz. Bunun yerine , adresin işaret ettiği değeri değiştiriyoruz ipp.
Aşağıdakileri yaparak

*ipp = ip2;

sadece depolanan değeri değiştiriyoruz ip1.

ipp = &ip1, anlamı *ipp = ip1 = &i,
Şimdi *ipp = ip2 = &j,.
Yani, *ipp = ip2aslında aynı ip1 = ip2.


5
ipp = &ip1;

Hiçbir ödevin değeri değişmedi ipp. Bu yüzden hala işaret ediyor ip1.

Sizin *ipp, yani onunla yaptığınız şey ip1, ippişaret ettiği gerçeği değiştirmez ip1.


5

Sorum şu: Neden ikinci resimde ipp hala ip1'i gösteriyor ama ip2'yi değil?

güzel resimler yerleştirdiniz, güzel ascii sanatı yapmaya çalışacağım:

@ Robert-S-Barnes'ın cevabında söylediği gibi: işaretçileri ve neyi işaret ettiğini unutun , ancak bellek açısından düşünün. Temel olarak, bir int*değişkenin adresini ve bir değişkenin adresini içeren bir değişkenin adresini içerdiği anlamına gelir int**. Ardından, değerlere veya adreslere erişmek için işaretçinin cebirini kullanabilirsiniz: &fooaraç address of foove *fooaraç value of the address contained in foo.

Dolayısıyla, işaretçiler bellekle uğraşmakla ilgili olduğundan, bunu gerçekten “somut” hale getirmenin en iyi yolu, işaretleyicilerin cebire ne yaptığını göstermektir.

Yani, programınızın belleği (örnek amacıyla basitleştirilmiştir):

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [   |   |   |   |   ]

ilk kodunuzu yaptığınızda:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

hafızanız şöyle görünür:

name:    i   j ip1 ip2
addr:    0   1   2   3
mem : [  5|  6|  0|  1]

Orada görebilirsiniz ip1ve ip2adreslerini alır ive jve ipphala da mevcut değil. Adreslerin yalnızca özel bir türle saklanan tamsayılar olduğunu unutmayın.

Sonra beyan edersiniz ve tanımlanırsınız ipp:

int **ipp = &ip1;

İşte hafızanız:

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  0|  1|  2]

ve o zaman, saklanan adresi ile işaret değerini değiştiriyoruz ippsaklanan adresidir ip1:

*ipp = ip2;

programın hafızası

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  1|  1|  2]

Not: int*özel bir tür olduğu gibi , her zaman aynı satırda birden fazla işaretçi bildirmekten kaçınmayı tercih ederim, çünkü int *x;ya da int *x, *y;gösterim yanıltıcı olabilir. Yazmayı tercih ederimint* x; int* y;

HTH


senin örnek ile, başlangıç değeri ip2olmalıdır 3değil 4.
Dipto

1
oh, hafızayı değiştirdim, bu yüzden bildirim sırasına uyuyor. Sanırım bunu düzelttim mi?
zmo

5

Çünkü dediğin zaman

*ipp = ip2

işaret eden ippbelleğin yönünü işaret etmek için 'ile gösterilen nesne' diyorsunuz ip2.

Göstermeye ippdemiyorsun ip2.


4

Dereference operatörünü eklerseniz *İşaretçiye işaretçiden sivri uçlu nesneye yönlendirirsiniz.

Örnekler:

int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
             //     it's not the dereference operator in this context
*p;          // <-- this expression uses the pointed-to object, that is `i`
p;           // <-- this expression uses the pointer object itself, that is `p`

Bu nedenle:

*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
            //     therefore, `ipp` still points to `ip1` afterwards.

3

Eğer ippişaret etmek ip2isterseniz, söylemelisiniz ipp = &ip2;. Ancak, bu ip1hala işaret eder i.


3

Çok başlangıçta,

ipp = &ip1;

Şimdi dereference,

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 

3

Her değişkenin şu şekilde temsil edildiğini düşünün:

type  : (name, adress, value)

yani değişkenleriniz şu şekilde temsil edilmelidir

int   : ( i ,  &i , 5 ); ( j ,  &j ,  6); ( k ,  &k , 5 )

int*  : (ip1, &ip1, &i); (ip1, &ip1, &j)

int** : (ipp, &ipp, &ip1)

Değeri ippolduğu &ip1gibi talimat:

*ipp = ip2;

addess'teki &ip1değeri değerine değiştirir ip2, yani ip1değiştirilir:

(ip1, &ip1, &i) -> (ip1, &ip1, &j)

Ama ippyine de:

(ipp, &ipp, &ip1)

Yani ipphareketsizliğin değeri, hâlâ işaret &ip1ettiği anlamına gelir ip1.


1

Çünkü imlecini değiştiriyorsun *ipp. Anlamı

  1. ipp (değişken isim) ---- içeri gir.
  2. içinde ippadresiip1 .
  3. şimdi *ippgit (iç adres) ip1.

Şimdi buradayız ip1. *ipp(ie ip1) = ip2.
ip2içerdiği adres j.so ip1içeriği ip2 (yani j adresi) ile değiştirilecek, ippİÇERİĞİ DEĞİŞTİRMİYORUZ . BU KADAR.


1

*ipp = ip2; ima:

İle ip2gösterilen değişkeni atayın ipp. Yani bu şuna eşdeğerdir:

ip1 = ip2;

Adresinin ip2saklanmasını istiyorsanız şunları yapın ipp:

ipp = &ip2;

Şimdi ippişaret ediyor ip2.


0

ippbir işaretçiyi işaretçi türü nesnesinin bir değerini (yani işaret) tutabilir . Ne zaman yaparsın

ipp = &ip2;  

Daha sonra ippiçeren değişken adresini (pointer)ip2 olduğu, ( &ip2tipi) işaretçisi işaretçi . Şimdi ippikinci resimdeki okip2 .

Wiki söyler: operatör KQUEUE operatör işaretçi değişkeni üzerinde çalışır ve bir döner L-değeri işaretçi adresinde değerine eşdeğer (değişken). Buna, işaretçinin kaydı silme denir.
*

Uygulama *ile ilgili operatör ippbir L-değerine derefrence işaretçiyeint yazın. Dereferenced l-değeri *ipptür işaretçisidirint , bir inttür verisinin adresini tutabilir . Açıklamadan sonra

ipp = &ip1;

ippadresini ip1ve *ippadresini (işaret ediyor) tutuyor i. Bunun *ippbir takma adı olduğunu söyleyebilirsiniz ip1. Hem **ippve *ip1için takma vardır i.
Yaparak

 *ipp = ip2;  

*ippve ip2her ikisi de aynı yeri gösteriyor ancak ipphala işaret ediyor ip1.

Ne *ipp = ip2;aslında yaptığı kopyalar içeriği olmasıdır ip2(adresine jkadar) ip1(aynı *ippiçin bir takma ad ip1yürürlükte), hem işaretçileri yapma ip1ve ip2aynı nesneye işaret ( j).
Yani, ikinci şekilde, bir ok ip1ve ip2işaret ediyorj ederken ipp, hala ip1değerini değiştirmek için hiçbir değişiklik yapılmadığına işaret ediyoripp .

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.