← Kursa Dön
📄 Text · 12 min

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

CaptureAnlamı
[]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 auto kullan. 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, transform gibi her algoritmada lambda kullanabilirsin.

  • Generic lambda (auto parametreler) 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.