5-bo‘lim

Funksiyalar va lambdalar

Qayta yuklash, standart argumentlar va lambda ifodalari - ular qanday ishlaydi va nimani ushlab qoladi.

🕑 16 daqiqa o‘qish 📄 882 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Qayta yuklash
  2. Standart argumentlar
  3. Lambda - joyida yoziladigan funksiya
  4. Ushlash (capture)
  5. Havola bilan ushlashning xavfi
  6. Lambda - bu obyekt
  7. std::function va shablon
  8. mutable va o'zgaruvchan holat
  9. Algoritmlar bilan birga
  10. Xulosa

C++ da funksiya C dagidan ko'ra ko'proq narsa qila oladi: bir nom bilan bir necha versiya, standart argumentlar va joyida yoziladigan funksiyalar.

Qayta yuklash #

C++
#include <print>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
C++
void korsat(int son)
{
    std::println("int:    {}", son);
}

void korsat(double kasr)
{
    std::println("double: {}", kasr);
}

void korsat(const std::string& matn)
{
    std::println("string: {}", matn);
}

void korsat(const std::vector<int>& royxat)
{
    std::println("vector: {} ta element", royxat.size());
}

int main()
{
    korsat(42);
    korsat(3.14);
    korsat(std::string{"salom"});
    korsat(std::vector<int>{1, 2, 3});

    return 0;
}
Natija
int:    42
double: 3.14
string: salom
vector: 3 ta element
Qayta yuklash argument turlariga qarab tanlanadi

Kompilyator har chaqiruvda eng mos versiyani qidiradi. Bu kompilyatsiya paytida hal bo'ladi - ish vaqtida qo'shimcha xarajat yo'q.

Muhim cheklov: faqat qaytish turi bilan farq qiluvchi versiyalar mumkin emas:

KOD
int  ishla(int x);
double ishla(int x);      // KOMPILYATSIYA XATOSI

Sabab: ishla(5); chaqiruvida qaysi birini tanlashni bilib bo'lmaydi.

Standart argumentlar #

C++
std::string bezash(const std::string& matn,
                   char belgi = '*',
                   int kenglik = 30)
{
    if (static_cast<int>(matn.size()) >= kenglik) {
        return matn;
    }

    int qolgan = kenglik - static_cast<int>(matn.size()) - 2;
    int chap = qolgan / 2;
    int ong = qolgan - chap;

    return std::string(chap, belgi) + ' ' + matn + ' '
         + std::string(ong, belgi);
}

int main()
{
    std::println("{}", bezash("Salom"));
    std::println("{}", bezash("Salom", '='));
    std::println("{}", bezash("Salom", '-', 20));

    return 0;
}
Natija
*********** Salom ************
=========== Salom ============
------ Salom -------
Standart argumentlar o'ngdan boshlanadi

Bir marta standart qiymat berilgach, undan keyingi barcha parametrlar ham standart qiymatga ega bo'lishi shart:

KOD
void f(int a, int b = 1, int c);      // KOMPILYATSIYA XATOSI
void f(int a, int b = 1, int c = 2);  // to'g'ri

Yana bir tuzoq: standart qiymat e'londa yoziladi, ta'rifda emas. Ikkalasida yozilsa - xato.

Sarlavha faylida:

KOD
std::string bezash(const std::string& m, char b = '*');

Manba faylida:

KOD
std::string bezash(const std::string& m, char b)   // = '*' YO'Q
{ ... }

Lambda - joyida yoziladigan funksiya #

C++
int main()
{
    // Eng oddiy lambda
    auto qoshish = [](int a, int b) { return a + b; };

    std::println("qoshish(3, 4) = {}", qoshish(3, 4));

    // Algoritmga uzatish
    std::vector<int> sonlar{5, 2, 8, 1, 9, 3};

    std::ranges::sort(sonlar, [](int a, int b) { return a > b; });
    std::println("kamayish bo'yicha: {}", sonlar);

    auto juftlarSoni = std::ranges::count_if(sonlar,
        [](int x) { return x % 2 == 0; });
    std::println("juft sonlar soni: {}", juftlarSoni);

    // Turlarni aniq yozish shart emas
    auto kvadrat = [](auto x) { return x * x; };
    std::println("kvadrat(5)   = {}", kvadrat(5));
    std::println("kvadrat(2.5) = {}", kvadrat(2.5));

    return 0;
}
Natija
qoshish(3, 4) = 7
kamayish bo'yicha: [9, 8, 5, 3, 2, 1]
juft sonlar soni: 2
kvadrat(5)   = 25
kvadrat(2.5) = 6.25

Ushlash (capture) #

C++
int main()
{
    int koeffitsient = 3;
    std::string prefiks{">> "};

    // [] - hech narsa ushlanmaydi
    auto oddiy = [](int x) { return x * 2; };

    // [koeffitsient] - NUSXA olinadi
    auto nusxaBilan = [koeffitsient](int x) { return x * koeffitsient; };

    // [&prefiks] - HAVOLA olinadi
    auto havolaBilan = [&prefiks](const std::string& s) {
        return prefiks + s;
    };

    std::println("oddiy(5)       = {}", oddiy(5));
    std::println("nusxaBilan(5)  = {}", nusxaBilan(5));
    std::println("havolaBilan    = {}", havolaBilan("matn"));

    // Tashqi o'zgaruvchini o'zgartiramiz
    koeffitsient = 100;
    prefiks = "## ";

    std::println("");
    std::println("koeffitsient = 100, prefiks = '## ' dan keyin:");
    std::println("nusxaBilan(5)  = {}  (eski qiymat saqlangan)",
                 nusxaBilan(5));
    std::println("havolaBilan    = {}  (yangi qiymat ko'rindi)",
                 havolaBilan("matn"));

    return 0;
}
Natija
oddiy(5)       = 10
nusxaBilan(5)  = 15
havolaBilan    = >> matn

koeffitsient = 100, prefiks = '## ' dan keyin:
nusxaBilan(5)  = 15  (eski qiymat saqlangan)
havolaBilan    = ## matn  (yangi qiymat ko'rindi)
Ushlash usullari
YozuvNima ushlanadi
[]Hech narsa
[x]x ning nusxasi
[&x]x ga havola
[=]Ishlatilganlarning hammasi nusxa bilan
[&]Ishlatilganlarning hammasi havola bilan
[this]Joriy obyekt (havola bilan)
[x = ifoda]Yangi nom bilan initsializatsiya (C++14)

[=] va [&] qulay, lekin aniq emas: kod o'qiganda nima ushlanganini ko'rib bo'lmaydi.

Amaliy tavsiya: kerakli o'zgaruvchilarni aniq sanang. Bu uzunroq, lekin xatolarning oldini oladi.

Havola bilan ushlashning xavfi #

C++
// Lambda qaytaruvchi funksiya
std::function<int()> xatoYaratuvchi()
{
    int mahalliy = 42;

    // XAVFLI: mahalliy o'zgaruvchiga havola ushlandi
    return [&mahalliy]() { return mahalliy; };
    // mahalliy shu yerda yo'q qilinadi
}

std::function<int()> togriYaratuvchi()
{
    int mahalliy = 42;

    // XAVFSIZ: nusxa olindi
    return [mahalliy]() { return mahalliy; };
}

int main()
{
    auto togri = togriYaratuvchi();
    std::println("to'g'ri versiya: {}", togri());

    std::println("");
    std::println("xatoYaratuvchi() osilgan havola qaytaradi.");
    std::println("Uni chaqirish - aniqlanmagan xulq-atvor.");
    std::println("ASan bilan ishga tushirilsa darhol topiladi.");

    return 0;
}
Natija
to'g'ri versiya: 42

xatoYaratuvchi() osilgan havola qaytaradi.
Uni chaqirish - aniqlanmagan xulq-atvor.
ASan bilan ishga tushirilsa darhol topiladi.
Lambda ushlangan obyektni ushlab turmaydi

[&mahalliy] yozuvi obyektning umrini uzaytirmaydi. U shunchaki manzilni eslab qoladi.

Funksiya tugagach mahalliy yo'q bo'ladi, lambda esa o'sha manzilni saqlab qoladi - bu 4-bo'limdagi osilgan havola ning aynan o'zi.

Xavf darajasi lambda qayerga ketishiga bog'liq:

Lambda qayerda ishlatiladiHavola bilan ushlash
Darhol, o'sha joyda (algoritm ichida)Xavfsiz
O'zgaruvchiga saqlanadiEhtiyot bo'ling
Funksiyadan qaytariladiXavfli
Boshqa oqimga uzatiladiJuda xavfli
Navbatga qo'yiladiJuda xavfli

Amaliy qoida:

Lambda hozir va shu yerda ishlatilsa - havola. Saqlanadigan yoki uzatiladigan bo'lsa - nusxa.

Lambda ifodasining qismlari [&x, y] (int a, int b) -> int { return a + b + x; } ushlash tashqi o'zgaruvchilar parametrlar ixtiyoriy qaytish turi odatda yozilmaydi tana Lambda aslida nima Kompilyator har lambda uchun nomsiz sinf yaratadi va unda operator() ni ta'riflaydi. Ushlangan o'zgaruvchilar - o'sha sinfning a'zolari. Shuning uchun lambda - obyekt. U hajmga ega va uni nusxalash mumkin. Nusxa bilan ushlash [x] Qiymat lambda ichiga ko'chiriladi Tashqaridagi o'zgarish ko'rinmaydi Lambda qayerga ketsa ham xavfsiz Narxi: katta obyekt ko'chiriladi Havola bilan ushlash [&x] Faqat manzil saqlanadi Tashqaridagi o'zgarish ko'rinadi Obyekt yo'q bo'lsa - osilgan havola Faqat darhol ishlatiladigan lambdalar uchun
Lambda - nomsiz sinf; ushlangan qiymatlar uning a'zolari

Lambda - bu obyekt #

C++
int main()
{
    int a = 1;
    double b = 2.0;
    std::string c{"uch"};

    auto hechnarsa = []() { return 0; };
    auto bittasi   = [a]() { return a; };
    auto ikkitasi  = [a, b]() { return a + b; };
    auto uchtasi   = [a, b, c]() { return c; };

    std::println("[]        hajmi: {} bayt", sizeof(hechnarsa));
    std::println("[a]       hajmi: {} bayt", sizeof(bittasi));
    std::println("[a, b]    hajmi: {} bayt", sizeof(ikkitasi));
    std::println("[a, b, c] hajmi: {} bayt", sizeof(uchtasi));

    std::println("");
    std::println("string hajmi:    {} bayt", sizeof(std::string));

    return 0;
}
Natija
[]        hajmi: 1 bayt
[a]       hajmi: 4 bayt
[a, b]    hajmi: 16 bayt
[a, b, c] hajmi: 48 bayt

string hajmi:    32 bayt
Bo'sh lambda ham 1 bayt egallaydi

C++ da hech qanday obyekt nol bayt bo'la olmaydi - aks holda ikki obyektning manzili bir xil bo'lib qolardi.

Ushlangan har bir o'zgaruvchi lambda hajmiga qo'shiladi. std::string ushlansa, lambda kamida 32 bayt bo'ladi.

Bu tezlikka ta'sir qiladi: lambda algoritmga uzatilganda nusxalanadi. Katta ushlash - katta nusxa.

Shuning uchun katta obyektlarni [&] bilan ushlash ham mantiqiy - agar lambda darhol ishlatilsa.

std::function va shablon #

C++
// Shablon - lambdaning ANIQ turi bilan ishlaydi
template <typename Amal>
int shablonBilan(const std::vector<int>& v, Amal amal)
{
    int soni = 0;
    for (int x : v) {
        if (amal(x)) {
            ++soni;
        }
    }
    return soni;
}

// std::function - istalgan chaqiriladigan narsani qabul qiladi
int funksiyaBilan(const std::vector<int>& v,
                  const std::function<bool(int)>& amal)
{
    int soni = 0;
    for (int x : v) {
        if (amal(x)) {
            ++soni;
        }
    }
    return soni;
}

bool musbatmi(int x) { return x > 0; }

int main()
{
    std::vector<int> v{-3, 5, -1, 8, 0, 12, -7};

    auto lambda = [](int x) { return x > 0; };

    std::println("shablon + lambda:    {}", shablonBilan(v, lambda));
    std::println("shablon + funksiya:  {}", shablonBilan(v, musbatmi));
    std::println("function + lambda:   {}", funksiyaBilan(v, lambda));
    std::println("function + funksiya: {}", funksiyaBilan(v, musbatmi));

    // std::function ni o'zgaruvchida saqlash mumkin
    std::vector<std::function<bool(int)>> shartlar{
        [](int x) { return x > 0; },
        [](int x) { return x % 2 == 0; },
        [](int x) { return x > 10; },
    };

    std::println("");
    const char* nomlar[] = {"musbat", "juft", "10 dan katta"};
    for (std::size_t i = 0; i < shartlar.size(); ++i) {
        std::println("{:<14} {} ta", nomlar[i], funksiyaBilan(v, shartlar[i]));
    }

    return 0;
}
Natija
shablon + lambda:    3
shablon + funksiya:  3
function + lambda:   3
function + funksiya: 3

musbat         3 ta
juft           3 ta
10 dan katta   1 ta
std::function narxi bor
Shablon parametristd::function
ChaqiruvTo'g'ridan-to'g'ri, ichkariga joylanadiBilvosita
XotiraQo'shimcha yo'qBa'zan dinamik ajratish
TurlarHar biriga alohida kodBitta kod
Konteynerda saqlashMumkin emasMumkin
KompilyatsiyaSekinroqTezroq

std::function - turni yashiruvchi (type erasure) vosita. U turli lambdalarni bitta turga keltiradi va shu sababli ularni bitta konteynerda saqlash mumkin bo'ladi.

Narxi - har chaqiruvda bilvosita o'tish. Ichki halqalarda bu sezilarli bo'lishi mumkin.

Amaliy tanlov:

VaziyatTanlov
Funksiyaga uzatishShablon
Konteynerda saqlashstd::function
Sinf a'zosi (qayta chaqiruv)std::function
Ichki halqadaShablon

mutable va o'zgaruvchan holat #

C++
int main()
{
    int hisoblagich = 0;

    // Nusxa ushlangan o'zgaruvchini o'zgartirish uchun mutable kerak
    auto sanovchi = [hisoblagich]() mutable {
        ++hisoblagich;
        return hisoblagich;
    };

    std::println("chaqiruv 1: {}", sanovchi());
    std::println("chaqiruv 2: {}", sanovchi());
    std::println("chaqiruv 3: {}", sanovchi());
    std::println("tashqaridagi hisoblagich: {}", hisoblagich);

    // Havola bilan - tashqaridagisi o'zgaradi
    auto tashqiSanovchi = [&hisoblagich]() {
        ++hisoblagich;
        return hisoblagich;
    };

    std::println("");
    std::println("havola bilan: {}", tashqiSanovchi());
    std::println("havola bilan: {}", tashqiSanovchi());
    std::println("tashqaridagi hisoblagich: {}", hisoblagich);

    return 0;
}
Natija
chaqiruv 1: 1
chaqiruv 2: 2
chaqiruv 3: 3
tashqaridagi hisoblagich: 0

havola bilan: 1
havola bilan: 2
tashqaridagi hisoblagich: 2

Algoritmlar bilan birga #

C++
struct Talaba
{
    std::string ism;
    int baho;
};

int main()
{
    std::vector<Talaba> talabalar{
        {"Husanboy", 85}, {"Malika", 92}, {"Nodira", 78},
        {"Aziza", 95},    {"Kamola", 88},
    };

    // Bahoga qarab tartiblash
    std::ranges::sort(talabalar, [](const Talaba& a, const Talaba& b) {
        return a.baho > b.baho;
    });

    std::println("Reyting:");
    for (const auto& t : talabalar) {
        std::println("  {:<10} {}", t.ism, t.baho);
    }

    // 90 dan yuqori baho olganlar
    auto alochilar = std::ranges::count_if(talabalar,
        [](const Talaba& t) { return t.baho >= 90; });

    // O'rtacha baho
    int yigindi = std::accumulate(talabalar.begin(), talabalar.end(), 0,
        [](int jami, const Talaba& t) { return jami + t.baho; });

    std::println("");
    std::println("a'lochilar:  {} ta", alochilar);
    std::println("o'rtacha:    {:.1f}",
                 static_cast<double>(yigindi) / talabalar.size());

    // Eng yuqori baho
    auto eng = std::ranges::max_element(talabalar,
        [](const Talaba& a, const Talaba& b) { return a.baho < b.baho; });
    std::println("eng yuqori:  {} ({})", eng->ism, eng->baho);

    return 0;
}
Natija
Reyting:
  Aziza      95
  Malika     92
  Kamola     88
  Husanboy   85
  Nodira     78

a'lochilar:  2 ta
o'rtacha:    87.6
eng yuqori:  Aziza (95)
Lambda algoritmlarni amaliy qiladi

Lambdalar C++11 da qo'shildi va ular standart algoritmlarni birdaniga foydali qildi.

Undan oldin har solishtirish uchun alohida sinf yozish kerak edi:

KOD
struct BahoBoyicha {
    bool operator()(const Talaba& a, const Talaba& b) const {
        return a.baho > b.baho;
    }
};
std::sort(v.begin(), v.end(), BahoBoyicha{});

Endi bu bir qatorga sig'adi va ishlatilgan joyda turadi - o'qish uchun ancha qulay.

Aynan shu sabab C++11 "yangi til" deb ataladi.

Amaliy topshiriq
  1. Bir nomdagi to'rtta qayta yuklangan funksiya yozing.
  2. Faqat qaytish turi bilan farq qiluvchi ikkita versiya yozib ko'ring.
  3. Standart argumentli funksiya yozing va uni uch xil chaqiring.
  4. Eng oddiy lambda yozing va uni chaqiring.
  5. Nusxa va havola bilan ushlashni solishtiring.
  6. Tashqi o'zgaruvchini o'zgartirib, ikkalasining natijasini ko'ring.
  7. Lambda qaytaruvchi funksiya yozing va nusxa bilan ushlang.
  8. Har xil ushlashli lambdalarning sizeof ini o'lchang.
  9. std::function larni vektorda saqlang.
  10. Talabalar ro'yxatini bahoga qarab tartiblang va o'rtachani hisoblang.

Xulosa #

  • Qayta yuklash argument turlariga qarab kompilyatsiya paytida hal qilinadi.
  • Faqat qaytish turi bilan farq qiluvchi versiyalar mumkin emas.
  • Standart argumentlar o'ngdan boshlanadi va e'londa yoziladi.
  • Lambda - joyida yoziladigan nomsiz funksiya obyekti.
  • [x] nusxa oladi, [&x] havola oladi - farq hal qiluvchi.
  • Havola bilan ushlangan obyekt yo'q bo'lsa - osilgan havola.
  • Lambda darhol ishlatilsa havola, saqlansa nusxa ishlating.
  • Lambda - obyekt: u hajmga ega va ushlangan qiymatlar uning a'zolari.
  • Shablon parametri tezroq, std::function esa saqlanishi mumkin.
  • Lambdalar standart algoritmlarni amalda qulay qildi.

Keyingi bo'limda satrlar bilan ishlashni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.