Tek İsim, Birçok Sürüm
Çoğu zaman aynı işlemi farklı veri türleri için yapman gerekir: bir int yazdırmak, bir string yazdırmak, bir double yazdırmak. Bazı dillerde printInt, printString, printDouble uydurursun. C++ ise hepsine aynı ismi vermene izin verir ve onları parametrelerine göre ayırt eder. İşte bu fonksiyon aşırı yüklemedir.
Kural basit: birden fazla fonksiyon, parametre listeleri farklı olduğu sürece bir ismi paylaşabilir - parametre sayısı, tipleri veya sırası bakımından. Derleyici her çağrı noktasındaki argümanlara bakar ve senin için eşleşen sürümü seçer.
Üç fonksiyon, tek isim. Her çağrı, parametre tipi argümana uyan sürüme iner. std::cout << x ifadesinin int, double ve string'ler için aynı şekilde çalışmasını sağlayan şey budur - operator<< defalarca aşırı yüklenmiştir.
Neler Farklı Bir Aşırı Yükleme Sayılır
Bir aşırı yükleme yalnızca parametre listesiyle ayırt edilir. Şunları değiştirebilirsin:
int area(int side); // 1 parametre
int area(int width, int height); // 2 parametre -> farklı
double area(double r); // farklı tip -> farklı
void log(string msg, int level); // sıra önemlidir...
void log(int level, string msg); // ...dolayısıyla bu da farklıdır
Bunların her biri geçerli ve ayrı bir aşırı yüklemedir. Derleyici, area adlı tüm fonksiyonlardan bir aday kümesi oluşturur, ardından argüman sayısı ve tipine göre eşleştirir.
Dönüş Tipi Tek Başına Yeterli Değil
İşte neredeyse herkesi tökezleten tuzak: dönüş tipine göre aşırı yükleme yapamazsın. Dönüş değeri, aşırı yüklemeyi seçmede hiçbir rol oynamaz, çünkü derleyici hangi fonksiyonu çağıracağına argümanlardan karar verir - dönen değere bakmadan çok önce.
int convert(double x); // OK
double convert(double x); // HATA: yeniden tanımlama - yalnızca dönüş tipi farklı
Bu derlenmez. Parametre listeleri aynıysa, aşırı yükleme açısından iki bildirim aynı fonksiyondur ve bir yeniden tanımlama hatası alırsın. Sonuç tipine göre dallanmak için bir parametreyi değiştir (ya da çağrı noktasında şablonları kullan veya çağrı noktasında bir tür dönüşümü uygula).
Aşırı Yükleme Çözümlemesi Kazananı Nasıl Seçer
Bir çağrı yaptığında, derleyici uygun tüm aşırı yüklemeleri sıralar ve en iyi eşleşmeyi seçer. Kabaca, şu sırayla tercih eder:
- Bir tam eşleşme (dönüşüm gerekmez).
- Bir yükseltme (örn.
charveyashort->int,float->double). - Bir standart dönüşüm (örn.
int->double,double->int, temel sınıfa işaretçi).
Diğerlerinin hepsinden kesinlikle daha iyi olan tam olarak bir aşırı yükleme varsa, o kazanır. Bir tam eşleşmenin bir dönüşümü nasıl yendiğini izle:
'A' bir char'tır, ama int'e yükseltme double'a dönüşümden daha üstündür, bu yüzden int aşırı yüklemesini çağırır. Bu sıralama kuralları, aşırı yükleme çözümlemesinin genellikle "doğru olanı yapmasının" - ve ara sıra seni şaşırtmasının - nedenidir.
Belirsizlik Tuzağı
İki aşırı yükleme eşit derecede iyiyse - hiçbiri kesinlikle daha iyi değilse - derleyici tahmin etmeyi reddeder ve bir belirsiz çağrı bildirir. Ders kitabı örneği, her biri aynı dereceden bir dönüşüme ihtiyaç duyan iki aşırı yüklemedir:
void f(int x);
void f(double x);
f(0L); // HATA: belirsiz - long -> int ve long -> double aynı dereceden dönüşümlerdir
Ne int ne de double, long için tam eşleşmedir ve her iki dönüşüm de aynı derecede yer alır, bu yüzden çağrı belirsizdir. İki temiz çözümün var:
İlgili bir sürpriz: bir dize değişmezi geçirmek. void g(const string&) ve void g(bool) ikisi de g("hi") için teklif verir ve bool kazanabilir, çünkü bir const char*, bir std::string inşa etmekten daha az adımda bool'a dönüşür (null değil -> true). Bir dize değişmezinin gizemli bir şekilde bool aşırı yüklemeni çağırdığını görürsen, sebebi budur - tam eşleşmeyi alması için bir const char* veya const string& aşırı yüklemesi ekle.
Aşırı Yükleme ve Varsayılan Argümanlar İyi Geçinmez
Varsayılan argümanlar aşırı yüklemenin yerini tutmaz ve ikisini birleştirmek belirsizlik yaratır. Her biri aynı çağrıyı yanıtlayabilir, bu yüzden derleyici seçim yapamaz:
void connect(string host, int port = 8080); // 1 argümanla çağrılabilir
void connect(string host); // bu da 1 argümanla çağrılabilir
connect("localhost"); // HATA: belirsiz - ikisi de tek argümanla eşleşir
Her çağrı biçimi için tek bir yaklaşım seç. Davranış aynıysa ve sadece isteğe bağlı parametreler istiyorsan varsayılan argümanları kullan; farklı argüman listeleri gerçekten farklı kod çalıştırmalıysa aşırı yüklemeyi kullan. İkisini, iki imzanın aynı argüman sayısı için çakışacağı şekilde karıştırmak garantili bir belirsizlik hatasıdır.
Çivilenmesi gereken bir ayrım daha: aşırı yükleme, ezme (override) değildir. Aşırı yükleme, aynı kapsamdaki aynı isimli ama farklı parametreli fonksiyonlar arasında derleme zamanında çözülür. Ezme ise türetilmiş bir sınıftaki bir virtual fonksiyonu çalışma zamanında değiştirir ve aynı imzayı gerektirir - bu, ileride sanal fonksiyonlar için bir konu.
Sırada: Lambda'lar
Aşırı yükleme, bir isme derleme zamanında seçilen birden fazla tipli uygulama verir. Ancak bazen isimli bir fonksiyon hiç istemezsin - kullandığın yerde tanımlanan, çoğu zaman sort gibi bir algoritmaya geçirilecek küçük, tek seferlik bir fonksiyona ihtiyacın olur. İşte tam olarak lambda'lar budur: satır içinde yazabildiğin, çevredeki değişkenleri yakalayabildiğin ve tek bir ifadede devredebildiğin anonim fonksiyonlar. Sırada bunları nasıl yazacağımızı ve ne zaman tam isimli bir fonksiyonu geçtiklerini göreceğiz.
Sıkça Sorulan Sorular
C++'ta fonksiyon aşırı yükleme nedir?
Fonksiyon aşırı yükleme, parametre listeleri farklı olduğu (sayı, tip veya sıra bakımından) sürece aynı isimli birden fazla fonksiyon tanımlamana izin verir. Derleyici, verdiğin argümanlara bakarak hangisini çağıracağını seçer; bu yüzden print(42) ve print("hi") iki farklı print fonksiyonunu çağırabilir.
İki C++ fonksiyonu yalnızca dönüş tipiyle birbirinden ayrılabilir mi?
Hayır. Aşırı yüklemeler parametre listelerinde farklılaşmalıdır. int f(int) ve double f(int) bir derleme hatasıdır - dönüş tipi, aşırı yükleme çözümlemesinde kullanılan imzanın bir parçası değildir, çünkü derleyici aşırı yüklemeyi çağrı noktasındaki argümanlardan seçer; dönüş değeri henüz hiç kullanılmamışken.
Aşırı yüklenmiş fonksiyonlarda "belirsiz çağrı" hatasına ne yol açar?
İki aşırı yükleme eşit derecede iyi eşleştiğinde ve derleyici birini tercih edemediğinde olur. Klasik bir örnek, f(int) ve f(double)'ın f(0L) (bir long) ile çağrılmasıdır; burada ikisi de aynı dereceden bir dönüşüm gerektirir. Tam eşleşen bir aşırı yükleme ekleyerek veya argümanı istediğin tipe dönüştürerek düzeltebilirsin.