Menu

C'de Fonksiyon Parametreleri: Değerle Geçme, Göstericiler ve Diziler

C her şeyi değerle geçirir - fonksiyonlar kopya alır, asıl nesneyi değil. Bunun pratikte ne anlama geldiği, göstericilerin referansla geçmeyi nasıl taklit ettiği, dizilerin neden farklı davrandığı ve birden fazla sonucun nasıl döndürüleceği.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Parametreler ve Argümanlar

Birbirinin yerine kullanılan ama ayırmaya değen iki kelime:

  • Parametre, fonksiyonun tanımındaki değişkendir - int square(int n) içindeki int n.
  • Argüman, çağrı yerinde sağladığınız değerdir - square(4) içindeki 4.

Bir fonksiyonu çağırmak, parametrelerini yeni yerel değişkenler olarak oluşturur ve argümanları onların içine kopyalar. Bu sayfanın tüm konusu o kopyadır.

C Her Zaman Değerle Geçirir

Kural budur ve C'nin buna istisnası yoktur: bir fonksiyon argümanlarının kopyalarını alır. Bir parametreye atama yapmak yalnızca kopyayı değiştirir.

value hâlâ 42'dir. Fonksiyon x adında bir kopya aldı, kopyayı sıfırladı ve fonksiyon döndüğünde kopya yok oldu. Parametreyi value diye yeniden adlandırmak hiçbir şeyi değiştirmez - isim onları bağlamaz; bunlar iki farklı değişkendir.

Aynısı her tür için geçerlidir: int, double, char, bir gösterici, hatta bütün bir yapı (alan alan kopyalanır). Python, Java ya da JavaScript'ten geliyorsanız, o dillerin nesne referanslarını değerle geçirdiğini, dolayısıyla bir fonksiyonun içinde nesneyi değiştirmenin dışarıdan görüneceğini not edin. C'de bu yoktur - nesnenin kendisini kopyalar.

Değerle geçmek bir kısıtlama değil bir özelliktir. Bir fonksiyon, çağıranın verisini kazara bozamaz; bu da üzerine akıl yürütmeyi çok daha kolaylaştırır.

Klasik Başarısızlık: swap

Ders kitabı gösterimi, iki değeri takas etmeye çalışan bir fonksiyondur:

Mantık doğrudur - gerçekten a ile b'yi takas eder. Ama a ve b kopyadır, dolayısıyla fonksiyon döndüğü anda iş çöpe gider.

Göstericilerle Referansla Geçmeyi Taklit Etmek

Bir fonksiyonun çağıranın değişkenini değiştirmesini sağlamak için ona değerini değil değişkenin adresini verin. O adres de değerle geçirilir - gösterici kopyalanır - ama kopya yine de özgün nesneyi gösterir, dolayısıyla onun üzerinden yazmak çağıranın belleğine ulaşır.

Fikrin tamamını üç söz dizimi parçası taşır:

  • Parametre listesindeki int *a, a'yı "bir int'e gösterici" olarak bildirir.
  • Çağrı yerindeki &x, x'in adresini üretir.
  • Fonksiyonun içindeki *a, "a'nın gösterdiği int" demektir - okunabilir ve atanabilir.

C programcılarının "referansla geçme" derken kastettiği budur ve kesin olmaya değer: bu hâlâ değerle geçmedir. Gösterici kopyalanır. a'nın kendisine atama (a = NULL;) yalnızca kopyayı değiştirirdi; *a'ya atama çağıranın değişkenini değiştirir. Göstericiler modeli tam olarak ele alıyor.

Bedeli, bir çağrı yerinin artık bir değişkenin değişip değişemeyeceğini söylememesidir - &'in zorunlu olmasının sebebi tam olarak budur. swap(x, y) derlenmez; swap(&x, &y) yazmanız gerekir ve o &, bu çağrının x'i değiştirebileceğinin görünür işaretidir.

Birden Fazla Sonuç Döndürmek

return tek bir değer üretir, dolayısıyla fazladan çıktılar gösterici parametreler üzerinden gider. Yaygın biçim, dönüş değeri olarak bir durum kodu ve göstericiler üzerinden yazılan gerçek sonuçlardır:

Çağrı başarısız olduğunda q ve r'nin önceki değerlerini koruduğuna dikkat edin - fonksiyon hiçbir şey yazmadan döndü. Çağıranın uyması gereken sözleşme budur ve durum denetiminin çıktıları kullanmadan önce gelmesinin sebebi de budur.

Alternatif, her iki alanı tutan bir yapı döndürmektir; bu göstericileri tamamen ortadan kaldırır ve değerler gerçekten bir arada durduğunda çoğu zaman daha hoş bir tasarımdır.

Diziler Farklıdır

Diziler kopyalama kuralına uymaz ve bu neredeyse herkesi tökezletir. Bir diziyi geçirdiğinizde ilk elemanına giden bir göstericiye dönüşür - dolayısıyla fonksiyon verinin kopyasını değil bir adres alır.

Çağıranın dizisi gerçekten değişti, hiçbir yerde & olmadan - çünkü bu bağlamda data zaten bir adrestir. Üç sonuç:

Bir parametre listesinde int a[] ile int *a tam olarak aynı şeydir. int a[100] bile öyledir; boyut yok sayılır. int a[] yazmak niyeti belgeler, ama onu "bir dizi kopyalanıyor" diye okumayın.

Fonksiyonun içindeki sizeof yanlıştır. Hata budur:

main içinde sizeof(data) 20 bayttır ve uzunluk 5 çıkar. Fonksiyonun içinde sizeof(a) bir göstericinin boyutudur - 64 bitlik bir sistemde 8 - dolayısıyla "uzunluk" 2'dir. Dizinin boyutu orada yalnızca mevcut değildir. Uzunluğu her zaman ayrı bir parametre olarak geçirin. Modern derleyiciler bunun için -Wall ile uyarır.

Yazmayacaksanız const kullanın. const int a[], fonksiyonun yalnızca okuduğunu söyler; bunu derleyici uygular ve okuyucu ona güvenebilir:

int sum(const int a[], int n);        /* diziyi değiştirmeyeceğine söz verir */

Metinler de dizidir, dolayısıyla aynısı geçerlidir: void greet(const char *name), bir metni değiştirmeden okuyan bir fonksiyon için standart imzadır.

Peki Maliyet?

Büyük bir yapıyı değerle geçirmek her çağrıda onun her baytını kopyalar. Bir avuç alanı olan bir yapı için bu sorunsuzdur ve çoğu zaman bir göstericinin eklediği dolaylılıktan daha hızlıdır. Büyük bir yapı için bir gösterici geçirin - ve fonksiyon yalnızca okuyorsa onu const işaretleyin:

struct Config { char name[64]; int flags[32]; double weights[128]; };

void applyConfig(const struct Config *cfg);   /* çağrı başına 512 baytlık kopya yok */

const struct Config *cfg, "değiştirmeyeceğim bir Config'e gösterici" demektir; bu, bir göstericinin verimliliğini bir kopyanın güvenliğiyle birlikte sunar.

Sık Yapılan Hatalar

  • Düz bir parametrenin çağıranın değişkenini değiştirmesini beklemek. Değiştiremez. Bir gösterici geçirin.
  • Çağrı yerinde &'i unutmak. swap gösterici isterken swap(x, y) bir tür hatasıdır - derleyici bunu yakalar, ama yalnızca kapsamda bir prototip varsa.
  • Fonksiyonun içinde *'ı unutmak. İki gösterici parametrede a = b; değerleri değil yerel kopyaları takas eder. İstediğiniz *a = *b;'dir.
  • Bir dizi parametresinde sizeof kullanmak. Her zaman yanlıştır. Uzunluğu geçirin.
  • Yerel bir değişkene gösterici döndürmek. Fonksiyon döndüğünde yerel ölür, dolayısıyla çağıran sarkan bir gösterici tutar ki bu tanımsız davranıştır. Değerle döndürün, çağıranın sağladığı bir tampona yazın ya da malloc ile tahsis edin.
  • Bir gösterici parametresini NULL için kontrol etmemek. Kendisine verileni dereferans eden bir fonksiyon, boş bir argümanda çöker.

Sıkça Sorulan Sorular

C'de referansla geçme var mı?

Hayır. C'nin tek bir kipi vardır: değerle geçme. Bir fonksiyon her zaman her argümanın bir kopyasını alır, dolayısıyla bir parametreye atama yapmak çağıranın değişkenine asla dokunmaz. Referansla geçme gibi görünen şey, bir göstericiyi değerle geçmektir - gösterici kopyalanır ama yine çağıranın nesnesini gösterir, dolayısıyla fonksiyon onu gösterici üzerinden değiştirebilir.

C'de bir fonksiyona dizi nasıl geçirilir?

Parametreyi int a[] ya da int *a olarak yazın - ikisi aynı şeydir - ve uzunluğu ayrı bir parametre olarak geçirin: int sum(const int a[], int n). Dizi kopyalanmaz; fonksiyon ilk elemanına bir gösterici alır, dolayısıyla a[i]'deki değişiklikler çağırana görünür.

C'de bir dizi parametresinde sizeof neden çalışmaz?

Çünkü parametre aslında bir göstericidir. Fonksiyonun içinde sizeof(a), dizinin değil bir göstericinin boyutunu (tipik olarak 8 bayt) verir - dolayısıyla sizeof(a)/sizeof(a[0]), eleman sayısı yerine 2 gibi bir şey üretir. Uzunluğu her zaman açıkça geçirin.

Bir C fonksiyonu birden fazla değeri nasıl döndürebilir?

Fazladan sonuçlar için göstericiler geçirin ve onların üzerinden yazın: int divide(int a, int b, int *quotient, int *remainder) bir başarı bayrağı döndürür ve her iki çıktıyı da doldurur. Alternatif, değerleri bir arada toplayan bir yapı döndürmektir.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA