---
title: Kapsayıcılar
source: 'https://academia.sh/tr/kurslar/cpp-standart-kutuphane/kapsayicilar'
course: 'Kaynak Yönetimi ve Standart Kütüphane'
language: tr
updated: '2026-09-26T03:02:47+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "std::map'te operator[] ile okumanın anahtar yoksa haritaya sıfırla kurulmuş bir eleman eklediğini ölçer"
    - "find ve contains'in aynı haritayı aynen bıraktığını gösterir"
    - 'Sıralı, ilişkisel ve sırasız kapsayıcılar arasında karmaşıklığa göre seçim yapar'
    - 'const bir haritada operator[] çağrısının neden derlenmediğini açıklar'
---

# Kapsayıcılar

map'in operator[] ile okumasının anahtar yoksa eleman eklediğinin, find ve contains'in kaynağı aynen bıraktığının ölçümü.

Önceki ders `Kopyalanamaz ve Taşınamaz Tipler`'de bir sınıfın kendi taşıyan
yapıcısını yazarak taşınmış kaynağa hangi boş durumu bırakacağına karar
verdiğini gösterdi — `Tutamac`'ın numarası `-1` oluyordu, çünkü taşıyan
yapıcı öyle yazılmıştı. Bu ders standart kütüphanenin hazır kapsayıcılarına
geçiyor ve aynı soruyu bu kez bir **okuma** işlemine soruyor: `stok["armut"]`
yazmak, bir değeri okumak gibi görünüyor. Ama `"armut"` anahtarı haritada
yoksa bu satır kaynağa ne bırakıyor?

## `operator[]` ile Okumak Aslında Yazmaktır

`std::map<std::string, int>::operator[]`, anahtar haritada zaten varsa ona
bağlı değeri döndürür. Anahtar yoksa farklı bir şey yapar: değeri sıfırla
kurulmuş yeni bir eleman **ekler** ve o elemana bir referans döndürür. Bu,
imzasından çıkarılamayan bir yan etkidir — `[]` köşeli parantezi çoğu
programcıya "sorgula" der, ama `map` için anlamı "varsa getir, yoksa kur"dur.

```cpp
// kapsayici-okuma.cpp — operator[] ile okuma haritaya eleman ekler mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};
    std::size_t once = stok.size();

    int adet = stok["armut"];
    (void)adet;

    std::printf("eleman sayisi degisti mi %s\n",
        (stok.size() != once) ? "evet" : "hayir");
    std::printf("armut degeri %d\n", stok["armut"]);
    return 0;
}
```

```text
eleman sayisi degisti mi evet
armut degeri 0
```

**KT1.** `stok["armut"]` çağrısı, `"armut"` haritada yokken haritayı
**değiştirdi**: eleman sayısı bir arttı ve yeni elemanın değeri `int`'in
varsayılan değeri olan `0` oldu. `adet` değişkenine atanan değer bir okuma
gibi görünse de, o okumanın kendisi haritaya yazdı. Bu ifadenin sınıfı
**değişti** — kaynak hâlâ bir değer taşıyor ve standart bu değeri söylüyor
(boş harita değil, bir elemanlı harita), ama değer işlemden öncekinden
farklı.

## `find` ve `contains`: Aynı Soruyu Eklemeden Sormak

Bir anahtarın haritada olup olmadığını öğrenmenin `operator[]`'i çağırmayan
iki yolu vardır. `find`, anahtarı bulursa o elemana bir yineleyici, bulamazsa
`end()` döndürür — hiçbir durumda eleman eklemez. `contains`, C++20 ile gelen
daha doğrudan yol; yalnız bir `bool` döndürür.

```cpp
// kapsayici-sorgu.cpp — find ve contains haritayi degistirir mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};
    auto once = stok;

    auto it = stok.find("armut");
    bool var = stok.contains("kiraz");

    std::printf("find sonucu %s\n", (it == stok.end()) ? "bulunamadi" : "bulundu");
    std::printf("contains sonucu %s\n", var ? "evet" : "hayir");
    std::printf("harita degisti mi %s\n", (stok != once) ? "evet" : "hayir");
    return 0;
}
```

```text
find sonucu bulunamadi
contains sonucu hayir
harita degisti mi hayir
```

**KT2.** `find("armut")` ve `contains("kiraz")` ikisi de haritada olmayan
anahtarları sorguladı ve ikisi de haritayı **aynen** bıraktı: `stok`, işlem
öncesindeki kopyasıyla hâlâ eşit. `find`'ın döndürdüğü `end()` yineleyicisi
"burada yok" demenin bir yolu, eklemenin bir tetikleyicisi değil. Bir haritada
bir anahtarın varlığını yalnız denetlemek isteyen kod `operator[]` yerine
`find` ya da `contains` kullanmalı — aksi hâlde sorgu, sorguladığı yapıyı
değiştirir.

## Kapsayıcı Aileleri ve Seçim Ölçütü

Standart kütüphane kapsayıcıları üç aileye ayrılır. **Sıralı kapsayıcılar**
(`vector`, `deque`, `list`, `array`) elemanın konumunu eklenme sırasından
alır — burada "sıralı" büyüklüğe göre dizilmiş anlamına gelmez, elemanın yeri
programcının onu koyduğu yerdir. `vector` bitişik bellekte durur ve sona
ekleme ortalama sabit zamanlıdır; sona erişim indeksle sabit zamanlıdır.
`list` çift yönlü bağlı bir listedir; ortadan ekleme sabit zamanlıdır ama
indeksle erişim yoktur, baştan gezilir.

**İlişkisel kapsayıcılar** (`map`, `set`) elemanları bir anahtara göre
sıralı tutar; genellikle dengeli bir ikili arama ağacıyla gerçeklenir ve
arama, ekleme, silme `O(log n)` karmaşıklıktadır. **Sırasız kapsayıcılar**
(`unordered_map`, `unordered_set`) elemanları bir özet tablosunda tutar;
ortalama durumda arama ve ekleme `O(1)`'dir, ama en kötü durumda bütün
elemanlar aynı kovaya düşerse `O(n)`'e düşer. Seçim, hangi işlemin sık
yapılacağına bağlıdır: sık sıralı gezinme gerekiyorsa `map`, yalnız hızlı
arama gerekiyor ve sıra önemsizse `unordered_map` tercih edilir.

```cpp
// aile-secimi.cpp — sirali, iliskisel ve sirasiz kapsayicidan okuma
#include <cstdio>
#include <map>
#include <unordered_map>
#include <vector>

int main() {
    std::vector<int> sirali{30, 10, 20};
    std::map<int, int> iliskisel{{1, 10}, {2, 20}};
    std::unordered_map<int, int> sirasiz{{1, 10}, {2, 20}};

    std::printf("vector ilk eleman %d\n", sirali.front());
    std::printf("map en kucuk anahtar %d\n", iliskisel.begin()->first);
    std::printf("unordered_map arama %d\n", sirasiz.at(1));
    return 0;
}
```

```text
vector ilk eleman 30
map en kucuk anahtar 1
unordered_map arama 10
```

**KT3.** Bu üç okuma da bir kapsayıcıyı **aynen** bıraktı: `front()`,
yineleyici üzerinden `first` okuması ve `at()` hiçbiri kaynağa yazmadı.
`unordered_map`'in elemanlarını hangi sırada tuttuğu burada hiç
sorgulanmadı ve sorgulanmayacak — sırasız kapsayıcının dolaşma sırası
gerçeklemeye bağlıdır ve bu kursta hiç basılmaz. `map`'te `begin()->first`
her koşumda `1` verir çünkü `map` anahtara göre sıralıdır; aynı çağrı bir
`unordered_map` üzerinde yapılsaydı sonuç güvenilir olmazdı.

## `at()`: Sınır Denetimi Olan Erişim

`operator[]`'in bir sorunu daha var: `const` bir haritada kullanılamaz,
çünkü anahtar yoksa eleman ekleyebilme ihtimali onu değiştirici bir işlem
yapar. `at()` bu sorunu ortadan kaldırır — anahtar yoksa eleman eklemez,
bunun yerine `std::out_of_range` fırlatır ve `const` bir haritada da
kullanılabilir.

```cpp
// at-erisimi.cpp — at() sinir disi anahtarda ne yapar
#include <cstdio>
#include <map>
#include <stdexcept>
#include <string>

int main() {
    const std::map<std::string, int> stok{{"elma", 4}};

    try {
        int deger = stok.at("armut");
        (void)deger;
        std::printf("yakalandi mi hayir\n");
    } catch (const std::out_of_range&) {
        std::printf("yakalandi mi evet\n");
    }

    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
```

```text
yakalandi mi evet
eleman sayisi 1
```

**KT4.** `stok.at("armut")` bir istisna fırlattı ve bu ders yalnız
**yakalandığını** bastı — istisnanın `what()` metnini basmadı, çünkü o
metnin kesin biçimi kütüphane uygulamasına bağlıdır ve dersin ölçtüğü şey
değil. Fırlatmadan sonra `stok`'un eleman sayısı hâlâ `1`: harita **aynen**
kaldı. Bu, `at()`'i `const` bir kapsayıcı üzerinde güvenle kullanılabilir
kılan şey — sınır dışı bir anahtar isteği kaynağı hiç değiştirmiyor, yalnız
bunu bir istisnayla bildiriyor.

## `insert` ve `emplace`: Var Olan Anahtara Dokunmama Kuralı

`operator[]` yalnız anahtar yoksa eleman ekleyip döndürmüyor; anahtar zaten
varsa değeri **değiştiriyor**. `insert` ve `emplace` bunun tam tersi bir
garanti taşır: anahtar zaten varsa hiçbir şeyi değiştirmezler ve mevcut
elemana dokunmadıklarını bir `bool` ile bildirirler.

```cpp
// insert-emplace.cpp — anahtar zaten varken insert kaynagi degistirir mi
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}};

    auto sonuc = stok.insert({"elma", 99});
    bool eklendi = sonuc.second;

    std::printf("eklendi mi %s\n", eklendi ? "evet" : "hayir");
    std::printf("elma degeri %d\n", stok.at("elma"));
    return 0;
}
```

```text
eklendi mi hayir
elma degeri 4
```

**KT5.** `insert({"elma", 99})` çağrısı `"elma"` anahtarı haritada zaten
varken çalıştı ve `sonuc.second` `false` döndü — hiçbir ekleme olmadı.
`elma` değeri hâlâ `4`: harita bu çağrıdan sonra **aynen** kaldı, `99`
değeri hiçbir zaman yazılmadı. Bu, `operator[]` ile taban çizgide bir fark
yaratır: `stok["elma"] = 99` yazılsaydı değer koşulsuz üzerine yazılır ve
sınıf **değişti** olurdu. `insert`'in bu geri çevirme davranışı, bir haritayı
yalnız yoksa doldurmak isteyen kodun `operator[]` yerine `insert` ya da
`emplace` seçmesinin gerekçesidir — `emplace` aynı garantiyi taşır, farkı
elemanı bir geçici nesne kurmadan doğrudan kapsayıcının belleğinde inşa
etmesidir.

## `erase`'in Karmaşıklığı Kapsayıcıya Göre Değişir

Bir elemanı silmek her kapsayıcıda aynı işi yapmaz. `std::vector::erase`
silinen konumdan sonraki her elemanı bir konum sola kaydırır — bu, silinen
konum başa ne kadar yakınsa o kadar çok eleman taşınacağı anlamına gelir ve
karmaşıklığı `O(n)`dir. `std::list::erase` bitişik bellek tutmadığı için
kaydırma yapmaz; yalnız silinen düğümün komşularının bağlantısını günceller
ve karmaşıklığı `O(1)`dir. `std::map::erase` ise bir anahtara göre arama
yapıp ardından ağaçtaki bir düğümü kaldırır, toplamda `O(log n)`.

```cpp
// erase-karmasikligi.cpp — vector erase kalan elemanlari kaydirir mi
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> sirali{10, 20, 30, 40};

    sirali.erase(sirali.begin());

    std::printf("eleman sayisi %zu\n", sirali.size());
    std::printf("ilk eleman %d\n", sirali.front());
    return 0;
}
```

```text
eleman sayisi 3
ilk eleman 20
```

**KT6.** `sirali.erase(sirali.begin())` ilk elemanı sildi ve kalan üç eleman
bir konum sola kaydı — `front()` artık eskiden ikinci sırada duran `20`'yi
veriyor. Kaynak bu işlemden sonra **değişti**: yalnız eleman sayısı bir
azalmadı, kalan elemanların bellekteki konumu da değişti. Bir `std::list`
üzerinde aynı çağrı yapılsaydı sonuç sınıf olarak yine **değişti** olurdu
— eleman sayısı yine bir azalırdı — ama kaydırma hiç gerçekleşmezdi;
kalmayan elemanların adresleri sabit kalırdı. Bu dersin sınıflandırması
(aynen/değişti/boş/belirsiz) bir işlemin *ne kadar* iş yaptığını değil,
kaynağı *ne durumda bıraktığını* ölçtüğü için, `vector` ve `list` üzerindeki
bu iki `erase` çağrısı aynı sınıfa düşer; karmaşıklık farkı ayrı bir eksendir
ve seçim yaparken karmaşıklık ile sınıf birlikte değerlendirilir, biri
diğerinin yerine geçmez.

## Sınır Dışı İndeksleme: Tanımsız Davranış ve Tanımlı Karşılığı

`std::vector::operator[]`, verdiği indeksin kapsayıcının sınırları içinde
olduğunu hiç denetlemez. `sirali[10]` çağrısı `sirali`'nin boyutu `3`
olduğunda **tanımsız davranıştır**: çalışma zamanı bir çökme üretebilir, çöp
bir değer döndürebilir ya da hiçbir hata vermeden "çalışabilir" — üçü de eşit
derecede olasıdır ve bu kurs hiçbirini göstermez, çünkü tanımsız davranışın
çıktısı standarda göre tanımlı değildir ve doğrulanamaz. `std::vector::at()`
aynı isteğe **tanımlı** bir karşılık verir: indeks sınır dışıysa
`std::out_of_range` fırlatır, sınır içindeyse `operator[]` ile aynı elemana
erişir. Fark performanstadır — `at()` her çağrıda bir sınır denetimi
yapar, `operator[]` yapmaz — ama bu fark yalnız sınır içindeki erişimlerde
görünür bir maliyettir; sınır dışı bir erişimde `operator[]`'in "maliyetsiz"
olması, kaynağın ne durumda kaldığının artık tanımsız olması pahasınadır.
Bir indeksin geçerliliğinden emin olunamayan her yerde `at()` tercih edilir;
indeksin geçerliliği çağıran tarafından zaten garanti edildiği sıkı döngülerde
`operator[]`'in denetimsiz hızı kabul edilir.

## Sınırlayıcı Ölçüm

`const std::map<std::string, int>& stok` üzerinde `stok["armut"]` yazmak
**derlenmez**: `operator[]`'in `const` olmayan bir aşırı yüklemesi vardır
ve `const` bir nesne üzerinde çağrılamaz, çünkü işlecin gövdesi anahtar
yoksa eleman ekleyebilir — bu, `const` sözleşmesiyle çelişir. Aşağıdaki
blok bu satırı **yorum olarak** taşır ve onun yerine derlenen sürümü,
`const` bir haritanın yalnız `find`, `at` ve `contains` ile okunabildiğini
gösterir:

```cpp
// const-harita.cpp — const haritada operator[] neden kullanilamaz
#include <cstdio>
#include <map>
#include <string>

int main() {
    const std::map<std::string, int> stok{{"elma", 4}};

    // Asagidaki satir derlenmez, cunku operator[] const olmayan bir islev:
    // int adet = stok["armut"];

    auto it = stok.find("elma");
    std::printf("elma bulundu mu %s\n", (it != stok.end()) ? "evet" : "hayir");
    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
```

```text
elma bulundu mu evet
eleman sayisi 1
```

Derleyicinin bu satırı reddetmesi bir kısıtlama değil, bir güvencedir:
`const` bir haritanın yalnız sorgulanabileceğini, hiçbir okumanın onu
değiştiremeyeceğini derleme anında zorunlu kılar. Bu dersin ölçtüğü işlem
— değiştirilebilir bir haritada `operator[]` ile okuma — bu güvenceyi
taşımaz; kaynağa yazan yol açıktır ve derleyici onu kapatmaz.

## Özet

- `std::map`'te `operator[]` ile okuma, anahtar yoksa haritaya sıfırla
  kurulmuş bir eleman ekler; bu dersin ölçtüğü işlemin sınıfı **değişti**.
- `find` ve `contains`, anahtarı ararken haritaya hiç yazmaz; kaynak
  **aynen** kalır.
- Sıralı (`vector`, `deque`, `list`, `array`), ilişkisel (`map`, `set`) ve
  sırasız (`unordered_map`, `unordered_set`) kapsayıcılar farklı karmaşıklık
  garantileri taşır (`erase` örneğin `vector`'de `O(n)`, `list`'te `O(1)`,
  `map`'te `O(log n)`); ama bu fark sınıflandırmayı etkilemez, hepsi kaynağı
  **değişti** durumuna taşır.
- `insert` ve `emplace`, anahtar zaten varsa hiçbir şey yazmaz — kaynak
  **aynen** kalır; `operator[]` ile yazmak ise koşulsuz üzerine yazar ve
  sınıf **değişti** olur.
- `at()`, anahtar yoksa istisna fırlatır ve haritayı hiç değiştirmez, bu
  yüzden `const` bir kapsayıcı üzerinde de kullanılabilir; buna karşın
  `const` bir haritada `operator[]` çağrısı derlenmez — derleyici, kaynağı
  değiştirebilecek bir yolu `const` sözleşmesiyle baştan kapatır.
- `vector::operator[]` sınır dışı bir indekste tanımsız davranışa girer;
  `vector::at()` aynı isteğe tanımlı bir istisna fırlatarak karşılık verir —
  ikisi de kaynağı okumaktan öteye gitmez, fark yalnız sınır dışı isteğe
  verilen yanıtın tanımlı olup olmadığındadır.

## Sonraki Adım

Bu ders bir kapsayıcının bütününe ne olduğunu ölçtü. Sonraki ders sınırı bir
kapsayıcının **içindeki bir konuma** indiriyor: bir yineleyici, işaret ettiği
elemanı silme işleminden sonra hâlâ okunabilir mi, yoksa o konuma dokunmak
tanımsız davranışa mı giriyor?
