Dizgilerin [string] C++ Dizgileri ile Karşılaştırılması
Dizgiler C++'da olduğu gibi kütüphanelerle de halledilebilirler. Bu bölümde D'de dizgilerin neden dilin bir iç olanağı olduklarını ve bunun getirdiği yararlar gösteriliyor.
Bu sayfadaki bilgiler Digital Mars'ın sitesindeki aslından alınmıştır.
Birleştirme işleci
C++'da dizgiler mevcut işleçleri kullanmak zorundadırlar. Bu konuda en uygun işleçler +
ve +=
işleçleridir. Bunun zararlarından birisi, +
işlecinin doğal anlamının "toplama işlemi" olmasıdır. Kod okunurken işlemin "toplama" mı yoksa "birleştirme" mi olduğunu anlamak için nesnelerin türlerinin ne olduklarının araştırılması gerekebilir.
Ek olarak, double
dizileri için yüklenmiş olan bir +
işlecinin dizilerle vektör toplamı mı yapacağı, yoksa dizileri mi birleştireceği açık değildir.
Bu sorunlar D'de dizilerle çalışan yeni ~
"birleştirme" işleciyle giderilir. "Sonuna ekleme" işleci de ~=
işlecidir. Böylece artık double
dizilerinde de karışıklık kalmamış olur: ~
işleci dizilerin birleştirilecekleri, +
işleci de vektör toplamlarının alınacağı anlamına gelir. Eklenen bu yeni işleç dizilerde tutarlılık da sağlamış olur. (D'de dizgi karakter dizisidir; özel bir tür değildir.)
Söz dizimi uyumluluğu
C++'da işleç yüklemenin çalışabilmesi için işlenenlerden birisinin yüklenebilen bir tür olması gerekir. O yüzden, C++'daki string
her ifadeyle kullanılamaz:
const char abc[5] = "dünya"; string str = "merhaba" + abc;
Onun çalışabilmesi için dizgi kavramının dilin bir iç olanağı olması gerekir:
const char abc[5] = "dünya"; string str = "merhaba" ~ abc;
Söz dizimi tutarlılığı
Bir C++ dizgisinin uzunluğunu bulmanın üç yolu vardır:
const char abc[] = "dünya"; : sizeof(abc)/sizeof(abc[0])-1 : strlen(abc) string str; : str.length()
O tutarsızlık da şablonlarda sorunlar doğurur. D'nin çözümü tutarlıdır:
char[5] abc = "dünya"; : abc.length char[] str : str.length
Boş dizgiler
C++'da string
'lerin boş olup olmadıkları bir fonksiyonla belirlenir:
string str; if (str.empty()) // bos dizgi
D'de boş dizgilerin uzunlukları sıfırdır:
char[] str; if (!str.length) // boş dizgi
Dizgi uzunluğunu değiştirmek
C++'da resize()
fonksiyonu kullanılır:
string str; str.resize(yeni_uzunluk);
D'de dizgiler dizi oldukları için length
niteliklerini değiştirmek yeterlidir:
char[] str;
str.length = yeni_uzunluk;
Dizgi dilimlemek [slicing]
C++'da bir kurucu fonksiyon kullanılır:
string s1 = "merhaba dünya"; string s2(s1, 8, 5); // s2 "dünya" olur
D'de dizi dilimleme kullanılır:
string s1 = "merhaba dünya"; string s2 = s1[8 .. 13]; // s2 "dünya" olur
Dizgiler arası kopyalama
C++'ta dizgiler replace
fonksiyonu ile kopyalanırlar:
string s1 = "merhaba dünya"; string s2 = "güle güle "; s2.replace(10, 5, s1, 8, 5); // s2 "güle güle dünya" olur
D'de ise sol tarafta dilimleme kullanılır:
char[] s1 = "merhaba dünya".dup; char[] s2 = "güle güle ".dup; s2[10..15] = s1[8..13]; // s2 "güle güle dünya" olur
D'de dizgi sabitleri değiştirilemedikleri için önce .dup
ile kopyalarını almak gerekti.
C dizgilerine dönüştürmek
C kütüphaneleriyle etkileşmek için gereken dönüşümler C++'da c_str()
ile yapılır.
void foo(const char *); string s1; foo(s1.c_str());
D'de .ptr
niteliği ile:
void foo(char*); char[] s1; foo(s1.ptr);
Ama bunun çalışabilmesi için s1
'in sonunda bir sonlandırma karakteri bulunması gerekir. Sonlandırma karakterinin bulunduğunu garanti eden std.string.toStringz
fonksiyonu da kullanılabilir:
void foo(char*); char[] s1; foo(std.string.toStringz(s1));
Dizgi dışına taşmaya karşı denetim
C++'da []
işleci dizginin sınırları dışına çıkılıp çıkılmadığını denetlemez. Bu denetim D'de varsayılan davranış olarak bulunur, ve programın hataları giderildikten sonra bir derleyici ayarıyla kapatılabilir.
switch
deyimlerinde dizgiler
Bu C++'da olanaksızdır ve kütüphaneyi daha büyütmek dışında bir yolu da yoktur. D'de ise doğaldır:
switch (str) { case "merhaba": case "dünya": ... }
case
'lerle sabit dizgiler yanında char[10]
gibi sabit karakter dizileri veya char[]
gibi dinamik diziler de kullanılabilir.
Birden fazla dizgi karakterini bir karakterle değiştirmek
C++'ta replace()
fonksiyonu ile yapılır:
string str = "merhaba"; str.replace(1,2,2,'?'); // str "m??haba" olur
D'de dizi dilimleme kullanılır:
char[5] str = "merhaba"; str[1..3] = '?'; // str "m??haba" olur
Değerler ve referanslar
C++ dizgileri, STLport'ta gerçekleştirildikleri gibi, değer türleridirler [value type] ve '\0'
karakteri ile sonlandırılmışlardır. [Aslında sonlandırma karakteri STLport'un bir seçimidir; öyle olmak zorunda değildir.] Çöp toplamanın eksikliği nedeniyle bunun bazı bedelleri vardır. Öncelikle, oluşturulan her string
'in karakterlerin kendisine ait bir kopyasını tutması gerekir. Karakterlerin hangi string
tarafından sahiplenildiğinin hesabının tutulması gerekir, çünkü sahip ortadan kalktığında bütün referanslar da geçersiz hale gelirler. Bu referansların geçersizliği [dangling reference] sorunundan kurtulmak amacıyla string
'lerin değer türleri olarak kullanılmaları gerektiği için; çok sayıdaki bellek ayırma, karakter kopyalama, ve bellek geri verme işlemleri nedeniyle bu tür yavaş hale gelir. Dahası, sonlandırma karakteri dizgilerin diğer dizgileri referans olarak göstermelerine engeldir. Data segment'teki, program yığıtındaki, vs. karakterlere referans kullanılamaz.
D'de dizgiler referans türleridirler ve bellek çöp toplamalıdır. Bu yüzden yalnızca referansların kopyalanmaları gerekir, karakterlerin değil... Karakterler nerede olurlarsa olsunlar D dizgileri onları referans olarak gösterebilir: static data segment, program yığıtı, başka dizgilerin bir bölümü, nesneler, dosya ara bellekleri, vs. Dizgideki karakterlerin sahibinin hesabını tutmaya gerek yoktur.
Bu noktadaki doğal soru; birden fazla dizginin ortaklaşa karakter kullandıkları durumda dizgilerden birisinde değişiklik yapıldığında ne olacağıdır. Karakterleri gösteren bütün dizgiler değişmiş olurlar. Bunun istenmediği durumda yazınca kopyalama [copy-on-write] yöntemi kullanılabilir. Bu yöntemde, dizgide değişiklik yapılmadan önce karakterlerin o dizgiye ait bir kopyası alınır ve değişiklik o kopyada yapılır.
D dizgilerinin referans türleri ve çöp toplamalı olmaları, dizgilerle yoğun işlemler yapan lzw sıkıştırma algoritması gibi işlemlerde hem hızda hem de bellek kullanımında kazanç sağlar.
Hız karşılaştırması
Bir dosyadaki sözcüklerin histogramını alan küçük bir programa bakalım. Bu program D'de şöyle yazılabilir:
import std.file; import std.stdio; int main (char[][] argümanlar) { int kelime_toplamı; int satır_toplamı; int karakter_toplamı; int[char[]] sözlük; writefln(" satır kelime bayt dosya"); for (int i = 1; i < argümanlar.length; ++i) { char[] giriş; int kelime_top, satır_top, karakter_top; int kelime_içindeyiz; int kelime_başı; giriş = cast(char[])std.file.read(argümanlar[i]); for (int j = 0; j < giriş.length; j++) { char karakter; karakter = giriş[j]; if (karakter == '\n') ++satır_top; if (karakter >= '0' && karakter <= '9') { } else if (karakter >= 'a' && karakter <= 'z' || karakter >= 'A' && karakter <= 'Z') { if (!kelime_içindeyiz) { kelime_başı = j; kelime_içindeyiz = 1; ++kelime_top; } } else if (kelime_içindeyiz) { char[] kelime = giriş[kelime_başı .. j]; sözlük[kelime]++; kelime_içindeyiz = 0; } ++karakter_top; } if (kelime_içindeyiz) { char[] kelime = giriş[kelime_başı .. giriş.length]; sözlük[kelime]++; } writefln("%8s%8s%8s %s", satır_top, kelime_top, karakter_top, argümanlar[i]); satır_toplamı += satır_top; kelime_toplamı += kelime_top; karakter_toplamı += karakter_top; } if (argümanlar.length > 2) { writefln("--------------------------------------\n" "%8s%8s%8s toplam", satır_toplamı, kelime_toplamı, karakter_toplamı); } writefln("--------------------------------------"); foreach (const char[] kelime; sözlük.keys.sort) { writefln("%3d %s", sözlük[kelime], kelime); } return 0; }
(Ara bellekli giriş/çıkış kullanan başka bir gerçekleştirmesi de var.)
İki programcı bu programın C++ gerçekleştirmelerini de yazdılar: wccpp1 ve wccpp2. Programa giriş olarak "Alice Harikalar Diyarında"nın metnini oluşturan alice30.txt verildi. D derleyicisi olarak dmd ve C++ derleyicisi olarak da dmc kullanıldı. Bu iki derleyici aynı eniyileştiriciyi ve aynı kod üreticiyi kullandıkları için, bu karşılaştırma iki dildeki dizgilerin daha gerçekçi bir karşılaştırmasını verir. Programlar bir Windows XP sisteminde denendiler ve dmc için şablon gerçekleştirmesi olarak STLport kullanıldı.
Program | Derleme Komutu | Derleme Süresi | Çalıştırma Komutu | Çalışma Süresi |
---|---|---|---|---|
D wc | dmd wc -O -release | 0.0719 | wc alice30.txt >log | 0.0326 |
C++ wccpp1 | dmc wccpp1 -o -I\dm\stlport\stlport | 2.1917 | wccpp1 alice30.txt >log | 0.0944 |
C++ wccpp2 | dmc wccpp2 -o -I\dm\stlport\stlport | 2.0463 | wccpp2 alice30.txt >log | 0.1012 |
Aşağıdaki sonuçlar ise Linux üzerinde ve yine aynı eniyileştiriciyi ve kod üreticiyi paylaşan iki derleyici ile alındılar: D için gdc ve C++ için g++. Bu seferki sistem gcc 3.4.2'li bir RedHat Linux 8.0 çalıştıran 800MHz'lik bir Pentium III'tü. Ek bilgi açısından Digital Mars'ın derleyicisi dmd'nin sonuçları da eklenmiştir.
Program | Derleme Komutu | Derleme Süresi | Çalıştırma Komutu | Çalışma Süresi |
---|---|---|---|---|
D wc | gdc -O2 -frelease -o wc wc.d | 0.326 | wc alice30.txt > /dev/null | 0.041 |
D wc | dmd wc -O -release | 0.235 | wc alice30.txt > /dev/null | 0.041 |
C++ wccpp1 | g++ -O2 -o wccpp1 wccpp1.cc | 2.874 | wccpp1 alice30.txt > /dev/null | 0.086 |
C++ wccpp2 | g++ -O2 -o wccpp2 wccpp2.cc | 2.886 | wccpp2 alice30.txt > /dev/null | 0.095 |
Aşağıdaki sonuçlar da gcc 3.4.2'li bir MacOS X 10.3.5 çalıştıran bir PowerMac G5 2x2.0GHz ile alınmışlardır. (Burada ölçümlerin doğruluğu biraz daha düşüktü.)
Program | Derleme Komutu | Derleme Süresi | Çalışma Komutu | Çalışma Süresi |
---|---|---|---|---|
D wc | gdc -O2 -frelease -o wc wc.d | 0.28 | wc alice30.txt > /dev/null | 0.03 |
C++ wccpp1 | g++ -O2 -o wccpp1 wccpp1.cc | 1.90 | wccpp1 alice30.txt > /dev/null | 0.07 |
C++ wccpp2 | g++ -O2 -o wccpp2 wccpp2.cc | 1.88 | wccpp2 alice30.txt > /dev/null | 0.08 |
Allan Odgaard'ın wccpp2 programı:
#include <algorithm> #include <cstdio> #include <fstream> #include <iterator> #include <map> #include <vector> bool isWordStartChar (char c) { return isalpha(c); } bool isWordEndChar (char c) { return !isalnum(c); } int main (int argc, char const* argv[]) { using namespace std; printf("Lines Words Bytes File:\n"); map<string, int> dict; int tLines = 0, tWords = 0, tBytes = 0; for(int i = 1; i < argc; i++) { ifstream file(argv[i]); istreambuf_iterator<char> from(file.rdbuf()), to; vector<char> v(from, to); vector<char>::iterator first = v.begin(), last = v.end(), bow, eow; int numLines = count(first, last, '\n'); int numWords = 0; int numBytes = last - first; for(eow = first; eow != last; ) { bow = find_if(eow, last, isWordStartChar); eow = find_if(bow, last, isWordEndChar); if(bow != eow) ++dict[string(bow, eow)], ++numWords; } printf("%5d %5d %5d %s\n", numLines, numWords, numBytes, argv[i]); tLines += numLines; tWords += numWords; tBytes += numBytes; } if(argc > 2) printf("-----------------------\n%5d %5d %5d\n", tLines, tWords, tBytes); printf("-----------------------\n\n"); for(map<string, int>::const_iterator it = dict.begin(); it != dict.end(); ++it) printf("%5d %s\n", it->second, it->first.c_str()); return 0; }