Örneklerle C Programlamada Fonksiyon İşaretçileri

⚡ Akıllı Özet

C dilinde fonksiyon işaretçileri, çalıştırılabilir kodun adresini saklar ve programların fonksiyonları dolaylı olarak çağırmasına, onları argüman olarak geçirmesine, dizilerden dağıtım tabloları oluşturmasına ve esnek, yeniden kullanılabilir tasarımlar için qsort karşılaştırma rutini gibi geri çağırma işlevlerini uygulamasına olanak tanır.

  • 🔗 beyanı: Fonksiyon işaretçisi, `return_type (*name)(arguments)` biçimini kullanır; adın etrafındaki parantezler zorunludur.
  • 🔄 Adrese göre takas: Değişken adreslerini işaretçi parametrelerine iletmek, bir fonksiyonun çağıranın orijinal değerlerini değiştirmesine olanak tanır; tıpkı takas örneğinde olduğu gibi.
  • ???? Diziler: Bir dizi adı zaten bir işaretçi olduğundan, fonksiyonlar bir dizi alabilir veya statik bir diziye işaretçi döndürebilir.
  • 🎛️ Dağıtım tabloları: Fonksiyon işaretçilerinden oluşan bir dizi, seçilen işleme doğrudan indeksleme yaparak uzun switch veya if zincirlerinin yerini alır.
  • 🧊 Boş işaretçiler: `void *` parametresi veya dönüş tipi herhangi bir türü kabul eder; fonksiyon, kullanımdan önce bu türü tekrar eski haline dönüştürür.
  • 📞 Geri aramalar: Bir fonksiyon işaretçisini argüman olarak geçirmek, qsort karşılaştırma fonksiyonu gibi geri çağırma fonksiyonlarını çalıştırır.
  • 🤖 Yapay zeka yardımı: GitHub Copilot ve yapay zeka asistanları, fonksiyon işaretçisi sözdizimini, tür tanımlarını ve geri çağrı imzalarını oluşturur, ardından uyuşmayan parametreleri işaretler.

C dilinde Fonksiyon İşaretçileri

İşaretçiler, tek bir değer döndürmekle sınırlı olduğumuz 'C' fonksiyonlarına büyük olanaklar sağlar. İşaretçi parametreleriyle, işlevlerimiz artık verilerin bir kopyası yerine gerçek verileri işleyebiliyor.

Değişkenlerin gerçek değerlerini değiştirmek için çağıran ifade, adresleri bir işlevdeki işaretçi parametrelerine iletir.

İşlev İşaretçileri Örneği

Örneğin, bir sonraki program ikinin iki değerini değiştirir:

void swap (int *a, int *b);
int main() {
  int m = 25;
  int n = 100;
  printf("m is %d, n is %d\n", m, n);
  swap(&m, &n);
  printf("m is %d, n is %d\n", m, n);
  return 0;}
void swap (int *a, int *b) {
  int temp;
  temp = *a;
  *a = *b;
  *b = temp;}
}

Çıktı:

m is 25, n is 100
m is 100, n is 25

İşlev İşaretçileri Örneği

Program, gerçek değişkenlerin değerlerini değiştirir çünkü işlev bunlara adres yoluyla erişir. işaretçi. Burada program sürecini tartışacağız:

  1. Takas işleminden sorumlu fonksiyonu açıklıyoruz.ping Bu iki değişkenli fonksiyon, parametre olarak iki tamsayı işaretçisi alır ve çağrıldığında herhangi bir değer döndürür.
  2. Ana fonksiyonda, iki tamsayı değişkeni ('m' ve 'n') bildirir ve başlatırız, ardından sırasıyla değerlerini yazdırırız.
  3. İki değişkenin adresini ve işareti kullanarak argüman olarak ileterek swap() işlevini çağırırız. Bundan sonra değişkenlerin yeni değiştirilen değerlerini yazdırıyoruz.
  4. Burada, iki tam sayı değişken adresini parametre olarak alan ve ikinci değişkene konulacak değer değişkenlerinden birini kaydetmek için üçüncü bir depolama kutusu olarak kullanılan geçici bir tam sayı değişkeni bildiren swap() fonksiyonunun içeriğini tanımlıyoruz.
  5. 'a' ile gösterilen ilk değişkenin içeriğini geçici değişkene kaydedin.
  6. B ile gösterilen ikinci değişkeni, a ile gösterilen ilk değişkende saklayın.
  7. İkinci değişkeni (b ile gösterilen), geçici değişkende kaydedilen ilk değişkenin değeriyle güncelleyin.

Dizi Parametreleriyle İşlevler

C'de bir diziyi değere göre bir fonksiyona aktaramayız. Oysa dizi adı bir işaretçidir (adres), bu nedenle diziye bir işaretçi iletmek anlamına gelen bir işleve yalnızca bir dizi adı iletiriz.

Örneğin, aşağıdaki programı ele alalım:

int add_array (int *a, int num_elements);
int main() {
  int Tab[5] = {100, 220, 37, 16, 98};
  printf("Total summation is %d\n", add_array(Tab, 5)); 
  return 0;}
int add_array (int *p, int size) {
  int total = 0;
  int k;
  for (k = 0; k < size; k++) {
    total += p[k];  /* it is equivalent to total +=*p ;p++; */}
 return (total);}

Çıktı:

 Total summation is 471

Burada program kodunu detaylarıyla açıklayacağız

Dizi Parametreleriyle İşlevler

  1. Bir dizi adresini (işaretçiyi) öğe sayısıyla birlikte parametre olarak alan ve bu öğelerin toplam toplamını döndüren add_array() işlevini bildirir ve tanımlarız. İşaretçi, dizi öğelerini yinelemek için kullanılır (p[k] gösterimini kullanarak) ve toplamı, tüm öğe dizisi yinelendikten sonra döndürülecek olan yerel bir değişkende biriktiririz.
  2. Beş tamsayı öğeli bir tamsayı dizisini bildiriyor ve başlatıyoruz. Dizi adını (adres görevi görür) ve dizi boyutunu ileterek toplam toplamı yazdırırız. add_array()argüman olarak işlev denir.

Dizi Döndüren İşlevler

C'de, aşağıdaki programda olduğu gibi bir diziye işaretçi döndürebiliriz:

#include <stdio.h>
int * build_array();
int main() {
  int *a;
  a = build_array(); /* get first 5 even numbers */
  for (k = 0; k < 5; k++)
    printf("%d\n", a[k]);
  return 0;}
int * build_array() {
  static int Tab[5]={1,2,3,4,5};
   return (Tab);}

Çıktı:

1
2
3
4
5

Ve burada program detaylarını tartışacağız

Dizi Döndüren İşlevler

  1. Tamsayı değeri içeren bir dizi adresi döndüren ve herhangi bir argüman almayan bir fonksiyon tanımlıyor ve bildiriyoruz.
  2. İşlev çağrıldıktan sonra oluşturulan dizinin tamamını alan bir tamsayı işaretçisi bildiririz ve beş öğeli dizinin tamamını yineleyerek içeriğini yazdırırız.

İşlev tarafından döndürülen dizi adresini depolamak için bir dizi değil, bir işaretçinin tanımlandığına dikkat edin. Ayrıca, bir işlevden yerel bir değişken döndürüldüğünde, onu işlevde statik olarak bildirmemiz gerektiğine dikkat edin.

İşlev İşaretçileri

Tanım gereği işaretçilerin herhangi bir bellek konumundaki bir adresi işaret ettiğini bildiğimiz gibi, bellekteki işlevler olarak yürütülebilir kodun başlangıcına da işaret edebilirler.
İşlev işaretçisi * ile bildirilir, bildiriminin genel ifadesi şöyledir:

return_type (*function_name)(arguments)

(*işlev_adı) etrafındaki parantezlerin önemli olduğunu unutmamalısınız çünkü bunlar olmadan derleyici, işlev_adı'nın dönüş_tipi işaretçisini döndürdüğünü düşünecektir.
Fonksiyon işaretçisini tanımladıktan sonra, onu bir fonksiyona atamamız gerekir. Örneğin, bir sonraki program sıradan bir fonksiyon bildirir, bir fonksiyon işaretçisi tanımlar, fonksiyon işaretçisini sıradan fonksiyona atar ve bundan sonra fonksiyonu işaretçi aracılığıyla çağırır:

#include <stdio.h>
void Hi_function (int times); /* function */
int main() {
  void (*function_ptr)(int);  /* function pointer Declaration */
  function_ptr = Hi_function;  /* pointer assignment */
  function_ptr (3);  /* function call */
 return 0;}
void Hi_function (int times) {
  int k;
  for (k = 0; k < times; k++) printf("Hi\n");}

Çıktı:

Hi
Hi
Hi

C'deki İşlev İşaretçileri

  1. Fonksiyon çağrıldığında times parametresiyle belirtilen k kez Hi metnini basan standart bir fonksiyon tanımlar ve bildiririz.
  2. Bir tamsayı parametresi alan ve hiçbir şey döndürmeyen bir işaretçi işlevi (özel bildirimiyle) tanımlarız.
  3. İşaretçi fonksiyonumuzu Hi_function ile başlatırız, bu da işaretçinin Hi_function()'a işaret ettiği anlamına gelir.
  4. ta tarafından yapılan standart fonksiyon çağrısı yerineping Fonksiyon adını argümanlarla birlikte verdiğimizde, sadece işaretçi fonksiyonunu 3 sayısını argüman olarak geçirerek çağırıyoruz ve işlem tamamlanıyor!

İşlev adının, ilk öğesine işaret eden bir dizi adı gibi, yürütülebilir kodun başlangıç ​​adresine işaret ettiğini unutmayın. Bu nedenle function_ptr = &Hi_function ve (*funptr)(3) gibi talimatlar doğrudur.
NOT: Fonksiyon ataması ve fonksiyon çağrısı sırasında adres operatörünü & ve dolaylı operatör *'i eklemek önemli değildir.

İşlev İşaretçileri Dizisi

Bir dizi işlev işaretçisi, bir sonraki programda olduğu gibi, karar vermede bir anahtar veya if ifadesi rolü oynayabilir:

#include <stdio.h>
int sum(int num1, int num2);
int sub(int num1, int num2);
int mult(int num1, int num2);
int div(int num1, int num2);

int main() 
{  int x, y, choice, result;
  int (*ope[4])(int, int);
  ope[0] = sum;
  ope[1] = sub;
  ope[2] = mult;
  ope[3] = div;
  printf("Enter two integer numbers: ");
  scanf("%d%d", &x, &y);
  printf("Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: ");
  scanf("%d", &choice);
  result = ope[choice](x, y);
  printf("%d", result);
return 0;}

int sum(int x, int y) {return(x + y);}
int sub(int x, int y) {return(x - y);}
int mult(int x, int y) {return(x * y);}
int div(int x, int y) {if (y != 0) return (x / y); else  return 0;}
Enter two integer numbers: 13 48
Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: 2
624

Programın detaylarını burada tartışıyoruz:

İşlev İşaretçileri Dizisi

  1. Dört tanesini ilan edip tanımlıyoruz fonksiyonlar İki tamsayı argümanı alan ve bir tamsayı değeri döndüren fonksiyonlar. Bu fonksiyonlar toplama, çıkarma işlemlerini gerçekleştirir.tracKullanıcının hangi fonksiyonu çağırdığına ilişkin iki argümanı çarpın ve bölün.
  2. İşlenenleri, işlem türünü ve sonucu işlemek için sırasıyla 4 tam sayı bildiririz. Ayrıca dört işlev işaretçisinden oluşan bir dizi bildiririz. Dizi öğesinin her işlev işaretçisi iki tamsayı parametresi alır ve bir tamsayı değeri döndürür.
  3. Her dizi öğesini önceden bildirilmiş olan işlevle atar ve başlatırız. Örneğin üçüncü fonksiyon işaretçisi olan üçüncü eleman çarpma işlemi fonksiyonuna işaret edecektir.
  4. Kullanıcının klavyeyle yazdığı işlenenleri ve işlem türünü ararız.
  5. Uygun dizi elemanını (Fonksiyon işaretçisi) argümanlarla çağırdık ve uygun fonksiyonun ürettiği sonucu sakladık.

Komut int (*ope[4])(int, int); işlev işaretçilerinin dizisini tanımlar. Her dizi öğesi aynı parametrelere ve dönüş türüne sahip olmalıdır.
Sonuç = ope[seçim](x, y); kullanıcının yaptığı seçime göre uygun fonksiyonu çalıştırır. Girilen iki tamsayı, fonksiyona iletilen argümanlardır.

Geçersiz İşaretçileri Kullanan İşlevler

İşlev bildirimleri sırasında geçersiz işaretçiler kullanılır. Herhangi bir türün döndürülmesine izin veren void * return türünü kullanırız. Bir fonksiyona geçerken parametrelerimizin değişmediğini varsayarsak onu const olarak bildiririz.
Örneğin:

 void * cube (const void *);

Aşağıdaki programı göz önünde bulundurun:

#include <stdio.h>
void* cube (const void* num);
int main() {
  int x, cube_int;
  x = 4;
  cube_int = cube (&x);
  printf("%d cubed is %d\n", x, cube_int);
  return 0;}

void* cube (const void *num) {
  int result;
  result = (*(int *)num) * (*(int *)num) * (*(int *)num);
  return result;}

Sonuç:

 4 cubed is 64

Program detaylarını burada tartışacağız:

Geçersiz İşaretçileri Kullanan İşlevler

  1. Tamsayı değeri döndüren ve belirli bir veri türü olmadan değiştirilemeyen değişkenin adresini alan bir fonksiyon tanımlıyor ve bildiriyoruz. Sayı işaretçisinin işaret ettiği içerik değişkeninin (x) küp değerini hesaplıyoruz ve bu bir geçersiz işaretçi olduğu için, bunu belirli bir gösterim (* veri türü) işaretçisi kullanarak bir tamsayı veri türüne dönüştürmemiz gerekiyor ve geri dönüyoruz küp değeri.
  2. İşleneni ve sonuç değişkenini bildiririz. Ayrıca işlenenimizi “4” değeriyle başlatıyoruz.
  3. İşlenen adresini ileterek küp fonksiyonunu çağırıyoruz ve sonuç değişkeninde dönen değeri işliyoruz.

Bağımsız Değişken Olarak İşlev İşaretçileri

Bir işlev işaretçisini, alıcı işlevin "geri çağırması" nedeniyle bazen "geri çağırma işlevi" olarak adlandırılan başka bir işleve argüman olarak ileterek yararlanmanın başka bir yolu.
Stdlib.h başlık dosyasındaki Quicksort “qsort()” işlevi, bir diziyi sıralamaya adanmış bir algoritma olan bu tekniği kullanır.

void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
  • void *base : diziye yönelik void işaretçisi.
  • size_t num : Dizi öğesi numarası.
  • size_t genişlik Öğe boyutu.
  • int (*compare (const void *, const void *) : iki bağımsız değişkenden oluşan işlev işaretçisi ve bağımsız değişkenler aynı değere sahip olduğunda 0, arg0 arg1'den önce geldiğinde <2 ve arg0 arg1'den sonra geldiğinde >2 döndürür.

Aşağıdaki program, qsort() fonksiyonunu kullanarak bir tamsayı dizisini küçükten büyüğe doğru sıralar:

#include <stdio.h>
#include <stdlib.h>
int compare (const void *, const void *); 
int main() {
  int arr[5] = {52, 14, 50, 48, 13};
  int num, width, i;
  num = sizeof(arr)/sizeof(arr[0]);
  width = sizeof(arr[0]);
  qsort((void *)arr, num, width, compare);
  for (i = 0; i < 5; i++)
    printf("%d ", arr[ i ]);
  return 0;}
int compare (const void *elem1, const void *elem2) {
  if ((*(int *)elem1) == (*(int *)elem2))  return 0;
  else if ((*(int *)elem1) < (*(int *)elem2)) return -1;
  else return 1;}

Sonuç:

 13 14 48 50 52

Program detaylarını burada tartışacağız:

Bağımsız Değişken Olarak İşlev İşaretçileri

  1. İki bağımsız değişkenden oluşan karşılaştırma işlevini tanımlarız ve bağımsız değişkenler aynı değere sahip olduğunda 0 değerini döndürür, arg0 arg1'den önce geldiğinde <2 ve arg0 arg1'den sonra geldiğinde >2 döndürür. Parametreler, uygun dizi veri türüne dönüştürülen bir geçersiz işaretçi türüdür. (tamsayı)
  2. Bir tamsayı dizisi tanımlar ve başlatırız. Dizi boyutu, num değişkendir ve her dizi öğesinin boyutu önceden tanımlanmış sizeof() kullanılarak genişlik değişkeninde saklanır C operatörü.
  3. Diziyi artan sırada sıralamak için `qsort` fonksiyonunu çağırıyoruz ve kullanıcı tarafından önceden tanımlanan dizi adını, boyutunu, genişliğini ve karşılaştırma fonksiyonunu bu fonksiyona iletiyoruz. Karşılaştırma, her yinelemede iki dizi elemanı alınarak, tüm dizi sıralanana kadar gerçekleştirilecektir.
  4. Dizimizin iyi sıralandığından emin olmak için dizi elemanlarını, diziyi kullanarak tüm diziyi yineleyerek yazdırıyoruz. döngü için.

SSS

Evet. Bir fonksiyon işaretçisi, hedef fonksiyonun dönüş türü ve parametre listesiyle tam olarak eşleşmelidir. Farklı bir imzaya sahip bir fonksiyona atama yapmak, derlense bile tanımsız davranıştır, çünkü çağrı çalışma zamanında argümanları yanlış bir şekilde ekler ve okur.

`typedef`, karmaşık bir fonksiyon işaretçi türüne kısa bir ad verir. `typedef int (*` yazmak gibi.Operation)(int, int); size bildirme olanağı sağlar. OperaBu sayede, işaretçi sözdiziminin tamamını tekrarlamak yerine, diziler, parametreler ve yapı üyeleri çok daha kolay okunabilir hale gelir.

Fonksiyon işaretçileri, geri çağırmaları, atlama veya dağıtım tablolarını, eklenti ve olay odaklı sistemleri, durum makinelerini ve qsort ve bsearch gibi kütüphane rutinlerini yönlendirir. OperaSistemler ve gömülü yazılımlar, uzun koşullu zincirler olmadan çalışma zamanında davranış seçmek için bunlara güvenir.

Veri işaretçisi bellekteki bir değişkenin adresini tutarken, fonksiyon işaretçisi çalıştırılabilir kodun giriş adresini tutar. Veri okumak veya yazmak için işaretçiyi kullanmak yerine, fonksiyonu çalıştırmak için fonksiyon işaretçisi aracılığıyla çağrı yaparsınız.

Evet. Bir fonksiyon işaretçisi, hiçbir fonksiyona atama yapılmadığı anlamına gelecek şekilde NULL olarak ayarlanabilir. NULL bir fonksiyon işaretçisini çağırmak tanımsız davranışa neden olur, bu nedenle özellikle isteğe bağlı geri çağırmalar için çağırmadan önce if (ptr != NULL) ile koruma sağlayın.

Evet. Fonksiyon işaretçilerini yapı üyeleri olarak yerleştirmek, bir yapının verileri ilgili işlemlerle bir araya getirmesine olanak tanır ve nesneleri ve sanal metot tablolarını taklit eder. Bu model, her örneğin kendi fonksiyon uygulamalarını seçtiği birçok C kütüphanesinin ve aygıt sürücüsünün temelini oluşturur.

Evet. Yapay zekâ destekli bir kodlama asistanı, düz metin komut isteminden fonksiyon işaretçisi bildirimleri, typedef'ler ve geri çağrı imzaları oluşturabilir, kafa karıştırıcı sözdizimini açıklayabilir ve uyumsuz parametreleri veya dönüş türlerini tespit edebilir. Önerilen koda güvenmeden önce her zaman derleyin ve test edin.

GitHub Yardımcı Pilotu Fonksiyon işaretçisi sözdizimini önerir, yorum veya imzadan geri çağrı gövdelerini tamamlar ve qsort karşılaştırma fonksiyonları önerir. Tamamlamaları, belirttiğiniz prototiplerle eşleşecek şekilde ilgili başlık dosyalarını açın, ardından mantığı inceleyin.

Bu yazıyı şu şekilde özetleyin: