Menu

C'de Fonksiyon Prototipleri: Bildirim ile Tanım Arasındaki Fark

C'nin bir fonksiyonun biçimini çağırmadan önce neden görmesi gerektiği - prototip yazmak, örtük bildirim hatalarını gidermek, prototipleri başlık dosyalarına koymak ve f(void) ile f() arasındaki gerçek fark.

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

Derleyici Yukarıdan Aşağı Okur

Bir C derleyicisi kaynak dosyayı ilk satırdan sonuncuya, tek geçişte işler. Bir fonksiyon çağrısına ulaştığında üç şeyi çoktan biliyor olması gerekir: fonksiyonun ne döndürdüğünü, kaç argüman aldığını ve tiplerinin ne olduğunu. Bunlar olmadan doğru kod üretemez ya da çağrıyı denetleyemez.

Dolayısıyla bu bir sorundur:

#include <stdio.h>

int main(void) {
    printf("%d\n", add(2, 3));   /* add henüz görülmedi */
    return 0;
}

int add(int a, int b) {
    return a + b;
}

Çözüm bir prototiptir: fonksiyonun imzası, çağrıdan önce yazılmış, gövdenin geleceği yerde bir noktalı virgülle.

Artık main önce gelebilir ki C dosyalarının çoğu böyle düzenlenir: en tepedeki prototipler dosyanın ne sunduğunu söyler, main bir taslak gibi okunur ve ayrıntılar aşağıda gelir.

Bildirim ile Tanım

Karıştırılması kolay ve ayrı tutmaya değen iki kelime:

  • Bildirim, bir fonksiyonun var olduğunu söyler ve tipini verir. Noktalı virgülle biter ve gövdesi yoktur. İstediğiniz kadar yazabilirsiniz.
  • Tanım, gövdeyi verir. Tüm programda tam olarak bir kez görünmelidir - aynı fonksiyonun iki tanımı bir bağlayıcı hatasıdır ("multiple definition of").
int add(int a, int b);                        /* bildirim (prototip) */
int add(int a, int b) { return a + b; }       /* tanım - aynı zamanda bir bildirim */

Bir prototipte parametre adları isteğe bağlıdır; derleyici için yalnızca tipler önemlidir:

int add(int, int);              /* geçerli ve eşdeğer */
int add(int a, int b);          /* daha iyi: adlar sırayı belgeler */

Adları kullanın. void drawRect(int, int, int, int); okuyucuya hiçbir şey söylemez, oysa void drawRect(int x, int y, int width, int height); her şeyi söyler.

Prototip Size Ne Kazandırır

Yalnızca tanımları yeniden sıralama yeteneği değil - derleyicinin çağrılarınızı denetlemesini sağlayan şeydir.

Prototip kapsamdayken 3 ve 2, çağrıdan önce double'a dönüştürülür. Onsuz int olarak itilirlerdi ve scale onları double diye okurdu - çöp, üstelik eski C lehçelerinde hiçbir tanı mesajı olmadan.

Prototip ayrıca yanlış argüman sayılarını ve uyumsuz tipleri derleme zamanında yakalar:

scale(3.0);              /* hata: çok az argüman */
scale(3.0, "iki");       /* hata: double beklenirken char * geçiliyor */

İstediğiniz hatalar bunlardır - yüksek sesle, derleme zamanında, satırı göstererek.

Örtük Bildirim Hataları

Derleyicinin hiç duymadığı bir fonksiyonu çağırın, C'deki en yaygın mesajlardan birini alırsınız:

warning: implicit declaration of function 'add' [-Wimplicit-function-declaration]

C89'da derleyici tahmin ederdi: fonksiyonun int döndürdüğünü varsayıp geçirdiğiniz argümanları kabul ederdi. O tahmin çoğu zaman yanlıştır ve gerçek fonksiyon bir double ya da bir gösterici döndürdüğünde sonuç saçmalıktır. C99 örtük bildirimleri dilden kaldırdı, dolayısıyla bu C99 ve sonrasında bir hatadır - modern GCC ve clang son sürümlerde varsayılan olarak reddeder.

İki sebep, iki çözüm:

Kendi fonksiyonunuz, henüz bildirilmemiş. Çağrının üstüne prototipi ekleyin ya da tanımı yukarı taşıyın.

Başlığını unuttuğunuz bir kütüphane fonksiyonu. Kütüphanenin prototipleri başlığında yaşar, dolayısıyla #include'a ihtiyacınız var:

printf, scanf, fopen        ->  #include <stdio.h>
malloc, free, exit, atoi    ->  #include <stdlib.h>
strlen, strcpy, strcmp      ->  #include <string.h>
sqrt, pow, sin, fabs        ->  #include <math.h>
isdigit, toupper            ->  #include <ctype.h>
bool, true, false           ->  #include <stdbool.h>

İlgili bir mesaj olan conflicting types for 'add', prototip ile tanımın anlaşamadığı anlamına gelir - bir parametre tipi ya da dönüş tipi farklıdır. Hangisi yanlışsa onu düzeltin; tam olarak eşleşmeleri gerekir.

Karşılıklı Özyineleme Bir Prototip İster

Bazen yeniden sıralamak çözemez: birbirini çağıran iki fonksiyon ikisi birden önce gelemez.

Tek bir prototip çemberi kırar. Bu, düz bir özyinelemeli fonksiyonun dayandığı mekanizmanın aynısıdır - bir fonksiyonun kendi adı gövdesinin içinde kapsamdadır, dolayısıyla doğrudan özyineleme prototip istemez, ama karşılıklı özyineleme ister.

Başlık Dosyalarındaki Prototipler

Bir program birkaç .c dosyasına yayıldığında prototipler bir başlığa taşınır, böylece her dosya aynı bildirimleri tek bir yerden görür.

mathutils.h - arayüz:

#ifndef MATHUTILS_H
#define MATHUTILS_H

int add(int a, int b);
int multiply(int a, int b);
double average(const int values[], int count);

#endif

mathutils.c - gerçekleştirim:

#include "mathutils.h"

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

double average(const int values[], int count) {
    if (count == 0) return 0.0;
    int total = 0;
    for (int i = 0; i < count; i++) total += values[i];
    return (double) total / count;
}

main.c - bir kullanıcı:

#include <stdio.h>
#include "mathutils.h"

int main(void) {
    printf("%d\n", add(2, 3));
    return 0;
}

Ardından:

gcc main.c mathutils.c -o program

Burada benimsemeye değer iki alışkanlık var. #ifndef / #define / #endif sarmalayıcısı bir include korumasıdır: başlık iki yoldan dahil edilirse bildirimlerin iki kez işlenmesini engeller. Ve mathutils.c kendi başlığını dahil eder - gereksiz görünür ama değildir, çünkü derleyicinin her tanımı, diğer dosyaların kullanacağı bildirimle karşılaştırmasını sağlar. Bir gün birbirlerinden koparlarsa bunu bağlama zamanında değil hemen öğrenirsiniz. Başlık dosyaları gerisini ele alıyor.

Açılı parantezler (<stdio.h>) sistem include yollarını arar; tırnaklar ("mathutils.h") önce projenizin dizinine bakar. Kendi başlıklarınız için tırnak kullanın.

f(void) ile f()

Bu gerçekten şaşırtıcıdır ve bu belgelerdeki her örneğin int main(void) yazmasının sebebidir.

void ping(void);      /* HİÇ argüman almaz - argümanlı çağrılar reddedilir */
void pong();          /* parametreler hakkında HİÇBİR ŞEY söylemez */

void ping(void); bir prototiptir: fonksiyonun parametre almadığını bildirir, dolayısıyla ping(1, 2, 3) bir derleme hatasıdır.

void pong();, standart öncesi C'den miras kalan eski tarz bir bildirimdir. Dönüş tipini ve parametreler hakkında hiçbir şey bildirmez, dolayısıyla derleyici çağrıları denetleyemez - pong(1, 2, 3) sessizce derlenir ve tanımsız bir şey yapar.

int main(void) {      /* doğru: main argüman almaz */
int main() {          /* geçerli, ama argüman denetimi kapalı */

Parametresiz bir fonksiyon için her zaman (void) yazın. C23, ()'yi (void) ile aynı anlama getirerek tuzağı nihayet kaldırıyor - ama pek çok kod ve pek çok derleyici henüz orada değil ve (void) her C standardında doğrudur.

Aynı ayrım tanım için de geçerlidir. void ping(void) { } prototip tarzı bir tanımdır; void ping() { } değildir ve kendisinden önce görünen çağrıların denetlenmesini sağlamaz.

Sık Yapılan Hatalar

  • Tanımda noktalı virgül. int add(int a, int b); { return a + b; }, add'ı bildirir ve ardından başıboş bir blok bırakır. Hata mesajı kafa karıştırıcıdır; sebep tek bir karakterdir.
  • Prototipte eksik noktalı virgül. Derleyici ardından gelene doğru okumaya devam eder ve birkaç satır aşağıda şaşırtıcı bir şey bildirir.
  • Prototip ile tanımın anlaşmaması. conflicting types for .... Onları özdeş yapın - ya da daha iyisi, denetim otomatik olsun diye başlığı gerçekleştirim dosyasında dahil edin.
  • Bir fonksiyonu başka bir fonksiyonun içinde bildirmek. Geçerlidir (main içinde int add(int, int);), ama bildirim o zaman yalnızca o fonksiyona kapsamlanır. Prototipleri dosya kapsamına koyun.
  • Bir başlıkta fonksiyon tanımlamak. Onu iki .c dosyasından dahil edin, bağlayıcı yinelenen tanım bildirir. Başlıklar bildirim tutar; tanımlar bir .c dosyasına gider.
  • Denetim için ()'ye güvenmek. Denetlemez. (void) yazın.

Sıkça Sorulan Sorular

C'de fonksiyon prototipi nedir?

Bir fonksiyonun imzasının - dönüş tipi, ad ve parametre tipleri - gövde yerine noktalı virgülle biten bir bildirimidir: int add(int a, int b);. Derleyiciye fonksiyonun nasıl çağrıldığını söyler, böylece çağrılar denetlenebilir; ne yaptığını söylemez.

C'de bildirim ile tanım arasındaki fark nedir?

Bildirim adı ve tipi tanıtır (int add(int, int);) ve birçok kez görünebilir. Tanım gövdeyi sağlar (int add(int a, int b) { return a + b; }) ve tüm programda tam olarak bir kez görünmelidir. Her tanım aynı zamanda bir bildirimdir.

C'de "implicit declaration of function" nasıl düzeltilir?

Çağrıdan önce bir prototip ekleyin. Kendi fonksiyonlarınız için dosyanın tepesine ya da bir başlığa donusTipi ad(parametreTipleri); koyun; kütüphane fonksiyonları için doğru başlığı ekleyin - printf için #include <stdio.h>, malloc için <stdlib.h>, strlen için <string.h>, sqrt için <math.h>.

C'de f(void) ile f() arasındaki fark nedir?

void f(void);, hiç argüman almayan bir fonksiyon bildirir ve derleyici argüman geçiren her çağrıyı reddeder. void f();, parametreler hakkında hiçbir şey söylemeyen eski tarz bir bildirimdir, dolayısıyla argüman denetimi kapalıdır. Her zaman (void) yazın; C23 nihayet ikisini aynı anlama getiriyor, ama eski kod ve eski derleyiciler hâlâ farklı davranıyor.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA