Derinlemesine Lambda İfadeleri
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası. Ders 85 / 104.
C++11 ile tanıtılan lambda ifadeleri, satır içinde tanımlayabileceğiniz anonim işlevlerdir. STL algoritmalarıyla temel lambda ifadelerini görmüş olsanız da tam söz dizimlerini anlamak, değişkenleri yakalama ve bunlara nasıl erişileceğini denetleme konusunda güçlü olanakların kilidini açar.
Tam lambda söz dizimi şöyledir: [capture](parameters) mutable -> return_type { body }. Yakalama yan tümcesi, lambda'nın hangi dış değişkenlere ve nasıl erişebileceğini belirler:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // x'in kopyası
auto byRef = [&y]() { y += 5; }; // y'ye referans
auto allByValue = [=]() { return x + y; }; // Tümünü kopyala
auto allByRef = [&]() { x++; y++; }; // Tümüne referans
auto mixed = [x, &y]() { y += x; }; // İkisini karıştır
byRef();
std::cout << y << "\n"; // 25
}Varsayılan olarak, değerle yakalanan değişkenler lambda içinde const olur. mutable anahtar sözcüğü bu kopyaların değiştirilmesine izin verir:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Lambda'nın kopyasını değiştirir
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - orijinal değişmediC++14, yeni değişkenler oluşturmanıza veya nesneleri lambda içine taşımanıza olanak tanıyan başlatma yakalamalarını ekledi:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Sahiplik lambda'ya aktarıldıLambdalar, ayrı işlev nesneleri tanımlamadan davranışı bir parametre olarak geçirmeniz gerektiğinde (geri çağrılar, özel karşılaştırıcılar veya olay işleyicileri için) OOP'de özellikle kullanışlıdır.
Görev
KolayFarklı yakalama kipleriyle lambda ifadelerinin gücünü sergileyen bir olay işleyici sistemi oluşturalım. Geri çağrıları depolayıp çağıran basit bir olay dağıtıcısı oluşturacak ve lambdaların dış durumu çeşitli şekillerde nasıl yakalayabildiğini göstereceksin.
Kodunu üç dosyaya ayıracaksın:
EventDispatcher.h: Olay geri çağrılarını yöneten birEventDispatchersınıfı tanımla.Dağıtıcın, geri çağrıları
std::vectortüründeki birstd::function<void()>kullanarak depolamalı. Şu metotları ekle:addCallback(std::function<void()> callback): listeye bir geri çağrı eklerfireAll(): depolanan tüm geri çağrıları sırayla çağırırclear(): tüm geri çağrıları kaldırır
<functional>ve<vector>dosyalarını eklemen gerekecek.EventDispatcher.cpp: Dağıtıcının metotlarını uygula.fireAll()metodu, tüm geri çağrılar üzerinde basitçe yineleme yapmalı ve her birini çağırmalıdır.main.cpp: İki girdi oku:- Bir temel sayı (tam sayı)
- Bir çarpan (tam sayı)
Bir
EventDispatcheroluştur ve üç geri çağrı ekleyerek farklı lambda yakalama tekniklerini göster:- Temel sayıyı değer ile yakalayan ve şunu yazdıran bir lambda:
Base value: [base] - Çarpanı referans ile yakalayan, değerini 1 artıran ve ardından şunu yazdıran bir lambda:
Multiplier after increment: [multiplier] - Değer ile yakaladığı bir sayaç değişkenini (0 olarak başlatılmıştır) her çağrıldığında artıran ve şunu yazdıran mutable bir lambda:
Call count: [counter]
Tüm geri çağrıları ekledikten sonra, farklı yakalama kiplerinin birden çok çağrı boyunca nasıl davrandığını görmek için
fireAll()metodunu iki kez çağır. İkifireAll()çağrısının arasında ayraç olarak---yazdır.Son olarak, her iki turdan sonra referansla yakalamanın bu değişkeni nasıl etkilediğini göstermek için main içindeki çarpan değişkeninin son değerini yazdır:
Final multiplier: [multiplier]
Örneğin, girdiler 10 ve 5 olduğunda:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7Girdiler 42 ve 0 olduğunda:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Temel noktadaki davranışlara dikkat et: değer ile yakalama özgün temeli değiştirmeden korur, referans ile yakalama main içindeki gerçek çarpan değişkenini değiştirir (çağrılar boyunca birikerek ilerler) ve mutable lambda'nın sayacı her turda 1 değerini okur. Bunun nedeni, fireAll() metodunun geri çağrılarını değer ile yinelemesidir: her turda depolanan std::function nesnesinin yeni bir kopyası çağrılır; bu kopyanın yakalanan sayacı 0'dan başlar ve 1'e artırılır. Bu nedenle, depolanan geri çağrının kendi durumu hiçbir zaman ilerletilmez.
Kendin dene
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: base'i BY VALUE yakalayan bir lambda ekle
// Şunu yazdırmalı: "Base value: [base]"
// TODO: multiplier'ı BY REFERENCE yakalayan bir lambda ekle
// It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
// TODO: Bir counter'ı (0 ile başlatılmış) by value yakalayan MUTABLE bir lambda ekle
// counter'ı artırmalı ve şunu yazdırmalı: "Call count: [counter]"
// TODO: Tüm callback'leri çağırmak için fireAll() çağır
// TODO: Ayırıcı olarak "---" yazdır
// TODO: fireAll()'ı tekrar çağır
// TODO: Print the final multiplier value: "Final multiplier: [multiplier]"
return 0;
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner12Modern C++ Özellikleri
Taşıma Semantiği ve R-değerleriMükemmel YönlendirmeDerinlemesine Lambda İfadeleristd::function ve std::bindconstexpr ve constevalYapılandırılmış Bağlamalaroptional, variant, anyKendi başına pratik yap: Online C++ derleyicisi