std::array ve std::vector
Bir önceki derste C-style dizileri gördük ve bir sürü sınırlamayla karşılaştık: boyut bilgisi kayboluyor, kopyalama yok, sınır kontrolü yok... Modern C++ bu sorunların hepsine çözüm sunuyor: std::array ve std::vector.
Bu ikisi C++'ın en çok kullanılan konteynerlerinden. std::array sabit boyutlu dizi istediğinde, std::vector ise boyutun çalışma zamanında değişmesi gerektiğinde kullanılır. İkisini de derinlemesine öğreneceğiz.
std::array — Sabit Boyutlu Modern Alternatif
std::array, C-style dizinin "akıllı versiyonu" gibi düşünebilirsin. Aynı performansı sunuyor ama ek özellikler getiriyor: boyut bilgisini biliyor, kopyalanabiliyor, karşılaştırılabiliyor.
Bunu bir kartvizitlik gibi düşün. Kartvizitliğin boyutu sabittir — 10 kart alır mesela. Ama en azından kaç kart kapasitesi olduğunu biliyor, kartları başka bir kartvizitliğe kopyalayabiliyor.
Tanımlama ve Kullanım
#include <iostream>
#include <array>
using namespace std;
int main() {
// Temel tanımlama: array<tip, boyut>
array<int, 5> notlar = {90, 85, 78, 92, 88};
// Erişim — tıpkı C-style gibi
cout << "İlk: " << notlar[0] << endl;
cout << "Son: " << notlar[4] << endl;
// .at() ile güvenli erişim — sınır kontrolü yapar
cout << "İkinci: " << notlar.at(1) << endl;
// notlar.at(10); // HATA: std::out_of_range exception fırlatır
// Boyut bilgisi her zaman mevcut
cout << "Boyut: " << notlar.size() << endl; // 5
return 0;
}Güvenli Erişim: [] vs .at()
#include <iostream>
#include <array>
using namespace std;
int main() {
array<int, 3> dizi = {10, 20, 30};
// [] — hızlı ama sınır kontrolü yok
// dizi[5] — tanımsız davranış, hata vermez
// .at() — sınır kontrolü yapar, hata fırlatır
try {
cout << dizi.at(5) << endl;
} catch (const out_of_range& e) {
cout << "Hata: " << e.what() << endl;
}
return 0;
}Faydalı Üye Fonksiyonlar
#include <iostream>
#include <array>
using namespace std;
int main() {
array<int, 5> dizi = {10, 20, 30, 40, 50};
cout << "İlk eleman: " << dizi.front() << endl; // 10
cout << "Son eleman: " << dizi.back() << endl; // 50
cout << "Boyut: " << dizi.size() << endl; // 5
cout << "Boş mu: " << dizi.empty() << endl; // 0 (false)
// Kopyalama — C-style dizide bu mümkün değildi!
array<int, 5> kopya = dizi;
cout << "Kopya[0]: " << kopya[0] << endl; // 10
// Karşılaştırma — eleman eleman karşılaştırır
if (dizi == kopya) {
cout << "İki dizi eşit!" << endl;
}
// Tüm elemanları bir değerle doldur
dizi.fill(0);
for (int x : dizi) cout << x << " "; // 0 0 0 0 0
cout << endl;
return 0;
}💡 İpucu:
std::arraystack üzerinde yaşar ve C-style dizi ile aynı performansı sunar. Overhead yok. Sabit boyutlu dizi gerektiğinde tercih et.
std::vector — Dinamik Boyutlu Dizi
std::vector, C++'ın en çok kullanılan konteyneridir. Boyutu çalışma zamanında büyüyüp küçülebilir. Eleman ekle, çıkar, ortasına sok — hepsini yapabilirsin.
Bunu bir akordeon dosya gibi düşün. Kaç dosya gelirse gelsin, kendini genişletip daraltır. Dışarıdan sabit bir kutu gibi görünür ama içeride dinamik olarak büyür.
Temel Tanımlama ve Kullanım
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Boş vector
vector<int> v1;
// Başlangıç değerleriyle
vector<int> v2 = {10, 20, 30, 40, 50};
// 5 elemanlı, hepsi 0
vector<int> v3(5);
// 5 elemanlı, hepsi 42
vector<int> v4(5, 42);
// Erişim
cout << "v2[0] = " << v2[0] << endl; // 10
cout << "v2.at(2) = " << v2.at(2) << endl; // 30
// Range-based for
for (int x : v2) {
cout << x << " ";
}
cout << endl;
return 0;
}push_back, pop_back, size, capacity, reserve
Eleman Ekleme ve Çıkarma
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> sayilar;
// Eleman ekleme — sona ekler
sayilar.push_back(10);
sayilar.push_back(20);
sayilar.push_back(30);
cout << "Boyut: " << sayilar.size() << endl; // 3
for (int x : sayilar) cout << x << " ";
cout << endl; // 10 20 30
// Son elemanı çıkar
sayilar.pop_back();
cout << "pop_back sonrası boyut: " << sayilar.size() << endl; // 2
for (int x : sayilar) cout << x << " ";
cout << endl; // 10 20
return 0;
}Size vs Capacity
Vector'ün iki önemli kavramı var:
size: Şu an kaç eleman var
capacity: Yeniden bellek ayırmadan kaç eleman alabilir
Vector büyüdükçe mevcut kapasitesi yetmezse, daha büyük bir bellek bloğu ayırır ve tüm elemanları oraya kopyalar. Bu maliyetli bir işlem.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
for (int i = 0; i < 20; i++) {
v.push_back(i);
cout << "size=" << v.size()
<< " capacity=" << v.capacity() << endl;
}
return 0;
}Çıktıda capacity'nin 1, 2, 4, 8, 16, 32... şeklinde ikiye katlanarak büyüdüğünü göreceksin. Bu strateji genellikle "amortized O(1)" olarak bilinir — ortalamada her ekleme sabit sürede yapılır.
reserve ile Önceden Yer Ayırma
Kaç eleman geleceğini aşağı yukarı biliyorsan, reserve ile önceden yer ayırabilirsin. Bu gereksiz kopyalamaları önler:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
v.reserve(1000); // 1000 elemanlık yer ayır
cout << "size: " << v.size() << endl; // 0 — eleman yok henüz
cout << "capacity: " << v.capacity() << endl; // 1000
for (int i = 0; i < 1000; i++) {
v.push_back(i); // hiç reallocation olmaz
}
cout << "size: " << v.size() << endl; // 1000
cout << "capacity: " << v.capacity() << endl; // 1000
return 0;
}💡 İpucu: Büyük bir döngüde
push_backyapacaksan, öncesindereserveçağır. Performans farkı büyük verilerde hissedilir.
emplace_back vs push_back
push_back bir eleman kopyasını vector'ün sonuna ekler. emplace_back ise elemanı doğrudan yerinde inşa eder (construct in place). Basit türlerde fark yok ama nesnelerle çalışırken emplace_back daha verimli olabilir.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
vector<string> isimler;
// push_back — önce string oluştur, sonra kopyala/taşı
isimler.push_back("Ali");
// emplace_back — doğrudan yerinde oluştur
isimler.emplace_back("Veli");
// Pratik fark: emplace_back constructor argümanlarını doğrudan alır
vector<pair<string, int>> ogrenciler;
// push_back ile — geçici pair oluşturman gerekir
ogrenciler.push_back({"Ali", 90});
// emplace_back ile — argümanları doğrudan geçir
ogrenciler.emplace_back("Veli", 85);
for (auto& [isim, not_] : ogrenciler) {
cout << isim << ": " << not_ << endl;
}
return 0;
}⚠️ Dikkat: Basit türlerde (
int,double)push_backveemplace_backarasında pratik fark yok. Karmaşık nesnelerdeemplace_backtercih edilir çünkü gereksiz kopyalamayı önler.
Diğer Önemli vector Fonksiyonları
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {10, 20, 30, 40, 50};
// İlk ve son eleman
cout << "İlk: " << v.front() << endl; // 10
cout << "Son: " << v.back() << endl; // 50
// Boş mu?
cout << "Boş mu: " << v.empty() << endl; // 0
// Belirli pozisyona ekleme — insert
v.insert(v.begin() + 2, 25); // indeks 2'ye 25 ekle
// v: 10 20 25 30 40 50
// Belirli pozisyondan silme — erase
v.erase(v.begin() + 4); // indeks 4'teki elemanı sil
// v: 10 20 25 30 50
// Tüm elemanları sil
v.clear();
cout << "clear sonrası size: " << v.size() << endl; // 0
// shrink_to_fit — fazla kapasiteyi serbest bırak
cout << "capacity: " << v.capacity() << endl; // muhtemelen hâlâ büyük
v.shrink_to_fit();
cout << "shrink sonrası capacity: " << v.capacity() << endl; // 0
return 0;
}Iterator ile Gezinme
Iterator, konteynerlerin elemanlarını gezen bir "imlec" gibidir. Pointer'a benzer ama daha soyut.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {10, 20, 30, 40, 50};
// begin() ilk elemanı, end() son elemandan bir SONRAKİNİ gösterir
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // * ile değere eriş
}
cout << endl;
// Ters gezinme
for (auto it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " "; // 50 40 30 20 10
}
cout << endl;
// const iterator — değer değiştirmeyi engeller
for (auto it = v.cbegin(); it != v.cend(); ++it) {
cout << *it << " ";
// *it = 99; // HATA! const iterator
}
cout << endl;
return 0;
}STL Algoritmalarıyla Kullanım
Iterator'ların asıl gücü, STL algoritmaları ile kullanıldığında ortaya çıkar:
#include <iostream>
#include <vector>
#include <algorithm> // sort, find, reverse, count
#include <numeric> // accumulate
using namespace std;
int main() {
vector<int> v = {50, 10, 40, 20, 30};
// Sıralama
sort(v.begin(), v.end());
for (int x : v) cout << x << " "; // 10 20 30 40 50
cout << endl;
// Ters sıralama
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << " "; // 50 40 30 20 10
cout << endl;
// Arama
auto it = find(v.begin(), v.end(), 30);
if (it != v.end()) {
cout << "30 bulundu, indeks: " << distance(v.begin(), it) << endl;
}
// Toplam
int toplam = accumulate(v.begin(), v.end(), 0);
cout << "Toplam: " << toplam << endl; // 150
// Sayma
vector<int> sayilar = {1, 2, 3, 2, 1, 2, 3};
cout << "2'lerin sayısı: " << count(sayilar.begin(), sayilar.end(), 2) << endl;
// Ters çevirme
reverse(v.begin(), v.end());
for (int x : v) cout << x << " ";
cout << endl;
return 0;
}std::array Fonksiyona Geçirme
std::array'i fonksiyona geçirirken boyut bilgisi korunur — C-style'daki decay sorunu yok:
#include <iostream>
#include <array>
using namespace std;
// Template ile her boyuttaki array'i kabul et
template <size_t N>
void yazdir(const array<int, N>& dizi) {
for (int x : dizi) {
cout << x << " ";
}
cout << endl;
cout << "Boyut: " << dizi.size() << endl;
}
// Belirli boyut için
void yazdir5(const array<int, 5>& dizi) {
for (int x : dizi) cout << x << " ";
cout << endl;
}
int main() {
array<int, 5> notlar = {90, 85, 78, 92, 88};
array<int, 3> kucuk = {1, 2, 3};
yazdir(notlar); // Boyut: 5
yazdir(kucuk); // Boyut: 3
yazdir5(notlar);
return 0;
}std::vector Fonksiyona Geçirme
Vector'ü fonksiyona geçirirken referans kullan — yoksa tüm vector kopyalanır:
#include <iostream>
#include <vector>
using namespace std;
// Kötü: vector kopyalanır — yavaş
void kotuYazdir(vector<int> v) {
for (int x : v) cout << x << " ";
cout << endl;
}
// İyi: const referans — kopyalama yok, değiştirme yasak
void yazdir(const vector<int>& v) {
for (int x : v) cout << x << " ";
cout << endl;
}
// Değiştirmek istiyorsan: referans (const olmadan)
void ikiyeCarp(vector<int>& v) {
for (int& x : v) {
x *= 2;
}
}
int main() {
vector<int> notlar = {90, 85, 78, 92, 88};
yazdir(notlar); // 90 85 78 92 88
ikiyeCarp(notlar);
yazdir(notlar); // 180 170 156 184 176
return 0;
}⚠️ Dikkat:
vector'ü fonksiyona değer ile (by value) geçirirsen tüm elemanlar kopyalanır. Büyük vector'lerde bu ciddi performans kaybına neden olur. Her zamanconst&veya&kullan.
Ne Zaman array, Ne Zaman vector?
| Durum | Tercih | Neden |
|---|---|---|
| Boyut derleme zamanında belli ve sabit | std::array | Stack'te yaşar, sıfır overhead |
| Boyut çalışma zamanında değişebilir | std::vector | Dinamik büyüme/küçülme |
| Performans kritik, boyut sabit | std::array | Cache dostu, allocation yok |
| Kullanıcıdan veri alınacak | std::vector | Boyut bilinmiyor |
| Fonksiyondan dizi döndürmek | std::vector | Kolay, güvenli |
| Küçük, sabit veri seti (günler, aylar vs.) | std::array | Derleyici optimizasyonları |
| Genel amaçlı "bilmiyorum kaç eleman gelecek" | std::vector | En esnek seçenek |
Kural: Emin değilsen std::vector kullan. Yanlış gidemezsin.
Pratik Örnek: Öğrenci Not Sistemi
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
vector<string> isimler;
vector<int> notlar;
// Veri girişi
int n;
cout << "Kaç öğrenci? ";
cin >> n;
for (int i = 0; i < n; i++) {
string isim;
int not_;
cout << "İsim: ";
cin >> isim;
cout << "Not: ";
cin >> not_;
isimler.push_back(isim);
notlar.push_back(not_);
}
// Ortalama
double ort = accumulate(notlar.begin(), notlar.end(), 0.0) / notlar.size();
cout << "Ortalama: " << ort << endl;
// En yüksek not
auto max_it = max_element(notlar.begin(), notlar.end());
int max_idx = distance(notlar.begin(), max_it);
cout << "En yüksek: " << isimler[max_idx] << " (" << *max_it << ")" << endl;
return 0;
}Özet
std::array sabit boyutlu, modern C-style dizi alternatifidir. Stack'te yaşar, sıfır overhead sunar, boyut bilgisini korur.
std::vector dinamik boyutlu dizidir;
push_backile eleman eklenir,pop_backile çıkarılır. C++'ın en çok kullanılan konteyneridir.Vector'de size mevcut eleman sayısı, capacity ise yeniden bellek ayırmadan alınabilecek eleman kapasitesidir.
reserveile önceden yer ayrılabilir.emplace_back elemanı yerinde inşa eder; karmaşık nesnelerde
push_back'e göre daha verimlidir.Iterator'lar konteynerleri gezen imleclerdir;
sort,find,accumulategibi STL algoritmaları ile birlikte güçlü bir araç setidir.Emin değilsen std::vector kullan — esnek, güvenli ve her duruma uygun.
AI Asistan
Sorularını yanıtlamaya hazır