Programınızdaki her değişken bellekte bir yerde yaşar ve her "bir yer"in bir numarası vardır - adresi. Gösterici, yalnızca o numaralardan birini saklayan bir değişkendir. Fikrin tamamı budur. Bu sayfadaki diğer her şey söz dizimi ve sonuçlardır.
Göstericilerin C'de bu kadar önemli olmasının sebebi, C'nin her fonksiyona argümanlarının bir kopyasını vermesidir. 7 sayısı verilen bir fonksiyon kopyasını istediği kadar değiştirebilir; çağıranın değişkeni asla kıpırdamaz. Fonksiyona onun yerine bir adres verin, geriye uzanıp özgünü değiştirebilir. Diziler, metinler, dinamik bellek ve C'de kuracağınız her veri yapısı o tek yeteneğe dayanır.
Bellek, Adresler ve Bir Şeyin Neden Adresi Olur
int score = 42; yazdığınızda derleyici birkaç bayt bellek ayırır ve onlara kullanabileceğiniz bir isim verir. O baytların ayrıca sayısal bir adresi vardır. Belleği numaralı evlerden oluşan çok uzun bir sokak olarak düşünün: score, 0x7ffd4c2a numaralı evde yaşayan ailedir - isim sizin için, numara makine için.
& operatörü o numarayı ister:
Çalıştırın. Üç adres farklıdır, çünkü üç değişken farklı baytları kaplar ve çalıştırmadan çalıştırmaya değişirler - bu normaldir ve bir adresi asla koda sabitlememenizin sebebidir. (void *) dönüşümüne dikkat edin: %p'nin bir void * alması belirtilmiştir ve başka bir gösterici tipi geçirmek teknik olarak tanımsız davranıştır. Dönüşümü alışkanlık hâline getirin.
Adreslerin öngörülemez olması bir sorun değildir, çünkü bir adresin ne olduğunu neredeyse hiç umursamazsınız. Ona sahip olmayı umursarsınız.
Bir Gösterici Bildirmek
Bir gösterici bildirimi, gösterdiği tipi adlandırır:
int *p; // p, bir int'in adresini tutabilir
double *q; // q, bir double'ın adresini tutabilir
char *name; // name, bir char'ın adresini tutabilir
int *p;'yi "*p bir int'tir" diye okuyun - p'yi dereferans etmek size bir int verir. Bu okuma, C'nin daha sonra önünüze atabileceği her karmaşık bildirimde ayakta kalır.
*'ı nereye koyduğunuz derleyicinin yok saydığı bir tarz seçimidir: int* p, int *p ve int * p hepsi aynı şeyi bildirir. C kodunun çoğu int *p yazar ve pratik bir sebebi vardır:
int* a, b; // a int'e göstericidir; b DÜZ bir int'tir, gösterici değil
int *a, *b; // ikisi de göstericidir - * tipe değil isme bağlanır
*, tipe değil bildirimciye bağlanır. Onu ismin yanına yazmak bunu görünür tutar.
& ve *: İki Operatör
Tüm işi iki operatör yapar ve bunlar tam olarak birbirinin zıddıdır.
&x- adres-al.x'in yaşadığı adresi üretir.*p- dereferans.p'deki adrese gider ve orada yaşayan nesneyi size verir.
Son iki satır göstericilerin tüm meselesidir. age'e doğrudan hiçbir şey atanmadı, yine de age değişti - çünkü *p = 31, "p'nin tuttuğu adrese 31 sakla" demektir ve o adres age'in adresidir.
İşte bellekteki resim:
p age
+------------+ +----------+
| 0x7ffd1c40 | --------> | 31 |
+------------+ +----------+
0x7ffd1c38'de 0x7ffd1c40'ta
p -> 0x7ffd1c40 adresi
*p -> orada saklanan int, 31
&age -> 0x7ffd1c40, p'nin tuttugu adresin ayni
&p -> 0x7ffd1c38, p'nin kendisinin yasadigi yer
Son satıra dikkat edin: gösterici de bellekte bir yerde bulunan bir değişkendir, dolayısıyla kendi adresi de vardır. Bu bir kurnazlık sorusu değil, yalnızca tutarlılıktır - ve göstericiye göstericiler (int **) böyle çalışır.
& ile * birbirini geri aldığı için *&age yalnızca age'dir ve &*p yalnızca p'dir.
Gösterici Tipleri Neden Önemlidir
Bir gösterici bir adres saklar ve adres yalnızca bir sayıdır - peki derleyici neden onun bir int'i mi yoksa bir double'ı mı gösterdiğini bilmekte ısrar eder? İki sebep:
- Kaç bayt okunacağı. Bir
int *'ı dereferans etmek (tipik olarak) 4 bayt okur; birdouble *'ı dereferans etmek 8 okur. Tip olmadan*panlamsız olurdu. - O baytların nasıl yorumlanacağı. Aynı bit örüntüsü
intolarak bir sayı,floatolarak tamamen başka bir sayıdır.
Öte yandan göstericinin kendi boyutu neyi gösterdiğine bağlı değildir - adres adrestir:
64 bitlik bir makinede üç göstericinin de boyutu 8 bayttır, oysa gösterdikleri şeyler 4, 8 ve 1'dir. *p'yi ve gösterici aritmetiğini anlamlı kılan şey gösterilen tiptir.
Gösterici tipleri arasında atama yapmak derleyicinin şikâyet edeceği bir hatadır ve haklı bir sebeple - pi = &d;, bir double'ın 4 baytını okuyup sonucu tam sayı diye adlandırmanıza yol açardı.
Klasik Gerekçe: swap()
İşte her C dersinin uzandığı fonksiyon, çünkü tam olarak göstericileri açıklayan biçimde başarısız olur.
Fonksiyon onları takas ettiğini bildirir - ve main hiçbir şey olmadığını bildirir. broken_swap, x ile y'nin kopyalarını aldı. Kopyalarını kusursuzca takas etti, sonra fonksiyon döndüğünde o kopyalar var olmayı bıraktı.
Onun yerine adresleri verin:
Şimdi a ve b yine kopyadır - iki adresin kopyası - ama bir adresin kopyası, özgününün gösterdiği aynı değişkeni gösterir. *a, fonksiyon nereden çağrılırsa çağrılsın x'tir.
Bu, çağıranın verisini değiştiren her C fonksiyonunun ve genel olarak fonksiyon parametrelerinin ardındaki kalıptır: C'de referansla geçme yoktur, dolayısıyla bir gösterici geçirip onu dereferans edersiniz. scanf'in bir & istemesinin sebebi de budur:
int n;
scanf("%d", &n); // scanf n'nin içine yazabilmeli, o yüzden n'nin adresine ihtiyacı var
Birden Fazla Değer Döndürmek
Bir C fonksiyonu bir değer döndürür. Göstericiler bunun etrafından dolaşma yolunuzdur: fazladan sonuçlar için adresler geçirin.
Dönüş değeri durumu, göstericiler sonuçları taşır. Bu ayrım C'nin kendi standart kütüphanesinde ve işletim sistemi API'lerinde her yerdedir.
Göstericiye Göstericiler
Bir gösterici bir değişken olduğu için onun adresini alabilirsiniz ve o adresin tipi "int'e göstericiye gösterici"dir:
pp p value
+------+ +------+ +------+
| &p | ----> | &val | -----> | 8 |
+------+ +------+ +------+
** ile gerçekten karşılaşacağınız yer, bir fonksiyonun çağıranın tuttuğu bir göstericiyi değiştirmesi gerektiğinde - örneğin çağıranın göstericisini taze belleğe ayarlayan bir tahsis edicide - ve komut satırı argümanlarının char *argv[]'sindedir.
Yapmadan Önce Bilmeye Değer Dört Hata
Hiçbir yere yöneltilmemiş bir göstericiyi kullanmak. İlk değersiz bir gösterici, orada zaten ne bayt varsa onu tutar. Onu dereferans etmek rastgele bir adresi okur ya da yazar.
int *p; // p çöp tutuyor
*p = 10; // tanımsız davranış - muhtemelen bir çökme
Her göstericiye gerçek bir adres ya da NULL ile ilk değer verin. Bununla ilgili disiplin için bkz. boş göstericiler.
*p ile p'yi karıştırmak. p = 5;, göstericiyi 5 adresine ayarlar (saçmalık); *p = 5;, gösterdiği yere 5 saklar. Derleyici birincisi için uyarır ve uyarılar okunmaya değerdir.
scanf'te &'i unutmak. scanf("%d", n), n'nin değerini bir adres olarak geçirir. Bir uyarıyla derlenir ve sonra o sayının adlandırdığı belleğe ne varsa yazar.
Yerel bir değişkenin adresini döndürmek. Fonksiyon döndüğü anda yerel gitmiştir; adres, yıkılmış bir mülkün tapusudur.
int *bad(void) {
int local = 42;
return &local; // çağıran sarkan bir gösterici alır
}
Çağrıdan daha uzun yaşayan belleğe ihtiyacınız varsa onu tahsis edin - dinamik bellek bunun içindir.
Sıradaki Nereye
Göstericiler "bir adres tut"ta durmaz. Bir göstericiye eklemek onu tipinin birimleri cinsinden bellekte adımlatır ki bu gösterici aritmetiğinin konusudur; ve C'de bir dizi adı ilk elemanına bir göstericiye dönüşür, göstericiler ve dizilerin ikisini de bildiğinizde fiilen aynı konu olmasının sebebi budur. Sıradaki o ikisini okuyun, C'nin geri kalanı sihir gibi görünmeyi bırakır.
Sıkça Sorulan Sorular
C'de gösterici nedir?
Gösterici, değeri bir bellek adresi olan bir değişkendir - bellekte başka bir nesnenin konumu. int *p = &age;, p'nin age'in adresini tutmasını sağlar ve *p o adres üzerinden age değişkenini okur ya da yazar.
C'de * ile & arasındaki fark nedir?
&x adres-al operatörüdür: x'in yaşadığı adresi üretir. *p dereferans operatörüdür: p'de saklı adrese gider ve oradaki nesneyi size verir. Birbirlerini geri alırlar, dolayısıyla *&x yalnızca x'tir.
* neden iki kez görünüyor - bildirimde ve göstericiyi kullanırken?
Bunlar aynı sembolün iki farklı kullanımıdır. int *p; içinde * tipin bir parçasıdır ve "p int'e bir göstericidir" der. *p = 5; içinde * dereferans operatörüdür ve "p'nin tuttuğu adrese 5 sakla" der. Bildirimi "*p bir int'tir" diye okumak ikisini de anlamlı kılar.
C'de göstericilere neden ihtiyacım var?
C her argümanı değerle geçirir, dolayısıyla bir fonksiyon, adres verilmedikçe çağıranın değişkenini asla değiştiremez. Göstericiler ayrıca dizilerde verimli dolaşmanızı, birden fazla sonuç döndürmenizi, bağlı yapılar kurmanızı ve malloc ile çalışma zamanında tahsis edilmiş belleği kullanmanızı sağlar.