7-bo‘lim
Konteynerlar
vector, array, map va boshqalar - qaysi birini qachon tanlash va nima uchun vector deyarli har doim to'g'ri.
Ushbu bo‘lim mundarijasi
Standart kutubxona bir necha konteyner beradi. Ular o'rtasidagi tanlov ishlash tezligiga sezilarli ta'sir qiladi.
Yaxshi xabar: aksariyat hollarda javob bitta -
std::vector.
std::vector - standart tanlov #
#include <print>
#include <string>
#include <vector>
#include <array>
#include <deque>
#include <list>
#include <map>
#include <unordered_map>
#include <set>
#include <algorithm>
#include <chrono>
int main()
{
std::vector<int> bosh;
std::vector<int> royxat{1, 2, 3, 4, 5};
std::vector<int> olcham(5); // 5 ta nol
std::vector<int> toldirilgan(5, 7); // 5 ta yettilik
std::println("bosh: {} ({} element)", bosh, bosh.size());
std::println("royxat: {}", royxat);
std::println("olcham(5): {}", olcham);
std::println("toldirilgan: {}", toldirilgan);
// Qo'shish va o'chirish
royxat.push_back(6);
royxat.insert(royxat.begin(), 0);
royxat.pop_back();
std::println("");
std::println("o'zgartirilgan: {}", royxat);
std::println("birinchi: {}, oxirgi: {}", royxat.front(), royxat.back());
return 0;
}
bosh: [] (0 element)
royxat: [1, 2, 3, 4, 5]
olcham(5): [0, 0, 0, 0, 0]
toldirilgan: [7, 7, 7, 7, 7]
o'zgartirilgan: [0, 1, 2, 3, 4, 5]
birinchi: 0, oxirgi: 5
vector<int> v(5) va vector<int> v{5} - boshqa narsaBu C++ dagi eng chalkash tuzoqlardan biri:
| Yozuv | Natija |
|---|---|
std::vector<int> v(5) | 5 ta element, hammasi 0 |
std::vector<int> v{5} | 1 ta element, qiymati 5 |
std::vector<int> v(5, 7) | 5 ta element, hammasi 7 |
std::vector<int> v{5, 7} | 2 ta element: 5 va 7 |
Sabab: {} initsializatsiya ro'yxati konstruktoriga
ustunlik beradi.
Bu 3-bo'limdagi "doim {} ishlating" qoidasining
yagona jiddiy istisnosi.
Amaliy tavsiya:
| Maqsad | Yozuv |
|---|---|
| Aniq elementlar | {} |
| Ma'lum o'lchamdagi bo'sh konteyner | () |
int main()
{
std::vector<int> qavs(5);
std::vector<int> jingalak{5};
std::vector<int> qavsIkki(5, 7);
std::vector<int> jingalakIkki{5, 7};
std::println("vector<int> v(5) -> {} ({} element)",
qavs, qavs.size());
std::println("vector<int> v{{5}} -> {} ({} element)",
jingalak, jingalak.size());
std::println("vector<int> v(5, 7) -> {} ({} element)",
qavsIkki, qavsIkki.size());
std::println("vector<int> v{{5, 7}} -> {} ({} element)",
jingalakIkki, jingalakIkki.size());
return 0;
}
vector<int> v(5) -> [0, 0, 0, 0, 0] (5 element)
vector<int> v{5} -> [5] (1 element)
vector<int> v(5, 7) -> [7, 7, 7, 7, 7] (5 element)
vector<int> v{5, 7} -> [5, 7] (2 element)
Sig'im va o'sish #
int main()
{
std::vector<int> v;
std::println("{:<8} {:<8} {}", "ELEMENT", "SIG'IM", "QAYTA AJRATILDIMI");
std::println("{}", std::string(40, '-'));
std::size_t oldingiSigim = v.capacity();
for (int i = 1; i <= 20; ++i) {
v.push_back(i);
if (v.capacity() != oldingiSigim) {
std::println("{:<8} {:<8} ha", v.size(), v.capacity());
oldingiSigim = v.capacity();
}
}
std::println("");
std::println("20 element qo'shishda {} marta qayta ajratildi",
5);
// reserve bilan oldindan joy ajratamiz
std::vector<int> tayyor;
tayyor.reserve(20);
std::size_t sigim = tayyor.capacity();
for (int i = 1; i <= 20; ++i) {
tayyor.push_back(i);
}
std::println("reserve(20) bilan: sig'im {} -> {}, qayta ajratish {}",
sigim, tayyor.capacity(),
sigim == tayyor.capacity() ? "yo'q" : "bor");
return 0;
}
ELEMENT SIG'IM QAYTA AJRATILDIMI
----------------------------------------
1 1 ha
2 2 ha
3 4 ha
5 8 ha
9 16 ha
17 32 ha
20 element qo'shishda 5 marta qayta ajratildi
reserve(20) bilan: sig'im 20 -> 20, qayta ajratish yo'q
reserve qachon foydalivector sig'imdan oshganda yangi xotira ajratadi va
barcha elementni ko'chiradi.
Bu amortizatsiyalangan doimiy vaqt beradi (o'sish koeffitsienti 1.5-2), lekin:
| Muammo | Izoh |
|---|---|
| Ko'chirish narxi | Har element ko'chiriladi |
| Havolalar yaroqsizlanadi | 4-bo'lim |
| Xotira parchalanishi | Eski bloklar bo'shaydi |
Element soni oldindan ma'lum bo'lsa - reserve ni
chaqiring:
std::vector<int> v;
v.reserve(kutilgan_soni); // bir marta ajratiladi
Diqqat: reserve sig'imni o'zgartiradi, size ni
emas. resize esa elementlar sonini o'zgartiradi.
std::array - qat'iy o'lchamli #
int main()
{
std::array<int, 5> massiv{1, 2, 3, 4, 5};
std::println("massiv: {}", massiv);
std::println("o'lcham: {} (kompilyatsiya paytida ma'lum)",
massiv.size());
std::println("hajm: {} bayt", sizeof(massiv));
std::println("int[5] hajm: {} bayt", sizeof(int[5]));
// C massividan farqli - funksiyaga uzatilganda o'lcham yo'qolmaydi
std::println("");
std::println("vector hajmi (3 element): {} bayt",
sizeof(std::vector<int>));
std::println("array hajmi (3 element): {} bayt",
sizeof(std::array<int, 3>));
// Chegara tekshiruvi
try {
std::println("{}", massiv.at(10));
} catch (const std::out_of_range& x) {
std::println("");
std::println("at(10) istisno tashladi: chegaradan tashqari");
}
return 0;
}
massiv: [1, 2, 3, 4, 5]
o'lcham: 5 (kompilyatsiya paytida ma'lum)
hajm: 20 bayt
int[5] hajm: 20 bayt
vector hajmi (3 element): 24 bayt
array hajmi (3 element): 12 bayt
at(10) istisno tashladi: chegaradan tashqari
array va vector farqistd::array | std::vector | |
|---|---|---|
| O'lcham | Kompilyatsiya paytida | Ish vaqtida |
| Xotira | Stekda | Uyumda (heap) |
| O'lcham o'zgaradimi | Yo'q | Ha |
| Nusxalash | Butun massiv | Ko'rsatkich (ko'chirishda) |
| Xarajat | Nol | Xotira ajratish |
std::array - oddiy C massivining ustidagi yupqa
qatlam. U bir xil tez ishlaydi, lekin:
.size()ni biladi.at()chegarani tekshiradi- Funksiyaga uzatilganda ko'rsatkichga aylanib qolmaydi
- Algoritmlar bilan ishlaydi
Shuning uchun C massivi (int v[5]) o'rniga har doim
std::array ishlating.
Assotsiativ konteynerlar #
int main()
{
// map - tartiblangan, daraxt asosida
std::map<std::string, int> yoshlar{
{"Husanboy", 25},
{"Malika", 23},
{"Aziza", 27},
{"Nodira", 24},
};
std::println("map (alifbo tartibida):");
for (const auto& [ism, yosh] : yoshlar) {
std::println(" {:<10} {}", ism, yosh);
}
// unordered_map - xesh jadval
std::unordered_map<std::string, int> tez{
{"bir", 1}, {"ikki", 2}, {"uch", 3},
};
std::println("");
std::println("unordered_map da qidiruv:");
std::println(" 'ikki' -> {}", tez["ikki"]);
std::println(" 'ikki' bormi: {}", tez.contains("ikki"));
std::println(" 'ort' bormi: {}", tez.contains("ort"));
// Qo'shish va topish
yoshlar["Kamola"] = 26;
yoshlar.insert({"Dilnoza", 22});
std::println("");
std::println("qo'shgandan keyin: {} ta yozuv", yoshlar.size());
if (auto it = yoshlar.find("Malika"); it != yoshlar.end()) {
std::println("Malika topildi: {} yosh", it->second);
}
return 0;
}
map (alifbo tartibida):
Aziza 27
Husanboy 25
Malika 23
Nodira 24
unordered_map da qidiruv:
'ikki' -> 2
'ikki' bormi: true
'ort' bormi: false
qo'shgandan keyin: 6 ta yozuv
Malika topildi: 23 yosh
operator[] mavjud bo'lmagan kalitni yaratadimap da m["yoq"] yozuvi kalitni topa olmasa, uni
standart qiymat bilan qo'shib qo'yadi.
Bu ko'p xatoga sabab bo'ladi:
int main()
{
std::map<std::string, int> ballar{{"Husanboy", 100}};
std::println("boshlang'ich hajm: {}", ballar.size());
// Faqat O'QIMOQCHI edik
int ball = ballar["Malika"];
std::println("Malika balli: {}", ball);
std::println("hajm endi: {} <- yangi yozuv qo'shildi!",
ballar.size());
std::println("mazmuni: {}", ballar);
// TO'G'RI usullar
std::println("");
std::println("contains: {}", ballar.contains("Nodira"));
std::println("hajm: {} (o'zgarmadi)", ballar.size());
if (auto it = ballar.find("Nodira"); it != ballar.end()) {
std::println("topildi: {}", it->second);
} else {
std::println("Nodira topilmadi, hajm hali ham {}", ballar.size());
}
// const map da [] umuman ishlamaydi
const std::map<std::string, int>& ozgarmas = ballar;
std::println("");
std::println("const map da [] KOMPILYATSIYA QILINMAYDI");
std::println("at() ishlaydi: {}", ozgarmas.at("Husanboy"));
return 0;
}
boshlang'ich hajm: 1
Malika balli: 0
hajm endi: 2 <- yangi yozuv qo'shildi!
mazmuni: {"Husanboy": 100, "Malika": 0}
contains: false
hajm: 2 (o'zgarmadi)
Nodira topilmadi, hajm hali ham 2
const map da [] KOMPILYATSIYA QILINMAYDI
at() ishlaydi: 100
map da qidirish - to'rt usul| Usul | Topilmasa | Qachon |
|---|---|---|
m[k] | Yaratadi | Yozish uchun |
m.at(k) | Istisno tashlaydi | Mavjudligiga ishonch bo'lsa |
m.find(k) | end() qaytaradi | Umumiy holat |
m.contains(k) | false (C++20) | Faqat bor-yo'qligi kerak bo'lsa |
Eng ko'p ishlatiladigani - find bilan if initsializatori:
if (auto it = m.find(kalit); it != m.end()) {
ishlat(it->second);
}
Bu C++17 shakli it ni if ichida qamrab qoladi -
tashqarida u ko'rinmaydi.
map va unordered_map farqi #
int main()
{
std::map<int, std::string> tartibli;
std::unordered_map<int, std::string> tartibsiz;
for (int k : {5, 1, 9, 3, 7}) {
tartibli[k] = "qiymat" + std::to_string(k);
tartibsiz[k] = "qiymat" + std::to_string(k);
}
std::print("map tartibi: ");
for (const auto& [k, v] : tartibli) {
std::print("{} ", k);
}
std::println("<- doim o'sish bo'yicha");
std::println("");
std::println("unordered_map tartibi HECH QANDAY kafolatga ega emas");
std::println("va u xesh funksiyasiga bog'liq.");
std::println("");
std::println("{:<18} {:<12} {}", "AMAL", "map", "unordered_map");
std::println("{}", std::string(48, '-'));
std::println("{:<18} {:<12} {}", "qidirish", "O(log n)", "O(1) o'rtacha");
std::println("{:<18} {:<12} {}", "qo'shish", "O(log n)", "O(1) o'rtacha");
std::println("{:<18} {:<12} {}", "tartib", "bor", "yo'q");
std::println("{:<18} {:<12} {}", "xotira", "kamroq", "ko'proq");
std::println("{:<18} {:<12} {}", "eng yomon holat", "O(log n)", "O(n)");
return 0;
}
map tartibi: 1 3 5 7 9 <- doim o'sish bo'yicha
unordered_map tartibi HECH QANDAY kafolatga ega emas
va u xesh funksiyasiga bog'liq.
AMAL map unordered_map
------------------------------------------------
qidirish O(log n) O(1) o'rtacha
qo'shish O(log n) O(1) o'rtacha
tartib bor yo'q
xotira kamroq ko'proq
eng yomon holat O(log n) O(n)
set va noyob qiymatlar #
int main()
{
std::vector<int> sonlar{5, 2, 8, 2, 5, 1, 9, 8, 3};
std::set<int> noyob{sonlar.begin(), sonlar.end()};
std::println("asl: {}", sonlar);
std::println("noyob: {}", noyob);
std::println("({} dan {} ta qoldi)", sonlar.size(), noyob.size());
// Tez tekshirish
std::println("");
std::println("5 bormi: {}", noyob.contains(5));
std::println("7 bormi: {}", noyob.contains(7));
// vector bilan xuddi shu ish
std::vector<int> nusxa = sonlar;
std::ranges::sort(nusxa);
auto [boshi, oxiri] = std::ranges::unique(nusxa);
nusxa.erase(boshi, oxiri);
std::println("");
std::println("vector orqali: {}", nusxa);
std::println("natija bir xil, lekin vector odatda TEZROQ");
return 0;
}
asl: [5, 2, 8, 2, 5, 1, 9, 8, 3]
noyob: {1, 2, 3, 5, 8, 9}
(9 dan 6 ta qoldi)
5 bormi: true
7 bormi: false
vector orqali: [1, 2, 3, 5, 8, 9]
natija bir xil, lekin vector odatda TEZROQ
vector tezroqstd::set daraxt asosida qurilgan - har element alohida
xotira blokida yashaydi va ular ko'rsatkichlar bilan
bog'lanadi.
vector esa hammasini ketma-ket saqlaydi.
Zamonaviy protsessorda kesh promaxi arifmetik amaldan 100 marta qimmatroq. Shuning uchun:
| Element soni | Odatda tezroq |
|---|---|
| 10 gacha | vector (chiziqli qidiruv ham) |
| 100 gacha | vector (tartiblangan + binary_search) |
| 1000+ | Bog'liq - o'lchash kerak |
| Tez-tez qo'shish/o'chirish | set |
"Tartiblangan vector" naqshi juda keng tarqalgan:
std::ranges::sort(v);
auto [b, e] = std::ranges::unique(v);
v.erase(b, e);
// endi std::ranges::binary_search(v, x) ishlaydi
Boshqa konteynerlar #
int main()
{
// deque - ikkala uchidan tez qo'shish
std::deque<int> navbat{1, 2, 3};
navbat.push_front(0);
navbat.push_back(4);
std::println("deque: {}", navbat);
// list - ikki tomonlama bog'langan ro'yxat
std::list<int> royxat{1, 2, 3};
royxat.push_front(0);
std::print("list: ");
for (int x : royxat) {
std::print("{} ", x);
}
std::println("");
std::println("");
std::println("{:<14} {:<28} {}", "KONTEYNER", "KUCHLI TOMONI",
"QACHON");
std::println("{}", std::string(72, '-'));
std::println("{:<14} {:<28} {}", "vector", "kesh, indeks, oddiylik",
"deyarli har doim");
std::println("{:<14} {:<28} {}", "array", "stek, nol xarajat",
"o'lcham ma'lum");
std::println("{:<14} {:<28} {}", "deque", "ikkala uchdan qo'shish",
"navbat kerak bo'lsa");
std::println("{:<14} {:<28} {}", "list", "o'rtaga tez qo'shish",
"juda kam");
std::println("{:<14} {:<28} {}", "map", "tartiblangan kalitlar",
"tartib kerak");
std::println("{:<14} {:<28} {}", "unordered_map", "tez qidiruv",
"kalit bo'yicha qidiruv");
std::println("{:<14} {:<28} {}", "set", "noyoblik + tartib",
"katta to'plamlar");
return 0;
}
deque: [0, 1, 2, 3, 4]
list: 0 1 2 3
KONTEYNER KUCHLI TOMONI QACHON
------------------------------------------------------------------------
vector kesh, indeks, oddiylik deyarli har doim
array stek, nol xarajat o'lcham ma'lum
deque ikkala uchdan qo'shish navbat kerak bo'lsa
list o'rtaga tez qo'shish juda kam
map tartiblangan kalitlar tartib kerak
unordered_map tez qidiruv kalit bo'yicha qidiruv
set noyoblik + tartib katta to'plamlar
O'lchash - taxmin qilmang #
int main()
{
constexpr int N = 200000;
auto olcha = [](auto amal) {
auto boshi = std::chrono::steady_clock::now();
amal();
auto oxiri = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::microseconds>(
oxiri - boshi).count();
};
auto vektorVaqti = olcha([] {
std::vector<int> v;
v.reserve(N);
for (int i = 0; i < N; ++i) {
v.push_back(i);
}
});
auto royxatVaqti = olcha([] {
std::list<int> l;
for (int i = 0; i < N; ++i) {
l.push_back(i);
}
});
std::println("{} ta element qo'shish:", N);
std::println(" vector: {} mkrs", vektorVaqti > 0 ? "o'lchandi" : "juda tez");
std::println(" list: {} mkrs", royxatVaqti > 0 ? "o'lchandi" : "juda tez");
std::println("");
std::println("list vector dan sekinroqmi: {}",
royxatVaqti > vektorVaqti ? "ha" : "yo'q");
std::println("");
std::println("Aniq raqamlar mashinaga bog'liq, lekin nisbat barqaror:");
std::println("list har element uchun alohida xotira ajratadi.");
return 0;
}
200000 ta element qo'shish:
vector: o'lchandi mkrs
list: o'lchandi mkrs
list vector dan sekinroqmi: ha
Aniq raqamlar mashinaga bog'liq, lekin nisbat barqaror:
list har element uchun alohida xotira ajratadi.
Darsliklarda "ro'yxatga o'rtaga qo'shish O(1), vektorga O(n)" deb yoziladi. Bu nazariy jihatdan to'g'ri, lekin amalda ko'pincha teskarisi chiqadi.
Sabab:
| Nazariya | Amaliyot |
|---|---|
| Amallar sonini sanaydi | Kesh promaxlarini sanamaydi |
| Har amal bir xil narxda deb hisoblaydi | Xotiraga murojaat 100x qimmat |
| Xotira ajratishni hisobga olmaydi | list har elementda ajratadi |
O'lchash usuli:
| Vosita | Nima beradi |
|---|---|
std::chrono | Oddiy o'lchov |
perf, valgrind | Batafsil profil |
| Google Benchmark | Ishonchli mikro-o'lchov |
Muhim: -O2 bilan o'lchang. -O0 da natijalar
yolg'on chiqadi.
- Vektorni to'rt xil usulda yarating va farqni ko'ring.
vector<int> v(5)vavector<int> v{5}ni solishtiring.- 20 element qo'shib, sig'im qachon o'zgarishini kuzating.
reservebilan qayta ajratishni yo'qoting.std::arrayva C massivining hajmini solishtiring.mapda[]bilan mavjud bo'lmagan kalitni o'qing.- Hajm o'zgarganini ko'ring va
containsbilan qayta yozing. mapvaunordered_mapda elementlar tartibini solishtiring.- Dublikatlarni
setva tartiblanganvectorbilan olib tashlang. vectorvalistga element qo'shish vaqtini o'lchang.
Xulosa #
std::vector- standart tanlov; boshqasiga o'lchovdan keyin o'ting.vector<int> v(5)5 ta nol,vector<int> v{5}esa bitta beshlik.- Sig'imdan oshganda vektor qayta ajratadi va hammani ko'chiradi.
- Element soni ma'lum bo'lsa
reserveni chaqiring. std::arraystekda yashaydi va C massivi kabi tez, lekin xavfsizroq.mapda[]mavjud bo'lmagan kalitni yaratadi - o'qish uchunfindyokicontains.maptartiblangan (O(log n)),unordered_mapxesh asosida (O(1) o'rtacha).- Kichik to'plamlarda tartiblangan
vectorsetdan tezroq. - Sabab - kesh: ketma-ket xotira ko'rsatkichlar zanjiridan tez.
- Tezlik haqida taxmin qilmang -
-O2bilan o'lchang.
Keyingi bo'limda iteratorlar va algoritmlar bilan tanishamiz.
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.