Yeni başlayanlara C işaretçileri (deklarasyon, tekli operatörler) nasıl açıklanır?


141

Bir C programlama acemi için işaretçiler açıklamak için son bir zevk oldu ve aşağıdaki zorluk tökezledi. İşaretçileri nasıl kullanacağınızı zaten biliyorsanız bir sorun gibi görünmeyebilir, ancak aşağıdaki örneğe açık bir şekilde bakmaya çalışın:

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

Mutlak yeni başlayanlar için çıktı şaşırtıcı olabilir. 2. satırda * bar 'ı foo olarak ilan etmişti, ama 4. satırda * bar aslında & foo yerine foo!

Diyelim ki, karışıklık * sembolünün belirsizliğinden kaynaklanıyor: 2. satırda bir işaretçi bildirmek için kullanılır. 4. satırda, işaretçinin işaret ettiği değeri alan tekli bir operatör olarak kullanılır. İki farklı şey, değil mi?

Ancak, bu "açıklama" yeni başlayanlara hiç yardımcı olmaz. İnce bir tutarsızlığa işaret ederek yeni bir konsept ortaya koyuyor. Bu öğretmenin doğru yolu olamaz.

Peki, Kernighan ve Ritchie bunu nasıl açıkladı?

Tekli operatör * dolaylı veya kayıttan çıkarma operatörüdür; bir işaretçiye uygulandığında, işaretçinin işaret ettiği nesneye erişir. [...]

İp işaretçisi bildirimi, int *ipbir anımsatıcı olarak tasarlanmıştır; ifadenin *ipbir int olduğunu söylüyor . Bir değişkene ilişkin bildirimin sözdizimi, değişkenin görünebileceği ifadelerin sözdizimini taklit eder .

int *ip" *ipdönecektir " gibi okunmalıdır int? Peki, bildirgeden sonraki ödev neden bu kalıba uymuyor? Bir acemi değişkeni başlatmak isterse ne olur? int *ip = 1(okuma: bir *ipdöner intve intis 1) beklendiği gibi çalışmaz. Kavramsal model tutarlı görünmüyor. Burada bir şey mi eksik?


Edit: Buradaki cevapları özetlemeye çalıştı .


15
En iyi açıklama, şeyleri bir kağıda
çizip

16
*İşaretçiler sözdizimini açıklamak zorunda kaldığımda, her zaman bir bildirgede "bir işaretçi bildir" anlamına gelen bir belirteç olduğu, ifadelerde dereference operatörü olduğu ve bu ikisinin aynı simgeye sahip olan farklı şeyleri temsil ettiği konusunda ısrar ettim. (çarpma operatörü ile aynı - aynı sembol, farklı anlam). Kafa karıştırıcı, ama gerçek durumdan farklı bir şey daha da kötü olacak.
Matteo Italia

40
belki int* barde yıldızın tanımlayıcının bir parçası değil, aslında türün bir parçası olduğunu daha açık hale getirir. Tabii ki bu sizi sezgisel olmayan şeylerle farklı problemlere sokar int* a, b.
Niklas

9
Her zaman K&R açıklamasının aptalca ve gereksiz olduğunu düşündüm. Dil, iki farklı şey için aynı sembolü kullanır ve bununla başa çıkmak zorundayız. *bağlama bağlı olarak iki farklı anlamı olabilir. Aynı harfin, içinde olduğu gibi birçok dili konuşmayı öğrenmeyi zorlaştıran kelimeye bağlı olarak farklı telaffuz edilebilmesi gibi. Her bir kavramın / işlemin kendi sembolü olsaydı, çok daha büyük klavyelere ihtiyacımız olurdu, bu yüzden semboller bunu yapmak mantıklı olduğunda geri dönüştürülür.
sanat

8
Aynı problemi başkalarına C öğretirken birçok kez karşılaştım ve tecrübelerime göre, buradaki çoğu insanın önerdiği şekilde çözülebilir. İlk olarak, C sözdizimi olmayan bir işaretçi kavramını açıklayın. Daha sonra, sözdizimini öğretin ve yıldız türünü ( int* p) türünün bir parçası olarak vurgulayın ve öğrencinizi işaretçiler söz konusu olduğunda aynı satırda birden çok bildirim kullanmaya karşı uyarın. Öğrenci işaretçi kavramını tamamen anladığında, öğrenciye int *psözdiziminin eşdeğer olduğunu açıklayın ve ardından sorunu birden fazla bildirimle açıklayın.
Theodoros Chatzigiannakis

Yanıtlar:


43

Öğrencinizin *sembolün farklı bağlamlardaki anlamını anlaması için, öncelikle bağlamların gerçekten farklı olduğunu anlamaları gerekir. Bağlamların farklı olduğunu anladıktan sonra (yani bir ödevin sol tarafı ile genel bir ifade arasındaki fark), farklılıkların ne olduğunu anlamak için çok fazla bilişsel bir sıçrama değildir.

Öncelikle, bir değişkenin bildiriminin işleçler içeremediğini açıklayın (bunu, bir değişken sembolüne bir -veya +sembol yerleştirmenin sadece bir hataya neden olduğunu göstererek gösterin ). Ardından, bir ifadenin (yani bir ödevin sağ tarafında) işleçler içerebileceğini göstermek için devam edin. Öğrencinin bir ifadenin ve değişken bir bildirimin tamamen farklı iki bağlam olduğunu anladığından emin olun.

Bağlamların farklı olduğunu anladıklarında, *sembolün değişken tanımlayıcısının önünde değişken bir bildirimde olduğu zaman, 'bu değişkeni bir işaretçi olarak bildir' anlamına geldiğini açıklamaya devam edebilirsiniz. Daha sonra bir ifadede (tekli bir operatör olarak) kullanıldığında *sembolün 'dereference operatörü' olduğunu ve önceki anlamından ziyade 'adresteki değer' anlamına geldiğini açıklayabilirsiniz.

Öğrencinizi gerçekten ikna etmek için, C yaratıcılarının dereference operatörünü (yani @bunun yerine kullanmış olabilirler ) ifade etmek için herhangi bir sembol kullanabileceklerini, ancak hangi nedenle tasarım kararını kullandıklarını açıkladılar *.

Sonuç olarak, bağlamların farklı olduğunu açıklamanın bir yolu yoktur. Öğrenci bağlamların farklı olduğunu anlamıyorsa, *sembolün neden farklı şeyler ifade edebileceğini anlayamaz .


80

Steno neden:

int *bar = &foo;

Örneğinizde kafa karıştırıcı olabilir ki, buna eşdeğer olarak yanlış anlamanın kolay olması:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

ne zaman aslında şu anlama gelir:

int *bar;
bar = &foo;

Bu şekilde yazıldığında, değişken beyan ve atama ayrılmış olarak, karışıklık için böyle bir potansiyel yoktur ve K&R teklifinizde açıklanan kullanım ↔ beyan paralelliği mükemmel çalışır:

  • Birinci çizgi bir değişken bildirir bar, böylece, *barbir bir int.

  • İkinci satır adresini atar fooiçin bar, verme *bar(bir int) için bir takma ad foo(aynı zamanda int).

Yeni başlayanlara C işaretçisi sözdizimini eklerken, başlangıçta işaretçi bildirimlerini atamalardan ayırmak için bu stile sadık kalmak ve yalnızca işaretçi kullanımının temel kavramları C yeterince içselleştirilmiştir.


4
Ben cazip olurdum typedef. typedef int *p_int;"" ifadesi, türdeki bir değişkenin bir p_intözelliğe sahip *p_intolduğu anlamına gelir int. Sonra var p_int bar = &foo;. Herhangi birini başlatılmamış veriler oluşturmaya ve daha sonra varsayılan alışkanlık meselesi olarak atamaya teşvik etmek kötü bir fikir gibi görünüyor.
Yakk - Adam Nevraumont

6
Bu sadece beyin hasarlı C deklarasyonları tarzıdır; işaretçiler için spesifik değildir. düşünün int a[2] = {47,11};, bu (var olmayan) element a[2]eiher'in başlatılması değildir .
Marc van Leeuwen

5
@MarcvanLeeuwen Beyin hasarı ile aynı fikirde. İdeal olarak, *değişkene bağlı olmayan türün bir parçası olmalı ve daha sonra int* foo_ptr, bar_ptriki işaretçi bildirmek için yazabilirsiniz . Ama aslında bir işaretçi ve bir tamsayı bildirir.
Barmar

1
Bu sadece "steno" bildirimleri / ödevleri ile ilgili değildir. İşaretçileri işlev bağımsız değişkenleri olarak kullanmak istediğiniz anda tüm sorun yeniden ortaya çıkar.
armin

30

Beyanlarda eksiklik

Beyan ve başlatma arasındaki farkı bilmek güzel. Değişkenleri tür olarak bildirir ve değerlerle başlatırız. Eğer ikisini de aynı anda yaparsak, buna genellikle bir tanım diyoruz.

1. int a; a = 42;

int a;
a = 42;

Biz beyan bir intadlandırılmış a . Sonra bir değer vererek başlattık 42.

2. int a = 42;

Biz beyan ve intadında bir ve ona bu başlatılır değerini 42. verir 42. Bir tanım.

3. a = 43;

Değişkenleri kullandığımızda bunlar üzerinde çalıştığımızı söyleriz . a = 43bir atama işlemidir. 43 değişkenini a değişkenine atarız.

Diyerek

int *bar;

çubuğun bir int işaretçisi olduğunu beyan ederiz . Diyerek

int *bar = &foo;

biz bar beyan ve foo adresi ile başlatmak .

Çubuğu başlattıktan sonra , foo değerine erişmek ve bu değer üzerinde çalışmak için aynı operatörü, yıldız işaretini kullanabiliriz . Operatör olmadan, işaretçinin işaret ettiği adrese erişir ve çalışırız.

Bunun yanında resmin konuşmasına izin verdim.

Ne

Neler olup bittiğine dair basitleştirilmiş bir BİLDİRİM. (Ve burada duraklatmak istiyorsanız bir oyuncu sürümü vb.)

          ASCIIMATION


22

2. ifade int *bar = &foo;hafızada resimsel olarak,

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

Şimdi bartüründe bir gösterici intadresini içeren &bir foo. Tekli işleci *kullanarak, imleci kullanarak 'foo' içinde yer alan değeri almayı kabul ederiz bar.

EDIT : Yeni başlayanlar ile benim yaklaşım memory addressbir değişken yani

Memory Address:Her değişkenin işletim sistemi tarafından sağlanan bir adresi vardır. In int a;, &adeğişkenin adresidir a.

Değişkenlerin temel türde açıklayan Devam Colarak,

Types of variables: Değişkenler, ilgili türlerin değerlerini tutabilir ancak adresleri tutamaz.

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: Yukarıda belirtildiği gibi değişkenler, örneğin

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

Mümkün atama olduğunu b = aancak b = &adeğişken beri, bkutu tutma değeri ancak adres, Dolayısıyla biz gerektiren İşaretçiler .

Pointer or Pointer variables :Bir değişken bir adres içeriyorsa, bu işaretçi değişkeni olarak bilinir. *Bir işaretçi olduğunu bildirmek için bildirimde kullanın .

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable

3
Sorun int *ip"ip gibi bir işaretçi (*) türünde okuma" gibi bir şey okurken sorun olsun olmasıdır x = (int) *ip.
armin

2
@abw Bu tamamen farklı bir şey, dolayısıyla parantez. İnsanların beyanlar ve döküm arasındaki farkı anlamakta zorluk çekeceğini sanmıyorum.
bzeaman

@abw olarak x = (int) *ip;, gösterici kaldırma tarafından değeri elde ipve değer dökme inttipi ne olursa olsun ipbir.
Sunil Bojanapally

1
@BennoZeeman Haklısınız: döküm ve beyanlar iki farklı şeydir. Yıldız işaretinin farklı rolüne ipucu vermeye çalıştım: 1 "bu bir int değil, int" 2 "için bir işaretçi size int verecektir, ancak int için işaretçi değil".
armin

2
@abw: Bu yüzden öğretim yükleri daha anlamlı int* bar = &foo;hale getirir . Evet, tek bir bildiride birden çok işaretçi bildirdiğinizde sorunlara neden olduğunu biliyorum. Hayır, bunun hiç önemli olduğunu düşünmüyorum.
Yörüngedeki Hafiflik Yarışları

17

Buradaki cevaplara ve yorumlara bakıldığında, sözdiziminin yeni başlayanlar için kafa karıştırıcı olabileceği konusunda genel bir anlaşma var gibi görünüyor. Çoğu bu çizgiler boyunca bir şey öneriyor:

  • Herhangi bir kodu göstermeden önce, işaretçilerin nasıl çalıştığını göstermek için diyagramlar, çizimler veya animasyonlar kullanın.
  • Sözdizimini sunarken, yıldız sembolünün iki farklı rolünü açıklayın . Birçok öğretici bu bölümü kaçırıyor veya kaçıyor. Karışıklık ortaya çıkıyor ("Bir işaretçi bildirimini bir bildirime ve daha sonra bir ödeve ayırdığınızda , * 'ı kaldırmayı hatırlamanız gerekir." - comp.lang.c SSS ) Alternatif bir yaklaşım bulmayı umdum, ama sanırım bu gidilecek yol.

Farkı vurgulamak int* baryerine yazabilirsiniz int *bar. Bu, K&R "bildirim taklitçileri kullanımı" yaklaşımını değil, Stroustrup C ++ yaklaşımını izlemeyeceğiniz anlamına gelir :

*barTamsayı ilan etmiyoruz . Biz beyan barbir olmak int*. Biz aynı çizgide yeni oluşturulan değişkeni başlatmak istiyorsanız, biz ilgileniyor olduğu açıktır bardeğil, *bar.int* bar = &foo;

Sakıncaları:

  • Öğrencinizi çoklu işaretçi bildirim sorunu ( int* foo, barvs int *foo, *bar) hakkında uyarmanız gerekir .
  • Onları acı dünyasına hazırlamalısın . Birçok programcı, değişkenin adının yanında bulunan yıldız işaretini görmek ister ve stillerini haklı çıkarmak için çok uzun zaman alacaktır. Ve birçok stil kılavuzu bu gösterimi açıkça uygular (Linux çekirdek kodlama stili, NASA C Stil Kılavuzu, vb.).

Düzenleme: Önerilen farklı bir yaklaşım , K & R "mimik" yol gitmek, ama "steno" sözdizimi ( buraya bakın ). En kısa zamanda olarak bir deklarasyon ve aynı çizgide atama yapıyor ihmal , her şey çok daha tutarlı bakacağız.

Ancak, er ya da geç öğrenci işaretçilerle fonksiyon argümanları olarak uğraşmak zorunda kalacaktır. Ve dönüş türleri olarak işaretçiler. Ve işlevlere işaret eder. int *func();Ve arasındaki farkı açıklamanız gerekecek int (*func)();. Bence er ya da geç işler dağılacak. Ve belki er ya da geç.


16

K&R stilinin iyilik int *pyapmasının ve Stroustrup stilinin iyilik yapmasının bir nedeni var int* p; her ikisi de her dilde geçerlidir (ve aynı anlama gelir), ancak Stroustrup'un dediği gibi:

"İnt * p;" arasındaki seçim ve "int * p;" doğru ve yanlış değil, stil ve vurgu ile ilgilidir. C ifadeleri vurguladı; bildirimler genellikle gerekli bir kötülükten biraz daha fazlası olarak kabul edildi. C ++ ise tiplere büyük önem vermektedir.

Şimdi, burada C'yi öğretmeye çalıştığınız için, bu, ifadeleri bu türlerden daha fazla vurgulamanız gerektiğini önerecektir, ancak bazı insanlar bir vurgu diğerinden daha hızlı bir şekilde daha hızlı bir şekilde anlayabilir ve bu, dilden ziyade onlarla ilgilidir.

Bu nedenle bazı insanlar , a'nın a'dan int*farklı bir şey olduğu fikrinden başlamak intve oradan gitmek daha kolay olacaktır .

Birisi hızla kullandığı bunun bakmanın yolu grok yoksa int* barolması barbir int olmayan bir şey olarak, ancak bir işaretçi int, daha sonra hızla göreceksiniz *baredilir bir şey yapıyor için barve gerisi takip eder. Bunu yaptıktan sonra C kodlayıcıların neden tercih etme eğiliminde olduğunu açıklayabilirsiniz int *bar.

Ya da değil. Herkesin kavramı ilk önce anlamanın bir yolu olsaydı, ilk başta herhangi bir sorun yaşamazdınız ve bunu bir kişiye açıklamanın en iyi yolu, onu başka bir kişiye açıklamanın en iyi yolu olmayacaktır.


1
Stroustrup'un argümanını seviyorum, ama neden referansları göstermek için & sembolünü seçtiğini merak ediyorum - başka bir olası tuzak.
armin

1
Bence @abw o yapabileceğimiz eğer simetriyi gördüğümüz int* p = &ao zaman yapabiliriz int* r = *p. Eminim ki , C ++ Tasarım ve Evrimi'nde yer aldı , ancak bunu okuduğumdan bu yana uzun zaman geçti ve kopyamı birisine aptalca eğdim.
Jon Hanna

3
Sanırım demek istiyorsun int& r = *p. Ve bahse girerim borçlu hala kitabı sindirmeye çalışıyor.
armin

@ abw, evet demek istediğim buydu. Yorumlardaki Alas yazım hataları derleme hatalarını artırmaz. Kitap aslında oldukça hızlı bir okuma.
Jon Hanna

4
Pascal'ın sözdizimini (popüler olarak genişletilmiş olarak) C'lere tercih etmemin nedenlerinden biri, Var A, B: ^Integer;"tamsayıya işaretçi" türünün hem Ave hem de için geçerli olduğunu açıkça ortaya koymasıdır B. Bir K&Rstil kullanmak int *a, *bda uygulanabilir; ancak böyle bir deklarasyon int* a,b;sanki ave bher ikisi de olarak ilan edilir int*, fakat gerçekte abir int*ve bolarak ilan eder int.
supercat

9

tl; dr:

S: Yeni başlayanlara C işaretçileri (deklarasyon, tekli operatörler) nasıl açıklanır?

A: yapma. Yeni başlayanlara işaretçileri açıklayın ve daha sonra C sözdiziminde işaretçi kavramlarını nasıl temsil edeceklerini gösterin.


Bir C programlama acemi için işaretçiler açıklamak için son bir zevk oldu ve aşağıdaki zorluk tökezledi.

IMO C sözdizimi korkunç değil, ama harika da değil: işaretçileri zaten anlarsanız ne büyük bir engel ne de bunları öğrenmede herhangi bir yardım.

Bu nedenle: işaretçileri açıklayarak başlayın ve onları gerçekten anladıklarından emin olun:

  • Bunları kutu-ok diyagramları ile açıklar. Onaltılık adresler olmadan yapabilirsiniz, eğer uygun değilse, başka bir kutuyu veya bazı boş simgeleri gösteren okları göstermeniz yeterlidir.

  • Pseudocode ile açıklayınız: sadece foo adresini ve barda depolanan değeri yazınız .

  • Sonra, aceminiz işaretçilerin ne olduğunu, nedenini ve nasıl kullanılacağını anladığında; ardından C sözdiziminde eşlemeyi gösterin.

K&R metninin kavramsal bir model sağlamadığından şüpheliyim ki , zaten işaretçileri anladılar ve muhtemelen o zamanki diğer tüm yetkili programcıları da kabul ettiler. Anımsatıcı, iyi anlaşılmış kavramdan sözdizimine eşleştirmenin sadece bir hatırlatıcısıdır.


Aslında; İlk önce teori ile başlayın, sözdizimi daha sonra gelir (ve önemli değildir). Bellek kullanımı teorisinin dile bağlı olmadığını unutmayın. Bu kutu ve ok modeli, herhangi bir programlama dilinde görevlerde size yardımcı olacaktır.
o

Bazı örnekler için buraya bakın (google da yardımcı olacaktır) eskimo.com/~scs/cclass/notes/sx10a.html
oɔɯǝɹ

7

Bu konu C öğrenmeye başlarken biraz kafa karıştırıcıdır.

Başlamanıza yardımcı olabilecek temel ilkeler şunlardır:

  1. C'de sadece birkaç temel tür vardır:

    • char: 1 bayt büyüklüğünde bir tamsayı değeri.

    • short: 2 bayt büyüklüğünde bir tamsayı değeri.

    • long: 4 bayt büyüklüğünde bir tamsayı değeri.

    • long long: 8 bayt büyüklüğünde bir tamsayı değeri.

    • float: 4 bayt boyutunda tamsayı olmayan bir değer.

    • double: 8 bayt büyüklüğünde tamsayı olmayan bir değer.

    Her türün boyutunun standart tarafından değil , derleyici tarafından tanımlandığını unutmayın.

    Tamsayı türleri short, longve long longgenellikle tarafından takip edilmektedir int.

    Ancak, bu bir zorunluluk değildir ve bunları int .

    Alternatif olarak, int ifade edebilirsiniz, ancak bu farklı derleyiciler tarafından farklı yorumlanabilir.

    Özetlemek gerekirse:

    • shortile aynıdır short intancak zorunlu olarak aynı değildir int.

    • longile aynıdır long intancak zorunlu olarak aynı değildir int.

    • long longile aynıdır long long intancak zorunlu olarak aynı değildir int.

    • Belirli bir derleyici günü, intya olduğu short intya long intya long long int.

  2. Bir tür değişkeni bildirirseniz, ona işaret eden başka bir değişkeni de bildirebilirsiniz.

    Örneğin:

    int a;

    int* b = &a;

    Aslında, her temel tip için, karşılık gelen bir işaretçi tipimiz de var.

    Örneğin: shortve short*.

    Değişkene "bakmanın" iki yolu vardır b (muhtemelen yeni başlayanların çoğunu şaşırtan şey budur) :

    • Bir btür değişkeni olarak düşünebilirsiniz int*.

    • Bir *btür değişkeni olarak düşünebilirsiniz int.

    Bu nedenle, bazı insanlar beyan int* bederken diğerleri açıklar int *b.

    Ancak konunun gerçeği, bu iki bildirimin aynı olduğu (boşlukların anlamsız olduğu).

    bBir tamsayı değerine işaretçi olarak kullanabilirsiniz veya*b gerçek bir sivri tamsayı değeri.

    Sivri değeri alabilirsiniz (okuyabilirsiniz): int c = *b .

    Ve sivri değeri ayarlayabilirsiniz (yazabilirsiniz) *b = 5.

  3. İşaretçi, yalnızca önceden bildirdiğiniz bir değişkenin adresini değil, herhangi bir bellek adresini işaret edebilir. Ancak, sivri bellek adresinde bulunan değeri almak veya ayarlamak için işaretçileri kullanırken dikkatli olmalısınız.

    Örneğin:

    int* a = (int*)0x8000000;

    Burada, a0x8000000 bellek adresine işaret eden değişkenimiz var .

    Bu bellek adresi programınızın bellek alanında eşlenmezse, herhangi bir okuma veya yazma işlemi *abellek erişim ihlali nedeniyle programınızın çökmesine neden olur.

    Değerini güvenli bir şekilde değiştirebilirsiniz a, ancak değerini değiştirirken çok dikkatli olmalısınız *a.

  4. Tür void*, kullanılabilecek karşılık gelen bir "değer türüne" sahip olmaması nedeniyle istisnai bir durumdur (yani, beyan edemezsiniz void a). Bu tür, o adreste bulunan veri türünü belirtmeden, yalnızca bir bellek adresine genel bir işaretçi olarak kullanılır.


7

Belki sadece biraz daha fazla adım atmak bunu kolaylaştırır:

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

Çıktının her satırda ne olmasını beklediklerini söylemelerini sağlayın, ardından programı çalıştırmasını ve neyin ortaya çıktığını görmelerini sağlayın. Sorularını açıklayın (oradaki çıplak sürüm kesinlikle birkaçını isteyecektir - ancak daha sonra stil, katılık ve taşınabilirlik hakkında endişelenebilirsiniz). Sonra, zihinleri aşırı düşünmeden lapa dönmeden veya öğle yemeğinden sonra bir zombi haline gelmeden önce, bir değer alan bir işlev yazın ve aynı şey bir işaretçi alır.

Deneyimlerime göre, "bu neden bu şekilde yazdırılıyor?" kambur ve daha sonra bu (string ayrıştırma / dizi işleme gibi bazı temel K & F malzemesi için bir başlangıç olarak) uygulamalı oyalanması ile fonksiyon parametrelerinde yararlıdır neden hemen sadece mantıklı ama kılmam değil ders yapar gösteren.

Bir sonraki adım, size nasıl i[0]alakalı olduklarını açıklamalarını sağlamaktır &i. Eğer bunu yapabilirlerse, bunu unutmayacaklar ve biraz zamanın ilerisinde bile yapılar hakkında konuşmaya başlayabilirsiniz.

Kutular ve oklarla ilgili yukarıdaki öneriler de iyidir, ancak hafızanın nasıl çalıştığı hakkında tam bir tartışmaya da girebilir - bu, bir noktada olması gereken, ancak eldeki noktadan dikkati dağıtabilecek bir konuşmadır. : C işaretçi gösterimi nasıl yorumlanır


Bu iyi bir egzersiz. Ancak ortaya çıkarmak istediğim konu öğrencilerin oluşturduğu zihinsel model üzerinde etkisi olabilecek belirli bir sözdizimsel sorunudur. Şunu bir düşünün: int foo = 1;. Şimdi bu sorun yok: int *bar; *bar = foo;. Bu doğru değil:int *bar = foo;
armin

1
@abw Mantıklı olan tek şey, öğrencilerin kendilerini anlatmak istedikleri şeydir. Bunun anlamı "birini görmek, birini yapmak, birini öğretmek". Ormanda hangi sözdizimi veya tarzı göreceklerini (eski depolarınız bile!) Koruyamaz veya tahmin edemezsiniz, bu nedenle temel kavramların stilden bağımsız olarak anlaşılması için yeterli permütasyon göstermeniz gerekir - ve sonra onlara belirli stillerin neden yerleştiğini öğretmeye başlayın. İngilizce öğretmek gibi: temel ifade, deyimler, stiller, belirli bir bağlamdaki belirli stiller. Maalesef kolay değil. Her durumda, iyi şanslar!
zxq9

6

Type ekspresyonu *bar olduğu int; böylece, değişkenin (ve ifadenin) türü barolur int *. Değişkenin işaretçi türü olduğundan başlatıcısının da işaretçi türü olması gerekir.

İşaretçi değişkeni başlatma ve atama arasında bir tutarsızlık var; bu sadece zor yoldan öğrenilmesi gereken bir şey.


3
Buradaki cevaplara baktığımda, birçok deneyimli programcının sorunu artık göremediğini hissediyorum . Sanırım bu "tutarsızlıklarla yaşamayı öğrenme" nin bir yan ürünü.
armin

3
@abw: başlatma kuralları atama kurallarından farklıdır; skaler aritmetik tipler için farklar ihmal edilebilir, ancak bunlar işaretçi ve toplu tipler için önemlidir. Bu, diğer her şeyle birlikte açıklamanız gerekecek bir şey.
John Bode

5

İlkinden daha fazlası *için geçerli olarak okumayı tercih ederim .intbar

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

2
O zaman neden int* a, bdüşündüklerini yapmadığını açıklamalısınız.
Pharap

4
Doğru, ama bunun hiç int* a,bkullanılması gerektiğini düşünmüyorum . Daha iyi anlaşılabilirlik, güncelleme, vb ... için hat başına sadece bir değişken bildirimi olmalı ve asla daha fazla olmamalıdır. Derleyicinin üstesinden gelse bile, yeni başlayanlara da açıklanması gereken bir şey.
grorel

Yine de bu bir insanın görüşü. Her satırda birden fazla değişkeni beyan etmeyi ve işlerinin bir parçası olarak her gün yapmayı tamamlayan milyonlarca programcı var. Öğrencileri bir şeyler yapmanın alternatif yollarından gizleyemezsiniz, onlara tüm alternatifleri göstermek ve bir şeyleri hangi yolla yapmak istediklerine karar vermelerine izin vermek daha iyidir, çünkü eğer işe girdikleri takdirde belirli bir stili izlemeleri beklenir. rahat olabilirler veya olmayabilirler. Bir programcı için, çok yönlülük çok iyi bir özelliktir.
Pharap

1
@Grorel ile hemfikirim. Türün bir *parçası olarak düşünmek ve sadece cesaret kırmak daha kolaydır int* a, b. Eğer söyleyerek tercih sürece bu *atiptedir intziyade abir göstericidir int...
Kevin Ushey

@grorel haklı: int *a, b;kullanılmamalıdır. Aynı ifadede farklı türlere sahip iki değişken bildirmek oldukça zayıf bir uygulamadır ve bakım sorunları için güçlü bir adaydır. Belki de gömülü bir alanda çalışmak bizler için farklıdır int*ve bir intgenellikle farklı boyutlarda ve bazen tamamen farklı belleklere depolanır. C dilinin en iyi şekilde 'izin verilir, ancak bunu yapma' olarak öğretilecek birçok yönünden biridir.
Evil Dog Pie

5
int *bar = &foo;

Question 1: Nedir bar?

Ans: İşaretçi değişkendir (yazmak için int). Bir işaretçi geçerli bir bellek konumuna işaret etmeli ve daha sonra *o konumda saklanan değeri okumak için tekli bir operatör kullanılarak kayıttan çıkarılmalıdır (* bar) .

Question 2: Nedir &foo?

Ans: foo, intbazı geçerli bellek konumlarında saklanan ve bu konumdan operatörden aldığımız bir değişkendir. &Şimdi elimizde bazı geçerli bellek konumları var &foo.

Yani her ikisi de bir araya getirilmiş, yani imlecin ihtiyacı olan şey geçerli bir bellek konumuydu ve &foobaşlangıç ​​noktası iyi olduğu için elde edildi .

Şimdi işaretçi bargeçerli bellek konumunu işaret ediyor ve içinde saklanan değerin kaydı silinebilir.*bar


5

Yeni başlayanlara * bildirimde ve ifadede farklı bir anlama sahip olduğunu belirtmelisiniz. Bildiğiniz gibi, ifadedeki * tekli bir işleçtir ve * Bildirgede bir işleç değildir ve derleyicinin bir işaretçi türü olduğunu bildirmek için türü ile birleştiren bir tür sözdizimi değildir. yeni başlayanlara “* farklı bir anlamı vardır. * anlamını anlamak için * nerede kullanıldığını bulmalısınız” demek daha iyidir.


4

Bence şeytan uzayda.

Yazarım (sadece yeni başlayanlar için değil, kendim için de): int * bar = & foo; int * bar = & foo yerine;

bu sözdizimi ve anlambilim arasındaki ilişkinin ne olduğunu açıklığa kavuşturmalıdır


4

* İşaretinin birden fazla rolü olduğu zaten belirtilmişti.

Yeni başlayanların bir şeyleri kavramasına yardımcı olabilecek başka bir basit fikir var:

"=" İfadesinin de birden fazla rolü olduğunu düşünün.

Atama, bildirimle aynı satırda kullanıldığında, bunu rastgele bir atama olarak değil, bir yapıcı çağrısı olarak düşünün.

Gördüğünde:

int *bar = &foo;

Neredeyse şuna eşit olduğunu düşünün:

int *bar(&foo);

Parantezler yıldız işaretine göre önceliklidir, bu nedenle "& foo", "* bar" yerine "bar" ile daha kolay ilişkilendirilir.


4

Bu soruyu birkaç gün önce gördüm ve Go'nun Go Blog'da tür bildiriminin açıklamasını okuyordum . Zaten verilen daha eksiksiz cevaplar olduğunu düşünüyorum bile, bu konu eklemek için yararlı bir kaynak gibi görünüyor C tipi bildirimleri bir hesap vererek başlar.

C, bildirim sözdizimine alışılmadık ve akıllı bir yaklaşım benimsedi. Özel sözdizimine sahip türleri tanımlamak yerine, bildirilen öğeyi içeren bir ifade yazar ve bu ifadenin ne tür olacağını belirtir. Böylece

int x;

x'in int olduğunu bildirir: 'x' ifadesi int türünde olacaktır. Genel olarak, yeni bir değişkenin türünün nasıl yazılacağını bulmak için, temel bir türe göre değerlendirilen bu değişkeni içeren bir ifade yazın, ardından temel türü sola ve ifadeyi sağa koyun.

Böylece bildiriler

int *p;
int a[3];

'* p' int türüne sahip olduğu için p'nin int için bir işaretçi olduğunu ve a [3] (dizinin boyutu olarak cezalandırılan belirli dizin değerini yok sayarak) türüne sahip olduğu için a'nın bir dizi olduğunu belirtir. int.

(Bu anlayışın işlev göstergelerine vb. Nasıl genişletileceğini açıklar.)

Bu, daha önce düşünmediğim bir yol, ancak sözdiziminin aşırı yüklenmesi için oldukça basit bir muhasebe yolu gibi görünüyor.


3

Sorun sözdizimi ise, template / using ile eşdeğer bir kod göstermeniz yararlı olabilir.

template<typename T>
using ptr = T*;

Bu daha sonra şu şekilde kullanılabilir:

ptr<int> bar = &foo;

Bundan sonra, normal / C sözdizimini bu C ++ yaklaşımıyla karşılaştırın. Bu, sabit işaretçileri açıklamak için de yararlıdır.


2
Yeni başlayanlar için çok daha kafa karıştırıcı olacak.
Karsten

Benim fikrim ptr tanımını göstermeyecek olmanızdı. İşaretçi bildirimleri için kullanın.
MI3Guy

3

Karışıklık kaynağı, *sembolün kullanıldığı gerçeğe bağlı olarak C'de farklı anlamlara sahip olabilmesinden kaynaklanır . İşaretçiyi yeni başlayanlara açıklamak için, *sembolün farklı bağlamdaki anlamı açıklanmalıdır.

Deklarasyonda

int *bar = &foo;  

*semboldür indirection operatör değil . Bunun yerine, bir işaretçibar olan derleyiciyi bilgilendirme türünü belirtmeye yardımcı olur . Öte yandan, bir ifadede göründüğünde sembol ( tekli bir operatör olarak kullanıldığında ) dolaylı olarak gerçekleştirir. Bu nedenle, açıklama barint*

*bar = &foo;

kendisine işaret fooeden nesnenin adresini atarken yanlış olur .barbar


3

"int * bar olarak yazmak, yıldızın tanımlayıcının bir parçası değil, aslında türün bir parçası olduğunu daha da belirginleştirir." Ben de öyle. Ve diyorum ki, Type gibi bir şey, ama sadece bir işaretçi adı için.

"Tabii ki bu sizi int * a, b gibi sezgisel olmayan şeylerle farklı sorunlara sürüklüyor."


2

Burada insan mantığını değil derleyici mantığını kullanmalı, anlamalı ve açıklamalısınız (biliyorum, sen bir insansınız, ama burada bilgisayarı taklit etmelisiniz ...).

Yazdığın zaman

int *bar = &foo;

derleyici,

{ int * } bar = &foo;

Yani: burada yeni bir değişken, adı, bartürü int'e işaretçi ve başlangıç ​​değeri&foo .

Ve Eklemek gerekir: =Yukarıdaki O anlamına gelir, bir başlatma değil özenti, aşağıdaki ifadelerde oysa *bar = 2;o olduğunu bir özenti

Yorum başına düzenleme:

Dikkat: çoklu beyan durumunda *sadece aşağıdaki değişkenle ilgilidir:

int *bar = &foo, b = 2;

bar, foo adresi tarafından başlatılan int için bir işaretçi, b 2 olarak başlatılan ve

int *bar=&foo, **p = &bar;

çubuğunu int için sabit işaretçide, p ise adrese veya çubuğa sıfırlanmış bir int için bir işaretçidir.


2
Aslında derleyici bunu şu şekilde gruplandırmaz: int* a, b;a'nın bir işaretçisi olduğunu int, b'nin bir olduğunu belirtir int. *Sembolü sadece iki farklı anlama sahiptir: a açıklamada, bir işaretçi türünü gösterir ve bir ifadede bu tekli KQUEUE operatördür.
tmlen

@tmlen: Demek istediğim, ilklendirmede, *türüne ratta edilmiş, böylece işaretçi başlatılırken, bir göstergede sivri değer etkilenir. Ama en azından bana güzel bir şapka verdin :-)
Serge Ballesta

0

Temel olarak İşaretçi bir dizi göstergesi değildir. Acemi, işaretçinin dizi gibi göründüğünü kolayca düşünür. dize örneklerinin çoğunu

"char * pstr" benziyor

"char str [80]"

Ancak, Önemli şeyler, Pointer derleyicinin alt düzeyinde tamsayı olarak kabul edilir.

Örneklere bakalım ::

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

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

Sonuçlar bu 0x2a6b7ed0 gibi olacaktır str [] adresidir

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

Yani, Temelde, Unutmayın Pointer bir çeşit Tamsayıdır. Adres sunmak.


-1

Ben ints yüzen, vb olduğu gibi nesneler olduğunu açıklar. Bir işaretçi değeri bellekte bir adresi temsil eden bir nesne türüdür (bu yüzden bir işaretçi neden NULL varsayılan).

Bir işaretçiyi ilk kez bildirdiğinizde type-pointer-name sözdizimini kullanırsınız. "Herhangi bir tamsayı nesnesinin adresini gösterebilen ad adlı tamsayı-işaretçisi" olarak okunur. Bu sözdizimini yalnızca int olarak 'int num1' olarak tanımladığımız gibi, decleration sırasında kullanırız, ancak 'int num1' yerine bu değişkeni kullanmak istediğimizde yalnızca 'num1' kullanırız.

int x = 5; // 5 değerine sahip bir tam sayı nesnesi

int * ptr; // varsayılan olarak NULL değerine sahip bir tam sayı

Bir nesnenin adresini işaret etmek için "&" sembolü olarak okunabilen '&' sembolünü kullanırız.

ptr = & x; // şimdi değer 'x' adresidir

İşaretçi yalnızca nesnenin adresi olduğundan, o adreste tutulan gerçek değeri elde etmek için, işaretçi önce kullanıldığında "adresin işaret ettiği değer" anlamına gelen '*' sembolünü kullanmalıyız.

std :: cout << * ptr; // adresteki değeri yazdır

' ' Farklı nesne türleriyle farklı sonuçlar döndüren bir 'operatör' olduğunu kısaca açıklayabilirsiniz . Bir işaretçi ile kullanıldığında, ' ' operatörü artık "çarpma" anlamına gelmez.

Bir değişkenin nasıl bir adı ve değeri olduğunu ve bir işaretçinin bir adresi (adı) ve bir değeri olduğunu gösteren bir diyagram çizmeye ve işaretçinin değerinin int adresi olacağını göstermeye yardımcı olur.


-1

İşaretçi, adresleri depolamak için kullanılan bir değişkendir.

Bilgisayardaki bellek, sıralı olarak düzenlenmiş baytlardan (A bayt 8 bitten oluşur) oluşur. Her baytın, bir dizideki dizin veya alt simge gibi, bayt adresi olarak adlandırılan bir numarası vardır. Bayt adresi 0'dan 1'e kadar bellek boyutundan daha az başlar. Örneğin, 64MB RAM'de 64 * 2 ^ 20 = 67108864 bayt olduğunu varsayalım. Bu nedenle, bu baytların adresi 0'dan 67108863'e başlayacaktır.

resim açıklamasını buraya girin

Bir değişken bildirdiğinizde ne olacağını görelim.

int işaretleri;

Bir int'in 4 bayt veri kapladığını bildiğimizden (32 bit derleyici kullandığımızı varsayarsak), derleyici bir tamsayı değeri saklamak için bellekten 4 ardışık bayt ayırır. Ayrılan 4 baytın ilk baytının adresi, değişken işaretlerinin adresi olarak bilinir. Diyelim ki 4 ardışık baytın adresi 5004, 5005, 5006 ve 5007, sonra değişken işaretlerinin adresi 5004 olacaktır. resim açıklamasını buraya girin

İşaretçi değişkenlerini bildirme

Daha önce de belirtildiği gibi, bir işaretçi bir bellek adresini saklayan bir değişkendir. Diğer tüm değişkenler gibi, bir işaretçi değişkenini kullanabilmeniz için önce bildirmeniz gerekir. İşaretçi değişkenini nasıl bildirebileceğiniz aşağıda açıklanmıştır.

Sözdizimi: data_type *pointer_name;

data_type, işaretçinin türüdür (işaretçinin temel türü olarak da bilinir). pointer_name değişkenin herhangi bir geçerli C tanımlayıcısı olabilen adıdır.

Bazı örnekler verelim:

int *ip;

float *fp;

int * ip, ip'in, int türündeki değişkenlere işaret edebilen bir işaretçi değişkeni olduğu anlamına gelir. Başka bir deyişle, bir işaretçi değişkeni ip yalnızca int türündeki değişkenlerin adresini saklayabilir. Benzer şekilde, işaretçi değişkeni fp yalnızca float türünde bir değişkenin adresini depolayabilir. Değişken türü (temel tür olarak da bilinir) ip, int'e bir işaretçi ve fp tipi, kayan bir işaretleyicidir. İnt için pointer tipinde bir pointer değişkeni sembolik olarak (int *) olarak temsil edilebilir. Benzer şekilde, kayan noktaya işaretçi türü bir işaretçi değişkeni (kayan nokta *) olarak temsil edilebilir

Bir işaretçi değişkeni bildirildikten sonra bir sonraki adım, ona geçerli bir bellek adresi atamaktır. Herhangi bir geçerli bellek adresi atamadan asla bir işaretçi değişkeni kullanmamalısınız, çünkü bildirimden hemen sonra çöp değeri içerir ve bellekte herhangi bir yere işaret ediyor olabilir. Atanmamış bir işaretçinin kullanılması öngörülemeyen bir sonuç verebilir. Hatta programın çökmesine neden olabilir.

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

Kaynak: thecguru şimdiye kadar bulduğum en basit ama ayrıntılı açıklama.

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.