İçeriğe geç
Educora
Orta18 dk6 / 8

İşaretçiler

Bir değişkenin bellekteki adresini al, bir işaretçi aracılığıyla değeri oku ve değiştir, işaretçi aritmetiğini ve işaretçilerin dizilerle ilişkisini kavra, bir swap fonksiyonu yaz.

Kendini test et
Bu derste öğreneceklerin
  • & ile adres almak ve * ile işaretçinin gösterdiği değeri okuyup değiştirmek
  • Bir fonksiyona işaretçi aktararak çağıran tarafın değişkenlerini değiştirmek
  • İşaretçi aritmetiğini ve arr[i] ile *(arr + i) ifadelerinin neden aynı olduğunu açıklamak
  • NULL ve ilk değeri verilmemiş işaretçilerin tehlikesini tanımak

Bir arkadaşına nerede oturduğunu göstermek için evini taşımazsın; sadece adresi verirsin: “Bakü, Nizami Caddesi, 25”. Adresi bilen biri evi bulabilir, içeri girebilir ve hatta orada bir şeyi değiştirebilir. Bilgisayarın belleği de böyledir: her değişken belirli bir adreste yaşar. Bir adresi saklayan değişkene işaretçi (pointer) denir. İşaretçiler C'nin en güçlü ve en çok korkulan konusudur ama fikri bu adres kadar basittir.

Adresler ve işaretçiler

Tanım
İşaretçi

Değeri başka bir değişkenin bellekteki adresi olan değişken. int *p;, “p bir int değerin adresini saklar” demektir.

YazımAnlamı
int *p;bir int'e işaretçi tanımlar
&xx değişkeninin adresi
p = &x;p artık x'i gösterir
*pp'deki adreste bulunan değer (başvuru çözme, dereference)
NULLhiçbir şeyi göstermeyen işaretçi
C
#include <stdio.h>

int main(void) {
    int x = 10;
    int *p = &x;
    printf("x = %d\n", x);
    printf("*p = %d\n", *p);
    *p = 25;
    printf("x = %d\n", x);
    printf("Same address? %d\n", p == &x);
    return 0;
}
Beklenen çıktı
x = 10
*p = 10
x = 25
Same address? 1
*p = 25;, adını hiç anmadan x'i değiştirdi; çünkü p onun adresini tutuyor.

Adresin kendisini %p belirteciyle yazdırabilirsin. Genellikle 0x7ffd… gibi on altılık bir sayı olarak görünür ve program her çalıştırıldığında farklı olabilir:

C
int x = 10;
int *p = &x;
printf("Address of x: %p\n", (void *) &x);
printf("Value of p:   %p\n", (void *) p);

Fonksiyonlara işaretçi aktarmak: swap

Hatırla: C'de bir fonksiyon argümanlarının kopyasını alır; bu yüzden sıradan parametrelerle iki değişkenin değerini takas edemezsin. Çözüm, fonksiyona değişkenlerin adreslerini vermektir. Fonksiyon adresler aracılığıyla asıl değişkenlere ulaşır. scanf("%d", &age) tam da bu yüzden & ister.

C
#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void) {
    int x = 3, y = 8;
    printf("Before: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("After:  x = %d, y = %d\n", x, y);
    return 0;
}
Beklenen çıktı
Before: x = 3, y = 8
After:  x = 8, y = 3

İşaretçiler bir fonksiyonun birden fazla sonuç “döndürmesini” de sağlar: return yalnızca tek bir değer döndürür ama fonksiyon sonuçlarını kendisine verilen adreslere yazabilir. Aşağıdaki fonksiyon Bakü'de ölçülen birkaç sıcaklığın en düşüğünü ve en yükseğini bulur:

C
#include <stdio.h>

void min_max(const int *arr, int n, int *min, int *max) {
    *min = arr[0];
    *max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

int main(void) {
    int temps[5] = {18, 25, 21, 30, 16};
    int low, high;
    min_max(temps, 5, &low, &high);
    printf("Min: %d, max: %d\n", low, high);
    return 0;
}
Beklenen çıktı
Min: 16, max: 30

İşaretçi aritmetiği ve diziler

İfadelerde bir dizinin adı ilk elemanının adresine dönüşür; bu yüzden int *p = arr; yazılabilir. Bir işaretçiye 1 eklediğinde 1 bayt değil, bir eleman ilerler: int * için genellikle 4 bayt. Yani *(p + 2) üçüncü elemandır; arr[i] yazımı da *(arr + i) ifadesinin kısaltmasından ibarettir. Bir diziyi fonksiyona aktardığında aslında ilk elemanının işaretçisi aktarılır; fonksiyonun asıl diziyi değiştirebilmesinin ve sizeof hilesinin parametrede çalışmamasının nedeni budur.

C
#include <stdio.h>

int main(void) {
    int arr[4] = {10, 20, 30, 40};
    int *p = arr;
    printf("%d\n", *p);
    printf("%d\n", *(p + 2));
    p++;
    printf("%d\n", *p);
    printf("%d\n", arr[3] == *(arr + 3));
    int sum = 0;
    for (int *q = arr; q < arr + 4; q++) {
        sum += *q;
    }
    printf("Sum: %d\n", sum);
    return 0;
}
Beklenen çıktı
10
30
20
1
Sum: 100
p++ işaretçiyi bir sonraki elemana taşıdı. Son döngü diziyi indeks yerine bir işaretçiyle dolaşıyor.

Tehlikeli işaretçiler

  • İlk değeri verilmemiş bir işaretçi rastgele bir adres tutar. Onun üzerinden yazmak başka verileri bozabilir. İşaretçiye her zaman bir değişkenin adresini ya da NULL ver.
  • **NULL bir işaretçinin başvurusunu çözmek** (p NULL iken *p) genellikle programı hemen çökertir. Kullanmadan önce denetle: if (p != NULL).
  • Sarkan işaretçi (dangling pointer) artık var olmayan bir değişkeni gösterir. Örneğin bir fonksiyon kendi yerel değişkeninin adresini döndürürse, fonksiyon bitince o değişken yok olur.
C
int *make_number(void) {
    int local = 5;
    return &local;
}
HATA örneği: local fonksiyon bitince yok olur, döndürülen adres ise “boşluğu” gösterir. Sonuç öngörülemez.

Önemli noktalar

  • Bir işaretçi adres saklar: int *p = &x; ile p, x'in adresini tutar.
  • & adresi verir, * o adresteki değeri okur ya da değiştirir.
  • İşaretçi alan bir fonksiyon çağıran tarafın değişkenlerini değiştirebilir (swap, scanf).
  • p + 1 bir eleman ileri gider; arr[i], *(arr + i) demektir.
  • İşaretçilere her zaman ilk değer ver, NULL kontrolü yap ve yerel bir değişkenin adresini asla döndürme.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
int x = 4; int *p = &x; *p = 9; satırlarından sonra x kaçtır?