Lambda İfadeleri
Lambda ifadeleri, isimsiz fonksiyonlar (anonymous functions) tanımlamanı sağlar. Bir fonksiyonu, onu kullanacağın yerde — o anda — yazarsın. Ayrı bir fonksiyon tanımlamana gerek kalmaz.
Bir post-it not gibi düşün: bir kitaba yapıştırırsın, orada işini görür, sonra çıkarıp atarsın. Kalıcı bir sayfa ekleme yerine, anlık ihtiyaç için hızlı bir çözüm. Lambda'lar da öyle — küçük, tek kullanımlık fonksiyonlar için mükemmel.
Lambda'lar özellikle STL algoritmaları ile birlikte kullanıldığında C++ kodunu dramatik şekilde kısaltır ve okunabilir kılar.
Lambda Söz Dizimi
Lambda'nın genel yapısı şöyle:
[capture](parameters) -> return_type { body }Her parçayı tek tek açıklayalım:
`[capture]` — Dış kapsamdan (enclosing scope) hangi değişkenleri kullanacağını belirtir
`(parameters)` — Fonksiyonun parametre listesi (opsiyonel)
`-> return_type` — Dönüş tipi (opsiyonel — derleyici çoğu zaman çıkarır)
`{ body }` — Fonksiyonun gövdesi
#include <iostream>
int main() {
// En basit lambda — parametresiz, capture'sız
auto greet = []() {
std::cout << "Merhaba Lambda!\n";
};
greet(); // Merhaba Lambda!
// Parametreli lambda
auto add = [](int a, int b) {
return a + b;
};
std::cout << "3 + 5 = " << add(3, 5) << "\n";
// Dönüş tipi açık belirtilmiş
auto divide = [](double a, double b) -> double {
if (b == 0) return 0.0;
return a / b;
};
std::cout << "10 / 3 = " << divide(10, 3) << "\n";
return 0;
}Capture — Dış Değişkenleri Yakalama
Lambda'nın en güçlü özelliği capture mekanizmasıdır. Tanımlandığı yerdeki değişkenleri "yakalar" ve kendi içinde kullanabilir.
Capture Türleri
#include <iostream>
int main() {
int x = 10;
int y = 20;
// [=] — Tüm değişkenleri KOPYA olarak yakala
auto byValue = [=]() {
std::cout << "x=" << x << " y=" << y << "\n";
// x = 99; // HATA! Kopya olarak yakalandı, değiştirilemez
};
byValue();
// [&] — Tüm değişkenleri REFERANS olarak yakala
auto byRef = [&]() {
x = 100; // Orijinal x'i değiştirir
y = 200;
};
byRef();
std::cout << "x=" << x << " y=" << y << "\n"; // 100 200
// [x] — Sadece x'i kopya olarak yakala
auto captureX = [x]() {
std::cout << "x=" << x << "\n";
// std::cout << y; // HATA! y yakalanmadı
};
// [&x] — Sadece x'i referans olarak yakala
auto refX = [&x]() {
x += 50;
};
refX();
std::cout << "x=" << x << "\n"; // 150
// Karma: x kopya, y referans
auto mixed = [x, &y]() {
// x salt okunur (kopya), y değiştirilebilir (referans)
y = x + 1;
};
return 0;
}Capture Özet Tablosu
| Capture | Anlamı |
|---|---|
[] | Hiçbir şey yakalama |
[=] | Tümünü kopya olarak |
[&] | Tümünü referans olarak |
[x] | Sadece x'i kopya olarak |
[&x] | Sadece x'i referans olarak |
[=, &x] | Tümünü kopya, x'i referans |
[&, x] | Tümünü referans, x'i kopya |
[this] | Sınıfın this pointer'ını yakala |
[*this] | Sınıf nesnesinin kopyasını yakala (C++17) |
mutable Lambda
Kopya ile yakalanan değişkenleri değiştirmek istersen mutable anahtar kelimesini kullanmalısın:
#include <iostream>
int main() {
int counter = 0;
// mutable — kopya üzerinde değişiklik yapabilir
auto increment = [counter]() mutable {
counter++; // Lambda'nın kendi kopyasını artırır
std::cout << "Lambda icinde: " << counter << "\n";
};
increment(); // Lambda icinde: 1
increment(); // Lambda icinde: 2
increment(); // Lambda icinde: 3
// Orijinal değişken etkilenmez!
std::cout << "Disarida: " << counter << "\n"; // 0
return 0;
}⚠️ Referans capture'da dangling reference tehlikesi: Lambda, yakaladığı referansın ömrünü uzatmaz. Eğer referans alınan değişken scope dışına çıkarsa, lambda'yı çağırmak tanımsız davranıştır.
Lambda ile STL Algoritmaları
Lambda'ların en parlak kullanım alanı STL algoritmalarıdır. Eskiden ayrı fonksiyon veya functor (fonksiyon nesnesi) yazmak zorundaydın, şimdi yerinde tanımlarsın.
sort ile
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
int main() {
std::vector<std::string> names = {
"Zeynep", "Ali", "Merve", "Burak", "Can"
};
// Uzunluğa göre sırala
std::sort(names.begin(), names.end(),
[](const std::string& a, const std::string& b) {
return a.length() < b.length();
});
for (const auto& name : names) {
std::cout << name << " ";
}
// Çıktı: Ali Can Merve Burak Zeynep
std::cout << "\n";
return 0;
}find_if ile
#include <vector>
#include <algorithm>
#include <iostream>
struct Product {
std::string name;
double price;
};
int main() {
std::vector<Product> products = {
{"Laptop", 15000}, {"Telefon", 8000},
{"Tablet", 5000}, {"Kulaklik", 500}
};
// 10000 TL üzeri ilk ürünü bul
auto it = std::find_if(products.begin(), products.end(),
[](const Product& p) { return p.price > 10000; });
if (it != products.end()) {
std::cout << "Pahali urun: " << it->name
<< " (" << it->price << " TL)\n";
}
// Bütçe ile filtreleme — capture kullanımı
double budget = 6000;
auto affordable = std::count_if(products.begin(), products.end(),
[budget](const Product& p) { return p.price <= budget; });
std::cout << budget << " TL butce ile " << affordable
<< " urun alinabilir\n";
return 0;
}transform ve accumulate ile
#include <vector>
#include <algorithm>
#include <numeric>
#include <iostream>
#include <cmath>
int main() {
std::vector<double> values = {1.0, 4.0, 9.0, 16.0, 25.0};
// Karekök al
std::vector<double> roots(values.size());
std::transform(values.begin(), values.end(), roots.begin(),
[](double x) { return std::sqrt(x); });
for (double r : roots) std::cout << r << " ";
// Çıktı: 1 2 3 4 5
std::cout << "\n";
// Toplam — accumulate ile lambda
double sum = std::accumulate(roots.begin(), roots.end(), 0.0,
[](double acc, double x) { return acc + x; });
std::cout << "Toplam: " << sum << "\n"; // 15
return 0;
}Generic Lambda (auto Parametreler)
C++14'ten itibaren lambda parametreleri auto olabilir. Bu, lambda'yı template gibi davranmaya iter — farklı tiplerle çalışabilir.
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
int main() {
// Generic lambda — herhangi bir tip ile çalışır
auto print = [](const auto& value) {
std::cout << value << "\n";
};
print(42); // int
print(3.14); // double
print("Merhaba"); // const char*
// Generic comparator
auto descending = [](const auto& a, const auto& b) {
return a > b;
};
std::vector<int> nums = {5, 3, 8, 1, 9};
std::sort(nums.begin(), nums.end(), descending);
for (int x : nums) std::cout << x << " ";
// Çıktı: 9 8 5 3 1
std::cout << "\n";
std::vector<std::string> words = {"elma", "armut", "muz"};
std::sort(words.begin(), words.end(), descending);
for (const auto& w : words) std::cout << w << " ";
// Çıktı: muz elma armut
std::cout << "\n";
return 0;
}Birden Fazla auto Parametre
#include <iostream>
#include <string>
int main() {
// İki farklı tipte parametre
auto combine = [](const auto& a, const auto& b) {
std::cout << a << " + " << b << "\n";
};
combine(1, 2); // int + int
combine("Merhaba", 42); // string + int
combine(3.14, "pi"); // double + string
return 0;
}💡 Generic lambda, arka planda template'li bir `operator()` üretir. Yani
auto print = [](const auto& x) {...}yazdığında, derleyici her farklı tip için ayrı bir fonksiyon üretir — tıpkı template gibi.
Immediately Invoked Lambda Expression (IIFE)
Lambda'yı tanımladığın anda çağırabilirsin. Bu tekniğe IIFE (Immediately Invoked Function Expression) denir. Karmaşık bir değişken başlatma (initialization) için çok kullanışlıdır.
#include <iostream>
#include <string>
#include <vector>
int main() {
int score = 75;
// Karmaşık başlatma — IIFE ile
const std::string grade = [&]() {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}(); // () ile hemen çağır!
std::cout << "Not: " << grade << "\n"; // C
return 0;
}IIFE'nin avantajı: grade değişkenini const yapabilirsin — çünkü değer bir kere hesaplanıyor ve atanıyor. If-else zincirini ayrı satırlarda yazsaydın, grade const olamazdı.
Karmaşık Container Başlatma
#include <vector>
#include <iostream>
int main() {
// Karmaşık başlatma — IIFE ile
const auto fibonacci = []() {
std::vector<int> fib = {0, 1};
for (int i = 2; i < 10; ++i) {
fib.push_back(fib[i-1] + fib[i-2]);
}
return fib;
}();
for (int x : fibonacci) std::cout << x << " ";
// Çıktı: 0 1 1 2 3 5 8 13 21 34
std::cout << "\n";
return 0;
}std::function ile Lambda Saklama
auto ile tanımlanan lambda'nın tipi benzersiz ve isimsiz bir tiptir — her lambda farklı bir tipe sahiptir. Eğer lambda'yı bir container'da saklamak, fonksiyon parametresi olarak geçirmek veya üye değişken yapmak istersen, std::function kullanırsın.
#include <functional>
#include <vector>
#include <iostream>
int main() {
// std::function ile lambda saklama
std::function<int(int, int)> operation;
operation = [](int a, int b) { return a + b; };
std::cout << "Toplam: " << operation(3, 5) << "\n";
operation = [](int a, int b) { return a * b; };
std::cout << "Carpim: " << operation(3, 5) << "\n";
// Lambda'ları bir container'da sakla
std::vector<std::function<int(int, int)>> operations;
operations.push_back([](int a, int b) { return a + b; });
operations.push_back([](int a, int b) { return a - b; });
operations.push_back([](int a, int b) { return a * b; });
for (const auto& op : operations) {
std::cout << op(10, 3) << " ";
}
// Çıktı: 13 7 30
std::cout << "\n";
return 0;
}Callback Pattern
#include <functional>
#include <string>
#include <iostream>
void processData(const std::string& data,
std::function<void(const std::string&)> onSuccess,
std::function<void(const std::string&)> onError) {
if (data.empty()) {
onError("Veri bos!");
} else {
onSuccess("Islendi: " + data);
}
}
int main() {
processData("test",
[](const std::string& msg) { std::cout << "Basarili: " << msg << "\n"; },
[](const std::string& err) { std::cout << "Hata: " << err << "\n"; }
);
processData("",
[](const std::string& msg) { std::cout << "Basarili: " << msg << "\n"; },
[](const std::string& err) { std::cout << "Hata: " << err << "\n"; }
);
return 0;
}⚠️ `std::function`'ın maliyeti var. Type erasure kullanır ve heap allocation yapabilir. Performans kritikse ve lambda tipini biliyorsan
autokullan.std::function'ı sadece polimorfik davranış gerektiğinde (aynı değişkene farklı lambda'lar atanacaksa) kullan.
Lambda'nın Sınıf İçinde Kullanımı
Lambda'lar sınıf metotları içinde kullanıldığında this pointer'ını yakalayabilir:
#include <vector>
#include <algorithm>
#include <iostream>
class Classroom {
std::vector<int> grades;
int passingGrade;
public:
Classroom(std::vector<int> g, int pass)
: grades(std::move(g)), passingGrade(pass) {}
int countPassing() const {
// [this] ile sınıfın üyelerine erişim
return std::count_if(grades.begin(), grades.end(),
[this](int g) { return g >= passingGrade; });
}
void printAboveAverage() const {
double avg = std::accumulate(grades.begin(), grades.end(), 0.0)
/ grades.size();
// avg'yi capture et
std::cout << "Ortalama ustu notlar: ";
std::for_each(grades.begin(), grades.end(),
[avg](int g) {
if (g > avg) std::cout << g << " ";
});
std::cout << "\n";
}
};
int main() {
Classroom room({45, 78, 92, 55, 88, 67, 33}, 60);
std::cout << "Gecen ogrenci: " << room.countPassing() << "\n";
room.printAboveAverage();
return 0;
}Lambda vs Functor vs Fonksiyon Pointer
Lambda gelmeden önce aynı işler functor (fonksiyon nesnesi) veya fonksiyon pointer'ı ile yapılıyordu. Karşılaştıralım:
#include <vector>
#include <algorithm>
#include <iostream>
// 1. Normal fonksiyon
bool isEven(int x) { return x % 2 == 0; }
// 2. Functor — operator() olan sınıf
struct IsGreaterThan {
int threshold;
IsGreaterThan(int t) : threshold(t) {}
bool operator()(int x) const { return x > threshold; }
};
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
// Fonksiyon pointer ile
auto c1 = std::count_if(nums.begin(), nums.end(), isEven);
// Functor ile
auto c2 = std::count_if(nums.begin(), nums.end(), IsGreaterThan(5));
// Lambda ile — en kısa ve okunabilir
auto c3 = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
int threshold = 5;
auto c4 = std::count_if(nums.begin(), nums.end(),
[threshold](int x) { return x > threshold; });
std::cout << "Cift: " << c1 << "\n";
std::cout << "5'ten buyuk (functor): " << c2 << "\n";
std::cout << "Cift (lambda): " << c3 << "\n";
std::cout << "5'ten buyuk (lambda): " << c4 << "\n";
return 0;
}Lambda, functor'ın yaptığı her şeyi daha az kodla yapar. Aslında derleyici, lambda'yı arka planda anonim bir functor sınıfına dönüştürür.
Özet
Lambda söz dizimi:
[capture](params) -> ret { body }. Dönüş tipi çoğu zaman otomatik çıkarılır.Capture mekanizması dış değişkenleri yakalar:
[=]kopya,[&]referans,[x]sadece x'i kopya,[&x]sadece x'i referans. Karma kullanım da mümkün.STL algoritmaları ile birlikte lambda kullanmak kodu kısaltır ve okunabilir kılar —
sort,find_if,count_if,transformgibi her algoritmada lambda kullanabilirsin.Generic lambda (
autoparametreler) ile lambda'lar template gibi çalışır — farklı tiplerle kullanılabilir.IIFE (Immediately Invoked Lambda) karmaşık değişken başlatmada const değişken tanımlamayı mümkün kılar.
`std::function` lambda'yı saklamak, container'a koymak veya polimorfik kullanmak için gereklidir — ama performans maliyeti vardır, sadece gerektiğinde kullan.
AI Asistan
Sorularını yanıtlamaya hazır