← Kursa Dön
📄 Text · 20 min

Input Validation, Stream States ve stringstream

Programın dış dünyayla temas ettiği her nokta bir güvenlik kapısıdır. Kullanıcı klavyeden bir sayı girmeli — ama "abc" yazar. Bir dosyadan veri okuyorsun — ama format beklediğin gibi değil. Bir string'i sayıya çevirmen gerekiyor — ama string boş.

Bunu bir havaalanı güvenlik kontrolüne benzetebilirsin. Yolcuların (verinin) terminale (programına) girmeden önce kontrol edilmesi gerekir. Pasaport geçerli mi? Bagajda yasak madde var mı? Bilet doğru kapıya mı? Eğer bu kontrolleri yapmazsan, uçak kalktıktan sonra sorun çıkar — ve o noktada müdahale etmek çok daha zor.

Bu derste cin'in hata durumlarını, stream state'leri, güvenli girdi okuma kalıplarını, getline inceliklerini ve stringstream ile string-sayı dönüşümlerini detaylıca işleyeceğiz.


cin ve Stream State'leri

Stream Nedir?

C++'ta cin, cout, dosya akışları — hepsi "stream" (akış) kavramının örnekleridir. Bir stream, veri kaynağı ile programın arasında akan bir nehir gibidir. Bu nehrin durumu her an sorgulanabilir: sorunsuz mu akıyor, tıkandı mı, kurudu mu?

Her stream'in dört adet durum bayrağı (state flag) vardır:

FlagAnlamıNe Zaman Set Edilir?
goodbitHer şey yolundaHata yok, okuma başarılı
failbitMantıksal hataSayı bekliyorken harf girildi, format uyuşmazlığı
badbitCiddi hataAkışın kendisi bozuldu (nadiren olur)
eofbitDosya sonuVeri kaynağı tükendi (EOF — End of File)

Bu flag'leri kontrol etmek için stream'in üye fonksiyonlarını kullanırsın:

#include <iostream>

int main() {
    int sayi;
    std::cout << "Bir sayi girin: ";
    std::cin >> sayi;
    
    std::cout << "good: " << std::cin.good() << std::endl;  // her sey OK mi?
    std::cout << "fail: " << std::cin.fail() << std::endl;  // format hatasi mi?
    std::cout << "bad:  " << std::cin.bad()  << std::endl;  // ciddi hata mi?
    std::cout << "eof:  " << std::cin.eof()  << std::endl;  // dosya sonu mu?
    
    return 0;
}

Eğer kullanıcı 42 girerse: good=1, fail=0, bad=0, eof=0. Her şey yolunda.

Eğer kullanıcı abc girerse: good=0, fail=1, bad=0, eof=0. Sayı bekliyorduk, harf geldi — mantıksal hata.

Sayı Bekliyorken Harf Girilince Ne Olur?

Bu, C++ programlamanın en klasik sorunlarından biri. Detaylı olarak ne olduğunu görelim:

  1. cin >> sayi çalışır ve input buffer'dan veri okumaya çalışır.

  2. Buffer'da abc\n var (kullanıcının girdiği).

  3. cin, a karakterini görür ve "bu bir sayı değil" der.

  4. failbit set edilir.

  5. sayi değişkeni değişmez — eski değeri (tanımsız veya önceki değer) kalır.

  6. Kritik: abc\n buffer'da kalır. Kimse onu oradan almadı.

  7. Bir sonraki cin >> çağrısı da başarısız olur — çünkü hem failbit hâlâ set, hem abc hâlâ buffer'da.

İşte bu yüzden programlar sonsuz döngüye girer. cin sürekli aynı geçersiz veriyi okumaya çalışır, sürekli başarısız olur, ve kullanıcıya tekrar sorma şansı vermez.

#include <iostream>

int main() {
    int sayi;
    
    // TEHLIKE: Sonsuz döngü riski!
    // Kullanıcı harf girerse, cin sürekli başarısız olur
    // ve bu döngü asla bitmez
    for (int i = 0; i < 3; ++i) {
        std::cout << "Sayi girin: ";
        std::cin >> sayi;
        
        if (std::cin.fail()) {
            std::cout << "HATA: fail durumunda!" << std::endl;
            // Burada bir şey yapmazsak, sonraki okumalar da başarısız olur
        } else {
            std::cout << "Okunan: " << sayi << std::endl;
        }
    }
    
    return 0;
}

Kullanıcı abc girerse, üç iterasyon da "HATA" basar — çünkü buffer temizlenmedi.


cin.clear() + cin.ignore() Pattern'i

Kurtarma Operasyonu

Stream'in hata durumundan kurtulması için iki adımlı bir operasyon gerekir. Bu iki adım her zaman birlikte kullanılır — birini unutmak sorunu çözmez.

Adım 1: `cin.clear()` — Hata bayraklarını sıfırla. Bu, stream'e "tamam, hata oldu ama şimdi tekrar çalışmaya hazırsın" demektir. Ama dikkat: buffer'daki çöp veri hâlâ orada!

Adım 2: `cin.ignore(...)` — Buffer'daki kalan veriyi at. Genellikle satır sonuna (\n) kadar her şeyi silmek istersin.

#include <iostream>
#include <limits>  // numeric_limits için

int main() {
    int sayi;
    std::cout << "Bir sayi girin: ";
    std::cin >> sayi;
    
    if (std::cin.fail()) {
        std::cout << "Gecersiz girdi!" << std::endl;
        
        // Adim 1: Hata flag'lerini temizle
        std::cin.clear();
        
        // Adim 2: Buffer'daki çöpü at
        // numeric_limits<streamsize>::max() = "mümkün olan en büyük sayı"
        // '\n' = satır sonuna kadar at
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        
        // Şimdi tekrar okuma yapabiliriz
        std::cout << "Tekrar deneyin: ";
        std::cin >> sayi;
        
        if (!std::cin.fail()) {
            std::cout << "Bu sefer oldu: " << sayi << std::endl;
        }
    } else {
        std::cout << "Okunan: " << sayi << std::endl;
    }
    
    return 0;
}

numeric_limits<streamsize>::max() ne yapıyor? cin.ignore(n, delim) fonksiyonu "en fazla n karakter at veya delim karakterini görene kadar at" der. Biz n yerine mümkün olan en büyük sayıyı veriyoruz, yani "satır sonuna kadar ne kadar çöp varsa hepsini at" diyoruz.

⚠️ Dikkat: cin.clear() olmadan cin.ignore() çağırırsan, ignore da başarısız olur — çünkü stream hâlâ hata durumunda ve hiçbir okuma/atlama işlemi çalışmaz. Her zaman önce clear(), sonra ignore().


Güvenli Sayı Okuma Döngüsü

Sağlam Girdi Okuma

Gerçek programlarda kullanıcıdan güvenli bir şekilde sayı okumanın standart kalıbı şudur:

#include <iostream>
#include <limits>

int main() {
    int sayi;
    
    std::cout << "Bir tam sayi girin: ";
    
    while (!(std::cin >> sayi)) {
        std::cout << "Gecersiz! Lutfen bir sayi girin: ";
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    
    std::cout << "Girdiginiz sayi: " << sayi << std::endl;
    return 0;
}

Bu kalıbı satır satır açıklayalım:

  • cin >> sayi ifadesi, başarılıysa stream'in kendisini döndürür — ve stream boolean'a dönüştürüldüğünde true olur.

  • Başarısızsa (harf girildi mesela), false olur.

  • !(cin >> sayi) → "okuma başarısız mı?" demek.

  • Başarısızsa: hata mesajı yaz, flag temizle, buffer temizle, döngü başa dön.

  • Başarılıysa: döngüden çık, sayi'da geçerli bir değer var.

Aralık Kontrolü Ekleyelim

Sadece sayı olması yetmez — genellikle belirli bir aralıkta olmasını da istersin:

#include <iostream>
#include <limits>

int guvenliSayiOku(int min, int max, const std::string& mesaj) {
    int sayi;
    
    while (true) {
        std::cout << mesaj;
        
        if (std::cin >> sayi) {
            // Okuma başarılı, aralık kontrolü yap
            if (sayi >= min && sayi <= max) {
                return sayi;  // her şey tamam
            }
            std::cout << "Sayi " << min << "-" << max 
                      << " araliginda olmali!" << std::endl;
        } else {
            // Okuma başarısız, temizle
            std::cout << "Gecersiz girdi!" << std::endl;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

int main() {
    int yas = guvenliSayiOku(0, 150, "Yasinizi girin (0-150): ");
    std::cout << "Yasiniz: " << yas << std::endl;
    
    int puan = guvenliSayiOku(0, 100, "Puaninizi girin (0-100): ");
    std::cout << "Puaniniz: " << puan << std::endl;
    
    return 0;
}

Bu guvenliSayiOku fonksiyonunu bir kere yazarsın, her yerde kullanırsın. Kullanıcı ne girerse girsin — harf, özel karakter, aralık dışı sayı — program çökmez. Profesyonel bir dokunuş.


getline İncelikleri

cin >> Sonrası getline Sorunu

Bu, C++ ile uğraşan herkesin er ya da geç çarptığı bir duvar. cin >> ile bir sayı okuduktan sonra getline kullanırsan, getline boş bir string okur ve atlar. Neden?

#include <iostream>
#include <string>

int main() {
    int yas;
    std::string isim;
    
    std::cout << "Yasiniz: ";
    std::cin >> yas;         // Kullanıcı "25\n" girer
                              // cin "25"yi okur, "\n" buffer'da KALIR
    
    std::cout << "Isminiz: ";
    std::getline(std::cin, isim);  // "\n"yi okur ve boş string döner!
    
    std::cout << "Yas: " << yas << ", Isim: '" << isim << "'" << std::endl;
    // Çıktı: Yas: 25, Isim: ''  ← isim boş!
    
    return 0;
}

Sorun şu: cin >> yas sayıyı okur ama satır sonu karakterini (\n) buffer'da bırakır. getline ise satır sonuna kadar okur — ve hemen orada bir \n bulur, "tamam, satır bitti" der ve boş string döndürür.

Çözüm: Araya cin.ignore() Koy

#include <iostream>
#include <string>
#include <limits>

int main() {
    int yas;
    std::string isim;
    
    std::cout << "Yasiniz: ";
    std::cin >> yas;
    
    // Buffer'daki kalan \n karakterini temizle
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    
    std::cout << "Isminiz: ";
    std::getline(std::cin, isim);  // Artık düzgün çalışır
    
    std::cout << "Yas: " << yas << ", Isim: '" << isim << "'" << std::endl;
    // Çıktı: Yas: 25, Isim: 'Ahmet Yilmaz'  ✓
    
    return 0;
}

💡 İpucu: Basit bir kural: cin >> ile okuma yaptıktan sonra getline kullanacaksan, araya mutlaka cin.ignore() koy. Bu kadar. Bu kuralı ezberle ve her zaman uygula.

Custom Delimiter ile getline

getline'ın üçüncü parametresi ile satır sonu yerine farklı bir ayırıcı (delimiter) belirleyebilirsin:

#include <iostream>
#include <string>

int main() {
    std::string parca;
    
    std::cout << "Virgülle ayrılmış isimler girin (örn: Ali,Veli,Ayse):" << std::endl;
    
    // Virgülü delimiter olarak kullan
    // Kullanıcı: Ali,Veli,Ayse\n
    
    std::getline(std::cin, parca, ',');  // parca = "Ali"
    std::cout << "1: " << parca << std::endl;
    
    std::getline(std::cin, parca, ',');  // parca = "Veli"
    std::cout << "2: " << parca << std::endl;
    
    std::getline(std::cin, parca);       // parca = "Ayse" (varsayılan \n)
    std::cout << "3: " << parca << std::endl;
    
    return 0;
}

Custom delimiter çoğu durumda doğrudan getline ile kullanılmaz — genellikle stringstream ile birlikte kullanılır. Birazdan o kısma geleceğiz.


stringstream: String ile Stream Arasında Köprü

Nedir?

stringstream, bir string'i tıpkı cin veya cout gibi kullanmanı sağlar. <sstream> header'ından gelir. Üç çeşidi var:

  • `istringstream` — String'den okuma (input). Tıpkı cin gibi, ama kaynak klavye değil, bir string.

  • `ostringstream` — String'e yazma (output). Tıpkı cout gibi, ama hedef ekran değil, bir string.

  • `stringstream` — Her ikisi birden (input + output).

String'den Sayı Çıkarma (istringstream)

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string veri = "42 3.14 Merhaba";
    std::istringstream akis(veri);
    
    int tamSayi;
    double ondalik;
    std::string kelime;
    
    akis >> tamSayi >> ondalik >> kelime;
    
    std::cout << "Tam sayi: " << tamSayi << std::endl;   // 42
    std::cout << "Ondalik: " << ondalik << std::endl;     // 3.14
    std::cout << "Kelime: " << kelime << std::endl;       // Merhaba
    
    return 0;
}

istringstream, tıpkı cin gibi boşlukları (whitespace) ayırıcı olarak kullanır ve sırayla okur. cin'den farkı: veri kaynağı klavye yerine bir string. Bu özellik, özellikle dosyadan okunan satırları parse ederken çok işe yarar.

Sayıdan String Oluşturma (ostringstream)

#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>

int main() {
    std::ostringstream akis;
    
    int gun = 20, ay = 2, yil = 2026;
    double sicaklik = 23.567;
    
    akis << std::setfill('0');
    akis << std::setw(2) << gun << "/" 
         << std::setw(2) << ay << "/" 
         << yil;
    
    std::string tarih = akis.str();  // "20/02/2026"
    std::cout << "Tarih: " << tarih << std::endl;
    
    // Yeni bir akış oluştur ya da str("") ile sıfırla
    std::ostringstream akis2;
    akis2 << std::fixed << std::setprecision(1) << sicaklik << " °C";
    
    std::string sicaklikStr = akis2.str();  // "23.6 °C"
    std::cout << "Sicaklik: " << sicaklikStr << std::endl;
    
    return 0;
}

ostringstream, formatlı string oluşturmak için harika. Özellikle << operatörü ile her türlü veriyi bir araya getirip, sonunda .str() ile string'e çevirebilirsin. C++20'de std::format gelene kadar, bu en temiz string formatlama yöntemiydi.

stoi/stod vs stringstream

C++11 ile birlikte gelen stoi, stod, stol gibi fonksiyonlar string-sayı dönüşümünü daha kolay hale getirdi. Peki ne zaman hangisini kullanmalı?

#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>

int main() {
    std::string s1 = "42";
    std::string s2 = "3.14";
    std::string s3 = "abc";
    
    // --- stoi/stod yaklaşımı ---
    try {
        int a = std::stoi(s1);          // 42
        double b = std::stod(s2);       // 3.14
        std::cout << "stoi: " << a << ", stod: " << b << std::endl;
        
        int c = std::stoi(s3);          // exception fırlatır!
    } catch (const std::invalid_argument& e) {
        std::cout << "stoi hatasi: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cout << "stoi taşma: " << e.what() << std::endl;
    }
    
    // --- stringstream yaklaşımı ---
    std::istringstream akis(s1);
    int d;
    if (akis >> d) {
        std::cout << "stringstream: " << d << std::endl;  // 42
    } else {
        std::cout << "stringstream: donusum basarisiz" << std::endl;
    }
    
    return 0;
}

`stoi/stod` avantajları: Daha kısa ve okunabilir. Tek bir dönüşüm için ideal. Exception ile hata yönetimi.

`stringstream` avantajları: Birden fazla değeri sırayla okuyabilir. >> operatörü ile format esnekliği. Hata durumunu fail() ile kontrol edebilirsin (exception gerektirmez).

Genel kural: tek bir dönüşüm yapacaksan stoi/stod, bir satırda birden fazla değer parse edeceksen stringstream.

CSV Satırı Parse Etme

stringstream + getline kombinasyonu, CSV (Comma-Separated Values) parse etmek için biçilmiş kaftan:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct Ogrenci {
    std::string isim;
    int yas;
    double not_ort;
};

Ogrenci csvSatirParse(const std::string& satir) {
    Ogrenci ogr;
    std::istringstream akis(satir);
    std::string temp;
    
    // isim,yas,not_ort formatında
    std::getline(akis, ogr.isim, ',');       // virgüle kadar oku → isim
    
    std::getline(akis, temp, ',');            // virgüle kadar oku → yas (string)
    ogr.yas = std::stoi(temp);               // string → int
    
    std::getline(akis, temp);                 // satır sonuna kadar oku → not (string)
    ogr.not_ort = std::stod(temp);            // string → double
    
    return ogr;
}

int main() {
    std::vector<std::string> satirlar = {
        "Ahmet Yilmaz,21,3.45",
        "Elif Kaya,20,3.78",
        "Burak Demir,22,2.90"
    };
    
    std::cout << "Ogrenci Listesi:" << std::endl;
    std::cout << "----------------------------" << std::endl;
    
    for (const auto& satir : satirlar) {
        Ogrenci ogr = csvSatirParse(satir);
        std::cout << ogr.isim << " | Yas: " << ogr.yas 
                  << " | Not: " << ogr.not_ort << std::endl;
    }
    
    return 0;
}

Bu pattern çok yaygın: dosyadan getline ile satır oku, istringstream'e ver, virgülle ayrılmış parçaları getline(akis, parca, ',') ile çıkar. Gerçek dünyada CSV dosyaları genellikle böyle parse edilir (tırnak içindeki virgüller gibi özel durumları saymıyorum — o zaman bir kütüphane kullan).


Stream State ile Dönüşüm Kontrolü

stringstream ile Güvenli Dönüşüm

stringstream, string'in tamamının geçerli bir sayı olup olmadığını kontrol etmek için de kullanılabilir:

#include <iostream>
#include <sstream>
#include <string>

bool stringToInt(const std::string& str, int& sonuc) {
    std::istringstream akis(str);
    
    // Sayıyı oku
    if (!(akis >> sonuc)) {
        return false;  // okuma başarısız
    }
    
    // Geride bir şey kaldı mı kontrol et
    char kalan;
    if (akis >> kalan) {
        return false;  // "42abc" gibi — sayıdan sonra çöp var
    }
    
    return true;  // temiz dönüşüm
}

int main() {
    int sayi;
    
    std::cout << std::boolalpha;  // true/false yazsın
    std::cout << "\"42\" -> "  << stringToInt("42", sayi)  << " (" << sayi << ")" << std::endl;
    std::cout << "\"abc\" -> " << stringToInt("abc", sayi) << std::endl;
    std::cout << "\"42abc\" -> " << stringToInt("42abc", sayi) << std::endl;
    std::cout << "\"\" -> "    << stringToInt("", sayi)    << std::endl;
    std::cout << "\" 42 \" -> " << stringToInt(" 42 ", sayi) << " (" << sayi << ")" << std::endl;
    
    return 0;
}

Bu fonksiyon, stoi'den daha titiz: stoi("42abc") başarılı olur ve 42 döndürür (gerisini görmezden gelir), ama stringToInt("42abc") false döndürür. Hangisini kullanacağın senaryoya bağlı.


Pratik Örnek: Güvenli Menü Sistemi

Tüm kavramları birleştiren bir örnek yapalım. Bu, gerçek bir programda görebileceğin tarzda bir menü sistemi:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <limits>
#include <iomanip>

// ---- Yardımcı fonksiyonlar ----

int guvenliTamSayiOku(const std::string& mesaj, int min, int max) {
    int sayi;
    while (true) {
        std::cout << mesaj;
        if (std::cin >> sayi && sayi >= min && sayi <= max) {
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            return sayi;
        }
        std::cout << "Gecersiz! " << min << "-" << max 
                  << " arasi bir sayi girin." << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

double guvenliOndalikOku(const std::string& mesaj) {
    double sayi;
    while (true) {
        std::cout << mesaj;
        if (std::cin >> sayi) {
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            return sayi;
        }
        std::cout << "Gecersiz! Bir sayi girin." << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

std::string guvenliStringOku(const std::string& mesaj) {
    std::string girdi;
    while (true) {
        std::cout << mesaj;
        std::getline(std::cin, girdi);
        if (!girdi.empty()) {
            return girdi;
        }
        std::cout << "Bos girdi kabul edilmez!" << std::endl;
    }
}

// ---- Öğrenci yönetimi ----

struct Ogrenci {
    std::string isim;
    int yas;
    double not_ort;
};

void ogrenciEkle(std::vector<Ogrenci>& liste) {
    Ogrenci ogr;
    ogr.isim = guvenliStringOku("Ogrenci adi: ");
    ogr.yas = guvenliTamSayiOku("Yas (15-65): ", 15, 65);
    ogr.not_ort = guvenliOndalikOku("Not ortalamasi: ");
    liste.push_back(ogr);
    std::cout << "Ogrenci eklendi!" << std::endl;
}

void listeGoster(const std::vector<Ogrenci>& liste) {
    if (liste.empty()) {
        std::cout << "Liste bos." << std::endl;
        return;
    }
    
    std::cout << std::left << std::setw(20) << "Isim"
              << std::setw(8) << "Yas"
              << std::setw(10) << "Not Ort." << std::endl;
    std::cout << std::string(38, '-') << std::endl;
    
    for (const auto& ogr : liste) {
        std::cout << std::left << std::setw(20) << ogr.isim
                  << std::setw(8) << ogr.yas
                  << std::fixed << std::setprecision(2) << ogr.not_ort 
                  << std::endl;
    }
}

void csvAktar(const std::vector<Ogrenci>& liste) {
    if (liste.empty()) {
        std::cout << "Aktarilacak veri yok." << std::endl;
        return;
    }
    
    std::ostringstream csv;
    csv << "isim,yas,not_ortalamasi" << std::endl;
    
    for (const auto& ogr : liste) {
        csv << ogr.isim << "," << ogr.yas << "," 
            << std::fixed << std::setprecision(2) << ogr.not_ort 
            << std::endl;
    }
    
    std::cout << "\n--- CSV Ciktisi ---" << std::endl;
    std::cout << csv.str();
    std::cout << "--- Bitti ---" << std::endl;
}

void csvIceAktar(std::vector<Ogrenci>& liste) {
    std::cout << "CSV satirlari girin (bos satir ile bitirin):" << std::endl;
    std::cout << "Format: isim,yas,not_ort" << std::endl;
    
    std::string satir;
    int eklenen = 0;
    
    while (std::getline(std::cin, satir) && !satir.empty()) {
        std::istringstream akis(satir);
        Ogrenci ogr;
        std::string temp;
        
        if (std::getline(akis, ogr.isim, ',') &&
            std::getline(akis, temp, ',')) {
            
            try {
                ogr.yas = std::stoi(temp);
                std::getline(akis, temp);
                ogr.not_ort = std::stod(temp);
                liste.push_back(ogr);
                eklenen++;
            } catch (...) {
                std::cout << "  Hatali satir: " << satir << std::endl;
            }
        } else {
            std::cout << "  Hatali format: " << satir << std::endl;
        }
    }
    
    std::cout << eklenen << " ogrenci eklendi." << std::endl;
}

// ---- Ana program ----

int main() {
    std::vector<Ogrenci> ogrenciler;
    
    while (true) {
        std::cout << "\n==== Ogrenci Yonetim Sistemi ====" << std::endl;
        std::cout << "1. Ogrenci Ekle" << std::endl;
        std::cout << "2. Listeyi Goster" << std::endl;
        std::cout << "3. CSV Olarak Aktar" << std::endl;
        std::cout << "4. CSV'den Ic Aktar" << std::endl;
        std::cout << "5. Cikis" << std::endl;
        
        int secim = guvenliTamSayiOku("Seciminiz (1-5): ", 1, 5);
        
        switch (secim) {
            case 1: ogrenciEkle(ogrenciler); break;
            case 2: listeGoster(ogrenciler); break;
            case 3: csvAktar(ogrenciler); break;
            case 4: csvIceAktar(ogrenciler); break;
            case 5:
                std::cout << "Cikiliyor..." << std::endl;
                return 0;
        }
    }
}

Bu örnekte neredeyse bu dersin tüm konuları bir arada:

  • cin.clear() + cin.ignore() ile güvenli sayı okuma

  • cin >> sonrası getline uyumu (ignore ile)

  • ostringstream ile CSV formatında string oluşturma

  • istringstream + getline(akis, parca, ',') ile CSV parse etme

  • stoi/stod ile string-sayı dönüşümü ve exception handling

  • Stream state kontrolü ile hata yönetimi

⚠️ Dikkat: Gerçek projelerde kullanıcı girdisi doğrulama sadece ilk savunma hattıdır. Veritabanına yazılan, ağ üzerinden gönderilen veya dosyaya kaydedilen her veri ek doğrulamalardan geçmelidir. "Kullanıcıya güvenme" prensibi güvenliğin temelidir.


Ekstra: Yaygın Hatalar ve Çözümleri

Hata 1: ignore'u Unutmak

// YANLIS — cin >> sonrası getline'a geçerken ignore unutulmuş
int yas;
std::string isim;
std::cin >> yas;
std::getline(std::cin, isim);  // boş string okur!

// DOGRU
std::cin >> yas;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::getline(std::cin, isim);  // düzgün çalışır

Hata 2: clear'sız ignore

// YANLIS — fail durumunda ignore çalışmaz
if (std::cin.fail()) {
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // çalışmaz!
    // clear olmadan stream hâlâ hata durumunda
}

// DOGRU
if (std::cin.fail()) {
    std::cin.clear();   // önce flag temizle
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // sonra buffer temizle
}

Hata 3: stoi Hatasını Yakalamamak

// YANLIS — exception yakalanmazsa program çöker
std::string girdi = "abc";
int sayi = std::stoi(girdi);  // terminate çağrılır!

// DOGRU
try {
    int sayi = std::stoi(girdi);
    std::cout << "Sayi: " << sayi << std::endl;
} catch (const std::invalid_argument&) {
    std::cout << "Gecersiz sayi formati" << std::endl;
} catch (const std::out_of_range&) {
    std::cout << "Sayi cok buyuk/kucuk" << std::endl;
}

Özet

  • Stream state flags (goodbit, failbit, badbit, eofbit) stream'in durumunu bildirir. Sayı bekliyorken harf girildiğinde failbit set edilir ve stream kilitlenir.

  • `cin.clear()` + `cin.ignore()` ikilisi stream'i kurtarmanın standart yoludur. Önce clear() ile flag'leri sıfırla, sonra ignore() ile buffer'daki çöpü at. İkisi her zaman birlikte kullanılır.

  • Güvenli okuma döngüsü while (!(cin >> sayi)) kalıbı ile yazılır. Kullanıcı geçerli bir değer girene kadar tekrar sorar — program asla çökmez.

  • `cin >>` sonrası `getline` kullanacaksan araya cin.ignore() koy. Aksi halde buffer'da kalan \n yüzünden getline boş string okur.

  • `stringstream` string ile stream dünyası arasında köprü kurar: istringstream string'den okumak, ostringstream string oluşturmak için. CSV parse etme, formatlı string üretme gibi işlerde vazgeçilmez.

  • `stoi`/`stod` tekil dönüşümler için kısa ve pratiktir, ama hata durumunda exception fırlatır — try-catch ile sar. Birden fazla değer parse edeceksen stringstream daha uygun.