İleri SAP2000 Yapı Sistemlerinin Analizi subat2007
Endüstri Meslek Liseleri ve diğer okullar için PLC Programlama
Descripción completa
Full description
Deskripsi lengkap
Descripción completa
Full description
C, C++, C# Programming ConceptsFull description
k.608 for wind quintetFull description
Description complète
Full description
Full description
đồ ánFull description
cDeskripsi lengkap
1Descrição completa
some important question for c++ language
Full description
Descrição completa
20-03-2002
Sınıf Tasarımında Dosya Organizasyonu Genellikle bir sınıfın fiziksel organizasyonu iki dosya halinde yapılır. Sınıfın ismi X olmak üzere X.h ve X.cpp dosyaları. X.h dosyasın dosyasının ın içerisi içerisine ne nesne nesne yaratm yaratmaya ayann bildir bildirim im işleml işlemleri eri yerleştirilir. Yani X.h dosyasının içeriği şunlar olabilir: -
Sınıf bildirimi Sembolik sabit tanımlamaları typedef ve typedef ve enum bildirimleri Konuyla ilgili çeşitli global fonksiyonların prototipleri inline fonksiyon tanımlamaları Global const değişken const değişken tanımlamaları
dosyasıı içerisi içerisine ne sınıfı sınıfınn üye fonksi fonksiyon yonlar larını ınınn tanımla tanımlamal maları, arı, sınıf sınıf ile ilgili ilgili global global X.cpp dosyas fonksiyonların tanımlamaları yerleştirilir. *.h ve *.cpp dosyalarının birbirlerinden ayrılması kütüphane oluşturma işlemi için zorunludur. *.h dosyası hem *.cpp dosyasından hem de *.cpp dosyası kütüphaneye yerleştirildikten sonra bu sınıfın kullanılması için dışarıdan include edilir. Dosyaların başına bir açıklama bloğu yerleştirilmelidir. Bu açıklama bloğunda dosyanın ismi, kodlayan kişinin ismi, son güncelleme tarihi, dosyanın içindekilerinin ne olduğu ve copyright bilgileri bulunabilir. Örnek bir açıklama bloğu şöyle olabilir: /*---------------------------------------------------------File Name : X.h/X.cpp Author : Kaan Aslan Last Last Updat Update e : 20/03 20/03/02 /02 This is a sample header/implementation file. Copyleft C and System Programmers Assosiation (1993) All rights free ------------------------------------------------------------*/
*.h dosyalarının büyük projelerde isteyerek ya da istemeyerek birden fazla include edilmesinde
problem oluşturmaması için include koruması (include guard) uygulanması gerekir. Include koruması sayesinde önişlemci dosyayı bir kez gördüğünde içeriğini derleme modülüne verir ancak ikinci gördüğünde vermez. Tipik bir include koruması şöyle oluşturulur: #ifndef _X_H_ #define _X_H_ #endif
Buradaki sembolik sabit ismi dosya isminden hareketle oluşturulmuş herhangi bir isimdir. 1
Bir başlık başlık dosyas dosyasını ınınn birden birden fazla fazla includ includee edilme edilmesi si genelli genellikle kle zorunlu zorunluluk luk nedeniy nedeniyle le oluşur. oluşur. Örneğin programcı bir sınıf için A.h için A.h başka başka bir sınıf için ise B.h ise B.h dosyalarının include etmiş olsun. Bu dosyaların dosyaların kendi içlerinde içlerinde general.h general.h isimli temel bir dosya include edilmiş olsun. Böyle bir durumda general.h dosyası iki kez include edilmiş gibi gözükür. general.h dosyası içerisinde includ includee koruması koruması uygula uygulandı ndığınd ğından an problem problem oluşmay oluşmayaca acaktı ktır. r. Ancak Ancak önişle önişlemci mci işleml işlemleri erini ni hızlandırmak için ortak başlık dosyalarının ek bir korumayla include edilmesi özellikle çok büyük projelerde önerilmektedir. Ek include koruması şöyle yapılabilir: #ifndef _GENERAL_H_ #include “general.h” #endif
Burada A.h içerisinde önişlemci ek koruma ya da include korumasına takılmaz, ancak B.h ancak B.h içerisinde ek korumaya takılır, dolayısıyla daha general.h dosyasını açmadan dosya önişlemci dışı bırak bırakılı ılır. r. Dosyan Dosyanın ın açıldık açıldıktan tan sonra sonra önişle önişlemci mci dışı dışı bırakı bırakılma lmasıy sıyla la açılma açılmadan dan önişle önişlemci mci dışı dışı bırakılması arasında büyük projelerde bir hız farkı oluşabilmektedir. Bazen özellikle çok küçük sınıflar için ayrı ayrı *.h ve *.cpp dosyaları oluşturmak yerine bunlar guruplanıp bir kaçı için bir *.h ve *.cpp dosyası oluşturulabilir. Pek çok geliştirme ortamı (örneğin VisualC) bir sınıf ismi verildiğinde bu düzenleme işlemini otomatik olarak yapmaktadır. Örneğin VC6.0’da Insert VC6.0’da Insert / NewClass seçildiğinde programcıdan sınıf ismi istenir ve otomatik şu işlemler yapılır: -
Sınıf bildirimini başlangıç ve bitiş fonksiyonu olacak biçimde *.h dosyası içerisinde yapar *.h dosyasına bir include koruması yerleştirir *.cpp dosyasını oluşturur, *.h dosyasını buradan include eder *.cpp dosyasında başlangıç ve bitiş fonksiyonlarını içi boş olarak yazar *.cpp dosyasını proje dosyasına ekler
Geliştirme ortamı gereksiz kodlama yükünü belli ölçüde programcının üzerinden almaktadır.
Projenin Disk Üzerindeki Organizasyonu Proje Proje gelişt geliştiri irirken rken disk disk üzerind üzerindee proje proje için için bir dizin dizin oluştu oluşturul rulmal malıı ve düzenl düzenlii bir çalışm çalışmaa sağlanmalıdır. Gurup halinde proje geliştirirken düzenin sağlanması için çeşitli düzen sağlayıcı programlar kullanılabilmektedir. Projenin dizin yapısı duruma uygun her hangi bir biçimde seçilebilir. Örneğin, projenin kaynak kodları SRC alt dizininde, dokümantasyon bilgileri DOC alt dizini dizininde, nde, object object modülle modüllerr ve çalışabi çalışabilen len programl programlar ar BIN alt dizini dizininde nde,, projeni projeninn kullan kullandığ dığıı kütüphaneler LIB alt dizininde, deneme kodları SAMPLE alt dizininde bulunabilir. Projenin başlık dosyaları bir araya getirilip tek bir başlık dosyası biçimine dönüştürülebilir. Yani, programcı tek bir başlık dosyası include eder fakat o başlık dosyasının içerisinde pek çok başlık dosyası include edilmiştir.
2
Bir başlık başlık dosyas dosyasını ınınn birden birden fazla fazla includ includee edilme edilmesi si genelli genellikle kle zorunlu zorunluluk luk nedeniy nedeniyle le oluşur. oluşur. Örneğin programcı bir sınıf için A.h için A.h başka başka bir sınıf için ise B.h ise B.h dosyalarının include etmiş olsun. Bu dosyaların dosyaların kendi içlerinde içlerinde general.h general.h isimli temel bir dosya include edilmiş olsun. Böyle bir durumda general.h dosyası iki kez include edilmiş gibi gözükür. general.h dosyası içerisinde includ includee koruması koruması uygula uygulandı ndığınd ğından an problem problem oluşmay oluşmayaca acaktı ktır. r. Ancak Ancak önişle önişlemci mci işleml işlemleri erini ni hızlandırmak için ortak başlık dosyalarının ek bir korumayla include edilmesi özellikle çok büyük projelerde önerilmektedir. Ek include koruması şöyle yapılabilir: #ifndef _GENERAL_H_ #include “general.h” #endif
Burada A.h içerisinde önişlemci ek koruma ya da include korumasına takılmaz, ancak B.h ancak B.h içerisinde ek korumaya takılır, dolayısıyla daha general.h dosyasını açmadan dosya önişlemci dışı bırak bırakılı ılır. r. Dosyan Dosyanın ın açıldık açıldıktan tan sonra sonra önişle önişlemci mci dışı dışı bırakı bırakılma lmasıy sıyla la açılma açılmadan dan önişle önişlemci mci dışı dışı bırakılması arasında büyük projelerde bir hız farkı oluşabilmektedir. Bazen özellikle çok küçük sınıflar için ayrı ayrı *.h ve *.cpp dosyaları oluşturmak yerine bunlar guruplanıp bir kaçı için bir *.h ve *.cpp dosyası oluşturulabilir. Pek çok geliştirme ortamı (örneğin VisualC) bir sınıf ismi verildiğinde bu düzenleme işlemini otomatik olarak yapmaktadır. Örneğin VC6.0’da Insert VC6.0’da Insert / NewClass seçildiğinde programcıdan sınıf ismi istenir ve otomatik şu işlemler yapılır: -
Sınıf bildirimini başlangıç ve bitiş fonksiyonu olacak biçimde *.h dosyası içerisinde yapar *.h dosyasına bir include koruması yerleştirir *.cpp dosyasını oluşturur, *.h dosyasını buradan include eder *.cpp dosyasında başlangıç ve bitiş fonksiyonlarını içi boş olarak yazar *.cpp dosyasını proje dosyasına ekler
Geliştirme ortamı gereksiz kodlama yükünü belli ölçüde programcının üzerinden almaktadır.
Projenin Disk Üzerindeki Organizasyonu Proje Proje gelişt geliştiri irirken rken disk disk üzerind üzerindee proje proje için için bir dizin dizin oluştu oluşturul rulmal malıı ve düzenl düzenlii bir çalışm çalışmaa sağlanmalıdır. Gurup halinde proje geliştirirken düzenin sağlanması için çeşitli düzen sağlayıcı programlar kullanılabilmektedir. Projenin dizin yapısı duruma uygun her hangi bir biçimde seçilebilir. Örneğin, projenin kaynak kodları SRC alt dizininde, dokümantasyon bilgileri DOC alt dizini dizininde, nde, object object modülle modüllerr ve çalışabi çalışabilen len programl programlar ar BIN alt dizini dizininde nde,, projeni projeninn kullan kullandığ dığıı kütüphaneler LIB alt dizininde, deneme kodları SAMPLE alt dizininde bulunabilir. Projenin başlık dosyaları bir araya getirilip tek bir başlık dosyası biçimine dönüştürülebilir. Yani, programcı tek bir başlık dosyası include eder fakat o başlık dosyasının içerisinde pek çok başlık dosyası include edilmiştir.
2
Değişkenlerin Değişkenlerin İsimlendirmesi İsimlendirmesi Değişken isimlendirilmesi konusunda programcı tutarlı bir yöntem izlemelidir. Örneğin Windows ortamında macar notasyonu denilen isimlendirme tekniği yoğun olarak kullanılmaktadır. Macar notasyonu C++’a özgü bir isimlendirme tekniği değildir. C ve yapısal programlama dilleri için düşünülmüştür. İster macar notasyonu kullanılsın ister başka bir notasyon kullanılsın C++ için şu konularda tutarlılık sağlanmalıdır: Sınıf isimleri her sözcüğün ilk harfi büyük olacak şekilde ya da tutarlı başka bir yöntemle belirl belirlenme enmelid lidir. ir. C++’da C++’da yapıla yapılarda rda bir sınıf sınıf olduğu olduğu için için yapıla yapılarla rla sınıfl sınıflar ar aynı biçimd biçimdee isimlendirilebilir. Yapıların tamamen C’deki gibi kullanıldığı durumlarda yapı isimleri her harfi büyük olacak biçimde belirlenebilir. Bazı kütüphanelerde sınıf isimlerinin başına özel bir bir kara karakt kter er de konu konula labi bilm lmek ekte tedi dir. r. Örne Örneği ğinn MFC’ MFC’de de sını sınıff isim isimle leri rini ninn başı başına na ‘C’ getirilmektedir (CWnd, CBrush, CObject gibi ...). - Sınıfın veri elemanları üye fonksiyonlar içerisinde kolay teşhis edilsin diye ayrı bir biçimde isimlendirilmelidir. Genellikle veri elemanları, başına ya da sonuna ‘ _’ konularak ya da ‘d_’, ‘m_’ gibi önekler ile başlatılır. Global değişken değişkenler ler de özel özel bir biçimd biçimdee isimle isimlendi ndiril rilmel melidi idir. r. Pek çok programc programcıı global global - Global değişkenleri ‘g_’ öneki ile başlatarak isimlendirmektedir. fonksiyon yonlar lar içerisi içerisinde nde global global fonksi fonksiyon yonlar lar çağırı çağırılır lırken ken vurgulam vurgulamaa için için unary unary :: - Üye fonksi operatörü kullanılmalıdır. Örneğin: -
::SetData(100);
Sınıfların İçsel Yerleşim Organizasyonu Sınıfın bölümleri yukarıdan aşağıya doğru public doğru public,, protected , private sırasıyla yazılmalıdır. Çünkü en fazla kişinin ilgileneceği bölümün sınıfın hemen başında olması daha açıklayıcı bir durumdur. Bir sınıf tür bildirimlerine, üye fonksiyon bildirimlerine ve veri eleman bildirimlerine sahip olabilir ve her bildirim grubunun üç bölümü olabilir. Düzenli bir çalışma için önce veri eleman bildiriml bildirimleri eri sonra üye fonksiyon bildirimle bildirimleri ri sonra da veri eleman bildirimleri bildirimleri her bir gurup public, protected, private sırasıyla yazılmalıdır. Örneğin: class Sample { public: typedef int SYSID; public: Sample(); ... ... private: void SetItem(SYSID id); ... ... public: int m_a; protected:
3
int m_b; private: int m_c, m_d; };
Sınıfın kullanıcı için dokümantasyonu yapılırken public ve protected bölümleri tam olarak açıklanmalıdır. public bölüm herkes için protected bölüm sınıftan türetme yapacak kişiler için ilgi çekicidir. Ancak private bölüme kimse tarafından erişilemez bu yüzden dokümantasyonunun yapılmasına gerek yoktur. Zaten tasarımda private bölüm daha sonra istenildiği gibi değiştirilebilecek bölümü temsil eder. Üye fonksiyonların *.cpp dosyasında bildirimdeki sırada tanımlanması iyi bir tekniktir.
Sınıfın Üye Fonksiyonlarının Guruplandırılması Sınıfın üye fonksiyonları da çeşitli biçimlerde guruplandırılarak alt alta yazılabilir. Bir sınıf genellikle aşağıdaki guruplara ilişkin üye fonksiyon içerir: 1) Başlangıç ve bitiş fonksiyonları: Bu fonksiyonlar sınıfın çeşitli parametre yapısındaki başlangıç fonksiyonlarıdır. Sınıf bitiş fonksiyonu içerebilir ya da içermeyebilir. 2) Sınıfın veri elemanlarının değerlerini alan fonksiyonlar (get fonksiyonları): Bu fonksiyonlar sınıfın korunmuş private ya da protected bölümündeki veri elemanlarının değerlerini alan fonksiyonlardır. Bu fonksiyonlar genellikle çok küçük olur bu nedenle genellikle inline olarak yazılırlar. Örneğin, Date isimli sınıfın gün, ay ve yıl değerlerini tutan üç private veri elemanı olabilir ve bu değerleri alan GetDay() , GetMonth() ve GetYear() get fonksiyonları olabilir. 3) Sınıfın veri elemanlarına değer yerleştiren fonksiyonlar (set fonksiyonları): Bu tür fonksiyonlar sınıfın private ve protected veri elemanlarına değer atarlar. Bir veri elemanının değerini hem alan hem de yerleştiren üye fonksiyon tanımlamak mümkündür. Yapılacak şey geri dönüş değerini referans almak ve return ifadesiyle o veri elemanına geri dönmektir. class Sample { public: int &GetSetA(); private: int m_a; }; int &Sample::GetSetA() { return m_a; }
Sample x; int y; x.GetSetA() = 100; y = x.GetSetA() + 100;
Ancak böyle bir tasarımdan özel durumlar yoksa kaçınmak gerekir. 4
4) Sınıfın durumu hakkında istatistiksel bilgi veren fonksiyonlar : Bu tür fonksiyonlar sınıfın ilgili olduğu konu hakkında istatistiksel bilgi verirler. Örneğin, Circle sınıfındaki GetArea() fonksiyonu gibi ya da bağlı listedeki eleman sayısını veren GetCount() fonksiyonu gibi. 5) Giriş çıkış fonksiyonları: Ekran, klavye ve dosya işlemlerini yapan fonksiyonlardır. 6) Operatör fonksiyonları: Bunlar okunabilirliği kolaylaştırmak amacıyla sınıfa yerleştirilmiş olan operatörle çağrışımsal bir ilgisi olan işlemleri yapan fonksiyonlardır. 7) Önemli işlevleri olan ana fonksiyonlar : Bu fonksiyonlar sınıf ile ilgili önemli işlemleri yapan genel fonksiyonlardır. 8) Sanal fonksiyonlar : Çok biçimli (polimorphic) bir sınıf yapısı söz konusuysa sınıfın bir gurup sanal fonksiyonu olmalıdır.
Sınıfların Türetilebilirlik Durumu Türetilebilirlik durumuna göre sınıfları üç guruba ayırabiliriz: 1- Somut sınıflar : Konu ile ilgili işlemlerin hepsini yapma iddiasında olan, türetmenin gerekli olmadığı sınıflardır. 2- Soyut sınıflar : Kendisinden türetme yapılmadıkça bir kullanım anlamı olmayan sınıflardır. C++’da soyut sınıf kavramı saf sanal fonksiyonlarla syntax’a dahil edilmiştir. Ancak saf sanal fonksiyona sahip olmasa da bu özellikteki sınıflara da soyut sınıf denir. 3- Ara sınıflar : Türetmenin ara kademelerinde olan sınıflardır. Bir türetme şeması söz konusuysa herzaman değil ama genellikle soyut sınıflar en tepede, somut sınıflar en aşağıda, ara sınıflar ise ara kademelerde bulunur.
Sınıfların İşlevlerine Göre Sınıflandırılması 1- Herhangi bir konuya özgü işlem yapan genel sınıflar : Bu tür sınıflar dış dünyadaki nesnelere karşılık gelen genel sınıflardır. 2- Yararlı sınıflar (utility class): Bunlar her türlü özel konulara ilişkin olmayan, her türlü projede kullanabileceğimiz genel sınıflardır. Örneğin, string işlemlerini yapan sınıflar, dosya işlemlerini yapan sınıflar, tarih işlemlerini yapan sınıflar gibi. 3- Nesne tutan sınıflar (container class / collection class): Dizi, bağlı liste, kuyruk, ikili ağaç, hash tabloları gibi veri yapılarını kurup çalıştıran, amacı bir algoritmaya göre birden çok nesne tutmak olan sınıflardır. 1996 yılında STL denilen template tabanlı kütüphane C++ programlama diline dahil edilmiştir ve C++’ın standart kütüphanesi yapılmıştır. STL içerisinde pek çok yararlı sınıf ve nesne tutan sınıf standart olarak vardır.
5
4- Arabirim sınıflar (interface class): Sisteme, donanıma ya da belli bir duruma özgü işlemler için kullanılan sınıflardır. Bu tür özel durum üzerinde işlem yapmak için ayrı sınıflar tasarlamak iyi bir yaklaşımdır. Böylece sisteme ya da donanıma özgü durumlar arabirim sınıflar tarafından ele alınabilir. Bu durumlar değiştiğinde diğer sınıflar çalışmadan etkilenmez, değişiklik sadece arabirim sınıflar üzerinde yapılır.
Nesne Yönelimli Programlamanın Temel İlkeleri Nesne yönelimli programlama tekniği sınıf kullanarak programlama yapmak demektir. Nesne yönelimli programlama tekniği üzerine pek çok kavramdan bahsedildiyse de bu programlama tekniğinin temel olarak üç ilkesi vardır. Bu üç ilke dışındaki kavramlar bu ilkelerden türetilmiş kavramlardır. 1- Sınıfsal temsil (encapsulation): Bu kavram dış dünyadaki nesnelerin ya da kavramların ayrıntılarını gözardı ederek bir sınıf ile temsil edilmesi anlamına gelir. Bir nesneyi ya da kavramı sınıf ile temsil etmek yeterli değildir, onun karmaşık özelliklerini gizlemek gerekir. Ayrıntıların gözardı edilmesine aynı zamanda soyutlama (abstraction) da denilmektedir. C++’da ayrıntıları gözden uzak tutmak için sınıfın private bölümünü kullanırız. Tabii bazı ayrıntılar vardır sıradan kullanıcıların gözünden uzak tutulur ama bir geliştirici için gerekli olabilir. Bu tür özellikler için sınıfın protected bölümü kullanılır. Sınıfsal temsil ile karmaşık nesnelerin ya da kavramların özeti dışarıya yansıtılmaktadır. Eskiden yazılım projeleri bugüne göre çok büyük değildi, böyle bir soyutlama olmadan da projeler tasarlanıp geliştirilebiliyordu. Ancak son yıllarda projelerdeki kod büyümesi aşırı boyutlara ulaşmıştır. Büyük projelerin modellemesi çok karmaşıklaşmıştır. Nesne yönelimli programlama bu karmaşıklığın üstesinden gelmek için tasarlanmıştır. 2- Türetme (inheritance): Türetme daha önceden başkaları tarafından yazılmış olan bir sınıfın işlevlerinin genişletilmesi anlamındadır. Türetme sayesinde daha önce yapılan çalışmalara ekleme yapılabilmektedir. C++’da bir sınıftan yeni bir sınıf türetilir, eklemeler türemiş sınıf üzerinde yapılır. 3- Çok biçimlilik (polymorphism): Sınıfsal temsil ve türetme temel ilkelerdir, ancak pek çok tasarımcıya göre bir dilin nesne yönelimli olması için çok biçimlilik özelliğine de sahip olması gerekir. Çok biçimlilik özelliğine sahip olmayan dillere nesne tabanlı (object based) diller denir (VB.NET versiyonuna kadar nesne tabanlı bir dil görünümündedir. .NET ile birlikte çok biçimlilik özelliği de eklenmiştir ve nesne yönelimli olabilmiştir). Çok biçimliliğin üç farklı tanımı yapılabilir. Her tanım çok biçimliliğin bir yönünü a çıklamaktadır. a- Birinci tanım: Çok biçimlilik taban sınıfın bir fonksiyonunun türemiş sınıfların her biri tarafından o sınıflara özgü biçimde işlem yapacak şekilde yazılmasıdır. Örneğin, Shape genel bir sınıf olabilir, bu sınıfın GetArea() isimli sanal bir fonksiyonu olabilir, bu fonksiyon bir geometrik şeklin alanını veren genel bir fonksiyondur. Rectangle sınıfı bu fonksiyonu dikdörtgenin alanını verecek biçimde, Circle sınıfının ise dairenin alanını verecek biçimde tanımlar. b- İkinci tanım: Çok biçimlilik önceden yazılarak derlenmiş olan kodların sonradan yazılan kodları çağırması özelliğidir. Örneğin, bir fonksiyon bir sınıf Shape türünden gösterici parametresine sahip olsun ve bu göstericiyle GetArea() isimli sanal fonksiyonunu
6
çağırarak işlem yapıyor olsun. Bu işlem yapılırken henüz Triangle sınıfı daha yazılmamış olabilir. Ancak kod yazılıp derlendikten sonra biz bu sınıfı oluşturup, bu sınıf türünden nesnenin adresini fonksiyona geçersek, fonksiyon Triangle sınıfının GetArea() fonksiyonunu çağıracaktır. c- Üçüncü tanım: Çok biçimlilik türden bağımsız sınıf işlemlerinin yapılmasına olanak sağlayan bir yöntemdir. Örneğin, bir programcı bir oyun programı yazıyor olsun, mesela tuğla kırma oyunu. Bu oyunda bir şekil hareketli bir cisme çarparak yansımaktadır. Yansıma, şeklin özelliğine bağlı olarak değişebilir. Programcı oyunu yazarken yansıyan şekli genel bir şekil olarak düşünür. Yani türü ne olursa olsun her türlü şeklin kendine özgü bir hareket biçimi, hızı, büyüklüğü ve yansıma biçimi vardır. Kodun şekille ilgili kısmı türden bağımsız yazılır, böylece kendisi ya da başka bir programcı Shape sınıfından bir sınıf türeterek ilgili sanal fonksiyonları yazarak kendi şeklini eskisi yerine etkin hale getirebilir. Ya da örneğin, programcı bir takım nesnelerin bir veri yapısında olduğu fikriyle programını yazabilir. Programını yazarken hangi veri yapısının kullanıldığını bilmek zorunda değildir. Collection isimli genel bir veri yapısını temsil eden sınıf tasarlanır, bu sınıfın her türden veri yapısı üzerinde geçerli olabilecek işlemlere ilişkin sanal fonksiyonları vardır. Böylece programcının kodu özel bir veri yapısına göre yazılmamış hale gelir, her veri yapısı için çalışabilir duruma getirilmiş olur. Buradaki türden bağımsızlık template işlemleriyle karıştırılmamalıdır. Template işlemlerinde derleme aşaması için bir türden bağımsızlık söz konusudur. Halbuki çok biçimlilikte derlenmiş olan kodun türden bağımsızlığı söz konusudur. Template’ler derlendikten sonra türü belirli hale gelen kodlardır.
Nesne Yönelimli Analiz ve Modelleme Büyük projeler çeşitli aşamalardan geçilerek ürün haline getirilirler. Tipik aşamalar sistemin analizi, kodlama için modellenmesi (yani, kodlamaya ilişkin belirlemelerin yapılması), kodlama işleminin kendisi, test işlemi (test işlemi kodlama işlemi ile beraber yürütülen bir işlem olabilir, tabii ürünün tamamının alfa ve beta testleri de söz konusu olabilir), dokümantasyon ve bakım işlemleri (yani, ürünün bir kitapçığı hazırlanabilir, ürünün oluşturulmasına ilişkin adımlar dokümante edilebilir, ürün oluşturulduktan sonra çıkacak çeşitli problemlere müdahale edilebilir ve hatta nihayi ürün üzerinde değiştirme ve geliştirme işlemleri yapılabilir). Her ne kadar proje geliştirme işleminin teorik tarafı bu adımları sırası ile içerse de küçük gruplar ya da tek kişilik çalışmalarda programcı kendi sezgisiyle bunları eş zamanlı olarak sağlamaya çalışabilir. Teorik açıklamalar ancak genel kurallardır. Bu genel kurallar izlendiği halde başarısız olunabilir, izlenmediği halde başarılı olunabilir. Nesne yönelimli teknik kullanılan projelerde analiz aşamasından sonra proje için gerekli sınıfların tespit edilmesi ve aralarındaki ilişki açıklanmalıdır. Eğer böyle yapılırsa bundan sonra projenin kodlama aşamasında problemleri azalır. Proje içerisindeki sınıfların tespit edilmesi, bunların arasındaki ilişkilerin belirlenmesi sürecine nesne yönelimli modelleme denilmektedir. Nesne yönelimli modellemede ilk yapılacak iş proje konusuna ilişkin dış dünyadaki gerçek nesneler ya da kavramları birer sınıfla temsil etmektir. Örneğin, C derneği otomasyona geçecek olsun bütün işlemleri yapacak bir proje geliştirilecek olsun. Konuya ilişkin gerçek hayattaki
7
nesneler ve kavramlar belirlenir, bunlar birer sınıfla temsil edilir (bu işleme transformation denilmektedir). Örneğin dernekte neler vardır? - derneğin yönetim kurulu - öğrenciler - bilgisayarlar ve demirbaşlar - maaşlı çalışanlar - hocalar - üyeler - sınıflar
Bu nesne ve kavramların hepsi birer sınıfla temsil edilir. Bu aşamadan sonra bu sınıflar arasındaki ilişkiler tespit edilmeye çalışılır. Örneğin, hangi sınıf hangi sınıftan türetilebilir? Hangi sınıf hangi sınıfı kullanacaktır? Hangi sınıfın derlenmesi için diğer sınıfın bilgilerine gereksinim vardır? Bunlar bir sınıf şeması ile belirtilebilir.
Sınıfların Sınıfları Kullanma Biçimi Sınıfların sınıfları kullanma biçimi dört biçimde olabilir: 1- Türetme ilişkisi ile kullanma (inheritance): Mesela A taban sınıftır, B A’dan türetilir, B A’yı bu biçimde kullanmaktadır. 2- Veri elemanı olarak kullanma (composition): Bir sınıfın başka bir sınıf türünden veri elemanına sahip olması durumunda eleman olan sınıf nesnesinin ömrü, elemana sahip sınıf nesnesinin ömrüyle ilgilidir. Yani, eleman olan sınıf nesnesi, elemana sahip sınıf nesnesi yaratıldığında yaratılır ve o nesne yok edildiğinde yok edilir. UML notasyonunda bu durum elemana sahip sınıftan eleman olarak kullanılan sınıfa doğru içi dolu yuvarlak ( •) ya da karo (♦) ile gösterilir. Örneğin: A
B Sınıf nesneleri büyükse eleman olan sınıf nesnelerinin heap üzerinde tahsis edilmesi daha uygun olabilir. Bu durumda elemana ilişkin sınıf türünden bir gösterici veri elemanı alınır, sınıfın başlangıç fonksiyonu içerisinde bu göstericiye tahsisat yapılır, bitiş fonksiyonu içinde de geri bırakılır. Örneğin: class B { private: A *m_pA; //... };
8
Bu biçimde bir kullanma ile diğerinin arasında kavramsal bir farklılık yoktur. Her iki durumda da eleman olan nesnenin ömürleri elemana sahip sınıfın ömrüyle aynıdır. 3- Başka bir sınıf nesnesinin adresini alarak veri elemanı biçiminde kullanma (aggregation): Bu durumda kullanılacak nesne kullanan nesneden daha önce yaratılmıştır, belki daha sonra da var olmaya devam edecektir. Sınıfın yine nesne adresini tutan bir gösterici veri elemanı vardır. Kullanılacak nesnenin adresi kullanan sınıfın başlangıç fonksiyonu içerisinde alınarak veri elemanına atanır. Yani bu durumda kullananılacak nesne kullanan sınıf tarafından yaratılmamıştır. Bu durum genellikle sınıf ilişki diyagramlarında içi boş yuvarlak (ο) ya da karo (◊) ile gösterilir. class B { public: B (A *pA) { m_pA = pA; } private: A *m_pA; //... }; A a; { B b(&a); ... } ...
Bu tür kullanma durumu genellikle bir nesnenin başka bir nesneyle ilişkili işlemler yaptığı durumlarda, ancak kullanılan nesnenin bağımsız olarak kullanılmasına devam ettiği durumlarda tercih edilir. Örneğin, bir bankada bir müşterinin hesabı üzerinde işlem yapmak için kullanılan bir sınıf olsun. Burada müşteri nesnesi daha önce yaratılmalıdır, belki üzerinde başka işlemler de uygulanmıştır, ancak hesap işlemleri söz konusu olduğunda o nesne başka bir sınıf tarafından kullanılacaktır. Gösterici yoluyla kullanma söz konusu olduğundan nesnedeki değişiklik kullanan sınıf tarafından hemen fark edilir. 4- Üye fonksiyon içerisinde kullanma (association): Bu durumda sınıfın bir üye fonksiyonu başka bir sınıf türünden gösterici parametresine sahiptir, yani sınıf başka bir sınıfı kısmen kullanıyordur. Bu durum genellikle sınıf ilişki diyagramlarında kesikli oklarla gösterilir. A
B
9
class B { public: void Func(A *pA); //... };
Bunların dışında bir sınıf başka bir sınıfı sınıfın yerel bloğu içerisinde kullanıyor olabilir. Ancak bu durum önemsiz bir durumdur, çünkü bu kullanma ilişkisi kimseyi ilgilendirmeyecek düzeydedir.
Çeşitli Yararlı Sınıfların Tasarımı Bu bölümde string, dosya, tarih gibi genel işlemler yapan yararlı sınıfların tasarımı üzerinde durulacaktır.
String Sınıfları Yazılarla işlemler yaparken klasik olarak char türden diziler kullanılır. Ancak dizilerin uzunluğu derleme zamanında sabit ifadesiyle belirtilmek zorundadır. Bu durum yazılar üzerinde ekleme ve çıkarma işlemleri yapıldığında bellek verimini düşürmekte ve programı karmaşık hale getirmektedir. Bellek kayıplarını engellemek için dizi dinamik tahsis edilebilir, bu durumda dizi büyütüleceği zaman yeterli uzunlukta yeni bir blok tahsis edilebilir. Ancak dinamik tahsisatlar programcıya ek yükler getirmektedir. İşte bu nedenlerden dolayı yazı işlemlerinin bir sınıf tarafından temsil edilmesi (yani encapsule edilmesi) çok sık rastlanılan bir çözümdür. Bir string sınıfının veri elemanları ne olmalıdır? Yazı için alan dinamik olarak tahsis edileceğine göre dinamik alanı tutan char türden bir gösterici olmalıdır. Yazının uzunluğunun tutulmasına gerek olmasa da pek çok işlemde hız kazancı sağladığından uzunluk da tutulmalıdır. Profesyönel uygulamalarda yazı için blok tam yazı uzunluğu kadar değil, daha büyük alınır. Böylece küçük ekleme işlemlerinde gereksiz tahsisat işlemleri engellenir. Tabii, yazı uzunluğunun yanı sıra tahsis edilen bloğun uzunluğu da tutulmalıdır. Bloklar önceden belirlenmiş bir sayının katları biçiminde tahsis edilebilir. Bu durumda string sınıfının tipik veri elemanları şöyle olacaktır: class CString { //... protected: char *m_pStr; unsigned m_size; unsigned m_length; static unsigned m_allocSize; }; unsigned CString::m_allocSize = CSTRING_ALLOC_SIZE;
10
Sınıfın m_allocSize isimli static veri elemanı hangi blok katlarında tahsisat yapılacağını belirtir. Bu static veri elemanı başlangıçta 10 gibi bir değerdedir. Yani, bu durumda blok 10’un katları biçiminde tahsis edilir. Bu durumda bir CString sınıf nesnesinin yaratılmasıyla şöyle bir durum oluşacaktır: ankara\0 m_pStr m_size m_length
Sınıf çalışması olarak tasarlanan string sınıfı MFC CString sınıfına çok benzetilmiştir. Sınıfın başlangıç fonksiyonları şunlardır: CString(); CString(const CString &a); CString(char ch, unsigned repeat = 1); CString(const char *pStr); CString(const char *pStr, unsigned length); ~CString();
Sınıf çalışmasındaki CString sınıfının pek çok türdeki üye fonksiyonu vardır. Bu fonksiyonlar şu işleri yaparlar: -
unsigned GetLength() const;
Sınıfın tuttuğu yazının uzunluğuna geri döner. -
BOOL IsEmpty() const;
Nesnenin hiç karaktere sahip olmayan bir nesneyi gösterip göstermediğini belirler. -
Bu fonksiyonlar nesne içerisindeki yazının soldan ve sağdan n karakterini alarak yeni bir yazı oluştururlar. Örneğin, CString path(“C:/autoexec.bat”);
11
CString drive; drive = path.Left(2);
Görüldüğü gibi bu fonksiyonlar geri dönüş değeri olarak geçici bir nesne yaratmaktadır. Tabi, CString sınıfının bir atama operatör fonksiyonu olmalıdır. drive = path.Left(2); işleminde şunlar yapılır: a- Fonksiyon içerisinde soldaki iki karakter bir CString nesnesi olarak elde edilir ve bu nesne ile return edilir. b- Geçici nesne kopya başlangıç fonksiyonu ile yaratılır. c- Geçici nesneden drive nesnesine atama için atama operatör fonksiyonu çağırılır. d- Geçici nesne için bitiş fonksiyonu çağırılır. -
CString Mid(int first) const; CString Mid(int first, int count) const;
Bu fonksiyonlar yazının belli bir karakter index’inden başlayarak n tane karakterini alıp yeni bir CString nesnesi olarak verir. Fonksiyonun tek parametreli biçimi geri kalan yazının tamamını almaktadır. -
void MakeUpper(); void MakeLower();
Sınıf içerisinde tutulan yazıyı büyük harfe ve küçük harfe dönüştürür. -
void Format(PCSTR pStr, ...);
Bu fonksiyon değişken sayıda parametre alan bir fonksiyondur. sprintf() gibi çalışır, sınıfın tuttuğu eski yazıyı silerek yeni yazıyı oluşturur. -
void MakeReverse();
Sınıfın tuttuğu yazıyı tersdüz eder. -
void TrimLeft(); void TrimRight();
Yazının solundaki ve sağındaki boşlukları atar. -
int Find(char ch) const; int Find(PCSTR pStr) const;
Bu fonksiyonlar yazı içerisinde bir karakteri ve bir yazıyı ararlar, geri dönüş değerleri başarılıysa bulunan yerin yazıdaki index numarası, başarısızsa –1 değeridir. Sınıfın ReverseFind() fonksiyonu aramayı tersten yapar.
CString Sınıfının Operatör Fonksiyonları -
Sınıfın [] operatör fonksiyonu sanki diziymiş gibi yazının bir indexine erişir. char &operator [](unsigned index); [] operatör fonksiyonu hem sol taraf hem de sağ taraf değeri olarak kullanılabilir. Örneğin: CString s = “Ankara”; s[0] = ‘a’; // s.operator[](0) = ‘a’;
12
-
Sınıfın const char * türüne dönüştürme yapan bir tür dönüştürme operatörü de vardır. operator const char *() const;
Bu tür dönüştürme operatör fonksiyonu doğrudan yazının tutulduğu adres ile geri döner, böylelikle biz CString türünden bir nesneyi doğrudan const char * türüne atayabiliriz. CString sınıfında bu işlem genellikle bir fonksiyonun çağırılması sonucunda oluşmaktadır. Örneğin: CString s = “Ankara”; puts(s);
Anımsatma:
// puts(s.operator const char *());
C++ tür dönüştürme operatör fonksiyonları şu durumlarda çağırılır:
1- Nesne tür dönüştürme operatörü ile ilgili türe dönüştürülmek istendiğinde. Örneğin: Date x; ... (int) x; 2- Sınıf nesnesini başka türden bir nesneye atanması durumunda. Örneğin: int a; Date b; ... a = b; 3- İşlem öncesinde otomatik tür dönüştürmesiyle. Örneğin: int a, b; Date c; a = b + c;
// a = b + c.operator int();
Eğer işlem soldaki operandın sağdakinin türüne, aynı zamanda sağdaki operandın soldakinin türüne dönüştürülerek yapılabiliyorsa iki anlamlılık hatası oluşur. C++ derleyicisi bir operatörle karşılaştığında önce operandların türlerini araştırır. Operandlar C’nin normal türlerine ilişkinse küçük tür büyük türe dönüştürülerek işlem gerçekleştirilir. Operandlardan en az biri bir nesneyse derleyici sırasıyla şu kontrolleri yapar: iiiiii-
İşlemi doğrudan yapacak global ya da üye operatör fonksiyonu araştırır. Her ikisinin birden bulunması error oluşturur. Birinci operandı ikinci operandın türüne ya da ikinci operandı birinci operandın türüne dönüştürerek işlemi yapmaya çalışır. Her iki biçimde de işlem yapılabiliyorsa bu durum error oluşturur. Bu dönüştürme işleminde derleyici sınıf nesnesini normal türlere dönüştürürken sınıfın tür dönüştürme operatör fonksiyonunu kullanır. Normal türü sınıf türüne dönüştürmek için ise başlangıç fonksiyonu yoluyla geçici nesne yaratma yöntemini kullanır. Örneğin: Complex a(3, 2); double b = 5, c;
13
c = a + b; Burada Complex sınıfının uygun bir operator + fonksiyonu varsa işlem o fonksiyonun çağırılmasıyla problemsiz yapılır. Eğer yoksa derleyici bu sefer Complex türünden nesneyi double türüne ya da double türünü Complex sınıfı türüne dönüştürerek işlemi yapmak isteyecektir. Yani,
1) c = a.operator double() + b; 2) c = a + Complex(b); Her iki biçim de mümkünse iki anlamlılık hatası oluşur. Eğer yalnızca bir durum sağlanıyorsa işlem normal olarak yapılır. Her iki operandın da diğerinin türüne dönüştürülebildiği durumlarda iki anlamlılık hatalarından kurtulmak için ifade açıkça yazılabilir. Yani, c = a + Complex(b); c = (double) a + b;
CString sınıfının const char * türüne dönüştürme yapan operatör fonksiyonu ile sanki CString nesnesi bir diziymiş gibi kullanılabilmektedir. Yazının tutulduğu adresi veren tür dönüştürme operatör fonksiyonunun char * değil de const char * türünden olduğuna
dikkat edilmelidir. Bu durumda örneğin, CString s(“Ankara”); char *p; p = s;
işlemi error ile sonuçlanır. Eğer bu işlem mümkün olsaydı biz CString nesnesinin kullandığı dinamik alan üzerinde değişiklik yapabilirdik ve sınıfın veri elemanı bütünlüğünü bozabilirdik. puts(s), strlen(s), strcpy(buf, s) gibi işlemler mümkündür, ancak strupr(s), strcpy(s, buf) gibi işlemler error ile sonuçlanır. MFC’de yazının tutulduğu adresi dışarıdan değiştirilebilecek biçimde veren GetBuffer() isimli bir üye fonksiyon da vardır. Ancak programcı bu fonksiyonu dikkatli kullanmalıdır. Yazının güncellenmesi bittikten sonra sınıfın ReleaseBuffer() isimli fonksiyonunu çağırmalıdır, çünkü ReleaseBuffer() dışarıdan yapılmış değişiklikleri görerek sınıfın veri elemanı bütünlüğünü korur. char *GetBuffer(unsigned minLength); void ReleaseBuffer(unsigned newLength);
Programcı yazının tutulduğu adresi elde ederken tahsisat alanının genişliğini de bilmelidir, bu yüzden GetBuffer() fonksiyonuna tahsisat alanını belirleyen bir parametre eklenmiştir. GetBuffer() genişletilmiş alanın adresiyle geri döner. Benzer biçimde ReleaseBuffer() yazının uzunluğunu belirleyerek işlemini bitirir. –1 özel değeri herhangi bir işlemin yapılmayacağını gösterir. Örnek:
14
CString s = “Ankara”; char *pUpdate; pUpdate = s.GetBuffer(30); s.ReleaseBuffer(-1);
-
sınıfının + operatör fonksiyonları iki CString nesnesini, bir CString nesnesinin sonuna bir karakteri ya da bir CString nesnesinin sonuna bir yazıyı ekler. Aynı işlemleri yapan += operatör fonksiyonları da vardır. Örneğin: CString
CString a = “ankara”, b = “izmir”; c = a + b; puts(c); a += b; c = a + “istanbul”; a += ‘x’;
-
CString sınıfının başka bir CString nesnesiyle, bir yazı ile her türlü karşılaştırmayı
yapan bir grup üye ve global operatör fonksiyonu vardır. - CString sınıfını başka CString nesnesine atamakta kullanılan ve bir karakter atamakta kullanılan atama operatör fonksiyonları vardır. - Nihayet sınıfın cout ve cin nesneleriyle işlem yapabilecek << ve >> operatör fonksiyonları vardır. Anahtar Notlar : a sayısını n’in katlarına çekmek için şu ifade kullanılır: (a
+ n – 1) / n * n
Anahtar Notlar : Bir sınıf için kopya başlangıç fonksiyonu gerekiyorsa atama operatör fonksiyonu da gerekir. Kopya başlangıç fonksiyonu ve atama operatör fonksiyonunun gerektiği tipik durumlar başlangıç fonksiyonlarına veri elemanları için dinamik tahsisat yapıldığı durumlardır. Atama operatör fonksiyonlarının hemen başında nesnenin kendi kendine atanıp atanmadığı tespit edilmelidir. C’de ve C++’da başına signed ya da unsigned anahtar sözcüğü getirilmeden char denildiğinde default durum derleyiciyi yazanların isteğine bırakılmıştır (implementation dependent). C’de bu durum bir taşınabilirlik problemine yol açmasın diye char *, signed char *, unsigned char * türlerinin hepsi aynı adres türüymüş gibi kabul edilmiştir. Böylelikle char türünün default durumu ne olursa olsun C’de aşağıdaki kod bir probleme yol açmaz.
Anımsatma:
char s[] = “Ankara”; unsigned char *p; p = s; Halbuki C++’da bu üç tür de tamamen farklı türler gibi ele alınmıştır. Bu nedenle yukarıdaki örnekte derleyicinin default char türü unsigned olsa bile error oluşur. Bu yüzden C++’da fonksiyonun parametresi char * türündense bu fonksiyon unsigned char * türü için çalışmayacaktır. Maalesef fonksiyon bu tür için yeniden yazılmalıdır.
15
Global operatör fonksiyonları işlevsel olarak üye operatör fonksiyonlarını kapsar. Ancak tür dönüştürme, atama, ok (->), yıldız (*) operatör fonksiyonları üye olmak zorundadırlar. Binary operatörlerde birinci operand doğal türlere ilişkin ikinci operand ise bir sınıf nesnesi olduğunda bu durum ancak global operatör fonksiyonlarıyla karşılanmaktadır. Üye operatör fonksiyonu olarak yazılmak zorunda olanların zaten böyle bir zorunluluğu yoktur. Bu yüzden bazı tasarımcılar soldaki operand sınıf nesnesi, sağdaki operand doğal türlerden olduğunda bunu üye operatör fonksiyonu olarak, tam tersi durum söz konusu olduğunda bunu global operatör fonksiyonu olarak yazmak yerine hepsini global operatör fonksiyonu olarak yazarlar. Global operatör fonksiyonlarının friend olması çoğu kez gerekmektedir.
Anımsatma:
Bir sınıf nesnesi aynı türden geçici bir nesneyle ilk değer verilerek yaratılıyorsa normal olarak işlemlerin şu sırada yapılması beklenir:
Anımsatma:
1- Geçici nesne yaratılır. 2- Yaratılan nesne için kopya başlangıç fonksiyonu çağırılır. Ancak standardizasyonda böylesi durumlarda derleyicinin optimizasyon amaçlı kopya başlangıç fonksiyonunu hiç çağırmayabileceği, yaratılan nesneyi doğrudan geçici nesnede belirtilen başlangıç fonksiyonuyla yaratabileceği belirtilmiştir. Aynı durum fonksiyonun geri dönüş değerinin bir sınıf türünden olduğu ve fonksiyondan geçici bir nesne yaratılarak return ifadesi ile dönüldüğü durumlarda da geçerlidir. Bu durumda da geçici bölge için return ifadesinde belirtilen başlangıç fonksiyonu çağırılacaktır. Bu nedenle CString sınıfının Mid() fonksiyonu aşağıdaki gibi düzeltilirse daha verimli olur: CString CString::Mid(int first) const { return CString(m_pStr + first); }
CString Sınıfının Kullanımına İlişkin Örnek Bu örnekte bir komut yorumlayıcı algoritmasının çatısı oluşturulacaktır. Komut yorumlayıcılarda bir prompt çıkar, kullanıcı bir komut yazar, komut yorumlayıcı bu komut kendi kümesinde varsa onu çalıştırır yoksa durumu bir mesajla bildirir. DOS komut satırı ve UNIX işletim sisteminin shell programları buna benzer programlardır. Bu uygulamadaki amaç bir string sınıfını kullanma çalışması yapmaktır. Tasarımımızda komut yorumlayıcı Shell isimli bir sınıf ile temsil edilecektir. Prompt yazısı sınıfın CString türünden bir veri elemanında tutulabilir, sınıfın başlangıç fonksiyonu bu prompt yazısını parametre olarak alabilir. Programın main kısmı şöyle olabilir: void main() { Shell shell(“CSD”); shell.Run(); }
Görüldüğü gibi program Run() üye fonksiyonu içerisinde gerçekleşmektedir. Komut yazıldığında komut ile parametreler ayrıştırılarak sınıfın iki veri elemanında tutulabilir. Komut yorumlayıcının döngüsü içerisinde yazı alınır, komut ve parametreler ayrıştırılır, komut önceden belirlenmiş komut kümesinde aranır.
16
Anahtar Notlar : İyi bir nesne yönelimli teknikte az sayıda global değişken kullanılmalıdır. Global değişkenler bir sınıf ile ilişkilendirilip sınıfın static veri elemanı yapılmalıdır. Anahtar Notlar : İyi bir nesne yönelimli teknikte sembolik sabitler için mümkün olduğu kadar az #define kullanılır. Bunun yerine sembolik sabitler bir sınıf içinde enum olarak bildirilir, böylece global faaliyet alanı kirletilmemiş olur. Komut, belirlenen komut kümesinde aranıp bulunduktan sonra komutu çalıştırmak için sınıfın bir üye fonksiyonu çağırılır. Komutları yorumlayan bu fonksiyonların çok biçimli olması faydalıdır, bu nedenle sanal yapılması uygundur. /* fonksiyon göstericisi kullanarak */ /* shell.h */ #ifndef _SHELL_H_ #define _SHELL_H_ #define LINELEN
for (int i = 0; cmdProc[i].pCommand != NULL; ++i) if(cmdProc[i].pCommand == m_command) { cmdProc[i].pProc(this); break; } if (cmdProc[i].pCommand == NULL) { cout << "Bad command or file name\n"; continue; } } } int main() { Shell shell("CSD"); shell.Run(); return 0; }
Line Editör Ödevi İçin Notlar Satır satır işlem yapılan editörlere line editör denir. DOS’un edlin editörü, UNIX’in ed editörü tipik line editörlerdir. Line editörlerde daha önce uygulamasını yaptığımız bir komut satırı vardır, kullanıcı bir komut ve bir satır numarası girer, editör o satır üzerinde ilgili işlemleri yapar. Böyle bir line editör uygulaması için bir Editor sınıfı ve bir Shell sınıfı alınabilir. Shell sınıfı Editor sınıfının bir veri elemanı gibi kullanılabilir. Bu sınıfların yanı sıra işlemleri kolaylaştıran String ve File sınıflarından da faydalanılabilir.
Dosya İşlemlerini Yapan Sınıflar Dosya işlemleri de tipik olarak sınıflarla temsil edilebilir. Pek çok sınıf kütüphanesinde dosya işlemlerini işlemlerini yapan bir sınıf vardır. Java, C# gibi nesne yönelimli dillerde dillerde dosya işlemleri işlemleri için tek bir sınıf değil polimorfik özelliği olan bir sınıf sistemi kullanılmaktadır. Ayrıca C++’ın standart kütüphanesinde dosya işlemleri iostream sınıf sistemi ile de yapılabilm yapılabilmektedir ektedir.. Maalesef Maalesef bu sını sınıff sist sistem emii dosy dosyaa işle işleml mleri eri için için yete yetersi rsizz kalma kalmakt ktadı adır. r. Bu nede nedenle nle dosya dosya işle işleml mleri eri için için iostream sınıf sistemini kullanmak yerine çoğu kez bu işlem için ayrı bir sınıf tasarlama yoluna yoluna gidilme gidilmekte ktedir dir.. MFC kütüpha kütüphanesi nesinde nde dosya dosya işleml işlemleri eri için için CFile isim isimli li bir bir sını sınıf f tasarlanmıştır. CFile sınıfı sınıfı binary binary dosyala dosyalarr üzerind üzerindee işleml işlemler er yapar, yapar, CFile sınıfından isimli li bir bir sını sınıff türe türeti tilm lmiş işti tir, r, bu sını sınıff da text text dosy dosyal aları arı üzeri üzerinde nde işle işlem m CStdioFile isim yapmaktadır. Örnek bir dosya sınıfı için MFC kütüphanesindeki CFile sınıfına benzer bir sınıf tasarlanacaktır.
CFile Sınıfının Tasarımı ve Kullanılması CFile sınıfı cfile.h ve cfile.cpp isimli iki dosya halinde tasarlanmıştır.
19
Anahtar Notlar : Bir sınıf başka bir sınıfı kullanıyor olsun. Örneğin B sınıfının A sınıfını kullandığını düşünelim. B sınıfı ve A sınıfı ikişer dosya halinde yazılmış olsun. A.h dosyası B sınıfının hangi dosyasında include edilmelidir? Bu sorunun yanıtı, biz dışarıdan B sınıfını kullanırken kullanırken yalnızca yalnızca B.h dosyasının dosyasının include include edilmesinin edilmesinin probleme yol açıp açmayacağıyla açmayacağıyla verilir. Yani, eğer A sınıfı B.h içerisinde kullanılmışsa, yani B sınıfının bildirimi içerisinde kullanılmışsa, sınıfın B.h içerisinde içerisinde include edilmesi edilmesi gerekir (genellikle (genellikle bu durum composition ya da aggregation durumudur). durumudur). Eğer A sınıfı yalnızca B sınıfının üye fonksiyonları içerisinde kullan kullanılm ılmışs ışsaa bu durumda durumda A sınıfı yalnızca yalnızca B.cpp içerisinde içerisinde kullanılmı kullanılmıştır, ştır, dolayısıyl dolayısıylaa B.cpp’nin içerisinden include edilmesi uygundur. Çok temel olan dosyaların include edilmesi sırasında ek bir include koruması uygulanabilir ya da çok temel dosyalar tamamen uygulama program programcısı cısı tarafı tarafından ndan zorunlu zorunlu olarak olarak include include edilme edilmesi si gereken gereken bir durum durum biçimi biçiminde nde ele alınabilir. Anahtar Notlar : Bir sınıfın içerisinde bir enum bildirimi yapılmışsa enum sabitleri o sınıfın içerisinde doğrudan kullanılabilir, ancak dışarıdan doğrudan kullanılamaz. Ancak enum sınıfın public public bölümündeyse bölümündeyse çözünürlük operatörüyle operatörüyle dışarıdan kullanılabil kullanılabilir. ir. Büyük projelerde global alandak alandakii isim isim çakışma çakışmasın sınıı engell engelleme emekk için için semboli sembolikk sabitl sabitlerin erin #define ile oluşturulmas oluşturulmasıı tavsiye edilmez, bunun yerine sembolik sabitler bir sınıfla ilişkilendirilmeli ve o sınıfın enum sabiti olarak bildirilmelidir. CFile sınıfının üye fonksiyonları şunlardır:
-
Sınıfın üç başlangıç fonksiyonu vardır: CFile(); CFile(FILE *fp); CFile(const char *pFileName, UINT openFlags);
Def Defaul ault
fonk fonksi siyyonu onu ile ile nesn nesnee yara yaratı tıllırsa ırsa dosy dosyaa daha daha sonr sonraa sını sınıffın CFile::Open() üye fonksiyonuyla açılmalıdır. İkinci başlangıç fonksiyonu daha önce fonksiyon yonuu ile ile açılmı açılmışş olan olan dosyay dosyayıı sınıf sınıf ile ilişki ilişkilend lendiri irili lipp kullanm kullanmak ak için için fopen() fonksi düşünülmüştür. Nihayet üçüncü fonksiyon, ismi ile belirtilen dosyayı belirtilen modda açar. -
başl başlan angı gıçç
Sınıfın bitiş fonksiyonu sınıf tarafından açılıp kullanılmakta olan bir dosya varsa onu kapatır, yoksa bir şey yapmaz. Anahtar Notlar : Pek çok sınıf için bitiş fonksiyonu koşulsuz bir geri alma işlemini yapmaz. Önce Önce bir kontrol kontrol yapar, yapar, bu kontrol kontrolle le başlangı başlangıçç işleml işlemlerin erinin in yapılı yapılıpp yapılm yapılmadı adığın ğınıı anlar, anlar, yapılmışsa yapılmışsa onu geri alır, yapılmamışsa yapılmamışsa hiç bir şey yapmaz. Örneğin CFile gibi bir sınıfın bitiş fonksiyonu hemen dosyayı kapatmaya yeltenmemelidir. Önce dosyanın açılıp açılmamış olduğuna bakmalı açılmışsa kapatmalıdır. Bu işlem genellikle sınıfın veri elemanına özel bir değerin yerleştirilmesi ve bunun kontrol edilmesi ile yapılmaktadır. Örneğin: CFile::CFile() {
20
m_f = NULL; } CFile::~CFile() { if (m_f) ::flose(m_t); }
-
Eğer dosya başlangıç fonksiyonu yoluyla açılmamışsa sınıfın Open() fonksiyonu ile açılabilir. virtual BOOL Open(const char *pFileName, UINT openFlags);
Sınıfın başlangıç fonksiyonunda ve bu fonksiyonda belirtilen açış modu sınıfın enum sabitlerinin bit or işlemlerine sokulmasıyla elde edilir. Örneğin: CFile f; if (f.Open(“x.dat”, CFile::modeCreate | CFileReadWrite) { ... ... }
Dosya açış modlarına ilişkin enum sabitleri bütün bitleri 0, yalnızca bir biti 1 olan sayılardır. -
Close() fonksiyonunun tasarımında dosya açıksa kapatılmıştır, zaten sınıfın bitiş
fonksiyonu da bu fonksiyonu çağırmaktadır. -
Sınıfın dosya göstericisinin gösterdiği yerden belli miktarda byte okuyup yazan Read() ve Write() fonksiyonları vardır. virtual UINT Read(void *pBuf, UINT count); virtual UINT Write(const void *pBuf, UINT count);
-
GetLength() fonksiyonu dosyanın uzunluğunu verir. virtual DWORD GetLength() const;
-
Seek() ve GetPosition() fonksiyonları sırasıyla dosya göstericisini konumlandırır ve
onun offset değerini elde eder. virtual BOOL Seek(long offset, UINT origin); virtual DWORD GetPosition() const;
21
fonksiyonunun ikinci parametresi CFile::begin, CFile::end, CFile::current olabilir. Ayrıca sınıfın SeekToBegin() ve SeekToEnd() fonksiyonları da vardır. Bu fonksiyonlar dosya göstericisini başa ve sona çeker. Seek()
-
Açılan dosyanın ismi sınıfın bir veri elemanında tutulmaktadır. GetFileName() fonksiyonu ile dosya ismi uzantısıyla beraber, GetFileTitle() fonksiyonu ile dosya ismi yalnızca isim olarak elde edilebilir.
sınıfında iki veri elemanı kullanılmıştır. Dosya içeride fopen() fonksiyonu ile açılmıştır, fopen() fonksiyonunun geri verdiği handle FILE *m_f veri elemanında saklanmıştır. Açılan dosyanın ismi CString m_fileName elemanında saklanmıştır. CFile
Anahtar Notlar : Sınıfın bazı üye fonksiyonları kısmen aynı şeyleri yapıyor olabilir. Bu durumda bu aynı işlemler bir üye fonksiyona yaptırılır, bu üye fonksiyon dışarıdan çağırılmayacağına göre sınıfın private bölümüne yerleştirilir. Anahtar Notlar : Başlangıç fonksiyonlarının geri dönüş değeri yoktur. Başlangıç fonksiyonlarında başarısız olunabilecek bir durum varsa bu durumu dışarıya bildirmenin üç yolu vardır: 1- Başarısızlık durumda hiç bir şey yapılmaz, bir mesaj verilir görmezlikten gelinir. 2- Başarısızlık başlangıç fonksiyonu içerisinde tespit edilir ve bir exception oluşturulur. Programcı da nesneyi try bloğunda yaratır. Örneğin, MFC’de dosya CFile sınıfının başlangıç fonksiyonunda açılamadıysa CFileException sınıfı türünden dinamik bir nesne yaratılıp o adres ile throw edilir. Örneğin, try { CFile f(...); //... } catch (CFileException *pFileException) { //... }
3- Başlangıç fonksiyonu içerisinde başarısız olunduğunda sınıfın bir veri elemanı set edilir, daha sonra o veri elemanına bakılarak başarı durumu tespit edilir. Bu bakılma işlemi için tipik olarak ! operatör fonksiyonu kullanılmaktadır. Örneğin, CFile f(...); if (!f) { cout << “Cannot open file...\n”; exit(EXIT_FAILURE); } class CFile { //... private: BOOL m_bSuccess; };
CFile Sınıfının Çokbiçimliliği CFile sınıfı çeşitli sanal fonksiyonlarla çokbiçimli (polymorphic) bir sınıf olarak yazılmıştır. Sınıfın önemli üye fonksiyonlarının hepsi sanal yapılmıştır. CFile sınıfından bir sınıf türetilip
bu fonksiyonlar yazılırsa türetilen sınıfın fonksiyonları çalıştırılacaktır. Sınıf kütüphanelerinde genellikle başka konularda yazılmış pek çok fonksiyon temel sınıfları kullanarak tasarlanmıştır. Örneğin, kütüphane içerisinde iki dosyayı kopyalayan Copy() isimli bir fonksiyon olsun. Bu fonksiyon global olabileceği gibi başka bir sınıfın üye fonksiyonu da olabilir. Copy() fonksiyonu dosyanın isimlerini değil de CFile nesnelerini parametre olarak alıp, onların Read(), Write() fonksiyonlarını kullanarak kopyalama işlemi yapsın. BOOL Copy(CFile *pSource, CFile *pDest);
Şimdi biz CFile sınıfından CSocketFile gibi bir sınıf türetip sanal fonksiyonları bu sınıf için yeniden yazalım. Bu sınıf TCP/IP portlarını bir dosya gibi kullanıyor olsun. Biz şimdi iki port arasında dosya transferi yapmak için yeni bir Copy() fonksiyonu yazmak yerine eski Copy() fonksiyonunu kullanabiliriz. CSocketFile fs, fd; //... Copy(&fs, &fd);
Bazen çokbiçimlilik yalnızca araya girme, yani kancalama işlemi için kullanılır, yani biz CFile sınıfından bir sınıf türetip o sınıf için Read(), Write() fonksiyonlarını yazarken yine taban sınıfın orjinal fonksiyonlarını çağırarak işlemlerimizi yaparız ama bu arada bazı ek işlemleri de araya sokarız. virtual CExtFile::Read(...) { //... return CFile::Read(...); }
23
İleri Template İşlemleri Template bir fonksiyonu ya da sınıfı derleyicinin belirli bir türe göre yazması anlamına gelir. Tek bir fonksiyon ya da bir sınıfın tamamı template olarak yazılabilir. Bir template fonksiyon çağırıldığında derleyici çağırılma ifadesindeki parametrelere bakarak template fonksiyonu o parametrelere göre yazar. Açısal parantezler içerisinde class anahtar sözcüğü yerine typename anahtar sözcüğü de kullanılabilmektedir. Template konusu C++’a sonradan eklenmiş ve geliştirilmiş bir konudur. Maalesef derleyiciler arasında template özelliklerinde ciddi biçimde uyumsuzluklar bulunabilmektedir. C++’ın son 1998 standardı bazı derleyiciler tarafından tam olarak desteklenememektedir. Derleyici bir template fonksiyon ya da sınıf ile karşılaştığında bazı syntax kontrollerini o anda yapar. Ancak henüz template parametresinin türü belli olmadığı için bazı kontrolleri template açılımını yaparken yapmaktadır. Örneğin: tepmlate void Func(T &a) { a.Func2(); //... }
Burada derleyici T türünü henüz bilmediği için a.Func2() işleminde her hangi bir error bildirimi yapmaz. Ancak Func() fonksiyonu şöyle çağırılmış olsun: Func(x);
Şimdi derleyici template açılımını yaparken x’in türüne bakacaktır, x bir sınıf türünden değilse ya da sınıf türündense ama Func2() isimli bir üye fonksiyonu yoksa işlem error ile sonuçlanacaktır. Özetle derleyiciler templateler için syntax kontrolünü iki aşamada yaparlar: 1- Template bildirimini gördüklerinde 2- Tamplate açılımlarının yapıldığında Template parametresi template fonksiyonlarda fonksiyonun çağırılma biçimine göre normal bir tür ya da bir gösterici türü olabilir. Örneğin: template void Func(T a) { T p; //... }
Burada eğer fonksiyon, int a[10]; Func(a);
24
biçiminde çağırılırsa T türü int * anlamına gelir. Dolayısıyla template fonksiyonu içerisindeki ‘p’ int * türünden bir göstericidir. Halbuki fonksiyon, Func(20);
biçiminde çağırılsaydı T int anlamına gelecekti, dolayısıyla p de int türünden bir değişken olacaktı. Bazı derleyiciler template fonksiyonları yalnızca template açılımlarını gördüklerinde syntax bakımından değerlendirirler (derleyicinin template fonksiyonun çağırıldığını gördüğünde ya da bir tamplate sınıf nesnesinin tanımlandığını gördüğünde yaptığı işlemlere template açılımı denilmektedir). Bir template fonksiyonuyla aynı isimli normal bir fonksiyon olabilir. Bu durumda fonksiyon çağırıldığında derleyici önce parametrenin normal fonksiyon ile uyuşup uyuşmadığına bakar, normal fonksiyon uyuşuyorsa normal fonksiyonunun çağırıldığını varsayar. Uyuşmuyorsa template açılımı uygular. Bir template fonksiyon parametresine bakılmaksızın belirli türden açılmaya zorlanabilir. Örneğin: Func(10);
Burada fonksiyonun parametresi int türünden olduğu halde biz derleyicinin double açılımı yapmasını isteyebiliriz. Bu sayede normal fonksiyon yerine template açılımının da uygulanması sağlanabilir. Örneğin: y = abs(x);
Burada abs() fonksiyonu için normal bir fonksiyon bulunmasına karşın template açılımı kullanılmıştır. Bu özellik 1996 ve sonrasında kabul edilmiştir. Derleyici template fonksiyon bildirimini gördüğünde bellekte her hangi bir yer ayırmaz. Template açılımı yapıldığında fonksiyon yazılır. Template fonksiyonların ve sınıfların bildirimleri başlık dosyalarında tutulmalıdır, çünkü her derleme aşamasında derleyici tarafından kullanılmaktadır. Açılımı yapılmayan template bildirimlerinin koda olumsuz bir etkisi o lmaz. Template sınıflarda bir sınıfın tüm üye fonksiyonları açılım sırasında derleyici tarafından yazılır. Template bir sınıf türünden nesne tanımlanırken template açılımının açısal parantezlerle belirtilmesi gerekir. Örneğin: list a;
Bir tamplate sınıfta template parametresi default bir tür ismi alabilir. Örneğin: template class Sample { //... };
Bu default template parametreleri açılımda belirtilmezse etkili olur. 25
Sample<> a; Sample a; Sample a;
Template bir sınıf hiçbir yerde yalnızca sınıf ismi ile kullanılamaz. Açısal paramtezlerle açılım belirtilerek kullanılır. Fonksiyonların parametreleri ya da geri dönüş değerleri bir template sınıf türünden olabilir, tabii template türünün belirtilmesi gerekir. Örneğin: SampleFunc(); void Func(Sample *p);
Template fonksiyonlar genellikle sınıf içinde yazılır ve bitirilir. Template sınıfının üye fonksiyonu dışarıda yazılacaksa bir template fonksyon gibi yazılmalıdır ve sınıf isminden sonra açılım türü de belirtilmelidir. Örneğin: template void Sample::Func() { //... }
Normal bir sınıfın her hangi bir fonksiyonu template fonksiyon olabilir (İngilizce member template denilmektedir). Örneğin: class Sample { public: template Sample(T a); //... };
(VisualC++ 6 member template konusunu desteklememektedir) Template bir sınıf taban sınıf olarak kullanılabilir ya da taban sınıf template sınıf olmadığı halde türemiş sınıf template sınıf olabilir. Birinci durumda tabii yine template türünün belirtilmesi gerekir. Örneğin: template class A { //... }; class B : public A { //... }; /* or */ class A { //... };
26
template class B : public A { //... };
Hem taban hem de türemiş sınıf template sınıf olabilir. Bu durumda türemiş sınıfta taban sınıf belirtilirken yine template türü geçirilmelidir. Tabii türemiş sınıftaki template parametresi taban sınıf template türü olarak verilebilir. Örneğin: template class A { //... }; template class B : public A { //... };
Template sınıfının template parametresi (yani T) bütün sınıf içinde ve üye fonksiyonlarının içerisinde bir tür ismi olarak kullanılabilir. Template sınıf türünden nesne tanımlarken template türü başka bir template sınıf olabilir. Örneğin: Queue > queue;
Burada derleyici önce list sınıfını int türü ile açar, Queue sınıfının template parametresinin türü int türüne açılmış list olur. Yani bu örnekte her elemanı bağlı liste olan bir kuyruk sınıfı oluşturulmuştur. Bağlı listede int türden bilgiler tutulmaktadır (burada ifadenin shift operatörü (>>) ile karışmaması için araya boşluk bırakılması gerekir). Template sınıf ile aynı isimli normal bir sınıf olabilir ama normal sınıfın template açılım türü belirtilmelidir. Örneğin: template class Sample { //... }; template <> // Bu yazılmak zorunda değil class Sample { //... };
Burada aşağıdaki sınıf bir template sınıf değildir. Sınıfın başındaki template bildirimi yazılmayabilir. Şimdi sınıf, Sample a;
27
biçiminde açılırsa aşağıdaki template olmayan sınıf kullanılacaktır.
28
Standart Template Kütüphanesi (STL) STL ilk kez HP şirketi programcıları tarafından geliştirilmiş ve kullanılmıştır. 1996’da C++’ın standardizasyon taslaklarında STL C++’ın standart kütüphanesi olarak kabul edilmiştir. STL tamamen template sınıflar ve fonksiyonlardan oluşan geniş bir kütüphanedir. STL kullanabilmek için ilgili fonksiyonun ya da sınıfın bulunduğu başlık dosyası include edilmelidir. STL fonksiyonları ve sınıfları guruplandırılarak çeşitli başlık dosyalarının içerisine yerleştirilmiştir. Eskiden STL sınıflarınınn ve fonksisyonlarını bulunduğu başlık dosyasının uzantısı *.h biçimindeydi. 1996 ve sonrasında bu uygulamaya son verilmiştir. Şimdi STL kütüphanesi uzantısı olmayan dosyaların içerisindedir. Örneğin eskiden bağlı liste sınıfı list.h içerisindeydi, şimdi yalnızca list dosyası içerisindedir. Bugünkü derleyicilerin çoğu hem *.h uzantılı dosyaları hem de uzantısız dosyaları bulundurmaktadır, dolayısıyla eskiden yazılmış kodlar problemsiz derlenmektedir. Tabii derleyicilerin standardizasyon öncesi dönemi desteklemesi zorunlu değildir. Tüm STL kodları bu dosyalar içerisinde olduğu için nasıl yazıldıkları kolaylıkla incelenebilir. STL kütüphansei üç tür elemandan oluşur: 1- Algoritmalar : STL içerisindeki global template fonksiyonlara algoritma denilmektedir. Bu fonksiyonlar özellikle bazı operatörler kullanılarak yazılmıştır, bu yüzden diğer template sınıflar ile birlikte kullanılabilir. 2- Nesne tutan sınıflar (container classes): İçerisinde birden fazla nesnenin tutulduğu, çeşitli veri yapılarının uygulandığı sınıflara nesne tutan sınıflar denir (Container class nesne yönelimli terminolojide genel bir terimdir. Container class terimi ile collection terimi eş anlamlı olarak kullanılmaktadır). Örneğin dizileri temsil eden sınıflar, kuyruk sınıfları, bağlı liste sınıfları tipik birer nesne tutan sınıftır. STL içerisinde bazı nesne tutan sınıflar başka nesne tutan sınıflardan faydalanılarak yazılmıştır. Örneğin, stack sınıfı deqeue sınıfı kullanılarak yazılmıştır. Böylecene bu tür sınıflara adaptör sınıflar (STL adaptors) denilmektedir. 3- Yararlı sınıflar (utility classes): Nesne tutma amacında olmayan genel sınıflardır. Nesne yönelimli programlama tekniğindeki en büyük gelişmelerden biri veri yapılarının standart bir biçimde sınıflarla temsil edilmesidir. Örneğin STL sayesinde programcının gereksinim duyacağı neredeyse algoritmik herşey standart olarak yazılmıştır. STL içerisinde olmayan veri yapıları ve algoritmalar STL kullanılarak programcılar tarafından yazılabilir. Her programcının aynı biçimdeki veri yapıları ve algoritmalar üzerinde çalışması kodların anlaşılmasını kolaylaştırmaktadır. STL içerisinde tüm temel algoritmalarının veri yapılarının bulunması işleri kolaylaştırmakla birlikte bütün problemleri kendi başına çözmemektedir. Pogramcının algoritmalar ve veri yapıları arasındaki farkları bilmesi, duruma göre bunlardan birini seçmesi gerekir. Hangi veri yapısının ve algoritmanın kullanılacağı yine belli düzeyde bir bilgi gerektirmektedir. STL sınıflarının tasarımında çokbiçimlilik (polymorphism) performansı düşürür gerekçesiyle kullanılmamıştır. Yani sınıflar bir türetme ilişkisi içerisinde değil, bağımsız bir biçimde bulunur. Ancak programcı isterse STL sınıflarından türetme yapabilir. Yine STL sınıflarında iostream
29
sistemi dışında türetme kullanılmamıştır. Exception handling mekanizması çok az düzeyde kullanılmıştır.
İsim Aralığı (Namespace) Faaliyet Alanı Bir isim aralığı (namespace) global faaliyet alanında tanımlanmış bir bloktur. Bir isim aralığı içerisindeki değişkenler ve fonksiyonlar yine global düzeydedir, ancak erişim yapılırken namespace ismi çözünürlük operatörüyle (::) belirtilmek zorundadır. Bir isim eğer namespace ismi belirtilmemişse kendi namespace’i ve kapsayan namespace faaliyet alanlarında otomatik olarak aranır. Hiçbir namespace içerisinde olmayan global bölgeye “ global namespace” denilmektedir. Global namespace diğer isim aralıklarını kapsar. Bir isim aralığının içerisinde prototipi bildirilmiş bir fonksiyon ya da bildirimi yapılmış bir sınıf başka bir isim aralığında tanımlanamaz. Global namespace içerisinde tanımlanabilir. Örneğin: namespace X { void Func(); } namespace Y { void X::Func() { } } void X::Func() { }
/* error */
/* doğru */
Bir namespace bildiriminden sonra aynı namespace tekrar bildirilirse bu namespace tanımlamaları tekbir tanımlamaymış gibi birleştirilir. Örneğin: namespace X { int a; //... } //... namespace X { int b; //... }
Tüm STL elemanları std isimli bir isim aralığında tanımlanmıştır. Eğer using bildirimi kullanılmamışsa STL isimlerini kullanırken bu namespace ismini eklemek gerekir. Örneğin: std::list x;
30
using Bildirimi using bildirimi bir namespace içerisindeki isme erişirken namespace ismini kullanmadan erişimi gerçekleştirmek amacıyla kullanılır. using bildirimi iki biçimde kullanılır:
1- using namespace ; Örneğin: using namespace std;
2- using ::
Örneğin: using std::cout;
Genellikle birinci çeşit using bildirimiyle sıklıkla karşılaşılır. Birinci çeşit using bildirimi global bir alana yerleştirilebilir, herhangi bir namespace içerisine yerleştirilebilir ya da herhangi bir blok içerisine yerleştirilebilir, sınıf bildirimi içerisine yerleştirilemez. Birinci çeşit using bildirimi nereye yerleştirilirse o yerleştirildiği faaliyet alanında aranacak her isim using bildiriminde belirtilen faaliyet alanında da aranır. Birinci çeşit namespace bildiriminde eğer bir isim namespace bildiriminin yerleştirildiği yerde varsa, using bildirimiyle belirtilen namespace içerisinde de varsa bu durum iki anlamlılık hatasına yol açar. Benzer biçimde bir isim using bildirimiyle belirtilmiş birden fazla namespace içerisinde varsa bu durum da error oluşturmaktadır. using bildirimlerini *.h dosyaları içerisine yerleştirmek tavsiye edilen bir yöntem değildir, çünkü bu durumda o *.h dosyasını include eden her modülde using bildirimi global düzeyde etkili olacaktır. Birinci çeşit using bildiriminde namespace anahtar sözcüğünden sonra kesinlikle namespace ismi gelmelidir, sınıf ismi gelirse error oluşur. İkinci çeşit using bildirimi seyrek kullanılır. Bu bildirimde using anahtar sözcüğünü sırasıyla bir namespace ismi sonra çözünürlük operatörü ve namespace içerisindeki bir isim izler. Örneğin: using A::B::Func;
Birinci çeşit using bildiriminde tüm bir namespace faaliyet alanına dahil edilmektedir. Halbuki ikinci çeşit using bildiriminde yalnızca using bildiriminde belirtilen isim using bildiriminde belirtilen faaliyet alanında aranmaktadır. Örneğin, biz std namespace’i içerisindeki cout için global düzeyde using std::cout;
bildirimini yapmış olalım. Şimdi biz cout nesnesini std namespace ismini belirtmeden doğrudan kullandığımızda bir problem oluşmaz, ancak bu durum sadece cout ismi için söz konusudur. Birinci çeşit using bildiriminin sonunda bir namespace ismi, ikinci çeşit using bildiriminin sonunda bir namespace içerisindeki ismin bulunduğuna dikkat edilmelidir. Örneğin:
31
using namespace A::B::C; using A::B::C::x;
İkinci çeşit using bildirimi her yere yerleştirilebilir, sınıf bildirimi içerisine de yerleştirilebilir. Sınıf bildirimi içerisine yerleştirilirse namespace ismi yerine sınıfın taban sınıflarından birinin ismi kullanılmak zorundadır. Örneğin: class A { protected: int m_a; //... }; class B : public A { public: using A::m_a; //... };
Sınıf içerisinde ikinci çeşit using bildiriminin kullanılması özellikle taban sınıftaki bir ismin ( protected bölümündeki bir ismin) türemiş sınıfta başka bir bölüme (örneğin private ya da public bölüme) aktarılması için kullanılmaktadır. Yukardaki örnekte bir B türünden bir nesneyle A’nın m_a elemanına erişemezdik, ancak using bildirimiyle A’daki m_a B sınıfında public bölüme aktarılmıştır, dolayısıyla artık erişebiliriz.
STL string Sınıfı C++’ın standart kütüphanesinde yazı işlerini kolaylaştırmak için kullanılan bir string sınıfı vardır. STL tamamen template tabanlı bir kütüphanedir, yani bütün global fonksiyonlar template fonksiyonlar, bütün sınıflar da template sınıflardır. İşte aslında string sınıfı basic_string template sınıfından yapılmış bir typedef ismidir. string ismi aşağıdaki gibi typedef edilmiştir: typedef basic_string string;
Yani yazı işlemleri için asıl template sınıf basic_string template sınıfıdır, string ismi bu sınıfın char türü için açılmış halidir. basic_string template sınıfı “ string ” dosyası içerisindedir (dosyanın *.h biçiminde uzantısı yoktur). basic_string sınıfı üç template parametresi içeren genel bir sınıftır. template , class A = allocator > class basic_string { //... };
Birinci template parametresinin verilmesi zorunludur, bu tür yazının her bir karakterinin hangi türden olduğunu anlatır. Örneğin ASCII yazıların her bir karakteri 1 byte’dır ve tipik olarak char 32
türüyle temsil edilir, ancak UNICODE yazılarda her bir karakter 2 byte yer kaplar ve wchar_t ile temsil edilir. Bu durumda bir ASCII yazıyı tutmak için nesne basic_string x;
biçiminde tanımlanır, UNICODE yazıyı tutmak için nesne basic_string x;
biçiminde tanımlanır. Genellikle ASCII yazılar yoğun olarak kullanıldığından işlemi kolaylaştırmak için string typedef ismi bildirilmiştir. Yani, basic_string str;
ile string str;
aynı anlamdadır. basic_string sınıfının ikinci template parametresi default olarak char_traits sınıfı türündendir. char_traits bir STL sınıfıdır ve iki karakteri karşılaştıran static üye fonksiyonları vardır. basic_string sınıfının karşılaştırma fonksiyonları bu sınıftaki static fonksiyonlar çağırılarak yazılmıştır. Örneğin, basic_string sınıfının < operatör fonksiyonu, ikinci template
parametresiyle belirtilen sınıfın lt() ve eq() static fonksiyonlarını çağırarak yazılmış olsun, template , class A = allocator > bool basic_string::operator <(const E *pStr) { for (int i = 0; i < SIZE; i++) { if (T::lt(m_pBuf[i], pStr[i])) return true; if (!T::eq(m_pBuf[i], pStr[i])) return false; } return false; }
char_trait sınıfının iki karakteri karşılaştıran üye fonksiyonları ASCII karakter tablosunu
temel alarak işlemlerini yapmaktadır. Biz örneğin karşılaştırma işlemlerinin Türkçe yapılmasını istersek char_trait sınıfının elemanlarını başka bir sınıf adı altında ama Türkçe’ye uygun bir biçimde yazmalıyız, böylece basic_string sınıfına hiç dokunmadan onun işlevini değiştirmiş oluruz. Örneğin bu sınıfın ismi trk_traits olsun. typedef std::basic_string trkstring; trkstring a(“ılgaz”); trkstring b(“ismail”);
33
if (a < b) { //... }
basic_string sınıfının üçüncü template parametresi default olarak allocator türündendir. Neredeyse tüm STL template sınıfları böyle bir allocator template parametresi almaktadır. Aslında STL içerisinde dinamik tahsisatlar doğrudan new operatörüyle yapılmamıştır, template argümanı olarak belirtilen sınıfın static üye fonksiyonu çağırılarak yapılmıştır. allocator bir STL sınıfıdır ve default olarak bu sınıfın tahsisat yapan fonksiyonu new operatörünü
kullanmaktadır. Programcı başka isimde yeni bir tahsisat sınıfı yazabilir ve böylece tüm STL sınıfları o sınıfın tahsisat fonksiyonunu çağıracak duruma gelir. Tahsisat sınıfının kullanım ve anlamı ileride ele alınacaktır.
basic_string Sınıfının Üye Fonksiyonları Başlangıç Fonksiyonları: Sınıfın daha önce yazmış olduğumuz CString sınıfına benzer parametre yapıları içeren şu başlangıç fonksiyonları vardır: 1- basic_string();
Default başlangıç fonksiyonudur. 2- basic_string(const basic_string &str);
Kopya başlangıç fonksiyonudur. 3- basic_string(const basic_string &str, size_type pos, size_type n); size_type, basic_string sınıfı içerisinde bildirilmiş bir typedef ismidir. Bu typedef default olarak size_t türündendir. Bu başlangıç fonksiyonu başka bir basic_string
nesnesinin belirli bir karakterinden başlayarak n tane karakteri alıp nesneyi oluşturur. Örneğin: string a(“ankara”); string b(a, 2, 4);
// b = kara
4- basic_string(const E *str, size_type n);
Adresiyle verilmiş bir yazının ilk n karakterinden nesne oluşturur. Örneğin: string a(“ankara”, 3);
// a = ank
5- basic_string(const E *str); Parametresiyle belirtilen adresten ‘\0’ görene kadarki kısımdan yazıyı oluşturur. En çok
kullanılan constructor’dur. Örneğin: string a(“ankara”);
34
6- basic_string(size_type n, E ch);
Aynı karakterden n tane olan bir nesne oluşturur. Örneğin: string a(4, ‘a’); string b(“aaaa”); assert(a == b);
7- basic_string(const iterator first, const iterator last); İki iterator arasından nesne oluşturur (iterator konusu STL’in en önemli kavramlarından biridir, ileride ele alınacaktır). Atama Operatör Fonksiyonları: Bilindiği gibi atama operatör fonksiyonları string sınıfı için yazının tutulduğu eski alanı boşaltıp yeni yazı için yeni bir alan tahsis etme eğilimindedir. 1- basic_string &operator =(const basic_string &str); İki basic_string nesnesinin atanmasında kullanılır. Örneğin: string a(“ankara”); string b = “istanbul”; b = a; assert(a == b);
2- basic_string &operator =(const E *str);
Adresiyle verilmiş olan bir yazıyı atamakta kullanılır. Örneğin: string a(“ankara”); a = “istanbul”;
3- basic_string &operator =(E ch);
Nesnenin tek bir karakterden oluşan yazıyı tutmasını sağlar. Örneğin: string a; a = ‘x’;
Atama operatör fonksiyonlarının hepsinin geri dönüş değeri sol taraftaki nesnenin kendisidir, yani aşağıdaki işlem geçerlidir: string a(“ankara”), b, c; c = b = a;
Anahtar Notlar : ostream türünden cout nesnesi 1996 ve sonrasında string türünü de yazdıracak operatör fonksiyonuna sahip olmuştur. 1996 ve sonrasında başlık dosyalarının uzantısı kaldırıldığından ancak eski pek çok derleyici *.h uzantılı eski sistemi de desteklediğinden bir karmaşa doğabilir. Şöyle ki, biz iostream.h dosyasını include edersek eski iostream
35
kütüphanesini kullanıyor duruma düşeriz, bu durumda cout ile string türünü yazdıramayız. cout ile string türünü yazdırabilmek için uzantısı olmayan iostream dosyasının include edilmesi gerekir.
string Sınıfının Diğer Operatör Fonksiyonları string sınıfının, CString sınıfında olduğu gibi bütün karşılaştırma operatörlerine ilişkin
operatör fonksiyonları vardır. Bu fonksiyonlar yazıları içerik bakımından karşılaştırmaktadır. Bu operatör fonksiyonlarının operandlarından biri string diğeri string ya da const char * türünden olabilir. Örneğin: string s = “ankara”; string k = “izmir”; if (s == k) { //... } if (s < “samsun”) { //... }
Bu operatör fonksiyonlarının birinci operandının string türünden olması zorunlu değildir, çünkü birinci operandı string olmayan fonksiyonlar global operatör fonksiyonuyla yazılmışlardır. Karşılaştırma operatör fonksiyonları yerine tıpkı strcmp() fonksiyonunda olduğu gibi üç durumu da belirten compare() üye fonksiyonu kullanılabilir. int compare(const char *str) const; int compare(const string &) const;
Fonksiyonlar birinci yazı ikinci yazıdan büyükse pozitif herhangi bir değere, küçükse negatif bir değere, eşitse sıfır değerine geri dönerler. Sınıfın daha ayrıntılı işlem yapmaya yarayan farklı parametreli compare() fonksiyonları da vardır. string sınıfının += ve + operatör fonksiyonları yazının sonuna başka bir yazı eklemek için ya da iki yazıyı toplamak için kullanılmaktadır. += operatör fonksiyonu yerine sınıfın append() üye fonksiyonu da kullanılabilir. += ve + operatör fonksiyonlarının bir operandı string türündendir, diğer operand string, const char * ya da char türünden olabilir. += fonksiyonunun geri dönüş değeri string türünden referans, + operatör fonksiyonunun ise string türündendir.
36
Sınıfın elemana erişim için kullanılan bir [] operatör fonksiyonu vardır. char &string::operator [](size_type idx); char string::operator [](size_type idx) const;
Bu fonksiyonlarda [] içerisindeki index değeri yazının uzunluğundan büyük olursa gösterici hatasına yol açar. Elemana erişme işlemi add() üye fonksiyonuyla da yapılabilir. add() üye fonksiyonu kullanılırsa index değeri yazının uzunluğunu aştığında out_of_range isimli exception oluşur. STL string sınıfında yazıların sonunda ‘\0’ bulunmamaktadır (sınıf içerisinde yazının uzunluğu tutulduğu için ‘\0’ ın ayrıca yer kaplaması istenmemiş olabilir). Sınıfın tuttuğu yazının karakter uzunluğu length() üye fonksiyonuyla alınabilir. size_type length() const;
Bu durumda yazının sonuna kadar karakter karakter ilerlemek için aşağıdaki yöntem kullanılmalıdır: for (int i = 0; i < s.length(); ++i) cout << s[i] << endl;
(Döngü içerisinde sürekli length() fonksiyonunun çağırılması performans problemine yol açmaz. Çünkü template fonksiyonlar inline olarak yazılmıştır.) string sınıfının size() üye fonksiyonu length() üye fonksiyonuyla eşdeğerdir.
string Sınıfının Yaralı Fonksiyonları string sınıfının bir grup arama işlemini yapan find() ve rfind() isimli üye fonksiyonları vardır. Bu fonksiyonlar yazı içerisinde arama yaparlar. Fonksiyonların parametresi string , const char * veya char türünden olabilmektedir. Fonksiyonlar yazının bulunduğu yerin index numarasıyla geri döner. rfind() aramayı sondan başa doğru yapar. Fonksiyonlar
başarısızlık durumunda string sınıfı içerisindeki npos değerine geri dönerler. string s(“ankara”); int index; index = s.find(“kara”); if (index == string::npos) { cerr << "error olustu" << endl; exit(1); } cout << index << endl;
Daha ayrıntılı parametrelere sahip olan find() ve rfind() fonksiyonları da vardır.
37
substr() fonksiyonu yazının belirli bir kısmından yeni bir yazı oluşturur. string substr(size_type idx) const; string substr(size_type idx, size_type len) const; idx parametrsi başlangıç offsetini, len parametresi ise o offsetten sonraki karakter sayısını anlatır. len parametresi yazılmazsa geri kalan tüm yazı alınır. string s(“ankara”); int index; index = s.find(“kara”); if (index == string::npos) { cerr << "error olustu" << endl; exit(1); } string t; t = s.substr(index, 2);
replace() fonksiyonu belirli bir offsetten başlayarak belirli bir uzunluktaki karakterleri başka
bir yazıyla değiştirir. Sınıfın clear() üye fonksiyonu ya da erase() üye fonksiyonu parametresiz kullanılırsa sınıftaki tüm yazıyı siler. Belirli bir aralığı silen versiyonları da vardır. Sınıfın insert() fonksiyonları da vardır. Ayrıca string sınıfı iteratör işlemlerini de desteklemektedir. string sınıfının okuma yapan bir >> operatör fonksiyonu vardır, ancak okuma ilk boşluk
karakteri görüldüğünde sonlandırılır. string sınıfının tasarımını yaptığımız CString sınıfında olduğu gibi const char *
türüne tür dönüştürme yapan operatör fonksiyonu yoktur, çünkü yazının saklandığı alan ‘\0’ kullanılmamıştır. Sınıfın c_str() üye fonksiyonu bu eksikliği kapatmak için tasarlanmıştır. const char *c_str() const;
Bu fonksiyon sınıf içerisinde tutulan yazıyı başka bir alana taşır, yazının sonuna ‘ \0’ koyar ve o alanına adresiyle geri döner. c_str() fonksiyonu string nesnesi içerisinde yazının tutulduğu bölgenin adresiyle geri dönmez, yazıyı başka bir bölgeye taşır oranın adresiyle geri döner. (c_str() üye fonksiyonu sınıf içerisindeki yazıyı taşırken taşıma alanı olarak nereyi kullanmaktadır? Bu durum standardizasyonda belirtilmemiştir ancak uygulamada derleyiciler şu yöntemlerden birini kullanmaktadır: Yeni bir dinamik alan yaratma. Bu yöntemde programcı c_str() fonksiyonunu çağırdığında tıpkı yazının tutulduğu gibi yeni bir dinamik alan tahsis edilir. Bu alan bitiş fonksiyonu tarafından free hale getirilmektedir ya da bu alan sınıfın içerisinde normal bir dizi olarak da tahsis edilebilir.)
38
string sınıfının data() üye fonksiyonu tamamen c_str() fonksiyonu gibi kullanılır ancak
bu fonksiyon doğrudan yazının bulunduğu bölgenin adresiyle geri döner. Programcı sonunda ‘\0’ olmadığını hesaba katmalıdır.
STL string Sınıfının İçsel Tasarımı string sınıfı genellikle CString uygulamasında olduğu gibi daha geniş bir tampon bölge
tahsis etme yöntemiyle tasarlanmıştır. Yani yazının tutulduğu blok yazının uzunluğundan büyük olabilmektedir. Sınıfın c_str() fonksiyonu başka bir blok tahsisatına yol açmaktadır. Bu fonksiyon yazıyı yeni bloğa taşıyarak sonuna ‘\0’ ekleyip taşıdığı bloğun adresiyle geri dönmektedir. Sınıfın capacity() fonksiyonu yeniden tahsisat yapılamayacak maksimum yazı uzunluğunu verir. Bu da aslında yazının saklanmasında kullanılan bloğun uzunluğudur. Sınıfın resize() fonksiyonları sınıfın tuttuğu yazıyı büyültüp küçültmekte kullanılır. void resize(size_type num); void resize(size_type num, char c);
Fonksiyonun birinci versiyonunda büyütme yapıldığında belirtilen kısım ‘\0’ karakterler ile, ikinci versiyonunda ikinci parametreyle belirtilen karakterler ile doldurulur. Bu işlemden sonra size() üye fonksiyonu çağırıldığında uzunluk değişir. reserve() üye fonksiyonu yazının tutulduğu blok büyüklüğünü değiştirmekte kullanılır. void reserve(size_type n = 0);
Kapasite önceki değerinden küçültülürse (örneğin default argüman 0 değerini alırsa) blok en fazla yazının uzunluğu kadar küçültülür. Bu fonksiyon bir nesne üzerinde yoğun işlemler yapılırken tahsisat sayısının azaltılması amacıyla capacity değerini yükseltmek için kullanılmaktadır. Klavyeden okuma sırasında doğrudan cin nesnesi yerine STL global getline() fonksiyonu kullanılabilir. Kullanımı şöyledir: string s; getline(cin, s); string sınıfının max_size() fonksiyonu nesnenin teorik olarak tutabileceği maksimum
karakter sayısını vermektedir. Bu değer sistemin limit değeridir ve sistemden sisteme değişebilir, pratik bir anlamı yoktur.
39
STL Sınıflarında Kullanılan Tür İsimleri STL sınıflarının içerisinde kendi sınıfıyla ilgili pek çok typedef edilmiş tür ismi tanımlanmıştır. Bu tür isimleri fonksiyonların parametrik yapılarında kullanılmıştır. Programcı bu fonksiyonlara parametre geçerken ya da geri dönüş değerlerini kullanırken tür uyuşumunu sağlamak için bu typedef isimlerinden faydalanabilir. Bu tür isimlerinin gerçek C++ karşılığının ne olacağı standardizasyonda belirtilmemiştir, derleyicileri yazanlara bırakılmıştır. Örneğin string sınıfında tanımlanan size_type tür ismi pek çok derleyicide unsigned int biçimindedir, ancak unsigned int olmak zorunda değildir. Örneğin standardizasyonda sınıfın find() üye fonksiyonunun geri dönüş değeri size_type türü olarak belirtilmiştir. Parametrik uyumu korumak için int ya da unsigned int yerine bu tür isminin kullanılması daha uygundur. string s(“anakara”); string::size_type pos; pos = s.find(‘k’);
Hemen
sınıfında pointer , const_pointer , reference ve const_reference türleri template açılım türünden gösterici ve referans biçiminde typedef edilmiştir. Template parametresi T olmak üzere bu typedefler şöyledir: typedef typedef typedef typedef
her
STL
T *pointer; const T *const_pointer; T &reference; const T &const_reference;
Algoritma Analizi Bir problemi tam olarak çözen adımlar topluluğuna algoritma denir. Algoritmaların karşılaştırılmasında iki ölçüt kullanılır: hız ve kaynak kullanımı. Ancak hız ölçütü algoritmaların karşılaştırılmasında çok daha önemli bir ölçüt olarak kabul edilmektedir. Algoritmaları hız bakımından karşılaştırmak ve matematiksel bir ifade ile durumu belirlemek çoğu zaman çok zor hatta olanaksızdır. Çünkü örneğin bir dizi üzerinde işlemler yapılırken algoritma dizinin dağılımına göre bir akış izleyebilir ve konu olasılık ve istatistiksel yöntemlere kayar. Algoritmaları hız bakımından kıyaslamak için pratik yöntemler önerilmiştir. Bu pratik yöntemlerden en çok kullanılanı algoritmanın karmaşıklığı (complexity of algorithm)’dır. Algoritmaların kesin kıyaslanması için en iyi yöntem şüphesiz simülasyon yöntemidir. Algoritmanın karmaşıklığını belirlemek için algoritma içerisinde bir işlem seçilir (bu işlem çoğu kez bir if değimi olur) ve algoritmayı çözüme götürmek için en kötü olasılıkla ve ortalama olasılıkla bu işlemden kaç tane gerektiğine bakılır. Dizi işlemlerinde karmaşıklık genellikle dizinin uzunluğunun bir fonksiyonudur. Örneğin, n elemanlı bir dizide bir eleman aranacak olsun, bunun için işlem olarak if değimi seçilebilir, karmaşıklık en kötü olasılıkla n, ortalama (n+1)/2 dir. Algoritmanın karmaşıklığının kesin sayısını bulmak bile bazı durumlarda çok zor olabilmektedir. Bu nedenle karmaşıklıkları sınıflara ayırarak algoritmaları karşılaştırma yöntemine gidilmiştir. Algoritmaları sınıflara ayırarak karşılaştıran yöntemlerden bir tanesi Big O yöntemidir. Big O yöntemine göre en iyiden kötüye doğru karmaşıklık sınıfları şunlardır:
40
O(1) O(log n) O(n) O(n log n) O( n 2 ) O( n 3 ) O( 2 ) n
sabit logaritmik doğrusal doğrusal logaritmik karesel küpsel üstel
Algoritma hiç bir döngü içermiyorsa sabit zamanlıdır, yani çok hızlıdır O(1) ile gösterilir. Algoritma dizinin uzunluğu n olmak üzere, n’in 2 tabanına göre logaritması logaritmik karmaşıklığa sahiptir. Örneğin ikili arama (binary search) işleminin karmaşıklığı log 2 ’dir. Logaritmik karmaşıklıklara kendi kendini çağıran fonksiyonlarda da rastlanır. Program tekil bir döngü içeriyorsa (iç içe olmayan ama ayrık birden fazla olabilen) karmaşıklık doğrusaldır. Karmaşıklık hem logaritmik hem de doğrusal olabilir (quick sort algoritmasında olduğu gibi). Nihayet algoritma iç içe iki döngü içeriyorsa karesel, üç döngü içeriyorsa küpseldir. İki algoritma bu biçimde kategorilere ayrılarak temel bir hız belirlemesi yapılabilir. Daha kesin bir değerlendirme için en kötü ve ortalama olasılıktaki kesin değerler hesaplanabilir. Kesin değerler O(n) = f(n) notasyonu ile gösterilir. Örneğin rastgele bir dizi içerisindeki arama karmaşıklığı O(n) = (n+1)/2’ dir. n
Veri Yapılarındaki Erişim Karmaşıklığı Kullanılan veri yapısının en önemli parametresinden birisi herhangi bir elemana erişileceği zaman bunun zamansal maliyetidir. Bazı veri yapılarında erişim sabit zamanlı (rastgele), bazılarında doğrusal, bazılarında logaritmik olabilir. Bazı veri yapılarında çeşitli özel elemanlara erişmek ile herhangi bir elemana erişmenin karmaşıklığı farklı olabilmektedir. Örneğin, bağlı liste uygulamalarında genellikle listenin ilk ve son elemanı bir göstericide tutulur, bu durumda listenin ilk ve son elemanına erişmek sabit zamanlı bir işlemdir. Ancak herhangi bir elemana erişmenin karmaşıklığı O(n) = (n + 1)/2, yani doğrusaldır. Örneğin ikili ağaç yapısında ağaç tam dengelenmişse herhangi bir elemana erişim logaritmik karmaşıklıktadır. Dizilerde herhangi bir elemana erişim sabit zamanlıdır.
STL’de Nesne Tutan Sınıflar STL’de temel veri yapıları ile nesne tutan pek çok sınıf vardır. Bir veri yapısının nerelerde kullanılacağını bilmek gerekir. STL içerisinde bu sınıflar hazırdır, ancak programlama sırasında bu kararın verilmesi tamamen programcıya bağlıdır. Bir programda hangi nesne tutan sınıfı kullanacağımız uygulamamıza ve o nesne tutan sınıfın algoritmik yapısına bağlıdır.
STL Bağlı Liste Sınıfı Bildirimi “list ” dosyasında olan list isimli template sınıf bağlı liste işlemlerinde kullanılmaktadır. Bağlı liste, elemanları ardışıl bulunmak zorunda olmayan dizilere denir. Bağlı 41
listenin her elemanı sonraki elemanın yerini gösterir, ilk ve son elemanın yeri sınıfın veri elemanlarında tutulur. Tek bağlı listelerde bir elemandan yalnızca ileriye doğru gidilebilir, halbuki çift bağlı listelerde her eleman sonrakinin ve öncekinin yerini tuttuğu için ileri ya da geri gitme işlemi yapılabilmektedir. list sınıfı çift bağlı liste şeklinde oluşturulmuştur. Bağlı listelerde ilk ve son elemana erişmek sabit zamanlı işlemlerdir, herhangi bir elemana erişilmesi doğrusal karmaşıklığa sahiptir. list sınıfı template parametresiyle belirtilen türden nesneleri tutar. Örneğin: list x;
Burada x bağlı listesinin elemanları int türden nesneleri tutar. Ya da örneğin: list y;
Burada y bağlı listesinin her elemanı Person türünden bir yapıyı tutmaktadır. STL bağlı listeleri elemanların kendisini tutan bir yapıdadır, yani bir yapıyı bağlı listeye eklediğimizde onun bir kopyası listede tutulmuş olur. Tabii eleman yerleştiren fonksiyonlar yerleştirilecek bilgiyi adres yoluyla alırlar, bu durum yalnıza fonksiyona parametre aktarımını hızlandırmak için düşünülmüştür. Fonksiyon o adresteki bilginin kendisini bağlı listeye yazmaktadır.
list Sınıfının Başlangıç Fonksiyonları Tüm STL sınıflarında olduğu gibi list sınıfı da allocator sınıfı türünden default bir template parametresi almaktadır. template > class list { //... };
Sınıfın başlangıç fonksiyonları şunlardır: 1- list();
Default başlangıç fonksiyonu ile başlangıçta boş bir bağlı liste yaratılır. 2- list(const list &r);
Kopya başlangıç fonksiyonudur. 3- explicit list(size_t n, const T &value = T());
Bu başlangıç fonksiyonu T template parametresi, yani bağlı listede saklanacak nesnelerin türü olmak üzere n tane bağlı liste elemanı oluşturur. n eleman da T normal türlere ilişkinse 0, sınıf türündense default başlangıç fonksiyonuyla doldurulur. Anahtar Notlar 1: Bir fonksiyonun referans parametresi default değer alabilir. Örneğin, void Func(const X &r = X()) { //...
42
}
Eğer fonksiyon parametresiz çağırılırsa bir geçici nesne oluşturulur, o geçici nesnenin adresi referansa atanır. Geçici nesne fonksiyon sonunda boşaltılır. Anahtar Notlar 2: C++’da C’dekinin yanı sıra ikinci bir tür dönüştürme operatörü daha vardır: tür (ifade). Örneğin, x = double (100);
Aslında başlangıç fonksiyonu yoluyla geçici nesne yaratma bu çeşit bir tür dönüştürmesi işlemidir. Bu tür dönüştürme işleminin iki özel durumu vardır: a- Tür bir sınıf ismiyse parantezin içerisinde virgüllerle ayrılmış birden fazla ifade bulunabilir. b- Tür doğal türlere ilişkinse ve parantezin içi boş bırakılmışsa 0 konulmuş kabul edilir. Bu durum template fonksiyonlar için düşünülmüştür. Bu başlangıç fonksiyonu şöyle kullanılabilir: list x(10); list y(10); list z(10, 500); list k(30, Person(“Noname”));
list sınıfının bitiş fonksiyonu alınan bütün elemanları geri bırakır.
list Sınıfının Önemli Üye Fonksiyonları Sınıfın size() üye fonksiyonu bağlı listedeki eleman sayısına geri döner. size_type size() const;
Örnek: list x(10); assert(x.size() == 10);
Sınıfın empty() üye fonksiyonu bağlı listenin boş olup olmadığı bilgisini verir. bool empty() const;
Sınıfın atama operatör fonksiyonu sol taraftaki operanda ilişkin bağlı listeyi önce boşaltır, sonra sağ taraftaki operanda ilişkin bağlı liste elemanlarının aynısı olacak biçimde yeni liste oluşturur. Örneğin, list x(10, 20); list y(5, 100);
43
y = x;
Burada int türünden iki ayrı bağlı liste vardır. Önce soldaki liste boşaltılır, sonra sağdaki listenin elemanlarından yeni bir bağlı liste yapılır. Her iki bağlı listenin elemanlarında da aynı değerler vardır ama elemanlar gerçekte farklıdır. Sınıfın karşılaştırma operatör fonksiyonları vardır ve bu fonksiyonlar karşılıklı elemanları operatör fonksiyonlarıyla karşılaştırır. Örneğin, if (x > y) { ... }
Sınıfın clear() üye fonksiyonu tüm bağlı liste elemanlarını siler. x.clear(); assert(x.empty());
resize() üye fonksiyonu bağlı listeyi daraltmak ya da genişletmek amacıyla kullanılır. void resize(size_type n, T x = T());
Eğer birinci parametrede girilen sayı bağlı listedeki eleman sayısından azsa bağı liste daraltılır, fazlaysa yeni elemanlar eklenir. Eklenen elemanlar ikinci parametresiyle belirtilen değerleri alır.
list Sınıfının Eleman Ekleyen ve Silen Fonksiyonları Bir bağlı liste için başa ya da sona eleman ekleme, araya eleman insert etme, herhangi bir elemanı silme en çok kullanılan işlemlerdir. Bu işlemleri yapan fonksiyon isimleri diğer nesne tutan sınıflar için de ortak isimlerdir. Genel olarak bütün nesne tutan sınıflar için (bazı istisnaları vardır) front() ve back() isimli fonksiyonlar ilk ve son elemanı almak için (bunları silmezler), push_front() ve push_back() isimli fonksiyonlar başa ve sona eleman eklemek için, pop_front() ve pop_back() isimli fonksiyonlar baştaki ve sondaki elemanları silmek için (silinen elemanları vermezler), insert() isimli fonksiyonlar araya eleman eklemek için ve remove() isimli fonksiyonlar eleman silmek için kullanılırlar. void push_front(const T &x); void push_back(const T &x); void pop_front(); void pop_back(); T &back(); T &front();
Anahtar Notlar : Bir referans geçici bir nesne ile ilk değer verilerek yaratılıyorsa (geçici nesneyi derleyici de oluşturabilir) referansın const olması gerekir. Referans C++’ın doğal türlerindense
44
verilen ilk değer referans ile aynı türden bir nesne değilse ya da sabitse derleyici geçici nesne oluşturacağından yine referansın const olması const olması gerekir. Görüldü Görüldüğü ğü gibi gibi push_front() ve push_back() fonksi fonksiyon yonları ları bilgiy bilgiyii adres adres yoluyl yoluylaa almaktadır. Insert ve delete işlemleri iteratör işlemi gerektirdiği için daha sonra ele alınacaktır. front() ve back() fonksiyonları bağlı liste düğümünde tutulan elemana ilişkin referansa geri
döner. Bu nedenle ilk ve son elemanlar bu fonksiyon yoluyla değiştirilebilir.
Iterator Kavramı Iterat Iterator or STL kütüpha kütüphanesi nesinin nin ana kavraml kavramları arından ndan biridi biridir. r. Iterat Iterator or veri yapıla yapıların rınıı dolaşma dolaşmakta kta kullanılan gösterici gibi kullanılabilen bir türdür. Iterator ya gerçek bir adres türüdür ya da *, -> operatör fonksiyonları yazılmış bir sınıfıtır. Kullanıcı bakış açısıyla iteratör bir gösterici gibi işlem gören bir türdür. STL’de herbir nesne tutan sınıfın içerisinde iteratör diye bir tür ismi vardır. Bu tür ismi ya doğrudan doğrudan templa template te paramet parametres resii türünd türünden en gösteric göstericii typede typedeff ismidi ismidirr ya da o sınıf sınıf içeris içerisind indee tanımlanmış bir sınıfın ismidir. Eğer Eğer itera iteratö törr ismi ismi bir bir göst gösteri erici ci ise ise X nesne nesne tuta tutann sını sınıff olma olmakk üzere üzere şöyle şöyle bir bir bild bildir irim im uygulanmıştır: template class X { public: typedef T *iterator; //... };
Şimdi aşağıdaki gibi bir bildirimde aslında int * türden bir gösterici tanımlanmıştır. X::iterator iter;
Iterator nesne tutan sınıf içerisinde bir sınıf ismi olabilir. Örneğin: template class X { public: class iterator { //... }; };
Şimdi biz aşağıdaki tanımlamayla aslında bir sınıf nesnesi tanımlamış oluyoruz.
45
X::iterator iter;
Iterator ister normal bir gösterici olsun isterse bir sınıf ismi olsun bu durum programcıyı ilgilendirmez. Iteratorler * ve -> operatörleriyle kullanılabilir. Eğer iterator bir göstericiyse * operatörünün zaten doğrudan bir anlamı vardır. Eğer iterator bir sınıf ismi ise o sınıf için * operatör fonksiyonu yazılmış olduğundan ifade yine anlamlıdır. Iterator isminin gerçek türü ne olursa olsun iterator türünden bir nesne * operatörüyle kullanıldığında template parametresi türünden bir nesne belirtir. Iterator türünden nesneye * operatörü uygulandığında elde edilen ifade bir sol taraf değeridir, yani atamanın solunda da sağında da kullanılabilir. Tabii eğer iterator ismi bir sınıf ise bunun sağlanabilmesi için sınıfın * operatör fonksiyonunun geri dönüş değeri T türden referans olmalıdır. Her nesne tutan sınıfın begin() ve end() isimli iki üye fonksiyonu vardır ve bu fonksiyon geri dönüş değeri olarak o sınıf türünden bir iterator verir. iterator begin(); iterator end();
Bir nesne tutan sınıf türünden sınıf nesnesiyle begin() ya da end() üye fonksiyonunu fonksiyonunu çağırırsak bu fonksiyonların geri dönüş değerleri kendi sınıfları içerisindeki iterator türünden olduğu için kendi sınıfları türünden bir iterator nesnesine atanmalıdır. list x; list::iterator iter; iter = x.begin();
Buraya Buraya kadar kadar anlatı anlatılanl lanların arın hepsi hepsi STL içerisi içerisindek ndekii tüm nesne nesne tutan tutan sınıfl sınıflar ar için için geçerli geçerlidir dir.. Örneğin vector de bir nesne tutan sınıftır, onun da içinde bir iterator tür ismi vardır, o sınıfın da begin() ve end() isimli üye fonksiyonları vardır. vector x; vector::iterator iter; iter = x.begin();
Iteratorler Neden Kullanılır? Iterator veri yapısını dolaşmak için gereken gösterici anlamında bir türdür. Nesne tutan sınıfın begin() üye fonksiyonuyla bir iterator alındığında ve bu iterator * operatörüyle kullanıldığında veri yapısının içerisindeki ilk elemana erişilir. Iterator kendi yeteneğine göre ++, -- ve [] operatörleriyle kullanılabilir. ++ sonraki elemana geçme, -- önceki elemana geçme ve [] herhangi bir elemana erişme anlamına gelir. Her sınıfın iteratör türü bu operatörlerin hepsini desteklemek zorunda değildir. Örneğin, list sınıfının iteratör türü ++ ve -- operatörlerini destekler. Iterator isminin gerçek türü ne olursa olsun genel olarak en azından == ve != operatörlerini destekler. >, <, >= ve <= operatörleri her sınıfın iterator türü tarafından desteklenmemektedir. 46
Sınıfın begin() üye fonksiyonuyla elde edilen iterator her zaman ilk elemana ilişkin, end() üye fonksiyonuyla elde edilen iterator her zaman temsili olarak sondan bir sonraki, yani nesne tutan sınıfta olmayan elemana ilişkindir. Bu durumda bir nesne tutan sınıfı baştan sona dolaşmak için kullanılan klasik kalıp aşağıdaki gibidir: list x; ... list::iterator iter; for (iter = x.begin(); iter != x.end(); ++iter) cout << *iter << endl;
Görüldüğü gibi iter önce ilk elemana ilişkin iteratör değerindedir, döngü içerisinde sürekli arttırılır, en sonunda x.end() ile alınan sondan bir sonraki iteratör değerine eşit olur ve döngüden çıkılır.
Iterator’lerin terator’lerin Sınıflandırılması Sınıflandırılması Iterat Iteratoru orunn türü türü o itrat itratoru orunn hangi hangi opera operatö törl rlerl erlee kulla kullanı nıla laca cağı ğını nı ve bu itera iteratö törün rün * ile kullan kullanıld ıldığı ığında nda sol taraf taraf değeri değeri olarak olarak kullan kullanılı ılıpp kullan kullanılm ılmaya ayacağı cağını nı belirl belirler. er. Iterat Iteratorl orler er bu bakımdan beş bölüme ayrılabilir. Iteratorlerin bu biçimde sınıflara ayrılması algoritma diye isimlendirilen hangi fonksiyonlarla kullanılabileceğini belirlemekte faydalı olur. Her nesne tutan sınıfın iteratorleri bu guruplardan birine girer, böylece biz bu iteratorlerle hangi işlemlerin yapılabileceğini anlarız. Iteratorler aşağıdaki guruplara ayrılmaktadır: 1- Input Iteratorleri: Iteratorleri: Input iteratorleri yalnızca okuma yapabilen iteratorlerdir, yani bu guruptan bir iterator * ile kullanıldığında atama operatörünün sol tarafına getirilemez. Input iteratorleri *, ->, =, ++, == ve != operatörleriy operatörleriyle le kullanılabi kullanılabilir. lir. Örneğin Örneğin iter bir input input iterat iterator or gurubundan olsun, *iter = n gibi bir işlemi yapamayız, ancak n = *iter gibi bir işlemi yapabiliriz. ++iter gibi bir işlemi yapabiliriz ama --iter gibi bir işlemi yapamayız. iter1 ve iter2 iki input input iterat iteratorü orü olsun, olsun, bu iki iterat iteratörü örü birbir birbirine ine atayab atayabili iliriz riz,, == ve != operatörleriyle karşılaştırabiliriz. 2- Output Iteratorleri: Iteratorleri: Bu gurup iteratorler veri yapısına yalnızca yazma yapabilen iteratorlerdir, yani iter bir output iterator olsun, *iter = n işlemi geçerlidir, ancak n = *iter işlemi geçerli değildir. Output iteratorleri yalnızca *, = ve ++ operatörlerini destekler. 3- Forward Forward Iteratorler Iteratorler : Bu guru gurupp iter iterat ator orle lerr veri veri yapı yapısı sına na hem hem okum okumaa hem hem de yazm yazmaa yapabilirler, yani input iteratorleri ile output iteratorlerinin birleşimidirler. iter bir forward iterator olmak üzere hem *iter = n işlemi hem de n = *iter işlemi geçerlidir. Forward iteratorler *, ->, =, ++, == ve != operatörlerini desteklerler. 4- Bidirectional Iteratorler : Bu iterator gurubu forward iteratorler gurubunun -- operatörü eklenmiş durumudur. Yani bidirectional iteratorler veri yapısından hem okuma hem yazma yapmakta kullanılabilirler, çift yönlü ilerleyebilirler. Bidirectional iteratorler *, ->, =, ++, --, == ve != operatörlerini desteklerler. 5- Random Access Iteratorler Iteratorler : En yetene yetenekli kli iterat iterator or gurubudu gurubudur. r. Bidire Bidirecti ctiona onall iterat iteratorl orlerin erin yeteneklerine sahiptir, ek olarak [] operatörünü ve diğer karşılaştırma operatörlerini de
Iterator’lerin const Olma Durumuna ve Doğrultularına Göre Sınıflandırılmaları Iteratorler const olma durumlarına ve doğrultularına göre dört guruba ayrılırlar. Her guruptaki iteratorler nesne tutan sınıfların üye fonksiyonlarıyla elde edilebilmektedir. 1- Normal Iteratorler : Normal iteratorler ++ operatörüyle ileri, -- operatörüyle geri giden operatörlerdir. Normal iteratorler const değillerdir, yani iter bir normal iterator olmak üzere *iter = n ve n = *iter biçimlerinde kullanılabilirler. Normal iteratorler nesne tutan sınıfların iterator tür ismiyle temsil edilir ve begin(), end() üye fonksiyonlarıyla elde edilir. 2- const Iteratorler : Normal iteratorler const olmayan bir göstericiyi temsil ediyorsa const iteratorler const bir göstericiyi temsil ederler. iter bir const iterator olmak üzere *iter = n ifadesi geçersizdir ama n = *iter ifadesi geçerlidir. Nesne tutan sınıfların const_iterator biçiminde bir tür ismi vardır, const iterator nesne tutan sınıfların begin() ve end() üye fonksiyonlarıyla elde edilebilir. Örneğin: list::const_iterator iter; list x; ... iter = x.begin(); *iter = 10; //error cout << *iter << ‘\n’; //ok
3- Reverse Iteratorler : Bu tür iteratorler const değildirler, ancak doğrultuları terstir, yani ++ operatörüyle geriye -- operatörüyle ileri doğru giderler. Reverse iteratorler algoritma denilen global fonksiyonların ters yönlü çalışmalarını mümkün hale getirmek için düşünülmüştür. Nesne tutan sınıfların reverse_iterator biçiminde bir tür ismi vardır. Reverse iterator almak için nesne tutan sınıfların rbegin() ve rend() isimli üye fonksiyonları kullanılır. rbegin() üye fonksiyonu son elemana ilişkin iterator değerini, rend() fonksiyonu baştan bir önceki elemana ilişkin iterator değerini verir. Örneğin aşağıdaki işlemle bir bağlı liste sondan başa yazdırılabilir: list x; list::reverse_iterator riter; for (int i = 0; i < 100; ++i) x.push_back(i); for (riter = x.rbegin(); riter != x.rend(); riter++) cout << *iter << ‘\n’;
Aslında bu işlem normal bir iteratörle sondan başa gidilerek de yapılabilirdi. Örneğin: list x; list::iterator iter; for (int i = 0; i < 100; ++i) x.push_back(i);
48
iter = x.end(); --iter; for (; iter != x.begin(); --iter) cout << *iter << ‘\n’; cout << *iter << ‘\n’;
Reverese iterator aslında global STL fonksiyonlarının düz doğrultuda yazıldığı halde onların ters de çalışmasını sağlamak amacıyla kullanılmaktadır. Örneğin, iki iteratör arasını ekrana yazdıran Disp() isimli bir template fonksiyon olsun, bu fonksiyon yalnızca ileri doğrultuda yazılmış olsun. templeta void Disp(T iter1, T iter2) { T iter; for (iter = iter1; iter != iter2; ++iter) cout << *iter << ‘\n’; }
Şimdi bu fonksiyonun aşağıdaki gibi çağırıldığını düşünelim: list x; for (int i = 0; i < 100; ++i) x.push_back(i); Disp(x.begin(), x.end());
Derleyici template fonksiyonu T türünü list::iterator olarak alıp açar. Fonksiyon da bağlı listenin tüm elemanlarını düz sırada yazar. Disp() fonksiyonu düz doğrultuda hareket ettiği halde reverse iterator kavramıyla biz ters doğrultuda işlem yaptırabiliriz. Disp(x.rbegin(), x.rend());
Şimdi derleyici T türünü list::reverse_iterator olarak alıp fonksiyonu yazar. Bu durumda Disp() fonksiyonu geriye doğru yazma işlemini yapacaktır. 4- const Reverse Iteratorler : Bu iteratorler hem const hem reverse özellik gösterirler. Nesne tutan sınıfların const_reverse_iterator biçiminde bir tür ismi vardır. const reverse iteratorlere nesne tutan sınıfların rbegin() ve rend() üye fonksiyonlarıyla iterator alınabilir.
STL Algoritmaları STL içerisindeki global template fonksiyonlara algoritma denilmektedir. STL template fonksiyonları iteratorlerle çalışacak biçimde yazılmışlardır, yani bu fonksiyonlar özellikle *, ++, --, !=, == gibi operatörlerle yazılmışlardır. Fonksiyonların parametreleri özellikle prototiplerinde iterator türü belirtilerek isimlendirilmişlerdir, bu da programcının bu fonksiyonları hangi iteratorlerle kullanabileceğini, başka bir deyişle bu fonksiyonların hangi 49
operatörler kullanılarak yazıldığını anlamasına olanak sağlar. STL algoritmaları “algorithm” başlık dosyası içerisinde bildirilmiştir. Bu bölümde bazı STL algoritmaları ele alınacaktır. STL algoritmaları çeşitli guruplara ayrılarak incelenebilir. Guruplandırma konusunda bir fikir birliği yoktur. Bazı yazarlar aşağıdaki guruplandırma biçimini tercih etmektedir: 123456-
Değer değiştiren algoritmalar (modifying algorithms) Değer değiştirmeyen algoritmalar (nonmodifying algorithms) Silme yapan algoritmalar (removing algorithms) Sıralama yapan algoritmalar (sorting algorithms) Sıralanmış diziler üzerinde işlem yapan algoritmalar (sorted range algorithms) Sayısal işlem yapan algoritmalar (numeric algorithms)
STL algoritmalarının hepsi başlangıç ve bitiş iterator parametrelerini aldıklarında başlangıç iteratorünü dahil, bitiş iteratorünü dahil değil olarak tanımlarlar. Yani [begin, end) aralığında çalışırlar.
accumulate() Fonksiyonu Bu fonksiyon bir veri yapısındaki belirli aralıktaki değerlerin toplamını bulmak amacıyla kullanılmaktadır. template T accumulate(InputIterator beg, InputIterator end, T initValue);
Fonksiyon iki template argümanı almıştır, fonksiyonun üç parametresi vardır, ilk iki parametre başlangıç ve bitiş iterator durumlarıdır, üçüncü parametre toplamın başlangıç değeridir. Bütün durumlarda belirtilen aralık soldan kapalı sağdan açık aralıktır. Bu fonksiyon iki iterator arasını toplayacak bir işleve sahiptir. Fonksiyon geri dönüş değeri olarak toplam değeri vermektedir. Fonksiyon parametreleri olan iteratorlerin gurubu input iteratordür. Fonksiyon muhtemelen aşağıdaki gibi yazılmış olmalıdır: template T accumulate(InputIterator beg, InputIterator end, T initValue) { T total = initValue; InputIterator iter; for (iter = beg; iter != end; ++iter) total += *iter; return total; }
STL algoritmalarının hepsi normal dizilerle de çalışabilir, çünkü dizinin başlangıç ve bitiş adresleri bir iterator olarak kullanılabilirler. Burada dikkat edilmesi gereken nokta şudur: dizinin tüm elemanları üzerinde işlemler yapılmak istendiğinde başlangıç iteratoru olarak dizinin ilk elemanının adresi, bitiş iteratorü olarak sondan bir sonraki elemanın adresinin verilmesi gerekir.
50
Çünkü bütün STL algoritmaları daha önce de belirtildiği gibi soldan kapalı sağdan açık aralıklar üzerinde işlem yapmaktadır. accumulate() fonksiyonunun örnek kullanımları şöyle olabilir: list x; for (int i = 0; i < 100; ++i) x.push_back(i); int total; total = accumulate(x.begin(), x.end(), 0); int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; total = accumulate(a, &a[10], 0);
Bazı STL algoritmaları dosyasının içerisindedir, accumulate() fonksiyonu bu dosya içerisindedir. Örnek: #include #include #include using namespace std; void main() { list x; int total; for (int i = 1; i <= 100; ++i) x.push_back(i); total = accumulate(x.begin(), x.end(), 0); cout << total << '\n'; }
for_each() Fonksiyonu Bu fonksiyon bir dizideki ya da nesne tutan sınıftaki tüm elemanları belirli bir fonksiyona sokmak için kullanılır. Bu fonksiyon pek çok derleyicide aşağıdaki gibi yazılmıştır: template Function for_each(InputIterator first, InputIterator last, Function f) { while (first != last) f(*first++); return f; }
51
Görüldüğü gibi for_each() fonksiyonu üç tane parametre alır, ilk iki parametre iterator ya da göstericidir, üçüncü parametre bir fonksiyon adresidir. Derleyici template fonksiyonu üçüncü parametre bir fonksiyon göstericisiymiş gibi açar. Buradaki fonksiyonun parametresi dizi ya da nesne tutan sınıf türünden normal bir parametre değişkeni ya da referans olmalıdır. Fonksiyon üçüncü parametresiyle verilen fonksiyon adresine geri döner. Üçüncü parametresiyle belirtilen fonksiyonun geri dönüş değeri herhangi bir biçimde olabilir. Örnek: #include #include #include using namespace std; void Disp(int x) { cout << x << endl; } void main() { int a[] = {1, 2, 3, 56, 65, 56, 12}; for_each(a, a+7, Disp); }
for_each() fonksiyonu bir veri yapısının elemanları üzerinde belirlenen bir işlemi uygulamak
için tercih edilmelidir.
transform() Fonksiyonu Bu fonksiyon bir veri yapısının elemanları üzerinde bir işlem uygulayıp sonucu başka bir veri yapısına yazmak için kullanılır. Tipik yazım biçimi şöyledir: template OutputIterator transform(InputIterator first, InputIterator last OutputIterator result, Function f) { while(first != last) *result++ = f(*first++); return result; }
Fonksiyon dört parametreyle kullanılır, ilk iki parametre kaynak veri yapısındaki iterator aralığını belirtir, üçüncü parametre hedef veri yapısındaki başlangıç yerini belirtir, son parametre uygulanacak işlemi belirten bir fonksiyon olmalıdır. Son parametreye ilişkin fonksiyonun parametresi veri yapısı türünden normal bir nesne ya da referans olmalıdır, geri dönüş değeri veri yapısı türünden olmalıdır. Örnek: #include
52
#include #include using namespace std; int Square(int a) { return a * a; } void Disp(int x) { cout << x << endl; } void main() { int a[] = {1, 2, 3, 56, 65, 56, 9}; list b(7); transform(a, a+7, b.begin(), Square); for_each(b.begin(), b.end(), Disp); }
Sınıf Çalışması: int türden bir bağlı liste ve 10 elemanlı bir dizi açınız, bağlı liste içerisindeki sayıları diziye karelerini alarak tersten yerleştiriniz. Açıklama: Bağlı liste a, dizi ise b olsun. Kare alan fonksiyon ismi Square ise işlem transform(a.rbegin(), a.rend(), b, Square);
biçiminde yapılabilir. Cevap: #include #include #include using namespace std; int Square(int a) { return a * a; } void Disp(int x) { cout << x << endl; } void main() {
53
int a[10]; list b; for (int i = 1; i <= 10; ++i) b.push_back(i); transform(b.rbegin(), b.rend(), a, Square); for_each(a, a+10, Disp); }
sort() Fonksiyonu Bu fonksiyonun iki biçimi vardır: 1- template void sort(RandomIterator first, RandomIterator last); 2- template void sort(RandomIterator first, RandomIterator last, Pred &r);
sort() fonksiynunun birinci biçimi < operatörü kullanılarak yazılmıştır. Sort edilecek veri
yapısı bir sınıf ise < operator fonksiyonunun yazılması gerekir. İkinci biçim üçüncü parametre olarak bir karşılaştırma fonksiyonu almıştır. Bu karşılaştırma fonksiyonu dizinin iki elemanı ile çağırılacaktır eğer soldaki eleman sağdaki elemandan küçükse sıfırdışı bir değere dönmelidir. Örnek: #include #include using namespace std; void Disp(int x) { cout << x << endl; } void main() { int a[] = { 1, 4, 2, 3, 8, 7}; sort(a, a + 6); for_each(a, a + 6, Disp); }
Fonksiyonun algoritmik karmaşıklığı O(nlogn) dir. Muhtemelen quick sort algoritması ile yazılmıştır. Person türünden bir class’ı numarasına göre sıraya dizen örnek: #include #include using namespace std;
Fonksiyon default olarak diziyi küçükten büyüğe sıralar, büyükten küçüğe sıralamak için söz konusu dizi bir sınıf dizisi ise sort() fonksiyonunun birinci versiyonunu sınıfın < operator fonksiyonu ters yazılarak sınıf düzenlenebilir. Bu işlemin en kolay yolu fonksiyonun ikinci versiyonunu kullanmak fakat son parametredeki karşılaştırma fonksiyonunu küçükse sıfır, küçük değilse sıfırdışı bir değere döndürmektir. Örnek: #include #include using namespace std; void Disp(int a) { cout << a << endl; } bool Cmp(int a, int b) { return !(a < b);
55
} void main() { int a[] = {1, 5, 3, 7, 2, 7 , 8}; sort(a, a + 7, Cmp); for_each(a, a + 7, Disp); }
reverse() Fonksiyonu Bu fonksiyon bir veri yapısını tersyüz etmekte kullanılır. template void reverse(BidirectionalIT first, Bidirectional last);
Örnek: #include #include #include using namespace std; void Disp(int a) { cout << a << endl; } void main() { list x; for (int i = 1; i <= 10; ++i) x.push_back(i); reverse(x.begin(), x.end()); for_each(x.begin(), x.end(), Disp); }
string sınıfı iterator işlemlerini desteklemektedir. string sınıfının iteratorleri random access iteratorlerdir. Örneğin string sınıfının tersyüz eden bir üye fonksiyonu yoktur bu işlem şöyle
yapılabilir: #include #include #include using namespace std; void main() {
56
string a("ankara"); reverse(a.begin(), a.end()); cout << a << endl; }
Bu fonksiyon üç parametreye sahiptir. İlk iki parametre kaynak dizideki iterator aralığını belirtir, üçüncü parametre hedef dizideki kopyalamanın yapılacağı başlangıç iterator pozisyonunu belirtmektedir. Fonksiyon hedef dizideki kopyalama işleminden sonraki elemanın iterator değeriyle geri döner. Kullanımına örnek: int a[5] = { 3, 5, 7, 9, 3 }; int b[5]; copy(a, a + 5, b);
ostream_iterator Sınıfı ostream_iterator sınıfı aşağıdaki gibi tanımlanmış bir template sınıftır: template > class ostream_iterator { //... };
Sınıf başlık dosyası içerisindedir, ancak bu başlık dosyası başlık dosyası içerisinden include edildiğinden yalnızca dosyasının include edilmesi yeterlidir. Sınıf üç template parametresi alır. Birinci parametre zorunludur. Sınıfın iki başlangıç fonksiyonu vardır: 1- ostream_iterator(ostream &r); 2- ostream_iterator(ostream &r, const char *delim);
Sınıf bir output iterator işlemini temsil eder, bu yüzden output iteratorlerin sahip olduğu operator fonksiyonlarına sahiptir. Sınıfın operatör fonksiyonları şunları yapmaktadır:
57
1- Sınıfın * operator fonklsiyonu hiçbir şey yapmaz, yalnızca nesnenin kendisine geri döner. Örneğin, ositer bu sınıf türünden bir nesne olsun, *ositer tamamen ositer ifadesine eşdeğerdir (yani bu operatör fonksiyonu içerisinde *this ile geri dönülmüştür). 2- Sınıfın ++ operatör fonksiyonu da tıpkı * operatör fonksiyonu gibi birşey yapmaz nesnenin kendisi ile geri döner. 3- Sınıfın template parametresi türünden parametreli bir atama operatör fonksiyonu vardır. Bu fonksiyon atanan değeri başlangıç fonksiyonunda belirtilen ostream nesnesi yoluyla ekrana yazdırır. Ekrana yazdırma işleminden sonra eğer nesne yaratılırken ikinci başlangıç fonksiyonu kullanılmışsa, ikinci başlangıç fonksiyonunda belirtilen yazı yazdırma işleminden sonra ayıraç olarak ekrana basılmaktadır. Örneğin aşağıdaki kodda ekrana 10-20 basılacaktır. ostream_iterator x(cout, “-“); x = 10; x = 20;
= operatör fonksiyonunun olası yazılmış biçimi şöyledir: ostream_iterator operator =(const T &r) { m_cout << r << m_delim; return *this; }
Sınıfın = operatör fonksiyonu nesnenin kendisine geri dönmektedir. ostream_iterator sınıfı algoritmalar kullanılarak ekrana ve dosyaya yazma amaçlı
düşünülmüştür. ostream_iterator sınıfı sayesinde copy() fonksiyonunu kullanarak bir dizi ya da
nesne tutan sınıf bir hamlede ekrana ya da dosyaya yazdırılabilmektedir. Örneğin: int a[] = { 3, 6, 4, 6, 2 }; ostream_iterator x(cout, “ “); copy(a, a + 5, x);
Şüphesiz yukarıdaki örnekte ekrana yazdırma işlemi * ya da ++ operatörleri yüzünden değil atama operatörü yüzünden yapılmaktadır. Yani copy() içerisinde şu tema kullanılmıştır: *ositer++ = val;
Burada * ve ++ hiçbir şey yapmayıp ositer nesnesinin kendisine geri döndüğüne göre aslında bu ifade ositer = val; ile eşdeğerdir. Bu da val değerinin ekrana yazılmasına yol açacaktır.
58
ostream_iterator türünden nesne copy() fonksiyonunun parametresinde o anda
geçici olarak da yaratılabilir. Örneğin: int a[] = { 4, 6, 4, 3, 2 }; copy(a, a + 5, ostream_iterator(cout, “ “));
ostream_iterator sınıfının template parametresi aslında ekrana yazdırılacak bilginin türünü belirtmektedir. Sınıfın başlangıç fonksiyonundaki ostream nesnesi ise hangi nesne
kullanılarak yazdırma yapılacağını belirtir. Tipik durum cout nesnesinin kullanılmasıdır. Ancak ofstream ya da fstream türünden nesneler kullanılarak dosyaya yazma yaptırılabilir.
copy_backward() Fonksiyonu Bu fonksiyon tersyüz ederek kopyalama yapmaktadır. template BiIterSource copy_backward(BiIterSource first, BiIterSource last, BiIterDest destLast);
Fonksiyon ilk iki parametresinde kaynak dizinin iterator aralığını alır, son parametrede hedef dizinin son iterator değerini alır (yani sondan bir sonraki değeri). Kaynak diziyi arttırarak hedef diziyi azaltarak kopyalama yapmaktadır. Örneğin: int a[5] = { 3, 6, 5, 1, 4 }; int b[5]; copy_backward(a, a + 5, b + 5); copy(b, b + 5, ostream_iterator(cout, “ “));
copy_backward() tamamen copy() fonksiyonu ile kopyalandıktan sonra reverse() ile
tersyüz edilmesine eşdeğer bir işlem yapar.
random_shuffle() Fonksiyonu Bu fonksiyon bir dizideki elemanları rastgele bir biçimde yerleştirmek için kullanılır. template void random_shuffle(RanIt first, RanIt last);
Bu fonksiyon iki iterator aralığı alarak aradaki elemanları karıştırır. Iterator random access iterator olmak zorundadır. Tipik olarak oyun programlarında rastgele bir oluşum sağlamak amacıyla kullanılır. Bu fonksiyon kendi içerisinde rastgele sayı üreten bir fonksiyon kullanıp karıştırma işlemi yapmaktadır. Rastgele sayı üreten fonksiyon olarak standart rand() 59
fonksiyonunun kullanılacağı standardizasyonda garanti edilmemiştir. Programın her çalışmasında farklı bir karışım elde etmek için böyle bir garantinin olması gerekir. Fonksiyonun ikinci versiyonu karıştırmada kullanılacak rastgele sayı üreten fonksiyonu da parametre olarak almaktadır. template void random_shuffle(RanIt first, RanIt last, RandFunc Func);
Fonksiyonun son parametresi 0 ile (dizi uzunluğu – 1) aralığında rastgele sayı üreten bir fonksiyon olmalıdır. Sözkonusu fonksiyonun parametresi olmamalıdır. Örneğin programın her çalışmasında 5 elemanlı bir diziyi rastgele sıraya dizen kod şöyle yazılabilir: int MyRand() { return rand() % 5; } srand(time(NULL)); int a[] = {3, 8, 4, 7, 6}; random_shuffle(a, a + 5, MyRand);
Sınıf Çalışması: Klavyeden alınan yazının sözcüklerini ters sırada yazan programı aşağıda belirtildiği gibi STL kullanarak yazınız. Açıklamalar: 1- Yazı klavyeden istream::getline() ya da standart gets() fonksiyonuyla alınır ve
string türünden bir nesneye atanır. 2- string sınıfının find_first_of() fonksiyonu ile find_first_not_of() fonksiyonu bir döngü içerisinde çağırılarak substr() fonksiyonu da kullanılarak sözcükler
elde edilir. 3- Elde edilen her sözcük string türünden bir bağlı listenin sonuna eklenir. 4- for_each() fonksiyonu ile reverse_iterator kullanarak ters sırada yazdırma işlemi yapılır. Cevap: /* reverse_write.cpp */ #pragma warning(disable:4786) #include #include #include #include
//4786 numaralı warning’i gösterme //256 karakterden uzun template //açılımları yüzünden bu olur
Sınıf Çalışması: Türemiş sınıf nesnelerinin adreslerini taban sınıf türünden göstericilerden oluşan bir bağlı listede saklayıp for_each() fonksiyonunu kullanarak ya da manuel olarak bütün nesne adresleri için disp() isimli bir sanal fonksiyonu çağırınız. Açıklamalar: 1- Türetme şeması aşağıdaki gibi olacaktır: A
B
C
D
E
2- A sınıfı Disp() safsanal fonksiyonunu içeren soyut bir sınıf olabilir.
3- Program döngü içerisinde aşağıdaki gibi bir menü çıkartmalıdır: 61
1) B nesnesi yarat 2) 3) 4) 5) 6)
C nesnesi yarat D nesnesi yarat E nesnesi yarat Listele Çıkış