String İşlemleri Derinlemesine
Daha önceki derslerde std::string'in temellerini görmüştük — tanımlama, birleştirme, karşılaştırma. Bu derste string'in gelişmiş yeteneklerini keşfedeceğiz: arama, parçalama, dönüştürme ve hatta düzenli ifadeler (regex).
String işlemlerini bir İsviçre çakısı gibi düşün. Basit işler için bıçağı açarsın, ama ihtiyaç olunca makas, tornavida, tirbuşon da var. Bu derste çakının tüm aletlerini öğreneceğiz.
find — String İçinde Arama
find fonksiyonu, bir alt string'in (substring) ilk geçtiği pozisyonu döndürür. Bulamazsa string::npos döner.
#include <iostream>
#include <string>
using namespace std;
int main() {
string metin = "Merhaba dünya, merhaba C++";
// İlk geçtiği yer
size_t pos = metin.find("merhaba");
if (pos != string::npos) {
cout << "'merhaba' bulundu, pozisyon: " << pos << endl; // 15
}
// Büyük/küçük harf duyarlı!
pos = metin.find("Merhaba");
cout << "'Merhaba' pozisyon: " << pos << endl; // 0
// Tek karakter arama
pos = metin.find('+');
cout << "'+' pozisyon: " << pos << endl; // 24
// Belirli pozisyondan itibaren arama
pos = metin.find("a", 5); // 5. indeksten itibaren 'a' ara
cout << "'a' (5'ten sonra) pozisyon: " << pos << endl;
// Bulunamama durumu
pos = metin.find("Python");
if (pos == string::npos) {
cout << "'Python' bulunamadı" << endl;
}
return 0;
}rfind — Sondan Arama
rfind, string'in sonundan başa doğru arar ve son eşleşmenin pozisyonunu döndürür:
#include <iostream>
#include <string>
using namespace std;
int main() {
string yol = "/home/kullanici/belgeler/rapor.txt";
// Son '/' karakterini bul
size_t pos = yol.rfind('/');
cout << "Son '/' pozisyon: " << pos << endl;
// Dosya adını çıkar
string dosya_adi = yol.substr(pos + 1);
cout << "Dosya: " << dosya_adi << endl; // rapor.txt
// Uzantıyı bul
pos = yol.rfind('.');
string uzanti = yol.substr(pos);
cout << "Uzantı: " << uzanti << endl; // .txt
return 0;
}find_first_of ve find_last_of
Bu fonksiyonlar bir karakter kümesinden herhangi birinin ilk veya son geçtiği pozisyonu bulur:
#include <iostream>
#include <string>
using namespace std;
int main() {
string metin = "Hello, World! 123";
// İlk rakamı bul
size_t pos = metin.find_first_of("0123456789");
cout << "İlk rakam pozisyon: " << pos << endl; // 14
// İlk sesli harfi bul
pos = metin.find_first_of("aeiouAEIOU");
cout << "İlk sesli harf pozisyon: " << pos << endl; // 1 (e)
// Son noktalama işaretini bul
pos = metin.find_last_of(",!.");
cout << "Son noktalama pozisyon: " << pos << endl; // 12 (!)
// Karakter kümesinde OLMAYAN ilk karakter
pos = metin.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
cout << "Harf olmayan ilk karakter pozisyon: " << pos << endl; // 5 (,)
return 0;
}substr — String Parçalama
substr(pos, len) belirtilen pozisyondan itibaren len karakter uzunluğunda bir alt string döndürür:
#include <iostream>
#include <string>
using namespace std;
int main() {
string tarih = "2024-03-15";
// Yıl, ay, gün çıkarma
string yil = tarih.substr(0, 4); // 0'dan 4 karakter
string ay = tarih.substr(5, 2); // 5'ten 2 karakter
string gun = tarih.substr(8, 2); // 8'den 2 karakter
cout << "Yıl: " << yil << endl; // 2024
cout << "Ay: " << ay << endl; // 03
cout << "Gün: " << gun << endl; // 15
// Uzunluk belirtmezsen sonuna kadar alır
string kalan = tarih.substr(5); // 5'ten sonuna kadar
cout << "Kalan: " << kalan << endl; // 03-15
return 0;
}Pratik: String'i Ayraçla Parçalama (Split)
C++'ta hazır bir split fonksiyonu yok ama find ve substr ile yazabiliriz:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<string> parcala(const string& metin, char ayrac) {
vector<string> parcalar;
size_t baslangic = 0;
size_t pos;
while ((pos = metin.find(ayrac, baslangic)) != string::npos) {
parcalar.push_back(metin.substr(baslangic, pos - baslangic));
baslangic = pos + 1;
}
parcalar.push_back(metin.substr(baslangic)); // son parça
return parcalar;
}
int main() {
string csv = "Ali,85,Matematik,Geçti";
vector<string> alanlar = parcala(csv, ',');
for (const auto& alan : alanlar) {
cout << "[" << alan << "]" << endl;
}
return 0;
}Çıktı:
[Ali]
[85]
[Matematik]
[Geçti]replace — Değiştirme
replace(pos, len, yeni_string) belirtilen aralığı yeni string ile değiştirir:
#include <iostream>
#include <string>
using namespace std;
int main() {
string metin = "Merhaba dünya";
// 8. pozisyondan 5 karakter ("dünya") yerine "C++" koy
metin.replace(8, 5, "C++");
cout << metin << endl; // Merhaba C++
return 0;
}Tüm Geçişleri Değiştirme
replace tek seferde bir yeri değiştirir. Tüm geçişleri değiştirmek için döngü kullanmalısın:
#include <iostream>
#include <string>
using namespace std;
string hepsini_degistir(string metin, const string& eski, const string& yeni) {
size_t pos = 0;
while ((pos = metin.find(eski, pos)) != string::npos) {
metin.replace(pos, eski.length(), yeni);
pos += yeni.length(); // sonsuz döngüden kaçın
}
return metin;
}
int main() {
string metin = "elma ve elma ve elma";
string sonuc = hepsini_degistir(metin, "elma", "armut");
cout << sonuc << endl; // armut ve armut ve armut
return 0;
}💡 İpucu:
pos += yeni.length()satırı kritik. Bu olmadan, eğer yeni string eski string'i içeriyorsa ("a" → "aa" gibi) sonsuz döngüye girersin.
Tür Dönüşümleri: stoi, stod, to_string
String'den Sayıya
#include <iostream>
#include <string>
using namespace std;
int main() {
// String -> int
string s1 = "42";
int sayi = stoi(s1);
cout << sayi + 8 << endl; // 50
// String -> long
string s2 = "1234567890123";
long buyuk = stol(s2);
// String -> double
string s3 = "3.14159";
double pi = stod(s3);
cout << pi * 2 << endl; // 6.28318
// String -> float
string s4 = "2.71";
float e = stof(s4);
// Hata durumu
try {
int x = stoi("merhaba"); // dönüştürülemez!
} catch (const invalid_argument& e) {
cout << "Hata: " << e.what() << endl;
}
try {
int y = stoi("99999999999999999"); // çok büyük!
} catch (const out_of_range& e) {
cout << "Taşma: " << e.what() << endl;
}
return 0;
}Sayıdan String'e
#include <iostream>
#include <string>
using namespace std;
int main() {
int sayi = 42;
double pi = 3.14159;
string s1 = to_string(sayi); // "42"
string s2 = to_string(pi); // "3.141590" (6 ondalık)
cout << "Sonuç: " + s1 + " ve " + s2 << endl;
// Birleştirme kolaylığı
int yas = 25;
string mesaj = "Yaşım " + to_string(yas) + " ve öğreniyorum!";
cout << mesaj << endl;
return 0;
}⚠️ Dikkat:
stoivestoddönüşüm yapılamayınca exception fırlatır. Kullanıcı girdisiyle çalışıyorsantry-catchile sarmala.
string_view — Kopyalamadan Bakma (C++17)
std::string_view bir string'e sahip olmayan (non-owning), sadece "bakan" bir türdür. String'i kopyalamadan okuma yapabilirsin. Bu özellikle fonksiyon parametrelerinde büyük avantaj sağlar.
Bunu bir vitrin camı gibi düşün. Mağazadaki ürünlere bakabilirsin ama dokunup değiştiremezsin. Ve vitrin camını taşımak, mağazanın tamamını taşımaktan çok daha hafif.
#include <iostream>
#include <string>
#include <string_view>
using namespace std;
// Eski yol: const string& — string olmayan şeylerde geçici string oluşturur
void eski_yazdir(const string& s) {
cout << s << endl;
}
// Yeni yol: string_view — kopyalama yok, her türlü string'i kabul eder
void yeni_yazdir(string_view sv) {
cout << sv << endl;
}
int main() {
string str = "Merhaba dünya";
const char* cstr = "C-style string";
// string_view her ikisini de kabul eder, kopyalama yapmadan
yeni_yazdir(str);
yeni_yazdir(cstr);
yeni_yazdir("literal string");
// string_view üzerinde arama yapılabilir
string_view sv = "Merhaba dünya";
cout << "Boyut: " << sv.size() << endl;
cout << "İlk 7: " << sv.substr(0, 7) << endl;
// Ama değiştirilemez!
// sv[0] = 'X'; // HATA!
return 0;
}Ne Zaman Kullanılmalı?
// Fonksiyon sadece okuyacaksa → string_view
void analiz_et(string_view metin) {
cout << "Uzunluk: " << metin.size() << endl;
auto pos = metin.find("C++");
if (pos != string_view::npos) {
cout << "C++ bulundu!" << endl;
}
}
// Fonksiyon string'i saklayacaksa → const string& veya string
void kaydet(string metin) {
// metin'in sahipliğini alıyoruz
// veritabanına yaz, vektöre ekle vs.
}⚠️ Dikkat:
string_view'ın baktığı string yok olursa,string_viewdangling (havada kalan) referans olur. Asla yerel string'den oluşturulmuşstring_view'ı fonksiyondan döndürme!
// TEHLİKE!
string_view tehlike() {
string yerel = "geçici";
return yerel; // yerel yok olur, string_view havada kalır!
}Basit Regex Kullanımı
Regex (regular expression — düzenli ifade), metin içinde kalıp eşleme yapmak için kullanılan güçlü bir araçtır. C++11'den itibaren <regex> kütüphanesi ile kullanılabilir.
regex_match — Tam Eşleşme
Tüm string'in kalıpla eşleşip eşleşmediğini kontrol eder:
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main() {
// E-posta doğrulama (basit)
regex email_kalip(R"(\w+@\w+\.\w+)");
string email1 = "ali@example.com";
string email2 = "hatali-email";
cout << email1 << ": "
<< (regex_match(email1, email_kalip) ? "Geçerli" : "Geçersiz")
<< endl; // Geçerli
cout << email2 << ": "
<< (regex_match(email2, email_kalip) ? "Geçerli" : "Geçersiz")
<< endl; // Geçersiz
// Telefon numarası doğrulama
regex tel_kalip(R"(\d{3}-\d{3}-\d{4})");
cout << "555-123-4567: "
<< (regex_match(string("555-123-4567"), tel_kalip) ? "Geçerli" : "Geçersiz")
<< endl; // Geçerli
return 0;
}regex_search — Kısmi Eşleşme
String içinde kalıbı arar (tam eşleşme şartı yok):
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main() {
string metin = "Fiyat: 42.99 TL ve KDV: 8.50 TL";
regex sayi_kalip(R"(\d+\.\d+)");
smatch eslesme;
// İlk eşleşmeyi bul
if (regex_search(metin, eslesme, sayi_kalip)) {
cout << "Bulunan: " << eslesme[0] << endl; // 42.99
}
// Tüm eşleşmeleri bul
string kalan = metin;
while (regex_search(kalan, eslesme, sayi_kalip)) {
cout << "Eşleşme: " << eslesme[0] << endl;
kalan = eslesme.suffix().str();
}
return 0;
}regex_replace — Kalıpla Değiştirme
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main() {
string metin = "Tarih: 2024-03-15 ve 2024-12-25";
regex tarih_kalip(R"((\d{4})-(\d{2})-(\d{2}))");
// YYYY-MM-DD → DD/MM/YYYY formatına çevir
string sonuc = regex_replace(metin, tarih_kalip, "$3/$2/$1");
cout << sonuc << endl;
// Tarih: 15/03/2024 ve 25/12/2024
// Hassas bilgileri gizle
string log = "Kullanıcı IP: 192.168.1.100 bağlandı";
regex ip_kalip(R"(\d+\.\d+\.\d+\.\d+)");
cout << regex_replace(log, ip_kalip, "***.***.***. ***") << endl;
return 0;
}Temel Regex Söz Dizimi
| Kalıp | Anlam | Örnek |
|---|---|---|
\d | Rakam (0-9) | \d{3} → 3 rakam |
\w | Harf, rakam veya _ | \w+ → bir kelime |
\s | Boşluk karakteri | \s+ → bir veya daha fazla boşluk |
. | Herhangi bir karakter | a.b → "a_b", "a1b" vs. |
+ | Bir veya daha fazla | \d+ → en az bir rakam |
* | Sıfır veya daha fazla | \d* → rakam olabilir de olmayabilir de |
? | Sıfır veya bir | colou?r → "color" veya "colour" |
{n} | Tam n kez | \d{4} → tam 4 rakam |
[abc] | a, b veya c | [aeiou] → herhangi bir sesli harf |
^ | String başı | ^Merhaba → "Merhaba" ile başlayan |
$ | String sonu | \.txt$ → ".txt" ile biten |
💡 İpucu: C++'ta regex yazarken raw string literal (
R"(...)") kullan. Böylece ters slash (\) için çift yazmana gerek kalmaz.R"(\d+)"yazmak,"\\d+"yazmaktan çok daha okunaklı.
⚠️ Dikkat: C++'ın regex kütüphanesi bazı implementasyonlarda yavaş olabilir. Performans kritikse (milyonlarca eşleşme) harici kütüphaneler (RE2 gibi) düşünülebilir. Basit doğrulamalar için gayet yeterli.
Pratik Örnek: Basit Metin İşlemci
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
// Baş ve sondaki boşlukları temizle (trim)
string trim(const string& s) {
size_t baslangic = s.find_first_not_of(" \t\n\r");
if (baslangic == string::npos) return "";
size_t bitis = s.find_last_not_of(" \t\n\r");
return s.substr(baslangic, bitis - baslangic + 1);
}
// Küçük harfe çevir
string kucuk_harf(string s) {
transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
// Büyük harfe çevir
string buyuk_harf(string s) {
transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
// Kelime say
int kelime_say(const string& s) {
int sayac = 0;
bool kelimede = false;
for (char c : s) {
if (c != ' ' && !kelimede) {
sayac++;
kelimede = true;
} else if (c == ' ') {
kelimede = false;
}
}
return sayac;
}
int main() {
string metin = " Merhaba Dünya ve C++ ";
cout << "Orijinal: [" << metin << "]" << endl;
cout << "Trim: [" << trim(metin) << "]" << endl;
cout << "Küçük: " << kucuk_harf(trim(metin)) << endl;
cout << "Büyük: " << buyuk_harf(trim(metin)) << endl;
cout << "Kelime sayısı: " << kelime_say(trim(metin)) << endl;
return 0;
}Pratik Örnek: URL Parser
#include <iostream>
#include <string>
using namespace std;
int main() {
string url = "https://www.example.com:8080/api/users?id=42";
// Protokolü çıkar
size_t pos = url.find("://");
string protokol = url.substr(0, pos);
cout << "Protokol: " << protokol << endl; // https
// Host kısmını çıkar
size_t host_baslangic = pos + 3;
size_t host_bitis = url.find_first_of(":/?", host_baslangic);
string host = url.substr(host_baslangic, host_bitis - host_baslangic);
cout << "Host: " << host << endl; // www.example.com
// Port çıkar (varsa)
if (url[host_bitis] == ':') {
size_t port_bitis = url.find_first_of("/?", host_bitis + 1);
string port = url.substr(host_bitis + 1, port_bitis - host_bitis - 1);
cout << "Port: " << port << endl; // 8080
}
// Path çıkar
size_t path_baslangic = url.find('/', host_baslangic);
size_t query_baslangic = url.find('?');
string path = url.substr(path_baslangic,
query_baslangic - path_baslangic);
cout << "Path: " << path << endl; // /api/users
// Query string çıkar
if (query_baslangic != string::npos) {
string query = url.substr(query_baslangic + 1);
cout << "Query: " << query << endl; // id=42
}
return 0;
}Özet
find ve rfind ile string içinde baştan ve sondan arama yapılır; bulunamazsa
string::nposdöner. find_first_of bir karakter kümesinden ilk eşleşeni bulur.substr(pos, len) ile string'in belirli bir bölümü çıkarılır; C++'ta hazır
splityoktur amafind+substrile yazılabilir.replace belirtilen aralığı yeni string ile değiştirir; tüm geçişleri değiştirmek için döngü gerekir.
stoi/stod string'den sayıya, to_string sayıdan string'e dönüşüm yapar. Dönüşüm başarısız olursa exception fırlatılır.
string_view (C++17) string'e kopyalamadan bakan, hafif bir türdür. Fonksiyon parametrelerinde
const string&yerine kullanılabilir ama dangling referans riskine dikkat edilmelidir.Regex (
<regex>) ile kalıp eşleme yapılır:regex_matchtam eşleşme,regex_searchkısmi arama,regex_replacekalıpla değiştirme sağlar.
AI Asistan
Sorularını yanıtlamaya hazır