5-bo‘lim
Funksiyalar va lambdalar
Qayta yuklash, standart argumentlar va lambda ifodalari - ular qanday ishlaydi va nimani ushlab qoladi.
Ushbu bo‘lim mundarijasi
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 #
#include <print>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
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;
}
int: 42
double: 3.14
string: salom
vector: 3 ta element
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:
int ishla(int x);
double ishla(int x); // KOMPILYATSIYA XATOSI
Sabab: ishla(5); chaqiruvida qaysi birini tanlashni
bilib bo'lmaydi.
Standart argumentlar #
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;
}
*********** Salom ************
=========== Salom ============
------ Salom -------
Bir marta standart qiymat berilgach, undan keyingi barcha parametrlar ham standart qiymatga ega bo'lishi shart:
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:
std::string bezash(const std::string& m, char b = '*');
Manba faylida:
std::string bezash(const std::string& m, char b) // = '*' YO'Q
{ ... }
Lambda - joyida yoziladigan funksiya #
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;
}
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) #
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;
}
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)
| Yozuv | Nima 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 #
// 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;
}
to'g'ri versiya: 42
xatoYaratuvchi() osilgan havola qaytaradi.
Uni chaqirish - aniqlanmagan xulq-atvor.
ASan bilan ishga tushirilsa darhol topiladi.
[&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 ishlatiladi | Havola bilan ushlash |
|---|---|
| Darhol, o'sha joyda (algoritm ichida) | Xavfsiz |
| O'zgaruvchiga saqlanadi | Ehtiyot bo'ling |
| Funksiyadan qaytariladi | Xavfli |
| Boshqa oqimga uzatiladi | Juda xavfli |
| Navbatga qo'yiladi | Juda xavfli |
Amaliy qoida:
Lambda hozir va shu yerda ishlatilsa - havola. Saqlanadigan yoki uzatiladigan bo'lsa - nusxa.
Lambda - bu obyekt #
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;
}
[] hajmi: 1 bayt
[a] hajmi: 4 bayt
[a, b] hajmi: 16 bayt
[a, b, c] hajmi: 48 bayt
string hajmi: 32 bayt
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 #
// 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;
}
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 parametri | std::function | |
|---|---|---|
| Chaqiruv | To'g'ridan-to'g'ri, ichkariga joylanadi | Bilvosita |
| Xotira | Qo'shimcha yo'q | Ba'zan dinamik ajratish |
| Turlar | Har biriga alohida kod | Bitta kod |
| Konteynerda saqlash | Mumkin emas | Mumkin |
| Kompilyatsiya | Sekinroq | Tezroq |
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:
| Vaziyat | Tanlov |
|---|---|
| Funksiyaga uzatish | Shablon |
| Konteynerda saqlash | std::function |
| Sinf a'zosi (qayta chaqiruv) | std::function |
| Ichki halqada | Shablon |
mutable va o'zgaruvchan holat #
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;
}
chaqiruv 1: 1
chaqiruv 2: 2
chaqiruv 3: 3
tashqaridagi hisoblagich: 0
havola bilan: 1
havola bilan: 2
tashqaridagi hisoblagich: 2
Algoritmlar bilan birga #
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;
}
Reyting:
Aziza 95
Malika 92
Kamola 88
Husanboy 85
Nodira 78
a'lochilar: 2 ta
o'rtacha: 87.6
eng yuqori: Aziza (95)
Lambdalar C++11 da qo'shildi va ular standart algoritmlarni birdaniga foydali qildi.
Undan oldin har solishtirish uchun alohida sinf yozish kerak edi:
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.
- Bir nomdagi to'rtta qayta yuklangan funksiya yozing.
- Faqat qaytish turi bilan farq qiluvchi ikkita versiya yozib ko'ring.
- Standart argumentli funksiya yozing va uni uch xil chaqiring.
- Eng oddiy lambda yozing va uni chaqiring.
- Nusxa va havola bilan ushlashni solishtiring.
- Tashqi o'zgaruvchini o'zgartirib, ikkalasining natijasini ko'ring.
- Lambda qaytaruvchi funksiya yozing va nusxa bilan ushlang.
- Har xil ushlashli lambdalarning
sizeofini o'lchang. std::functionlarni vektorda saqlang.- 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::functionesa saqlanishi mumkin. - Lambdalar standart algoritmlarni amalda qulay qildi.
Keyingi bo'limda satrlar bilan ishlashni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.