+ Konu Cevaplama Paneli
1. Sayfa - Toplam 3 Sayfa var 1 2 3 SonuncuSonuncu
Gösterilen sonuçlar: 1 ile 10 ve 22

Konu: C++ İleri Derecede Programlama

  1. #1
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb C++ İleri Derecede Programlama

    İLERİ DERECEDE C++ PROGRAMLAMA
    Tam grafiklerle desteklenmiş kaynak dosyaya GİT


    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ın içerisine nesne yaratmayan bildirim işlemleri yerleştirilir. Yani X.h dosyasının içeriği şunlar olabilir:

    - Sınıf bildirimi
    - Sembolik sabit tanımlamaları
    - typedef ve enum bildirimleri
    - Konuyla ilgili çeşitli global fonksiyonların prototipleri
    - inline fonksiyon tanımlamaları
    - Global const değişken tanımlamaları

    X.cpp dosyası içerisine sınıfın üye fonksiyonlarının tanımlamaları, sınıf ile ilgili global 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 Update : 20/03/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_

    <Dosya içeriği>

    #endif

    Buradaki sembolik sabit ismi dosya isminden hareketle oluşturulmuş herhangi bir isimdir.

    Bir başlık dosyasının birden fazla include edilmesi genellikle zorunluluk nedeniyle oluşur. Örneğin programcı bir sınıf için A.h başka bir sınıf için ise B.h dosyalarının include etmiş olsun. Bu dosyaların kendi içlerinde 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 include koruması uygulandığından problem oluşmayacaktır. Ancak önişlemci işlemlerini 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 içerisinde ek korumaya takılır, dolayısıyla daha general.h dosyasını açmadan dosya önişlemci dışı bırakılır. Dosyanın açıldıktan sonra önişlemci dışı bırakılmasıyla açılmadan önişlemci 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 / 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 geliştirirken disk üzerinde proje için bir dizin oluşturulmalı ve düzenli bir çalışma 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 dizininde, object modüller ve çalışabilen programlar BIN alt dizininde, projenin kullandığı 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.


    Değişkenlerin İ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 belirlenmelidir. C++’da yapılarda bir sınıf olduğu için yapılarla sınıflar aynı biçimde 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 karakter de konulabilmektedir. Örneğin MFC’de sınıf isimlerinin başı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şkenler de özel bir biçimde isimlendirilmelidir. Pek çok programcı global değişkenleri ‘g_’ öneki ile başlatarak isimlendirmektedir.
    - Üye fonksiyonlar içerisinde global fonksiyonlar çağırılırken vurgulama için unary :: 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, 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 bildirimleri sonra üye fonksiyon bildirimleri sonra da veri eleman 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:
    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) 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.
    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 ç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 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;
    //...
    };

    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 (o) 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












    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;


    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.
    - void Empty();
    Nesnenin tuttuğu yazıyı siler.
    - void SetAt(unsigned index, char ch);
    char GetAt(unsigned index) const;
    Bu fonksiyonlar yazının belli bir karakterini alıp yerleştirmekte kullanılır.
    - int Compare(PCSTR s) const;
    Nesnenin içerisindeki yazı ile parametre olarak girilen yazıyı karşılaştırır.
    - int CompareNoCase(PCSTR s) const;
    Nesnenin tuttuğu yazı ile parametre olarak girilen yazı büyük harf küçük harf duyarlılığı olmadan karşılaştırılır.
    - CString Left(int count) const;
    CString Right(int count) const;
    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”);
    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’;



    - 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); // puts(s.operator const char *());




    Anımsatma: 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:

    i- İşlemi doğrudan yapacak global ya da üye operatör fonksiyonu araştırır. Her ikisinin birden bulunması error oluşturur.
    ii- 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.
    iii- 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;
    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;


    CStringsı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:

    CString s = “Ankara”;
    char *pUpdate;

    pUpdate = s.GetBuffer(30);
    s.ReleaseBuffer(-1);

    - CString 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 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.




    Anımsatma: 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.

    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.

    Anımsatma: 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:

    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.

    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 128

    class Shell {
    public:
    Shell(const char *pPrompt):m_prompt(pPrompt){}
    void Run();
    private:
    CString m_prompt;
    CString m_command;
    CString m_param;
    static char *m_cmd[];
    };

    typedef struct _CMDPROC {
    char *pCommand;
    void (*pProc)(Shell *pShell);
    } CMDPROC;

    void DirProc(Shell *pShell);
    void RenameProc(Shell *pShell);
    void CopyProc(Shell *pShell);
    void MoveProc(Shell *pShell);
    void RemoveProc(Shell *pShell);
    void XcopyProc(Shell *pShell);
    void QuitProc(Shell *pShell);

    #endif

    /* shell.cpp */

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream.h>
    #include "general.h"
    #include "cstring.h"
    #include "shell.h"

    CMDPROC cmdProc[] = {{"dir", DirProc},
    {"rename", RenameProc},
    {"copy", CopyProc},
    {"remove", RemoveProc},
    {"xcopy", XcopyProc},
    {"move", MoveProc},
    {"quit", QuitProc},
    {NULL, NULL}};

    void DirProc(Shell *pShell)
    {
    cout << "DirProc, param :" << endl;
    }
    void RenameProc(Shell *pShell)
    {
    cout << "RenameProc, param :" << endl;
    }

    void CopyProc(Shell *pShell)
    {
    cout << "CopyProc, param :" << endl;
    }

    void MoveProc(Shell *pShell)
    {
    cout << "MoveProc, param :" << endl;
    }

    void RemoveProc(Shell *pShell)
    {
    cout << "RemoveProc, param :" << endl;
    }

    void XcopyProc(Shell *pShell)
    {
    cout << "XcopyProc, param :" << endl;
    }

    void QuitProc(Shell *pShell)
    {
    exit(1);
    }

    void Shell::Run()
    {
    char buf[LINELEN];
    CString cmd;

    for (; {
    cout << m_prompt << '>';
    gets(buf);
    cmd = buf;
    cmd.TrimLeft();
    int cmdIndex = cmd.FindOneOf(" \t\0");
    m_command = cmd.Left(cmdIndex);
    m_param = cmd.Mid(cmdIndex);
    m_param.TrimLeft();
    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 yapan bir sınıf vardır. Java, C# gibi nesne yönelimli dillerde dosya 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ılabilmektedir. Maalesef bu sınıf sistemi dosya işlemleri için yetersiz kalmaktadır. Bu nedenle dosya işlemleri için iostream sınıf sistemini kullanmak yerine çoğu kez bu işlem için ayrı bir sınıf tasarlama yoluna gidilmektedir. MFC kütüphanesinde dosya işlemleri için CFile isimli bir sınıf tasarlanmıştır. CFile sınıfı binary dosyalar üzerinde işlemler yapar, CFile sınıfından CStdioFile isimli bir sınıf türetilmiştir, bu sınıf da text dosyaları üzerinde işlem 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.

    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 yalnızca B.h dosyasının include edilmesinin probleme yol açıp 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 include edilmesi gerekir (genellikle bu durum composition ya da aggregation durumudur). Eğer A sınıfı yalnızca B sınıfının üye fonksiyonları içerisinde kullanılmışsa bu durumda A sınıfı yalnızca B.cpp içerisinde kullanılmıştır, dolayısıyla 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 programcısı tarafından zorunlu olarak include edilmesi gereken bir durum biçiminde 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 bölümündeyse çözünürlük operatörüyle dışarıdan kullanılabilir. Büyük projelerde global alandaki isim çakışmasını engellemek için sembolik sabitlerin #define ile 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);

    Default başlangıç fonksiyonu ile nesne yaratılırsa dosya daha sonra sınıfın CFile::Open() üye fonksiyonuyla açılmalıdır. İkinci başlangıç fonksiyonu daha önce fopen() fonksiyonu ile açılmış olan dosyayı sınıf ile ilişkilendirilip kullanmak için düşünülmüştür. Nihayet üçüncü fonksiyon, ismi ile belirtilen dosyayı belirtilen modda açar.

    - 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 bir kontrol yapar, bu kontrolle başlangıç işlemlerinin yapılıp yapılmadığını anlar, yapılmışsa onu geri alır, 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()
    {
    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.

    - Sınıfın Close() fonksiyonu dosyayı kapatmaktadır.

    virtual void Close();

    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;

    Seek() 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.

    - 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.

    CFile 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.

    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::CFile(....)
    {
    if (Dosya açılamadıysa)
    m_bSuccess = FALSE;
    //...
    }

    BOOL CFile:perator !() const
    {
    return !m_bSuccess;
    }

  2. #2
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb



    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(...);
    }



    İ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 <class T>
    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 <class T>
    void Func(T a)
    {
    T p;
    //...
    }

    Burada eğer fonksiyon,

    int a[10];
    Func(a);

    biçiminde çağırılırsa T türü int * anlamına gelir. Dolayısıyla template fonksiyonu içerisindeki ‘pint * 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<double>(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<int>(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 olmaz.

    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<int> a;

    Bir tamplate sınıfta template parametresi default bir tür ismi alabilir. Örneğin:

    template <class T1 = int, class T2 = float>
    class Sample {
    //...
    };

    Bu default template parametreleri açılımda belirtilmezse etkili olur.

    Sample<> a;
    Sample<long> a;
    Sample<short, long> 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:

    Sample<int>Func();
    void Func(Sample<int> *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 <class T>
    void Sample<T>::Func()
    {
    //...
    }

    Normal bir sınıfın her hangi bir fonksiyonu template fonksiyon olabilir (İngilizce member template denilmektedir). Örneğin:

    class Sample {
    public:
    template <class T>
    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 T>
    class A {
    //...
    };

    class B : public A<int> {
    //...
    };

    /* or */

    class A {
    //...
    };

    template <class T>
    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 T>
    class A {
    //...
    };

    template <class T>
    class B : public A<T> {
    //...
    };

    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<list<int> > 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 T>
    class Sample {
    //...
    };

    template <> // Bu yazılmak zorunda değil
    class Sample<int> {
    //...
    };

    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<int> a;

    biçiminde açılırsa aşağıdaki template olmayan sınıf kullanılacaktır.




    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 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() /* error */
    {
    }
    }

    void X::Func() /* 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<int> x;


    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 <namespace_ismi>;

    Örneğin:

    using namespace std;

    2- using <namespace_ismi>::<namespace içerisindeki bir isim>

    Ö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:

    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_aB 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<char> 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 E,
    class T = char_traits<E>,
    class A = allocator<E> >
    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 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<char> x;

    biçiminde tanımlanır, UNICODE yazıyı tutmak için nesne

    basic_string<wchar_t> 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<char> 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 E, class T = char_traits<E>, class A = allocator<E> >
    bool basic_string<E, T, A>:perator <(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<char, trk_traits> trkstring;

    trkstring a(“ılgaz”);
    trkstring b(“ismail”);

    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”);

    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 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.



    Sınıfın elemana erişim için kullanılan bir [] operatör fonksiyonu vardır.

    char &string:perator [](size_type idx);
    char string:perator [](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.

    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.)

    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.




    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 her STL 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 T *pointer;
    typedef const T *const_pointer;
    typedef T &reference;
    typedef 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:

    O(1) sabit
    O(log n) logaritmik
    O(n) doğrusal
    O(n log n) doğrusal logaritmik
    O( ) karesel
    O( ) küpsel
    O( ) ü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ığı ’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.


    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ı 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<int> x;

    Burada x bağlı listesinin elemanları int türden nesneleri tutar. Ya da örneğin:

    list<Person> 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 T, class A = allocator<T> >
    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())
    {
    //...
    }

    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<int> x(10);
    list<Person> y(10);
    list<int> z(10, 500);
    list<Person> 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<int> 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<int> x(10, 20);
    list<int> y(5, 100);
    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 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ı gerekir.

    Görüldüğü gibi push_front() ve push_back() fonksiyonları bilgiyi adres yoluyla 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ı


    Iterator STL kütüphanesinin ana kavramlarından biridir. Iterator veri yapılarını dolaşmakta 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 template parametresi türünden gösterici typedef ismidir ya da o sınıf içerisinde tanımlanmış bir sınıfın ismidir.

    Eğer iteratör ismi bir gösterici ise X nesne tutan sınıf olmak üzere şöyle bir bildirim uygulanmıştır:

    template <class T, ...>
    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<int>::iterator iter;

    Iterator nesne tutan sınıf içerisinde bir sınıf ismi olabilir. Örneğin:

    template <class T, ...>
    class X {
    public:
    class iterator {
    //...
    };
    };

    Şimdi biz aşağıdaki tanımlamayla aslında bir sınıf nesnesi tanımlamış oluyoruz.

    X<int>::iterator iter;

  3. #3
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb



    vector Sınıfı ile Normal Dizilerin Karşılaştırılması

    vector sınıfı tamamen bir dizi gibi kullanılabilmektedir.

    1- vector otomatik olarak insert() ve push_back() işlemleri ile büyütülür. Bu da kolay kullanım sağlar.
    2- vector sınıfını normal dizi gibi kullandığımızda normal dizilere göre erişim göreli olarak daha yavaştır.


    reserve() Fonksiyonu

    reserve() fonksiyonu string sınıfında olduğu gibi vector sınıfında da kapasiteyi arttırmak amacı ile kullanılır. Tabii vector sınıfında aslında kapasite hiç bir zaman küçültülmemektedir.

    void reserve(size_type n);

    Fonksiyonun parametresi önceki kapasitenin değerinden büyük ya da önceki değere eşitse yeniden tahsisat yapılır ve vector alanı capacity = n yapılarak büyütülür. Tabii bu işlem size değerini etkilememektedir. Parametre kapasite değerinden küçük ise fonksiyon hiç bir şey yapmaz. Eğer çalışacağımız dizinin uzunluğunu kestirebiliyorsak aşağıdaki gibi hemen başlangıçta bir reserve() işleminin yapılması hız bakımından faydalı olabilir.

    vector<int> v;
    v.reserve(80);

    reserve() işleminden sonra elde edilen alan bir dizi gibi kullanılıp [] ile erişim sağlanabilir. Tabii henüz size = 0 olduğundan [] ile erişim size değerini güncellemeyecektir. size değeri yalnızca push_back(), insert() ve erase() işlemlerinden etkilenir. Örneğin insert() ve erase() işlemlerinde sınıf size değerini göz önüne alarak kaydırma yapacaktır. end() fonksiyonu ile verilen iterator şüphesiz string sınıfında olduğu gibi capacity değil size ile ilgilidir.


    vector Sınıfının Diğer Önemli Fonksiyonları

    vector sınıfının diğer nesne tutan sınıflarda olan klasik fonksiyonları vardır. Örneğin empty() fonksiyonu size değerine bakarak boş mu değil mi kontrolü yapar. front() ve back() fonksiyonları ilk ve son elemanı elde etmekte kullanılır. vector sınıfında push_front() ve pop_front() fonksiyonları tanımlı değildir, ancak push_back(), pop_back() fonksiyonları klasik işlemleri yapar. clear() fonksiyonu tamamen vector sınıfını boşaltır. Yani bu fonksiyon sınıfın tuttuğu alanı tamamen free hale getirmektedir. Bu işlemden sonra size = capacity = 0 olur. resize() fonksiyonu size değerini büyütmekte, yani sona eleman eklemekte kullanılır. resize() ile küçültme yapılmaya çalışılırsa size küçülür ama alan küçültmesi yapılmadığından dolayı capacity aynı kalır. Sınıfın bitiş fonksiyonu nesnenin tuttuğu tüm alanları boşaltmaktadır.

    Sınıf Çalışması: Bir bağlı liste bir de vector nesnesi tanımlayınız. Bağlı listeye 1 ile 100 arasında rastgele 20 eleman ekleyiniz. Bağlı listedeki elemanları vector nesnesine kopyalayınız. vector nesnesini sort() fonksiyonu ile sort ediniz. sort edilmiş vectorü yeniden bağlı listeye kopyalayınız ve bağlı listedeki elemanları yazdırınız.

    Açıklamalar: Bağlı listenin sort edilmesi gerektiği zaman bu işlem iki biçimde yapılabilir:

    1- Bağlı listenin sort() fonksiyonu ile sort işlemi yapılabilir. Bu işlem çok yavaş bir işlemdir.
    2- Bağlı liste vector sınıfına ya da bir diziye taşınır, orada sort() işlemi uygulanıp geri yazılır. Bu yöntem daha hızlıdır.

    Cevap:

    /* vector_sort.cpp */

    #include <iostream>
    #include <stdlib.h>
    #include <list>
    #include <vector>
    #include <algorithm>

    using namespace std;

    int main()
    {
    list<int> list;
    vector<int> vec;

    for (int i = 0; i < 20; ++i)
    list.push_back(rand() % 100 + 1);

    cout << "random list :" << endl;
    copy(list.begin(), list.end(), ostream_iterator<int>(cout, " "));

    vec.reserve(list.size());
    vec.resize(list.size());
    copy(list.begin(), list.end(), vec.begin());
    sort(vec.begin(), vec.end());
    copy(vec.begin(), vec.end(), list.begin());

    cout << "\n";
    cout << "sorted list :" << endl;
    copy(list.begin(), list.end(), ostream_iterator<int>(cout, " "));
    cout << "\n";

    system("PAUSE");
    return 0;
    }


    vector Sınıfının Eleman Silen Fonksiyonları

    vector sınıfının iki erase() fonksiyonu vardır. Bu fonksiyonlar elemanı silip kaydırma yapıp size değerini güncellerler. Yani bu işlemler doğrusal bir karmaşıklığa sahiptir.

    1- void erase(iter);
    2- void erase(iterfirst, iterlast);

    Bu işlemlerle capacity değeri hiç bir şekilde etkilenmez.

    Sınıf Çalışması: vector sınıfı kullanarak aşağıdaki kuyruk sistemini yazınız.

    template <class T>
    class Queue {
    public:
    Queue(size_t size);
    ~Queue();
    void Put(const T &r);
    void Get(T &r);
    bool IsEmpty();
    void Disp();
    private:
    vector<T> v;
    //...
    };

    Cevap:

    /* vector_queue.cpp */

    #include <iostream>
    #include <vector>
    #include <algorithm>

    using namespace std;

    template <class T>
    class Queue {
    public:
    Queue(size_t size);
    ~Queue();
    void Put(const T &r);
    void Get(T &r);
    bool IsEmpty();
    void Disp();
    private:
    vector<T> v;
    };

    template <class T>
    Queue<T>::Queue(size_t size)
    {
    v.reserve(size);
    }

    template <class T>
    Queue<T>::~Queue()
    {
    v.clear();
    }

    template <class T>
    void Queue<T>::Put(const T &r)
    {
    v.push_back(r);
    }

    template <class T>
    void Queue<T>::Get(T &r)
    {
    r = v.front();
    v.erase(v.begin());
    }

    template <class T>
    bool Queue<T>::IsEmpty()
    {
    return v.empty();
    }

    template <class T>
    void Queue<T>:isp()
    {
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout << "\n";
    }

    void main()
    {
    int val;
    Queue<int> q(10);

    for (int i = 0; i < 5; ++i)
    q.Put(i);

    q.Disp();

    for (int j = 0; j < 5; ++j) {
    q.Get(val);
    cout << val << endl;
    }

    }


    Editör Tasarımına İlişkin Notlar


    Editörler satır editörler ve tam ekranlı editörler olmak üzere ikiye ayrılırlar. Tam ekranlı (full screen) editörlerde kullanıcı ok tuşlarıyla tüm ekran üzerinde gezinebilir. Yatay ve düşey scroll işlemleri söz konusudur. Bu tür editörlerde aslında ekranda editördeki bilginin belirli bir bölümü gösterilir. Editördeki tüm bilgi bellekte ya da diskte ayrıca tutulmalıdır. Programcı ekranda bilginin hangi kısmının görüntülendiğini bilmek zorundadır. Veri yapısı bakımından en önemli problem insert ve delete problemleridir. Çünkü bu işlemlerin görüntüde yapılması problem değildir ama veri yapısı üzerinde yapılması problemlidir. Genellikle iki algoritmik teknik kullanılır:

    1- Editördeki bilgiler tamamen birebir bir karakter dizisi içerisinde tutulur. Insert ve delete işlemlerinde blok kaydırmaları yapılır. 64K’ya kadar editörlerde bu yöntemin kullanılması ciddi bir probleme yol açmaz (Windows’un edit kontrolünde muhtemelen bu yöntem kullanılmıştır).
    2- Insert ve delete işlemlerinin verimini arttırmak için bağlı liste tekniği kullanılabilir. Tabii her karakteri bağlı liste elemanı yapmak verimli değildir. Her satır bir blok olarak bağlı listede tutulabilir. Böylece bir satır üzerinde insert ve delete işlemleri bütünü etkilemez yalnızca o satırı etkiler. Sarma (wrapping) yapan editörlerde satırlar değişken uzunlukta olabilmektedir. Ancak sarma yapmayan editörler çok daha yaygın kullanılır. Bu editörlerde satırın bir maximum uzunluğu vardır. Satır daha fazla karakter içeremez. Sarma yapan editörlerde blok uzunluğu olarak ne alınacaktır? Burada bloklar yetmedikçe otomatik olarak büyütülebilir. Satırların büyütülmesi otomatik malloc(), realloc() sistemi ile yapılabilir (yani vector sınıfı bu iş için idealdir) ya da her satırın blokları yine ayrı bir bağlı listede tutulabilir.


    Çokbiçimliliğin Anlamı ve Kullanımı


    Çokbiçimlilik nesne yönelimli programlama tekniğinde aşağıdaki gibi üç anlama gelmektedir:

    1- Taban sınıfın bir üye fonksiyonunu türemiş sınıfların her birinin kendine özgü bir biçimde çalıştırması. Örneğin Shape taban sınıfının move() diye bir sanal fonksiyonu olabilir, bu sınıftan türetilmiş her şeklin hareketi kendine özgü olabilir.
    2- Daha önce yazılmış olan kodların daha sonra yazılmış olan kodları çağırabilmesi durumu. Örneğin Shell sınıfının process() fonksiyonu execute() isimli sanal fonksiyonu çağırıyor olsun, şimdi Shell sınıfından LineEditor gibi bir sınıf türetip bu fonksiyonu yazarsak eskiden yazılmış olan kodlar LineEditor sınıfının execute() fonksiyonunu çağıracaktır.
    3- Türden bağımsız program yazılması. Programcı taban sınıf türünden bir gösterici ya da referansla çalışır. Bunu genel bir tür olarak kullanır. Programın işlevi o sınıftan sınıf türetilerek değiştirilebilir.

    Çokbiçimlilik içeren uygulamalarda genellikle bir türetme şeması ve bu şemanın en tepesinde bir taban sınıf bulunur. Şemada yukarıdakiler daha genel durumları, aşağıdakiler daha özel durumları temsil eder. Çokbiçimli uygulamalarda en sık kullanılan yöntemlerden biri taban sınıf göstericilerine ilişkin bir veri yapısı oluşturmak ve çeşitli türemiş sınıf nesnelerinin adreslerini bu veri yapısında saklamaktır. Böylelikle heterojen sınıflar sanki aynı sınıfmış gibi bir veri yapısında saklanabilmektedir. Programcı istediği bir zaman bu veri yapısını dolaşarak taban sınıfıtaki sanal fonksiyonları çağırıp her nesnenin kendine özgü bir iş yapmasını sağlayabilmektedir. Bu tür uygulamalarda veri yapısının türü taban sınıf türünden göstericiler içermelidir. Veri yapısının nasıl ve hangi algoritmik yöntemle oluşturulduğu ikinci derecede önemli bir konudur.

    Diğer nesne yönelimli dillerde de çokbiçimlilik basitleştirilmiş biçimde ama yukarıdaki anlamıyla kullanılmaktadır.


    Java ve C#’da Çokbiçimlilik


    Java ve C#’da gösterici olmamasına karşın bütün sınıf ve dizi türleri aslında tamamen bir gösterici gibi işlem görmektedir. Bu dillerde en tepede Object isimli bir sınıf bulunur. Programcı bu sınıftan syntax olarak bir türetme yapmamış olsa bile sınıfın default olarak Object sınıfından türetildiği varsayılır. Bu dillerde gösterici yoktur ama aslında göstericiler gizli olarak kullanılmaktadır. Bu dilerde Object sınıfı tüm sınıfların en tepedeki taban sınıfı görevini yapmaktadır. Örneğin aşağıdaki Java ve C++ kodları tamamen eşdeğerdir:

    Java ya da C#

    C++

    Sample s;
    s = new Sample();
    Object o;
    o = s;
    o.ToString();
    Sample *s;
    s = new Sample();
    Object *o;
    o = s;
    o->ToString();


    Görüldüğü gibi Java’da yerel ya da global sınıf nesnesi tanımlamak mümkün değildir. Bütün sınıf nesneleri ve diziler new operatörü ile heap üzerinde yaratılmalıdır. Yukarıdaki örnekte ToString()Object sınıfının sanal bir fonksiyonudur ve aslında Sample sınıfnın sanal fonksiyonu çağırılmaktadır. Java’da bir fonksiyonu sanal yapmak için virtual gibi bir anahtar sözcük kullanılmaz. Bütün fonksiyonlar default olarak zaten sanaldır. Türemiş sınıfta bir fonksiyon taban sınıfın aynı isimli fonksiyonları ile yazılırsa sanallık mekanizması devreye girer. Bütün sınıflara ve dizi türlerine ilişkin nesne isimleri aslında gizli birer göstericidir, aktarım sırasında adresiyle geçirilmektedir. Örneğin:

    void Func(Object o)
    {
    ...
    }

    Sample s = new Sample();
    Func(s);

    Bu dillerde sınıf ve dizi dışındaki tüm türler normal bir biçimde değerle aktarılmaktadır. Java ve C#’daki nesne tutan sınıfların hepsi Object türünden nesneleri tutar, yani C++’a göre aslında Object sınıfı türünden göstericilerden oluşmaktadır. Bu dillerde nesne tutan sınıf içerisine eleman eklemek çok kolay bir biçimde yapılabilmektedir. Ancak tüm nesne tutan sınıflar Object sınıfına ilişkin olduğu için sınıfı dolaşıp sanal fonksiyonları çağırabilmek için önce bir aşağıya dönüşüm uygulamak gerekebilir. Örneğin Triangle Shape sınıfından türetilmiş bir sınıf olsun, Shape sınıfı da default olarak Object sınıfından türetilmiş olsun.

    Triangle t = new Triangle();
    list.Add(t);
    Shape s = (Shape) list.Get();

    Java ve C# gibi dillerde çöp toplayıcı (garbage collector) mekanizması dile entegre edilmiştir. Bu dillerde new ile tahsis edilen nesneler hiçbir kod tarafından kullanılmıyor durumuna gelince çöp toplayıcı tarafından otomatik olarak silinirler.

    Java’da int, long gibi doğal türler bir sınıf değildir, bu türleri nesne tutan sınıflarda saklayabilmek için çeşitli sarma sınıflar kullanılır. Örneğin:

    list.Add(new Integer(i));

    Ancak C#’da doğal türler de Object sınıfından türemiş birer sınıf nesnesi gibi kabul edilir.


    Örnek Bir Çizim Programı


    Örnek uygulamada daire, dikdörtgen, üçgen gibi heterojen geometrik şekiller bir çizim programı tarafından çizilmektedir. Kullanıcı bu şekiller üzerinde click yaparak bu şekilleri taşımak ya da silmek için seçebilecektir. Power point gibi bütün çizim programlarının genel sistematiği bu şekildedir. Bu örnekte bu tür programların çokbiçimlilik özelliği kullanılarak nasıl nesne yönelimli bir teknikle tasarlanacağı ele alınmaktadır. Şüphesiz bütün çizim şekillerinin bilgileri bir veri yapısı içerisinde tutulmalıdır. Bu veri yapısı bağlı liste olmalıdır ama türü ne olmalıdır?

    Heterojen şekillerin C’de saklanabilmesi için neredeyse tek yol bir tür bilgisini de şekil ile birlikte saklamak olabilir. Yani örneğin aşağıdaki gibi Shape türünden bir bağlı liste oluşturulabilir.

    typedef struct _SHAPE {
    int type;
    void *pShape;
    } SHAPE;

    Burada programcı type elemanından faydalanarak gerçek şekli tespit eder ve yapının void * elemanının türünü değiştirerek işlemleri yapar.

    Tasarım C++’da tamamen çokbiçimli düzeyde yapılabilir. Taban sınıf olarak Shape isimli bir sınıf alınabilir, bu sınıftan şekil sınıfları türetilebilir.




    Shape


    Shape


    Circle


    Triangle












    Bağlı liste Shape * türünden olmalıdır.

    std::list<Shape *> g_shapes;

    Uygulamanın kendisini App isimli bir sınıf ile temsil edersek bu bağlı listenin global olmak yerine bu sınıfın bir veri elemanı olması daha uygun olur.

    Bir şekil çizildiği zaman şekle ilişkin bir sınıf nesnesi dinamik olarak yaratılır ve bağlı listeye eklenir. Böylece bağlı liste heterojen nesnelerin adreslerini tutan bir biçime getirilmiş olur. Fareyle click yapıldığında hangi şekle click yapıldığının anlaşılması için bağlı listenin dolaşılarak Shape sınıfının IsInside() sanal fonksiyonu çağırılır. Her şekil sınıfının IsInside() isimli fonksiyonu bir noktanın kendisinin içerisinde olup olmadığını tespit etmek amacıyla yazılmalıdır.

    virtual bool IsInside(int x, int y) const = 0;

    Shape sınıfının hangi sanal fonksiyonları olmalıdır? Uygulamanın genişliğine bağlı olarak çok çeşitli sanal fonksiyonlar olabilir. Örneğin, Windows programlamada WM_PAINT mesajında bütün şekillerin yeniden çizilebilmesi için bir Draw() fonksiyonu eklenebilir. Yani Draw() fonksiyonu DC’yi parametre olarak alıp kendini hangi sınıfın Draw() fonksiyonu ise ona göre çizilmelidir. Örneğin bir şeklin silinmesi çok basittir. Tek yapılacak şey şeklin bağlı listeden çıkartılması ve görüntünün tazelenmesidir. Şeklin diske kaydedilmesi için bir dosya formatı tasarlanabilir. Dosya formatı için aşağıdaki gibi bir değişken uzunlukta kayıt içeren yöntem uydurulabilir:

    Başlık

    Tür

    Uzunluk

    İçerik

    Tür

    Uzunluk

    İçerik

    ...

    ...

    ...



    Başlık kısmı dosya hakkında genel bilgilerin bulunduğu bir kısımdır. Örneğin dosya içerisinde kaç şekil vardır? Dosya gerçekten de istediğimiz türden bir dosya mıdır? Bunun için bir magic number tespit edilebilir. Formatın versiyon numarası nedir? Sonra her şekil değişken uzunlukta kayıtlar biçiminde dosyaya sıralı bir biçimde yazılır. Örneğin tür WORD bir bilgi olabilir ve şeklin ne şekli olduğunu belirtir. Sonraki alan kayıdın uzunluğudur, bu alan iki nedenden dolayı gerekmektedir.

    1- Kayıtlar üzerinde sıralı erişimi sağlamak için
    2- Line noktalardan oluşur, yani her line şekli diğerinden farklı uzunlukta yer kaplayabilir. Uzunluk alanı uzunlukları değişebilen şekillerin algılanması için gerekmektedir.

    Nihayet içerik kısmında şeklin ham verileri bulunur.



    Çokbiçimliliğe İlişkin Diğer Bir Örnek: Tetris Programı


    Tetris oyun programı tipik olarak çokbiçimlilik özelliği yoğun bir biçimde kullanılarak tasarlanabilir. Oyunun tasarımı için önce oyundaki elemanları sınıflarla temsil etmek gerekir (transformation). Örneğin düşen şekiller, puanlama mekanizması, ekran işlemleri, uygulamanın kendisi birer sınıfla temsil edilebilir. Şekillerin düşmesi ve hareket etmesi tipik olarak çokbiçimli bir mekanizma ile sağlanabilir. Şöyleki, şekiller örneğin Shape gibi bir sınıftan türetilmiş sınıflarla temsil edilir. Shape sınıfının sola döndürme, sağa döndürme, sola hareket etme, sağa hareket etme ve aşağıya doğru hareket etme fonksiyonları olur. Bütün bu fonksiyonlar sanal ya da saf sanal alınabilir. Şekillerin hareketleri tamamen türden bağımsız olarak Shape sınıfı ile temsil edilir. Algoritmalar belirli bir şeklin hareket etmesine göre değil genel bir şeklin hareket etmesine göre düzenlenir. Her şekil kendi hareketini sanallık mekanizması içinde yapacaktır. Örneğin,

    Shape *pShape = createNewShape();

    for (; {
    sleep(50);
    pShape->MoveDown();
    //...
    }

    görüldüğü gibi createNewShape() fonksiyonu ile new operatörü kullanılarak bir tetris şekli yaratılır. Algoritma tamamen pShape göstericisine dayalı olarak tasarlanacaktır. Yani şekil bir yandan MoveDown() sanal fonksiyonu ile düşürülür, bir yandan da bekleme yapmadan klavyeden tuş alınır ve alınan tuşa bakılarak şekil genel bir şekilmiş gibi hareket ettirilir. Böylece oyunu oynayan algoritmalarda bir genellik sağlanmış olur. Oyuna yeni bir şeklin eklenmesi kolaylaşır. Çünkü bu durumda oyunu oynayan kısmın kodlarında bir değişiklik yapılmaz. Çünkü oyunu oynayan kısım hangi şekil olursa olsun çalışacak şekilde yazılmıştır.

    Şekil sınıfları çizim işlemini yapacak biçimde tasarlanmalıdır. Şeklin hareket etmesi ve döndürülmesi sırasında sınıf kendi çizimini kendisi yapar. Nesne yönelimli programlama tekniğinde mümkün olduğu kadar dış dünyadaki fiziksel nesneler ve kavramlar sınıflarla temsil edilmelidir. Örneğin tetris oyununda ekran ve klavye işlemleri bir sınıfla temsil edilebilir. Bu durumda şekil sınıfları ekran ile klavye işlemlerini yapan sınıfı kullanmak zorunda kalacaktır. Burada eleman olarak kullanmak yerine (composition) gösterici yoluyla kullanma (aggregation) tercih edilmesi gereken bir durumdur. Yani özetle ekran ve klavye işlemlerini yapan sınıf nesnesi bir kere dışarıda yaratılmalı bu nesnenin adresi başlangıç fonksiyonu yoluyla şekil sınıflarına geçirilerek şekil sınıfları içerisindeki bir gösterici veri elemanında saklanmalıdır. Madem ki tüm şekil sınıfları ekran ve klavye işlemini yapan sınıfı kullanacaklar, o halde gösterici veri elemanının taban sınıf olan Shape sınıf elemanında tutulması daha anlamlıdır.

    STL Stack Sistemi


    stack sınıfı adaptör bir sınıftır, yani başka bir STL sınıfından faydalanılarak yazılmıştır (yani stack sınıfı yazılırken başka bir sınıf veri elemanı biçiminde kullanılarak (composition) yazılmıştır). Ancak stack sınıfında kendisinden faydalanılan sınıf bir template parametresi yapılmıştır, yani değiştirilebilir.

    template <class T, class Container = deque<T> >
    class stack {
    //...
    Container c;
    };

    Görüldüğü gibi sınıfın iki template parametresi vardır, birinci parametre veri yapısında tutulacak bilginin türünü belirtir, ikinci parametre stack yapısının oluşturulması için hangi veri yapısının kullanılacağını belirtir. Default olarak deque sınıfı kullanılmıştır. stack sınıfı yardımcı bir sınıf kullanılarak çok kolay yazılabilir.

    stack sınıfının fonksiyonları şunlardır:

    bool empty() const;
    size_type size() const;
    void push(const T &x);
    void pop();
    T &top();
    const T &top() const;

    Ayrıca sınıfın diğer nesne tutan sınıflarda olduğu gibi karşılaştırma operatör fonksiyonları da vardır. stack sınıfı iterator kullanımını desteklememektedir (çünkü iteratore ilişkin yararlı bir işlem yapmak bu sınıfta mümkün değildir). Stack veri yapısı LIFO tarzı çalışan bir kuyruk sistemidir, stack sistemine push() fonksiyonuyla eleman yerleştirilir ve pop() fonksiyonuyla son yerleştirilen eleman atılır. top() üye fonksiyonu stack göstericisinin gösterdiği yerdeki elemanı alır, yani top() fonksiyonuyla elemanı aldıktan sonra pop() fonksiyonuyla silmek gerekir.

    Bilindiği gibi stack sisteminde stack’in yukarıdan ve aşağıdan taşması (stack overflow ve stack underflow) gibi hata kaynakları vardır. Stack sistemi başka nesne tutan sınıflar kullanılarak yazıldığından ve bu sınıflar da dinamik olarak büyütüldüğünden stack’in yukarıdan taşması durumuyla pek karşılaşılmaz. Stack’in yukarıdan taşması tahsisat hatasıyla anlaşılır. Stack’in aşağıdan taşması rastlanabilecek bir durumdur ve bu durumda ne olacağı, yani çıkacak olumsuzluklar standart olarak tespit edilmemiştir. top() ve pop() fonksiyonlarını kullanırken size > 0 olmasına dikkat edilmelidir. stack sınıfı <stack> dosyasında bulunur.




    Çokbiçimliliğe ve Stack Sistemine Diğer Bir Örnek: Undo Sistemi


    Bir undo sistemi için şu işlemler yapılmalıdır:

    1- Undo işlemine konu olacak tüm durumlar belirlenmelidir.
    2- Her durumun ek birtakım bilgileri olmalıdır. Örneğin işlem blok silme ise hangi aralıktaki bloğun silinmesi ve silinen bilgiler gibi.
    3- Tüm undo işlemleri stack veri yapısı içerisinde ifade edilmelidir.

    Böyle bir uygulamanın C’de yazılması algısal bakımdan zorluk içerir. Çünkü yapılan undo işelemleri farklı işlemlerdir ve farklı heterojen yapılar içerir. Veri yapısı yine stack sistemi olurdu ancak stack sistemini oluşturan yapının bir elemanı farklı türleri gösterebilen bir gösterici biçiminde alınırdı. Programcı stack’ten bilgiyi çektiğinde önce onun türüne bakar daha sonra bu göstericiyi uygun türe dönüştürerek işlemlerini yapar. Tabii böyle bir sistemde aşırı heap işlemlerinin olacağı açıktır. Örneğin:

    typedef struct _OPERATIONS {
    int type;
    void *pOperation;
    }OPERATIONS;

    typedef struct _BLOCKDELETE {
    int first, last;
    char *pContents;
    }BLOCKDELETE;

    C++’da undo işlemleri türetme ve çokbiçimlilik özellikleri kullanılarak çok daha etkin bir biçimde yürütülebilir. Yapılan her undo işlemi bir taban sınıftan türetilen sınıflarla ifade edilir. Taban sınıf soyut olabilir.

    Operation


    RemoveChar


    Remove block


    InsertChar


    InsertBlock


    ....












    virtual void TakeBack() = 0;

    Tasarımın ana noktaları şöyledir:

    1- Her sınıf undo işlemlerine ilişkin gerekli bilgileri kendi içerisinde tutar. Örneğin RemoveChar sınıfı silinen karakteri ve onun editördeki yerini tutar. InsertBlock insert edilen bloğun yerini tutabilir.
    2- Her sınıf geri alma işlemini kendine özgü biçimde, yani çokbiçimli olarak yapar. Undo işlemi stack’in tepesinden bilgiyi çekip TakeBack() sanal fonksiyonunun çağırılmasıyla yapılır.
    3- Undo veri yapısı Operation * türünden bir stack sisteminde tutulur ve TakeBack() sanal fonksiyonu aşağıdaki gibi çağırılır:

    stack<Operation *> undoStack;
    //......
    Operation *pOperation = undoStack.top();
    pOperation->TakeBack();
    undoStack->pop();

    Uygulamada undo işleminin kendisi için bir sınıf tasarlanabilir. Örneğin:

    class UndoProc {
    public:
    UndoProc(){}
    ~UndoProc();
    void Record(Operation *pOperation);
    void Undo();
    private:
    std::stack<Operation *> m_process;
    //...
    };

    Şimdi bir Undo işlemine konu olacak olay gerçekleştiğinde bu işlem Record() fonksiyonu ile kayıt edilir. Undo yapılmak istendiğinde Undo() fonksiyonu çağırılır.

    void UndoProc::Record(Operation *pOperation)
    {
    if (!m_process.empty()) {
    Operation *pOperation = m_process.top();
    pOperation->TakeBack();
    m_process.pop();
    delete pOperation;
    }
    }

    Bu tür uygulamalarda kesinlikle taban sınıfın bitiş fonksiyonu sanal alınmalıdır. UndoProc sınıfının bitiş fonksiyonunun da stack sisteminde tutulan göstericilere ilişkin bölgeyi de boşaltması gerekir.

    UndoProc::~UndoProc()
    {
    while (!m_process.empty()) {
    delete m_process.top();
    m_process.pop();
    }
    }




    Fonksiyon Nesneleri (Smart Sınıflar)

    Bir sınıfın fonksiyon gibi davranabilmesi için o sınıfın fonksiyon çağırma operatörünün yazılmış olması gerekir. Fonksiyon gibi davranabilen sınıflara smart sınıflar, bu türden sınıf nesnelerine ise fonksiyon nesneleri denilmektedir. Örneğin:

    template <class T>
    void Func(T f)
    {
    //...
    f(...);
    //...
    }

    Şimdi fonksiyon şöyle çağırılmış olsun.

    class X {
    //...
    };

    Func(X());

    Burada derleyici template fonksiyonu açarken T türünü X sınıfı olarak alır. Dolayısıyla fonksiyonun derlenebilmesi için sınıfın fonksiyon çağırma operatör fonksiyonunun yazılmış olması gerekir.


    Fonksiyon Çağırma Operatör Fonksiyonları


    Bir sınıfın farklı parametrik yapıya sahip birden fazla fonksiyon çağırma operatör fonksiyonu olabilir. Bu operatör fonkisyonlarının geridönüş değerleri herhangi bir biçimde olabilir. Fonksiyon çağırma operatör fonksiyonu şu biçimde çağırılabilir:

    class X {
    //...
    void operator()(int a);
    void operator()(int a, int b);
    //...
    };

    X a;

    a(10,20);
    a.operator()(10,20);


    STL’de Smart Sınıfların Kullanımı


    STL içerisinde pek çok algoritma bir fonksiyon parametresi istemektedir. Bu tür durumlarda bu algoritmalara fonksiyon parametresi geçmek yerine smart sınıf kullanmak çok daha kullanışlı bir yöntemdir. Çünkü sınıfın veri elemanlarında çeşitli bilgiler tutulabilir ve fonksiyon çağırma operatör fonksiyonları bu bilgileri kullanabilir. Örneğin bir dizide belirli bir sayıdan büyük olan elemanları sıfırlamak isteyelim. Ancak bu sayı değişebilsin. Şimdi eğer biz for_each() fonksiyonunu kullanıyorsak ilgili değerler de birden fazlaysa her biri için farklı fonksiyon yazmamız gerekir. Şöyle ki

    int a[] = { 3, 8, 4, 5, 2, 8, 20, 67, 34, 1 };

    void Func1(int &r)
    {
    if (r > 10)
    r = 0;
    }

    for_each(a, a + 10, Func1);

    Burada Func1() fonksiyonu yanlızca 10 değeri için çalışır. Oysa biz bu işlemi smart sınıfa yaptırırsak karşılaştırılacak eleman sınıfın bir veri elemanında tutulabilir ve karşılaştırma istenilen elemana göre yapılabilir.

    class MakeZero {
    public:
    MakeZero(int val) : m_val(val)
    {}
    void operator()(int &r)
    {
    if (r > m_val)
    r = 0;
    }
    private:
    int m_val;
    };

    int a[] = { 3, 8, 4, 5, 2, 8, 20, 67, 34, 1 };

    for_each(a, a + 10, MakeZero(10));
    for_each(a, a + 10, MakeZero(20));


    C++’da Faaliyet Alanları ve İsim Arama


    Faaliyet alanı bir değişkenin kullanılabildiği program aralığıdır. C++’da dört tür faaliyet alanı vardır:

    1- Blok faaliyet alanı
    2- Fonksiyon faaliyet alanı
    3- Sınıf faaliyet alanı
    4- Dosya faaliyet alanı

    C++’da karmaşık pek çok durum için faaliyet alanı kavramı yetersiz kalmıştır, bu yüzden isim arama (name lookup) kavramı geliştirilmiştir. Derleyici bir isimle karşılaştığında onu sırasıyla nerelerde arayacaktır? İsim arama işleminde arama aşama aşama yapılır, isim bir yerde bulunduğunda arama kesilir. İsim hiçbir yerde bulunamazsa bu durum error oluşturur.

    C++ derleyicisi önce ismi isim arama özelliğine göre arar, bulursa bulduktan sonra erişim kontrolüne bakar, daha sonra da kullanım kontrolü uygulayarak ifadenin geçerli olup olmadığına bakar.Yani önce isim bulunmakta sonra erişime bakılmaktadır.

    Bu kurallara ek olarak şöyle ilginç bir kural daha eklenmek zorunda kalınmıştır. Normal olarak bir fonksiyon ismi çağırılma yerine bağlı olarak içiçe namespace’ler içerisinde ve global namespace içerisinde aranır. Ancak buna ek olarak fonksiyon parametrelerinin namespace’leri içerisinde de aranmaktadır. Örneğin:

    std::string x;
    Func(x);

    Burada Func(), std namespace’i içerisinde de aranacaktır.


    Exception Handling Mekanizması


    Exception handling mekanizması derleyici için zor bir mekanizmadır. Kullanılması çalışabilen programda yer ve zaman kaybı oluşturur. Bir throw işlemi oluştuğunda derleyici try bloğu girişinden itibaren tüm yerel sınıf nesneleri için ters sırada bitiş fonksiyonu çağırır. Throw işlemine karşılık bir catch bloğu bulunamazsa std::terminate() fonksiyonu çağırılarak program sonlandırılır. terminate() fonksiyonu programı sonlandırmak için abort() fonksiyonunu çağırır, abort() ise “Abnormal program termination!” yazısını basarak programdan çıkar. Bu durumda çağırılacak olan terminate() fonksiyonu set_terminate() fonksiyonuyla set edilebilir. Bu fonksiyonu yazacak olan kişi çeşitli işlemleri yaptıktan sonra orijinal std::terminate() fonksiyonunu çağırabilir. Throw işlemi sırasında heap üzerinde tahsis edilmiş olan sınıf nesneleri ya da global sınıf nesneleri için bitiş fonksiyonu çağırılmamaktadır.


    Başlangıç ve Bitiş Fonksiyonlarında Throw İşlemleri

    Başlangıç fonksiyonlarının herhangi bir noktasında throw oluşmuş olsun,hangi nesneler için bitiş fonksiyonları çağırılacaktır? Throw işlemine kadar başlangıç fonksiyonları tam olarak bitirilmiş olan sınıf nesneleri için bitiş fonksiyonları ters sırada çağırılır. Örneğin:

    class A {

    //...
    A();
    B b;
    C c;
    };

    A::A() : b(), c()
    {
    X d, e;
    //...
    throw;
    }

    try {
    A a;
    }
    catch (...) {
    }

    Burada throw işemi oluştuğunda sırasıyla e, d, c ve b nesneleri için bitiş fonksiyonu çağırılır. A nesnesinin kendisi için bitiş fonksiyonu çağırılmaz çünkü başlangıç fonksiyonu tam olarak bitmemiştir. Bir sınıf nesnesi new operatörü ile dinamik olarak tahsis edildiğinde sınıf nesnesi için çağırılan başlangıç fonksiyonununda throw oluşmuşsa sınıf nesnesi için bitiş fonksiyonu çağırılmaz ancak tahsis edilen alan da derleyici tarafından otomatik olarak boşaltılır. Örneğin:

    try {
    A *pA;
    pA = new A();
    }
    catch(...) {

    }

    Burada A sınıfının başlangıç fonksiyonu içerisinde throw oluşursa catch içerisinde delete pA yapmaya gerek yoktur. Bu işlem zaten derleyici tarafından yapılmaktadır. Ancak tabii bitiş fonksiyonu yine çağırılmaz.

    Genel olarak bitiş fonksiyonu içerisinde throw işlemi yapılması tavsiye edilmez. Çünkü bilindiği gibi throw işlemi sırasında yerel sınıf nesneleri için bitiş fonksiyonu çağırılırken o bitiş fonksiyonlarının içerisinde yeniden throw uygulanırsa derleyici tarafından std::terminate() çağrılarak program sonlandırılır. Bu nedenle böyle bir potansiyel yüzünden bitiş fonksiyonu içerisinde throw uygulanması uygun değildir. Şüphesiz bitiş fonksiyonu içerisinde throw’un uygulanmaması bitiş fonksiyonunun dışına throw edilmemesi anlamına gelir. Yoksa try-catch işlemi bitiş fonksiyonu içerisinde yapılabilir. Bu durumda terminate() fonksiyonu çağırılmaz.


    new Operatör Fonksiyonunun Başarısızlığı

    Global operatör new fonksiyonu başarısız olduğunda eskiden set_new_handler() fonksiyonu ile set edilen fonksiyonu çağırırdı, eğer bu fonksiyonda hiçbir set işlemi yapılmadıysa new operatörü NULL üretiyordu. 1996 ve sonrasında global new operatör fonksiyonu başarısızlık durumunda std::bad_alloc sınıfına throw etmektedir. Yani artık set_new_handler() fonksiyonu ile set edilen fonksiyonların çağırılması zorunlu değildir, derleyicileri yazanlara bırakılmıştır. Başarısızlık durumunda operator new fonksiyonu geri dönmemektedir. Bu nedenle artık operator new fonksiyonunun başarısızlılığının try–catch işlemi ile ele alınması gerekir. Programlarda new işlemi için try–catch işlemleri yapılmasa da olur. Programın az bir heap alanı varsa çökeceği kabul edilir.


    Exception Specification

    Exception specification bir fonksiyonun en fazla dışarıya hangi türler için throw uygulayabileceğini belirleyen bir syntax’dır. Örneğin:

    void Func(int a, int b) throw (X, Y, Z);
    void Func(int a, int b) throw (X, Y, Z)
    {
    //... Sample();
    }

    Exception specification’un fonksiyonun hem prototipinde hem de tanımlamasında aynı biçimde belirtilmesi gerekir. Bu biçimdeki exception belirlemelerinin dışında her hangi bir biçimde fonksiyonun dışına bir throw işlemi yapılırsa derleyici tarafından std::unexpected() fonksiyonu çağırılmaktadır. Yukarıdaki örnekte X, Y, Z birer sınıf isimleri olsun. Şimdi biz Func() fonksiyonu içerisinde int bir tür ile throw edersek ve bunu işlemeyip akışı dışarıya kaçırırsak derleyici tarafından std::unexpected() fonksiyonu çağırılır. Aynı durum Func() fonksiyonun çağırdığı Sample() içerisinde de olsaydı ve akışı Func() fonksiyonunun dışına int türü ile throw etseydi aynı durum oluşurdu.

    Exception specification okunabilirliği ve kod kontrolünü güçlendirmek için eklenmiştir. Fonksiyonun prototipine bakan kişi fonksiyonun en kötü olasılıkla hangi türler ile throw edeceğini anlar ve catch bloklarını ona göre düzenler. Exception specification kullanan ve kullanmayan aşağıdaki iki fonksiyon tamamen eşdeğer çalışmaktadır.

    void Func() throw(X, Y)
    {
    //...
    }

    /* Yukarıdaki ile aşağıdaki eşdeğerdir. */

    void Func()
    {
    try {
    //...
    }
    catch(X) {
    throw;
    }
    catch(Y) {
    throw;
    }
    catch(...) {
    std::unexpected();
    }
    }

    throw() belirlemesi dışarıya hiçbir throw işlemi yapılamayacağını belirtir. Örneğin:

    void Func() throw()
    {
    //...
    }

    void Func()
    {
    try {
    //...
    }
    catch(...) {
    std::unexpected();
    }
    }

    Nihayet exception specification kullanılmaması fonksiyonun her türlü değerle dışarıya throw edebileceği anlamına gelir. Exception specification sınıfın başlangıç ve bitiş fonksiyonlarına da uygulanabilir.

    std::unexpected() fonksiyonu default olarak std::terminate() fonksiyonunu çağırmaktadır. Çağırılacak fonksiyon set_unexpected() fonksiyonu ile set edilebilir.


    throw ile catch Geçişi Arasında İşlemler

    Bilindiği gibi C++’da bir fonksiyonun parametre değişkenlerinin isimleri yazılmayabilir. Örneğin:

    void Func(int)
    {
    //...
    }

    tanımlaması geçerlidir. Tabii parametre fonksiyonun içerisinden kullanılamaz ama fonksiyon parametre varmış gibi çağırılmalıdır. Aynı durum catch blokları için de söz konusudur. Örneğin:

    catch (int)
    {
    //...
    }

    throw işlemi bir ifade ile yapıldığında bu ifadenin değerinin catch parametresine aktarılması tamamen fonksiyon çağırma işleminde yapıldığı gibi yapılmaktadır. Bunun için derleyici önce throw ifadesinin türüyle aynı tür olan muhtemelen static ömürlü bir geçici nesne alır ve bu geçici nesne yoluyla aktarımı gerçekleştirir. Örneğin:

    throw [ifade];


    catch (<tür> <param>)
    {
    //...
    }

    temp = ifade;
    param = temp;

    throw ifadesinden geçici bölgeye yapılan atama tıpkı fonksiyonun return işleminde olduğu gibi gerçekleşir. Eğer throw ifadesi bir sınıf türündense geçici bölge de sınıf türündendir. Bu durumda geçici bölge için kopya başlangıç fonksiyonu çağırılır. catch parametresi de sınıf türündense catch parametresi için de kopya başlangıç fonksiyonu çağırılır. Geçici bölge catch bloğu sonlanana kadar tutulmaktadır. Örneğin geçici bölge sınıf türündense ve catch parametresi de sınıf türündense akış catch bloğunu bitirdiğinde önce catch parametresi için sonra geçici bölge için bitiş fonksiyonları çağırılacaktır. try – catch aktarımları genellikle programlarda karşımıza üç biçimde çıkar;

    1- throw ifadesi ve catch parametresinin C++’ın doğal türlerinden ya da bir sınıf türünden olması durumu. Örneğin:

    throw 100;

    catch (int a)
    {
    //...
    }

    throw X();

    catch (X a)
    {
    //...
    }

    Genellikle nesne yönelimli kütüphanelerde throw ifadesi C++’ın doğal türlerine ilişkin değil bir sınıf türüne ilişkin olur.

    Bu durum etkin bir yöntem değildir. Çünkü bir sınıf türü ile throw edildiğinde geçici bölge ve catch parametreleri için başlangıç ve bitiş fonksiyonları çağırılacaktır.

    2- throw ifadesi C++’ın doğal türüne ilişkin ya da bir sınıf türüne ilişkindir. Ancak catch ifadesi aynı türden bir referanstır. Örneğin:

    throw x;

    catch (int &a)
    {
    //...
    }

    throw X();

    catch (X &a)
    {
    //...
    }

    Bu durumda catch parametresi olan referans geçici bölgenin adresini tutmaktadır. Bu teknik önceki teknikten biraz daha iyidir çünkü catch parametresi için başlangıç ve bitiş fonksiyonları çağırılmamaktadır. STL’de bu yöntem kullanılmıştır.

    3- throw ifadesi C++’ın doğal türünden ya da bir sınıf türünden adrestir. catch parametresi ise aynı türden bir göstericidir. Bu durumda geçici bölge de gösterici türünden olacaktır. Yani geçici bölge için de başlangıç ve bitiş fonksiyonları çağırılmayacaktır. Bu durumda throw ile aktarılan adresin stack’teki (yani yerel) bir nesneye ilişkin olmaması gerekir. Global ya da heap üzerindeki bir nesnenin adresi olmalıdır. Örneğin:

    throw new int;

    catch (int *p)
    {
    //...
    }

    throw new X();

    catch (X *pX)
    {
    //...
    }

    Eğer tahsisat heap üzerinde yapılmışsa throw işlemi için tahsis edilen alanın boşaltılması catch bloğunu düzenleyen programcı tarafından yapılmalıdır. Örneğin:

    throw new X();

    catch(X *pX)
    {
    //...
    delete pX;
    }

    Pek çok sınıf kütüphanesinde bu yöntem tercih edilmektedir. Örneğin MFC’de kütüphane içerisindeki fonksiyonlar CException denilen bir sınıftan türetilen sınıf nesnelerinin adresleriyle throw etmektedir. Örneğin MFC’de tipik bir try – catch işlemi şöyle yapılmaktadır:

    try {
    CFile f(“a.dat”, ...);
    }

    catch (CFileException *pFileException)
    {
    //...
    pFileException->Delete();
    }

    Burada MFC için özel bir durum söz konusudur. MFC kütüphanesindeki fonksiyonlar bazen global nesneleri adresleriyle de throw edebilmektedir. Bu nedenle exception nesnesinin silinmesi delete operatörü ile değil CException sınıfının Delete() fonksiyonu ile yapılmaktadır. Delete() fonksiyonu adresin heap üzerinde tahsis edilip edilmediğine bakar. Edilmişse delete operatörü ile nesneyi siler. Global bir tahsisat söz konusuysa nesneyi silmez.


    Exception İşlemleri İçin Bir Sınıf Sisteminin Kullanılması


    Profesyönel sınıf kütüphanelerinde throw – catch işlemleri için bir sınıf sistemi kullanılır. Kütüphane içerisindeki bu tür sınıf sistemlerine exception sınıfları denir. Doğal türlerle throw yapmak yerine sınıflarla throw yapmak ve bunu için bir sınıf sistemi kullanmak çok daha etkin bir yöntemdir. Genellikle kütüphaneleri düzenleyenler exception mekanizması için en tepede bir exception sınıfı bulundurup exception işlemlerini konulara ayırıp bu sınıftan türetilmiş sınıflar biçiminde temsil ederler. Exception sınıfları çokbiçimli olarak da düzenlenebilir. Bu durumda en tepedeki exception sınıfının sanal fonksiyonları olur. Bu tür kütüphanelerde programcı tüm konulara ilişkin exception durumlarını yakalamak isterse catch parametresini taban sınıf türünden alır. Tabii çokbiçimlilik de söz konusu ise catch parametresinin taban sınıf türünden bir gösterici olması en normal durumdur. Örneğin MFC’de bir bloktaki akışta her türlü exception işlemini yakalamak için aşağıdaki gibi bir düzenleme yapılabilir:

    try {
    //...
    }

    catch (CException *pException)
    {
    //...
    }


    İfadesiz throw İşlemleri


    İfadesiz throw işlemleri genellikle akış bakımından catch bloklarının içerisinde yapılır. Bu işlemlerde amaç exception durumunu kısmi olarak ele alıp sanki hiç ele alınmamış gibi bir dışardaki catch bloğuna atmaktır. Örneğin:

    try {
    Func();
    }

    catch (X *pX)
    {
    //...
    }

    Func()
    {
    try {
    Sample();
    }

    catch (X *pX)
    {
    //...
    throw;
    }
    }

    Burada Sample() fonksiyonu içerisinde bir throw oluştuğunda bu durum önce kısmen ele alınmıştır, daha sonra dıştaki catch bloğuna bırakılmıştır. İfadesiz throw kullanıldığında yeni bir geçici nesne oluşturulmaz. Dıştaki catch bloğunun parametresine yeniden eski geçici nesnenin içeriği atanır. Şüphesiz catch bloğunun içerisinde normal yani ifadeli bir throw da kullanılabilir. İfadeli throw ile ifadesiz throw arasındaki tek fark geçici bölgenin korunması yani bir önceki throw işleminin kullanılıp kullanılmamasıdır.


    Java ve C# Dillerirnin C++ Bakımından Değerlendirilmesi

    Son yıllarda basit nesne yönelimli dillere olan gereksinim artmıştır. Java Sun firması tarafından basit bir nesne yönelimli programlama dili olarak tasarlanmıştır. C# javanın biraz daha iyileştirilmiş biraz daha C++’a yaklaştırılmış ve Microsoft teknolojileriyle entegre edilmiş biçimidir. Java ve C# dillerinin tasarımındaki ikinci büyük kavram çalışabilen kodun taşınabilirliği (binary portibility) yani Java ve C# derleyicilerinin ürettiği kod o anda çalıştığımız makinanın işlemcisinin makina komutları değildir. Aslında hiç bir işlemcinin makina komutları değildir, bir arakoddur. Java derleyicilerinin çıktısı *.class, C# derleyicilerinin çıktısı *.exe biçimindedir. Bu arakoda Java terminolojisinde “byte code”, .NET terminolojisinde “Microsoft Intermediate Language” denir. C#’ın ürettiği *.exe kodu normal bir *.exe kodu değildir. Byte code ve MIL kodları başka bir programdan faydalanılarak çalıştırılmaktadır. Örneğin bir java programının derlenip çalıştılırması için şöyle yapılmaktadır:

    javac x.java
    java x.class

    javac, java derleyicisidir. java isimli program ise byte code’ları yorumlayarak çalıştıran “Java Virtual Machine” dir. C#’da ise *.exe kodunun içerisinde ara kod vardır. Ancak programda küçük bir giriş fonksiyonu bulunur. Bu giriş fonksiyonu “MScore.dll” isimli dll’den çağırma yapar ve akış dll’e geçer. Bu dll de ara kodları yorumlayarak çalıştırır. Yani özetle .NET ortamındaki *.exe programı dışarıdan bakıldığında normal bir program gibi çalıştırılmaktadır.

    Java’da C++’da olan şu konular basitleştirilme yapmak için çıkarılmıştır:

    - Önişlemci
    - Göstericiler
    - Operatör fonksiyonları
    - Template işlemleri
    - Default parametre kavramı
    - Çoklu türetme
    - Yerel sınıf nesneleri tanımlama
    Diğer küçük özellikler

  4. #4
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb



    Üye Fonksiyon Göstericilerini ya da Referanslarını Kullanarak Üye Fonksiyonların Çağırılması

    p bir üye fonksiyon göstericisi olsun, p() gibi bir çağırma geçerli değildir. Çünkü üye fonksiyonlar sınıf nesneleriyle çağırılmalıdır. İlk akla gelen aşağıdaki gibi bir çağırımdır ama o da geçerli değildir:

    Sample x;
    x.p();

    Çünkü bu syntax'da derleyici p ismini x nesnesnesinin ilişkin olduğu sınıfın (burada Sample) faaliyet alanında arar. Halbuki p sınıfın faaliyet alanında olan bir isim olmak zorunda değildir. Üye fonksiyon göstericilerini çağırmak için C++'a .* ve ->* biçiminde iki yeni operatör eklenmiştir. Bu operatörler binary infix operatörlerdir, C++ öncelik tablosunun ikinci düzeyinde bulunurlar. C'nin klasik unary operatörleri C++'da tablonun üçüncü önceliğine indirilmişlerdir. Yani bu operatörler ++ ve -- gibi unary operatörlerden yüksek öncelikli, ancak (), [] gibi operatörlerden düşük önceliklidir.

    .* operatörünün sol tarafındaki operand bir sınıf nesnesinin kendisi, sağ tarafındaki operand ise o sınıf türünden bir üye fonksiyon göstericisi olmalıdır. Tipik çağırma aşağıdaki gibi yapılır:

    Sample x;
    void (Sample::*p)(void);
    p = &Sample::Func;
    (x.*p)();

    Burada parantezler zorunludur çünkü fonksiyon çağırma operatörünün önceliği .* operatöründen fazladır. ->* operatörü de aynı biçimde üye fonksiyonu çağırmak için kullanılır ama bu operatörün sol tarafındaki operand sınıf türünden nesne değil adres olmalıdır.

    Sample *pX = new Sample();
    void (Sample::*p)(void);
    p = &Sample::Func;
    (pX->*p)();

    Bir sınıfın veri elemanı olarak kendi sınıfı türünden bir fonksiyon göstericisine sahip olması durumuna sıkça rastlanır. Örneğin:

    class Sample {
    public:
    void Func();
    void (Sample::*m_pf)();
    };

    void Sample::Func()
    {
    (this->*m_pf)();
    //...
    }

    Burada Func() üye fonksiyonu içerisinde m_pf üye fonksiyon göstericisinin gösterdiği üye fonksiyon Func() fonksiyonunun çağırıldığı aynı nesne ile çağırılmış olur. Özellikle sınıfın static bir üye fonksiyon gösterici dizisinin olması ve bu dizi içerisinde üye fonksiyonların adreslerinin tutulması ve bu fonksiyonların da sınıfın başka bir üye fonksiyonu içerisinden çağırılması gibi durumlarla karşılaşılmaktadır. Örneğin MFC'deki mesaj haritaları bu tür yapılardır.

    C++'da taban sınıf türünden üye fonksiyon göstericisine türemiş sınıfın üye fonksiyon adresi atanamaz. Çünkü bu durum gösterici hatasına yol açabilecek tehlikeli bir durumdur. Yani taban sınıf türünden üye fonksiyon göstericisi taban sınıf türünden nesneyle çağırılır, fakat aslında çağırılacak olan fonksiyon türemiş sınıfa ait olacağından bu gösterici hatasına yol açar.

    class X {
    public:
    void FuncX();
    };

    class Y : public X {
    public:
    void FuncY();
    };

    void (X::*pX)();
    pX = &Y::FuncY; //error
    X x;
    (x.*pX)();

    Ancak bunun tersi, yani türemiş sınıf üye fonksiyon göstericisine taban sınıf üye fonksiyonunun adresinin atanması normal ve geçerli bir durumdur. Çünkü türemiş sınıf nesnesiyle taban sınıf üye fonksiyonunun çağırılması gibi bir durum oluşur.

    void (Y::*pY)();
    pY = &X::FuncX; //normal
    Y y;
    (y.*pY)();


    mem_fun_ref() ve mem_fun() Adaptör Fonksiyonları

    STL içerisinde pek çok algoritma global fonksiyonların çağırılması üzerine dayandırılmıştır, halbuki pek çok durumda bir sınıfın üye fonksiyonunun çağırılması istenir. Örneğin, çokbiçimli bir sınıf sistemi olduğunu düşünelim ve taban sınıf göstericilerine ilişkin bir nesne tutan sınıfımız olsun.

    list<Base *> x;

    Şimdi biz bu bağlı liste içerisindeki her gösterici için Func() isimli bir sanal fonksiyon çağıracak olalım. Iterator'lerden faydalanılarak aşağıdaki gibi bir kod yazılabilir:

    list<Base *>::iterator iter;
    for (iter = x.begin(); iter != x.end(); ++iter)
    (*iter)->Func();

    İşte bu işlem aşağıdaki gibi de yapılabilir:

    for_each(x.begin(), x.end(), mem_fun(&Base::Func));

    Burada bu işlem bağlı listenin içerisindeki herbir adres ile sınıfın Func() üye fonksiyonunun çağırılmasını sağlamaktadır.

    mem_fun() ile mem_fun_ref() fonksiyonları arasında gösterici ya da nesnenin kendisiyle çağırılması bakımından bir fark vardır. Şüphesiz nesnenin kendisiyle çağırılması durumunda çokbiçimlilik devreye girmez. Örneğin:

    list<A> x;
    ...
    for_each(x.begin(), x.end(), mem_fun_ref(&A::Func));

    Gerçekte neler olmaktadır?

    Aslında mem_fun_ref() fonksiyonunun geri dönüş değeri mem_fun_ref_t sınıfı türünden bir nesnedir. Olaylar şöyle gerçekleşmektedir:

    1- mem_fun_ref() fonksiyonunun geri dönüş değeri mem_fun_ref_t türünden bir sınıf nesnesidir. Bu durumda for_each() fonksiyonunun son parametresi bu sınıf türünden olacaktır.
    2- mem_fun_ref_t sınıfının veri elemanı içerisinde mem_fun_ref() fonksiyonunun içerisinde belirtilen üye fonksiyon adresi tutulur.
    3- mem_fun_ref_t sınıfının () operatör fonksiyonunun parametresi template türünden nesnedir ve bu fonksiyon bu nesne ile ilgili üye fonksiyonu çağırır. Örneğin Microsoft derleyicilerinin functional başlık dosyasında tasarımı aşağıdaki gibidir:

    template<class _R, class _Ty> inline
    mem_fun_ref_t<_R, _Ty> mem_fun_ref(_R (_Ty::*_Pm)())
    {
    return mem_fun_ref_t<_R, _Ty>(_Pm);
    }

    Görüldüğü gibi fonksiyonun parametresi bir üye fonksiyon göstericisi, geri dönüş değeri ise template sınıf türünden bir nesnedir.

    template<class _R, class _Ty>
    class mem_fun_ref_t : public unary_function<_Ty, _R> {
    public:
    explicit mem_fun_ref_t(_R (_Ty::*_Pm)()) : _Ptr(_Pm)
    {}
    _R operator()(_Ty &_X) const
    {
    return (_X.*_Ptr)();
    }
    private:
    _R (_Ty::*_Ptr)();
    };

    mem_fun() fonksiyonu ve mem_fun_t sınıfları tamamen buradaki gibidir. Tek fark üye fonksiyonun .* ile değil ->* ile çağırılmasıdır. Görüldüğü gibi burada çağırılacak üye fonksiyon herhangi bir geri dönüş değerine sahip olabilir ama parametresi void olmalıdır.

    Fakat bunların yanısıra mem_fun1() ve mem_fun1_ref() fonksiyonları ve sınıfları da vardır. Bu fonksiyonların farkı çağırılacak üye fonksiyonun void değil bir parametresinin olmasıdır.


    mem_fun() ve mem_fun_ref() Fonksiyonlarına İlişkin Uygulamalar

    Örnek 1:

    #include <iostream>
    #include <functional>
    #include <algorithm>
    #include <list>

    using namespace std;

    class A {
    public:
    virtual bool Func() = 0;
    };

    class B : public A {
    public:
    virtual bool Func()
    {
    cout << "I am B::Func\n";
    return true;
    }
    };

    class C : public A {
    public:
    virtual bool Func()
    {
    cout << "I am C::Func\n";
    return true;
    }
    };

    void main(void)
    {
    list<A *> x;

    x.push_back(new B());
    x.push_back(new C());
    x.push_back(new B());

    for_each(x.begin(), x.end(), mem_fun(&A::Func));
    }


    Örnek 2:

    #include <list>
    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <string>

    using namespace std;

    void main(void)
    {
    list<string> x;

    x.push_back("Ali");
    x.push_back("Mehmet");
    x.push_back("Ziya");
    x.push_back("Cenk");
    x.push_back("Ali");

    const char *pstr[5];

    transform(x.begin(), x.end(), pstr, mem_fun_ref(&string::c_str));

    copy(pstr, pstr + 5, ostream_iterator<const char *>(cout, "\n"));
    }

    Not: Yukarıdaki kod VC++ 98'de çalışmamaktadır.

    Sınıf Çalışması: string'lerden oluşan bir bağlı liste kurunuz, bazı elemanların içini erase() fonksiyonu ile siliniz, daha sonra mem_fun_ref() fonksiyonunu kullanarak boş olan elemanları remove_if() ve ardından erase() fonksiyonuyla siliniz. Çağırılacak üye fonksiyon string::empty() olmalıdır.

    Cevap:

    #include <iostream>
    #include <string>
    #include <list>
    #include <algorithm>
    #include <stdlib.h>
    #include <functional>

    using namespace std;

    void main (void)
    {
    list<string> l;
    list<string>::iterator iter;

    l.push_back("adana");
    l.push_back("izmit");
    l.push_back("sakarya");
    l.push_back("samsun");
    l.push_back("bolu");
    l.push_back("sivas");
    l.push_back("ankara");

    iter = l.begin();
    iter++;
    (*iter).erase();
    for (int i = 0; i < 3; i++)
    iter++;
    (*iter).erase();

    iter = remove_if(l.begin(), l.end(), mem_fun_ref(&string::empty));
    l.erase(iter, l.end());
    copy(l.begin(), l.end(), ostream_iterator<string>(cout, "\n"));

    system("pause");
    }

    Not: Yukarıdaki kod VC++ 98'de çalışmamaktadır.


    mem_fun1() ve mem_fun1_ref() fonksiyonları anlamlı bir biçimde bind fonksiyonlarıyla kullanılabilir. Örneğin:

    bind2nd(mem_fun1_ref(&X::Func), val);

    Burada sonuçta X sınıfının tek parametreli Func() fonksiyonu hep val parametresiyle çağırılmaktadır. İşlemin mekanizması biraz karışıktır.


    pair Sınıfı

    Bu sınıf first ve second isimli iki public veri elemanına sahiptir. Bu iki elemanın türü iki template parametresi türündendir. Sınıf yalnızca birbirleriyle ilişkili iki elemanı birlikte tutmakta kullanılır. Bildirimi şöyledir:

    template <class T1, class T2>
    struct pair {
    T1 first;
    T2 second;
    pair() {}
    pair(const T1 &x, const T2 &y)
    {
    first = x;
    second = y;
    }
    template <class u, class v>
    pair (const pair <u, v> &p)
    {
    first = p.first;
    second = p.second;
    }
    };

    Görüldüğü gibi pair sınıfı yalnızca iki elemanı tutmakta kullanılır, ancak STL içerisinde bir yardımcı sınıf olarak da kullanılmaktadır. Özellikle fonksiyonun birbiriyle ilişkili iki bilgi vermesi durumunda fonksiyonun geri dönüş değerinin pair sınıfı türünden olması biçiminde kullanımlara rastlanmaktadır. Örneğin, bir fonksiyon bir arama yapsın ve bir numara bulacak olsun. Numara int türünden olsun ve her değeri alabilsin. Bu durumda elde edilecek numara her değeri alabileceğine göre başarısızlık nasıl anlaşılacaktır? işte bunun için fonksiyon aşağıdaki gibi tasarlanabilir:

    pair<bool, int> SearchNumber(const char *name);

    Kullanımı şöyle olabilir:

    pair<bool, int> result;
    result = SearchNumber("aliserce");
    if (!result.first) {
    cerr << "error\n";
    exit(1);
    }
    cout << result.second << endl;


    new ve delete Operatörlerinin Ayrıntılı Bir Biçimde İncelenmesi

    new ve delete işlemleri yapıldığında derleyici dinamik tahsisatları yapabilmek için global düzeyde tanımlanmış operator new() ve operator delete() fonksiyonlarını kullanır. Örneğin X bir sınıf olmak üzere,

    p = new X();

    bu işlemde önce operator new() fonksiyonu çağrılarak sizeof(X) kadar bir alan tahsis edilir, sonra bu tahsisattan elde edilen adres this göstericisi yapılarak sınıfın başlangıç fonksiyonu çağırılır. Burada eğer X sınıfının bir operator new() fonksiyonu var ise global olan değil öncelikle sınıfa ilişkin olan çağırılacaktır. new ve delete operatör fonksiyonlarının tekil ([]'siz) ve çoğul ([]'li) versiyonları vardır.

    C++ derleyicileri global düzeyde (yani hiçbir namespace içerisinde değil) aşağıdaki tahsisat için gereken operatör fonksiyonlarını bulundurmalıdır:

    1- void *operator new(std::size_t size) throw(std::bad_alloc);

    Bu operatör fonksiyonu new T; yapıldığında çağırılan operatör fonksiyonudur. Yani biz

    p = new T; // p = new T();

    yaptığımızda derleyici aslında şu işlemi yapmaktadır:

    p = (T *)operator new(sizeof(T));

    operator new() fonksiyonu doğrudan bizim tarafımızdan da çağırılabilir. Örneğin:

    int *p;
    p = (int *) operator new(sizeof(int));

    Fonksiyon başarısızlık durumunda bad_alloc isimli bir sınıf ile throw eder. Yani, eğer tahsisatın başarısı kontrol edilmek isteniyorsa aşağıdaki gibi yapılabilir:

    try {
    p = new T;
    //...
    }
    catch (std::bad_alloc) {
    //...
    }

    Bu fonksiyonun doğrudan çağırılabilmesi için <new> dosyasının include edilmesine gerek yoktur.

    operator new() fonksiyonu programcı tarafından global düzeyde yazılabilir, bu durumda programcının yazdığı fonksiyon new işlemlerinde çağırılır. Örneğin:

    void *operator new(size_t size) throw(std::bad_alloc)
    {
    void *pBuf;
    pBuf = malloc(size);
    if (pBuf == NULL)
    throw std::bad_alloc();
    return pBuf;
    }

    Global operator new() fonksiyonunu yazdığımız zaman kütüphanedeki global sınıf nesneleri için çağırılan başlangıç fonksiyonları içerisinde new yapılmışsa yine bizim yazdığımız fonksiyon çağırılacaktır. Yani yazdığımız fonksiyonun main() fonksiyonuna girişten daha önce çağırılmış olması normaldir. Yukarıdaki global operator new fonksiyonunun eski versiyonunda exception specification kullanılmamıştır.




    2- void *operator new(std::size_t size, const std::nothrow_t &) throw();

    Bu versiyonu kullanmak için prototipinin bulunduğu <new> dosyasını include etmek gerekir. <new> içerisinde aşağıdaki tanımlamalar da yapılmıştır:

    namespace std {
    struct nothrow_t {};
    extern const nothrow_t nothrow;
    }

    Bu biçimi kullanmak aşağıdaki gibi olabilir:

    p = new(nothrow) T;

    nothrow nothrow_t türünden <new> içerisinde tanımlanmış global bir nesnedir. Yalnızca tür bilgisi oluştursun diye tanımlanmıştır. Bu fonksiyonun öncekinden tek farkı başarısızlık durumunda bad_alloc değerine throw etmemesi, NULL ile geri dönmesidir. Yani programcı exception handling mekanizmasını kullanmak istemezse bu şekli kullanabilir.

    3- void *operator new[] (std::size_t size) throw(std::bad_alloc);

    Bu biçim birinci biçimin []'li versiyonudur. Örneğin aşağıdaki durumda bu fonksiyon çağırılır:

    p = new T[n];

    Bu durumda derleyici aşağıdaki gibi bir çağırma yapar:

    p = (T *)operator new(n * sizeof(T));

    Derleyiciler bu fonksiyonu doğrudan

    return operator new(size);

    biçiminde yazarlar. Yani, biz []'li versiyonunu yazmasak ama []'siz versiyonunu yazsak []'li tahsisat yaptığımızda en sonunda bizim yazdığımız []'siz versiyonu çağırılır.

    4- void *operator new[] (std::size_t size, const std::nothrow_t &) throw();

    Bu versiyon ikinci biçimin []'li versiyonudur. Derleyicinin default olarak bulundurduğu bu fonksiyon []'siz versiyonunu çağırır.

    5- void *operator new(std::size_t size, void *ptr) throw();

    Bu placement versiyonu ileride ele alınacağı gibi başlangıç fonksiyonu çağırmakta kullanılır. Örneğin:

    new(p) T();

    Kütüphanedeki default versiyon ikinci parametresine dönmekten başka birşey yapmaz ve aşağıdaki gibi yazlımıştır:

    void *operator new(std::size_t size, void *ptr) throw()
    {
    return ptr;
    }

    Bu biçim önceden yaratılmış bir alan için başlangıç fonksiyonunu çağırmak amacıyla yaygın olarak kullanılır. Bilindiği gibi new operatörü kullanıldığında derleyici yukarıda belirtilen operator new() fonksiyonlarından birini çağırıp bir adres elde etmekte ve o adresi this göstericisi olarak kullanıp başlangıç fonksiyonunu çağırmaktadır. Şimdi biz s isminde yerel bir diziyi sanki bir sınıf gibi kullanıp başlangıç fonksiyonunu çağırmak isteyelim. Sınıfın başlangıç fonksiyonu normal bir fonksiyon gibi çağırılamadığı için tek yöntem aşağıdaki gibidir:

    char s[sizeof(T)];
    new(s) T();

    Burada new mekanizması kandırılarak aslında bir tahsisat yapılmadan başlangıç fonksiyonu çağırılmaktadır. Yukarıdaki kodda operator new() fonksiyonunun placement versiyonu çağırılacak, bu da hiç birşey yapmadan s'in kendisine geri dönecek ve böylece s için başlangıç fonksiyonu çağırılacaktır. Bu durumda dinamik tahsisat sırasında başlangıç fonksiyonunda oluşan throw işleminin otomatik free işlemine yol açmasını da dikkate alarak,

    p = new T();

    işleminin tamamen sembolik eşdeğeri,

    p = (T *)operator new(sizeof(T));
    try {
    new(p) T();
    }
    catch(...) {
    operator delete(p);
    throw;
    }

    6- void *operator new[] (std::size_t size, void *) throw();

    Bu biçim tekil placement versiyonunun []’li biçimidir. Bu fonksiyon da hiç bir şey yapmadan ikinci parametresi ile belirtilen adrese geri döner. Bir grup sınıf nesnesi için başlangıç fonksiyonu çağırmak için kullanılır. Örneğin:

    X s[sizeof(X) * SIZE];
    new(s) X[SIZE];

    Burada önce new operatörünün yukarıda belirtilen []’li placement versiyonu çağırılır. Elde edilen adres kullanılarak SIZE kadar eleman için tek tek başlangıç fonksiyonu çağırılır.

    Burada ele alınan altı new tahsisat fonksiyonunun hepsi eğer programcı aynısından yazarsa yer değiştirilme özelliğine sahiptir.

    7- void operator delete(void *ptr) throw();

    Bu normal []’siz delete fonksiyonudur. Örneğin

    delete p;

    gibi bir işlemde derleyici önce p adresindeki nesne için bitiş fonksiyonunu çağırır daha sonra bu operator delete fonksiyonunu çağırarak boşaltma işlemini gerçekleştirir. Yani delete p; işleminin karşılığı şöyledir:

    p->~X();
    operator delete(p);

    8- void operator delete[] (void *ptr) throw();

    Bu biçim []’li delete işlemi için çağırılan operatör fonksiyonudur. Yani

    delete[] p;

    gibi bir işlem yapıldığında eğer p bir sınıf türünden ise derleyici önce daha önce tahsis edilen dizi elemanları için bitiş fonksiyonunu çağırır, sonra da bu delete operatör fonksiyonunu çağırır.

    []’li delete işlemlerinde derleyici kaç eleman için bitiş fonksiyonunu çağıracağını nereden bilecektir? Derleyici nasıl bir kod üretmelidir ki daha önce tahsis edilen tüm dizi elemanları için bitiş fonksiyonları çağırılsın? Derleyici bu bilgiyi derleme zamanı içerisinde elde edemez. Kullanılan tahsisat algoritmasından da bu bilgiyi elde edemez. Bu bilgiyi []’li tahsisat işlemi sırasında tahsis edilen alanın içerisine yazmak zorundadır. Bu nedenle []’li versiyon kullanılarak X sınıfı türünden n elemanlı bir alan n * sizeof(X) değerinden daha büyük olabilir.

    9- void operator delete(void *ptr, void *) throw();

    Bu operatör fonksiyonu hiçbir şey yapmaz. Yalnızca durum tespitinin yapılması için düşünülmüştür. Anımsanacağı gibi bir sınıf türünden dinamik tahsisat yapıldığı durumlarda tahsisat başarılı olup sınıfın başlangıç fonksiyonu çağırıldığı zaman başlangıç fonksiyonunda throw oluşmuş ise akış catch bloğuna gidiyor. Ancak tahsis edilen alan otomatik olarak boşaltılıyor. İşte bu otomatik boşaltma sırasında da yine delete operatör fonsiyonu çağırılmaktadır. Tahsisat hangi türden new operatör fonksiyonu ile yapılmış ise o türden delete operatör fonksiyonu ile boşaltılır. Örneğin aşağıdaki gibi placement new operatörü ile başlangıç fonksiyonu çağırılırken başlangıç fonksiyonu içerisinde throw oluşmuş olsun

    new(p) X();

    işte derleyici tahsis edilen alanın otomatik boşaltımı için yine yukarıdaki placement delete operatörünü çağıracaktır. Bu placement delete operatörünün ciddi bir işlevi yoktur. Ancak programcı çeşitli durum tespit mesajlarını bu fonksiyonu içerisine yazdırabilir.

    10- void operator delete[] (void *ptr, void *) throw();

    Bu fonksiyon da hiç bir şey yapmaz. []’li placement new fonksiyonunun delete versiyonudur.

    11- void operator delete(void *ptr, const std::nothrow_t &) throw();

    nothrow biçimli bir delete normal olarak anlamlı değildir. Çünkü zaten normal delete operatör fonksiyonları da herhangi bir değere throw edemez. Bu biçim de sınıf türünden nothrow biçimli new operatör foksiyonu kullanılarak yapılan tahsisatlarda oluşan throw işlemlerinde delete operatör fonksiyonu olarak kullanılmaktadır. Çünkü yukarıda da belirtildiği gibi tahsisat sırasındaki otomatik silme işlemlerinde hangi türden new ile işlem yapılmışsa o türden delete derleyici tarafından otomatik olarak çağırmaktadır. Bu fonksiyonun kütüphanedeki orijinali normal operator delete fonksiyonunu çağırmaktadır.

    12- void operator delete[](void *ptr, const std::nothrow_t &) throw();

    Bu biçim []’li delete operatörünün nothrow’lu versiyonudur.


    allocator Sınıfı

    STL içerisinde allocator isimli bir template sınıf vardır. Bu sınıf tek bir template parametresi alır.

    template <class T>
    class allocator{
    //...
    };

    allocator sınıfı dinamik tahsisat yapmakta kullanılan bir sınıftır. Yani bu sınıfın üye fonksiyonları tahsisat yapar, tahsis edilmiş alanı boşaltır, tahsis edilmiş alan için başlangıç ve bitiş fonksiyonlarını çağırır. STL içerisindeki nesne tutan sınıfların hepsi template parametresi olarak tahsisat işlemlerinde kullanılacak bir sınıf ister ve tahsisat işlemlerini bu sınıfın üye fonksiyonlarını kullanarak yapar. Yani örneğin list sınıfı tahsisatı doğrudan new operatörünü kullanark değil allocator sınıfının üye fonksiyonunu kullanarak yapar. Böylelikle programcı isterse kendisi bir tahsisat sınıfı yazabilir ve tahsisat işleminde nesne tutan sınıfların kendi tahsisat sınıflarını kullanmasını sağlayabilir. Nesne tutan sınıfların hepsi programcıdan bir tahsisat sınıfı ister ve bu tahsisat sınıfı türünden nesneyi sınıfın protected bölümünde tanımlar. Bu nesneyi kullanarak tahsisat işlemlerini yapar. Tabii nesne tutan sınıflar ile çalışırken tahsisat sınıfına ilişkin template parametresi default değer almıştır. Programcı tahsisat sınıfını belirtmez ise allocator sınıfı tahsisat sınıfı olarak kullanılacaktır. Örneğin list sınıfı aşağıdaki gibidir:

    template <class T, class A = allocator<T> >
    class list {
    public:
    //...
    protected:
    A a;
    };

    Görüldüğü gibi tasarımcı list sınıfının üye fonksiyonlarını yazarken gereken tahsisatlar için doğrudan new operatörünü kullanmamıştır. Tahsisat işlemlerini tahsisat sınıfının üye fonksiyonlarını çağırarak yapmıştır.

    Programcı yeni bir tahsisat sınıfı yazacaksa standartlarda belirtilen typedef isimlerini ve üye fonksiyonlarını aynı isimle yazmak zorundadır. Çünkü bu isimler doğrudan nesne tutan template sınıflar tarafından kullanılmaktadır. Bu nedenle yeni bir tahsisat sınıfı yazacak olan kişiler bu işlemi kolay yapmak için zaten var olan allocator sınıfından sınıf türetip yalnızca gereken fonksiyonları o sınıf için yazarlar. Yani örneğin myallocator isimli yeni bir tahsisat sınıfı yazacak olalım. Bu işlem türetme yöntemi ile şöyle olabilir:

    template <class T>
    class myallocator : public allocator<T> {
    //...
    };


    Tahsisat Sınıfları Neden Kullanılır?

    Programcı bir tahsisat sınıfı yazmamışsa tahsisat sınıfı olarak default template parametresinden hareketle allocator sınıfı kullanılacaktır. Bu allocator sınıfının tahsisat yapan fonksiyonları da tahsisat işlemlerinde global tahsisat fonksiyonlarını kullanır. Sonuç olarak aksi belirtilmediği sürece nesne tutan sınıflar için tahsisatlar yine operator new() ve
    operator delete() fonksiyonları ile yapılmış olur. Ancak programcı başka heap alanları kullanıyor olabilir. Hatta başka tahsisat fonksiyonları kullanıyor olabilir. Hatta bu alanlardan tahsisat yapan başka tahsisat fonksiyonları kullanıyor olabilir. Bu durumda bu nesne tutan sınıfların istenilen heap alanlarından tahsisat yapabilmesi için ayrı tahsisat sınıflarının yazılması gerekir. Örneğin Win32’de birden fazla heap yaratılabilmektedir. operator new() ve operator delete() fonksiyonları CRT(C runtime library) heap’ini kullanırlar (Win32’de operator new() malloc() fonksiyonunu, malloc() fonksiyonu HeapAlloc() API fonksiyonunu çağırır. HeapAlloc() API fonksiyonu da CRT üzerinden tahsisat yapar). Şimdi biz Win32’de CreateHeap() fonksiyonu ile başka bir heap yaratmış olalım ve STL list sınıfının bu heap üzerinden tahsisat yapmasını isteyelim. Bu durumda biz bir tahsisat sınıfı yazmalıyız. Tahsisat sınıfındaki tahsisat yapan fonksiyonun da bizim yarattığımız heap’den tahsisat yapmasını sağlamalıyız.





    allocator Sınıfının Elemanları

    Sınıfta aşağıdaki typedef isimleri bulunmak zorundadır.

    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T *pointer;
    typedef const T *const_pointer;
    typedef T &reference;
    typedef const T &const_reference;
    typedef T value_type;

    size_t ve ptrdiff_t türlerinin ne olduğu bilindiği gibi derleyicileri yazanlara bırakılmıştır. size_t işaretsiz herhangi bir tür olabilir. ptrdiff_t de herhangi bir tür olabilmektedir. Genellikle derleyiciler size_t türünü unsigned int, ptrdiff_t türünü ise signed int olarak alırlar. C'de bu türler stddef.h dosyasında C++'da ise cstddef dosyasında typedef edilmiştir.


    allocate() Fonksiyonu

    Tahsisat sınıfının tahsisat işlemini bu fonksiyon yapar.

    pointer allocator(size_type n, allocator<void>::const_pointer hint = 0);

    Fonksiyonun birinci parametresi template türü T olmak üzere kaç tane T türünden tahsisat yapılacağıdır. Fonksiyonun ikinci parametresi const void * türündendir ve default NULL değeri alır. Orijinal allocator sınıfının bu fonksiyonu global operator new() fonksiyonunu çağırarak tahsisat yapar. Fonksiyonun geri dönüş değeri tahsis edilen alanın başlangıç adresidir ve T * türündendir. Fonksiyonun ikinci parametresi tahsisat fonksiyonunun performansını arttırmak amacı ile düşünülmüştür. Yani bu parametreye daha önce tahsis edilmiş bir bloğun adresi geçirilirse belki de tahsisat algoritması daha iyi yöntemler kullanabilecektir. Bu fonksiyon yalnızca tahsisat işlemini yapar, yani aslında doğrudan operator new(n * sizeof(T)) parametresi ile çağırılmış şeklidir. Bütün nesne tutan sınıflar tahsisatlarını tahsisat sınıfının allocate() fonksiyonu ile yaparlar.

    /* allocate.cpp */

    #include <iostream>
    #include <algorithm>

    using namespace std;

    int main()
    {

    allocator<int> x;
    allocator<int>:ointer p;

    p = x.allocate(10, NULL);
    memset(p, 0, 10 * sizeof(int));

    copy(p, p+10, ostream_iterator<int>(cout, "\n"));

    return 0;
    }


    deallocate() Fonksiyonu

    Bu fonksiyon tamamen tahsis edilen alanın boşaltılması amacı ile kullanılır. Nesne tutan sınıflar free işlemi için bu fonksiyonu çağırırlar.

    void deallocate(pointer p, size_type n);

    Fonksiyonun birinci parametresi boşaltılacak bellek bölgesinin başlangıç adresidir. İkinci parametre boşaltılacak alandaki eleman sayısıdır. Aslında bilindiği gibi tahsis edilen eleman sayısı zaten tahsisat algoritmaları tarafından bir biçimde bilinir. Ancak tahsisat sınıfında esnek davranılmıştır. Yani eleman sayısı tahsisat fonksiyonları tarafından bilinmese de boşaltma gerçekleşebilir. Orijinal allocator sınıfının bu fonksiyonu global operator delete() fonksiyonunu çağırmaktadır. Bu fonksiyonda sadece boşaltma yapar. Yani bitiş fonksiyonunun çağırılmasına yol açmaz. Fonksiyonun ikinci parametresi aslında modern tahsisat algoritmalarında hiç kullanılmaz. Ancak programcı belki kullanılıyordur diye bu parametreyi doğru yazmalıdır.


    construct() Fonksiyonu

    allocate() fonksiyonu ile sınıf için yer tahsis edilir fakat başlangıç fonksiyonunun çağırılmasına yol açmaz. Başlangıç fonksiyonu construct() fonksiyonu tarafından çağırılır.

    void construct(pointer p, const_reference val);

    Fonksiyonun birinci parametresi başlangıç fonksiyonu çağırılacak nesnenin adresidir. İkinci parametresi başlangıç fonksiyonunda kullanılacak sınıf nesnesini belirtir. Yani aslında val parametresi aynı sınıf türünden olduğuna göre kopya başlangıç fonksiyonunun çağırılmasına yol açmaktadır. allocator() fonksiyonunun orijinali

    new(p) T(val);

    değeri ile geri döner.

    #include <iostream>
    #include <algorithm>

    using namespace std;

    class X {
    public:
    X()
    {
    cout << "default constructor called\n";
    }

    X(int a, int b) : m_a(a), m_b(b)
    {
    cout << "2 parameter constructor called\n";
    }

    X(const X &r)
    {
    cout << "copy consturctor called\n";
    m_a = r.m_a;
    m_b = r.m_b;
    }

    ~X()
    {
    cout << "destructor called\n";
    }
    int m_a, m_b;
    };

    int main()
    {

    allocator<X> x;
    allocator<X>:ointer p;

    p = x.allocate(1, NULL);
    x.construct(p, X(10, 20));

    cout << p->m_a << '\n' << p->m_b << '\n';
    x.deallocate(p, 1);

    return 0;
    }


    destroy() Fonksiyonu

    Bu fonksiyon bir eleman için bitiş fonksiyonunu çağırır.

    void destroy(pointer p);

    Fonksiyon p->~T() işlemini yapar.




    Örnek Bir Tahsisat Sınıfı

    /* myallocator.cpp */

    #include <iostream>
    #include <algorithm>
    #include <vector>

    using namespace std;

    template <class T>
    class myallocator : public allocator<T> {
    public:
    pointer allocate(size_type n,
    allocator<void>::const_pointer hint = 0)
    {
    m_val += n * sizeof(T);
    return allocator<T>::allocate(n, hint);
    }
    static int m_val;
    };

    int myallocator<int>::m_val = 0;

    int main()
    {
    vector<int, myallocator<int> > x;

    x.push_back(10);
    x.push_back(20);

    cout << myallocator<int>::m_val << endl;
    return 0;
    }


    Nesne Tutan Sınıflardan Türetme Yapılması

    Bazen nesne tutan sınıflar doğrudan değil de türetme yapılarak kullanılır. Türetme yapılmasının nedeni işlev genişletme olabileceği gibi başka nedenler de olabilir. Örneğin tipik bir neden adreslerden oluşan bilgilerin tutulduğu veri yapılarında otomatik silme işlemlerinin sağlanmasıdır. Örneğin A bir sınıf olmak üzere A sınıfı türünden adresleri tutan bir bağlı liste olsun. Bilindiği gibi bu tür durumlar çok biçimli uygulamalarda çok sık rastlanmaktadır. Şimdi biz dinamik olarak tahsis edilen alanların adreslerini bağlı listeye yerleştireceğiz. Örneğin:

    list<A *> x;
    ...
    x.push_back(new A());
    x.push_back(new A());

    Şimdi burada bağlı listesinin faaliyet alanı bittiğinde x için bitiş fonksiyonu çağırılacaktır. Ancak bitiş fonksiyonu yalnızca kendi gösterici olan düğümleri siler. Dinamik tahsis edilmiş olan A nesnelerini silmez. Bu nedenle bu tür durumlarda bağlı listenin faaliyet alanı bitmaden onların gösterdiği alanların silinmesi gerekir.

    for (list<A *>::iterator iter = x.begin(); iter != x.end(); ++iter)
    delete *iter;

    İşte eğer nesne tutan sınıf adresleri tutuyorsa nesne tutan sınıf için çağırılan bitiş fonksiyonu o adreslerin gösterdikleri alanları free hale getirmez. Yalnızca o adresleri tutmakta kullanılan göstericileri free hale getirir. Bu işlemin otomatik yapılmasını sağlamak için nesne tutan sınıftan bir sınıf türetilir ve o sınıf için bitiş fonksiyonu yazılır.Bitiş fonksiyonunda bu göstericilerin gösterdiği yerler boşaltılır, program içerisinde de türetilen sınıf kullanılır. Örneğin:

    class listptr : public list<A *> {
    public:
    ~listptr()
    {
    for (iterator iter = begin(); iter != end(); ++iter)
    delete *iter;
    }
    };

    int main()
    {
    listptr x;

    x.push_back(new A());
    x.push_back(new A());
    }


    Yeni Tür Dönüştürme Operatörleri

    Bilindiği gibi C++'da C'de kullanılan klasik tür dönüştürme operatörü aynı şekilde kullanılmaktadır. Yine ayrıca tür dönüştürme operatörünün fonksiyonel biçimi denilen biçimi de C++'a özgü olarak kullanılır. Örneğin:

    (int) a; //Normal biçim
    int (a); //Fonksiyonel biçim

    Ancak bunların dışında tamamen konulara ayrılarak uzmanlaştırılmış özel tür dönüştürme operatörleri de vardır. C++'ın yeni tür dönüştürme operatörleri şunlardır:

    static_cast
    const_cast
    reinterpret_cast
    dynamic_cast

    Bu operatörlerin kullanım syntaxı şöyledir:

    operator_ismi<dönüştürülecek tür>(dönüştürülecek ifade)

    Örneğin:

    a = static_cast<int>(b);

    Aslında normal tür dönüştürme operatörü bunların hepsinin yerini tutar. Yeni operatörler belirli konularda uzmanlaştığı için daha güvenli kabul edilmektedir.


    static_cast Operatörü

    Bu operatör standart dönüştürmelerde kullanılır, yani:

    1- C'nin normal türleri arasında yapılan dönüştürmelerde. Örneğin:

    long b;
    int a;
    a = static_cast<int>(b);

    2- Türemiş sınıf türünden adresin taban sınıf göstericisine dönüştürülmesi durumunda. Örneğin:

    A *pA;
    B b;
    pA = static_cast<A *>(&b);

    3- Taban sınıf türünden adresin türemiş sınıf türüne dönüştürülmesi durumunda. Örneğin:

    A *pA;
    B b;
    B *pB;
    pA = static_cast<A *>(&b);
    pB = static_cast<B *>(pA);


    const_cast Operatörü

    Bu operatör const ve/veya volatile özelliklerini bir göstericiden kaldırmak için kullanılır, yani örneğin const int * türünü int * türüne dönüştürmek için bu operatör kullanılmalıdır. Örnek:

    const int x;
    int *p;

    p = const_cast<int *>(&x);

    Bu operatör const/volatile özelliğini kaldırarak başka bir türe dönüştürme yapamaz. Örneğin:

    int *pi;
    const char *pcc;

    pi = const_cast<int *>(pcc); //error

    Görüldüğü gibi bu operatörde dönüştürülecek tür dönüştürülecek ifade ile aynı türden olmalıdır. Bu operatörle gerekmese bile const olmayan adresten const adrese dönüştürme yapılabilir.


    reinterpret_cast Operatörü

    Bu operatör bir adresi başka türden bir adrese dönüştürmek için ve adreslerle adres olmayan türler arasındaki dönüştürmeler için kullanılır. Örneğin:

    int *pi;
    char *pc;
    ...
    pc = reinterpret_cast<char *>(pi);
    pi = reinterpret_cast<int *>(pc);

    Bu operatör const/volatile özelliklerini kaldırmaz. Bu operatörle taban sınıf türemiş sınıf arasında da dönüştürme yapılabilir. Ancak bu operatör const bir adresten başka türün const olmayan bir adresine dönüşüm yapmaz. Bu işlem aşağıdaki gibi iki aşamada yapılabilir:

    const char *pcc;
    int *pi;
    ...
    pi = reinterpret_cast<int *>(const_cast<char *>(pcc));


    RTTI Özelliği, typeid ve dynamic_cast Operatörleri


    RTTI (Run Time Type Information) özelliği aslında temel olarak çokbiçimlilik konusunda bir göstericinin ya da referansın gerçekte hangi türemiş sınıfı gösterdiğini tespit etmek için düşünülmüştür. Örneğin bazen türemiş sınıfın adresi bir taban sınıf göstericisine atanır sonra yeniden orijinal türe dönüştürülmek istenir. Ancak programcı çeşirli nedenlerden dolayı orijinal türü tespit edemiyor olabilir. Bir türetme şeması olsun en tepedeki taban sınıfın A sınıfı olduğunu varsayalım. A sınıfı türünden pA isimli bir gösterici tanımlamış olalım, programın çalışma zamanı sırasında pA'ya herhangi bir türemiş sınıf nesnesinin adresi atanmış olsun. Biz bunu bilmiyorsak çalışma zamanı sırasında tespit edebilir miyiz? İşte RTTI konusunun ana noktasını bu oluşturmaktadır. RTTI mekanizması derleyiciye pekçok yük getirdiği için ve çalışabilen kodun verimini düşürebildiği için derleyicilerin pekçoğunda bu özellik isteğe bağlı bir biçimde yüklenebilmektedir. RTTI özelliği VC++ derleyicisinde Project/Settings/C-C++/C++ Language/RTTI kısmından ayarlanabilir ve bu özellik default olarak kapalıdır.

    RTTI özelliği için type_info isimli bir sınıf kullanılır. Bu sınıf <typeinfo> dosyasında bildirilmiştir.



    class type_info {
    public:
    virtual ~type_info();
    bool operator==(const type_info &rhs) const;
    bool operator!=(const type_info &rhs) const;
    bool before(const type_info &rhs) const;
    const char *name() const;
    };

    type_info sınıfı da diğer sınıflarda olduğu gibi std namespace'i içerisinde bildirilmiştir. Sınıfın == ve != operatör fonksiyonu tamamen iki sınıfın aynı türden olup olmadığını kontrol etmek için kullanılır. name() üye fonksiyonu ilgili türün ismini elde etmekte kullanılır. before() fonksiyonu ilgili çokbiçimli sınıfın türetme ağacında daha yukarıda olup olmadığı bilgisini elde etmekte kullanılır.


    typeid Operatörü

    typeid RTTI konusunda kullanılan bir operatördür. Kullanımı şöyledir:

    typeid(ifade)

    Bu operatör ifadenin türünü tespit eder ve ifadenin türüne uygun const type_info & türünden bir değer üretir. Burada belirtilen ifade herhangi bir tür ismi olabilir (sizeof operatöründe olduğu gibi) ya da normal bir ifade olabilir. İfade çokbiçimli bir sınıf içerisinde bir nesne belirtiyorsa elde edilecek bilgi çalışma zamanı sırasındaki gerçek sınıfa ilişkindir. Örneğin p çokbiçimli bir türetme şeması içerisinde bir gösterici olsun *p typeid operatörüne operand yapılırsa p'nin gösterdiği gerçek sınıfın tür bilgisi elde edilir. Yani B sınıfı A sınıfından türetilmiş olsun aşağıdaki örnekte B sınıfının bilgileri elde edilecektir:

    A *pA = new B();

    typeid(*pA) //Burada B ile ilgili bilgiler elde edilir

    Ancak derleyiciler bu bilgileri sanal fonksiyon tablolarından elde ettiği için sınıf sisteminin bir çokbiçimlilik özelliğine sahip olması gerekir. Bunu yapmak için en pratik yöntem en taban sınıfa bir sanal bitiş fonksiyonu eklemektir. type_info sınıfının atama operatör fonksiyonları ve başlangıç fonksiyonları sınıfın private bölümüne yerleştirilmiştir, bu yüzden type_info sınıfı türünden bir nesne tanımlanamaz ve typeid operatörünün ürettiği değer başka bir sınıf nesnesine atanamaz. Bu değer doğrudan aşağıdaki gibi kullanılmalıdır:

    cout << typeid(int).name() << "\n";

    typeid operatörüyle derleyicinin yaptığı işlemleri şöyle özetleyebiliriz:

    1- Eğer RTTI özelliği etkin hale getirilmişse derleyici bütün çokbiçimli olmayan sınıflar için ve C++'ın doğal türleri için statik düzeyde bir tane type_info sınıfı tahsis eder ve typeid operatörü doğrudan bu sınıf ile geri döner.

    2- Eğer çokbiçimli bir sınıf sistemi sözkonusuysa derleyici type_info sınıf bilgilerini sınıfların sanal fonksiyon tablolarında tutar böylece p bu sınıf sisteminde bir adres olmak üzere typeid(*p) ifadesi ile p göstericisinin ilişkin olduğu sınıfa ilişkin bilgi değil, onun gerçekte gösterdiği sınıfa ilişkin bilgi elde edilmektedir. Burada önemli bir nokta eğer sınıf sistemi çokbiçimli değilse typeid(*p) ile p'nin gösterdiği yerdeki gerçek sınıfa ilişkin değil p'nin türüne ilişkin değer elde edilir. Örnek:

    #include <iostream>

    class A {
    public:
    virtual ~A() {}
    };

    class B : public A{
    public:
    virtual ~B() {}
    };

    using namespace std;

    void main()
    {
    A *pA = new B();

    cout << typeid(*pA).name() << endl; // class B yazar
    }

    type_info sınıfının == ve != operatör fonksiyonları aslında sınıfların isimlerine bakarak bir karşılaştırma yapar. Biz bu operatör fonksiyonlarını çokbiçimli bir yapı içerisinde bir göstericinin içerisindeki adresin gerçekte belirli bir sınıfı gösterip göstermediğini anlamak için kullanırız. Örneğin pA A sınıfı türünden bir gösterici olsun, şimdi biz pA'nın gerçekte B sınıfını gösterip göstermediğini aşağıdaki gibi anlayabiliriz:

    if (typeid(*pA) == typeid(B)) {
    //...
    }

    Türetme şemasında aşağıdan yukarıya doğru yapılan dönüştürmeler (upcast) normal dönüştürmelerdir. Halbuki yukarıdan aşağıya yapılan dönüştürmeler (downcast) eğer haklı bir gerekçe yoksa güvensiz bir dönüştürmedir. Örneğin türemiş sınıf nesnesinin adresi taban sınıf göstericisine atanabilir ve sonra yeniden türemiş sınıf adresine dönüştürülebilir. Buradaki dönüştürme haklı bir dönüştürmedir. Bilindiği gibi yukarıya ve aşağıya dönüştürme işlemeri aslında static_cast operatörüyle yapılabilir. Ancak static_cast göstericinin gösterdiği yere bakarak bu dönüştürmenin yerinde olup olmadığına bakmaz, halbuki dynamic_cast RTTI özelliğini dikkate alarak eğer dönüştürme yerindeyse bu işlemi yapar. dynamic_cast dönüştürmenin uygun olup olmadığını şöyle belirler: RTTI mekanizmasını kullanarak dönüştürülecek türün göstericinin gösterdiği gerçek tür içerisinde var olup olmadığını araştırır. Eğer dönüştürülecek tür göstericinin gösterdiği yerdeki gerçek bileşik türün parçalarından biriyse dönüştürme yerindedir ve dynamic_cast dönüştürmeyi yapar. Örneğin şöyle bir türetme şeması olsun:

  5. #5
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    A



    B



    C



    D



    E








    Şimdi D türünden bir nesne olsun bunun adresini doğrudan dönüştürme yapmadan A türünden bir göstericiye atayabiliriz:

    D d;
    A *pA;
    pA = &d;

    Şimdi pA'nın B, C ve D türüne dönüştürülmesi uygun ve güvenli işlemlerdir. Ancak E türüne dönüştürülmesi uygun ve güvenli değildir ve gösterici hatasına yol açabilir. Çünkü A, B ve C, D nesnesinin içerisinde vardır ama E nesnesi D'nin içerisinde yoktur. Ancak yine de static_cast operatörüyle pAE türüne dönüştürülebilir.

    E *pA = static_cast<E *>(pA);

    Çünkü static_cast işleminde derleyici yalnızca ismi üzerinde static olarak türetme şemasına bakmaktadır. Halbuki dynamic_cast dönüştürülecek türün göstericinin gösterdiği gerçek nesne içerisinde olup olmadığına bakarak dönüştürmeyi yapmaktadır. Tıpkı typeid operatöründe olduğu gibi dynamic_cast operatöründe de doğru işlemlerin yapılabilmesi için türetmenin çokbiçimli olması gerekir (yani tabandaki sınıfın en az bir sanal fonksiyonunun olması gerekir). dynamic_cast dönüştürmeyi yapabilirse dönüştürülmüş adrese, yapamazsa NULL değerine geri döner. Örnek:

    /* dynamic_cast.cpp */

    #include <iostream>

    using namespace std;

    struct A {
    public:
    virtual ~A() {}
    };

    struct B : A {

    };

    struct C : B {

    };

    struct D : C {

    };

    struct E : A {

    };


    void main()
    {
    D d;
    A *pA;

    pA = &d;
    C *pC;

    pC = dynamic_cast<C *>(pA);
    if (pC == NULL)
    cout << "gecersiz donusturme\n";
    else
    cout << "gecerli donusturme\n";
    }


    Referansa Dönüştürme İşlemleri

    Göstericilerle referanslar aslında tamamen benzer türlerdir. Şimdiye kadar çokbiçimlilik örneklerinin çoğu göstericiler üzerine yapıldı halbuki aynı örnekler referanslar üzerinde de yapılabilir. Normal olarak bir referansa tür dönüştürmesi yapıldığında derleyici dönüştürülecek olan operandın adresini tutmak üzere bir geçici bölge oluşturur, sonra o geçici bölgedeki adres yoluyla nesneye erişimi sağlar. İfadenin sonunda geçici bölge boşaltılmaktadır. Eğer dönüştürülecek operand dönüştürülecek olan referansla aynı türden olmayan bir nesneyse ya da sabit ise o zaman dönüştürülecek referansın const referans olması gerekir, bu durumda iki geçici bölge yaratlılır. Birincisi referansın türünden olan ve operandı tutacak geçici bölge, ikincisi operandın adresini tutacak geçici bölge. Referansa dönüştürme işlemi standart bir dönüştürme işlemidir ve yeni dönüştürme operatörlerinden static_cast ile yapılabilir.

    Normal türler için referansa dönüştürmenin bir faydası yoktur, ancak bir türetme şeması içerisinde referansa dönüştürmeler tıpkı gösterici dönüştürmeleri gibi etkili bir biçimde kullanılabilir. Yani yukarı ve aşağı dönüştürme işlemleri gösterici yerine referanslarla da yürütülebilir. Örneğin D sınıfı A sınıfının bir türemiş sınıfı olsun, Func() ise D'nin bir üye fonksiyonu olsun, aşağıdaki işlem tamamen geçerlidir:

    D d;
    A &r = d;
    static_cast<D &>(r).Func();

    Ancak görüldüğü gibi bu tür işlemlerde referans kullamak iyi bir görüntü vermemektedir, tabii bazen zorunlu olabilir. Örneğin başkası tarafından yazılan fonksiyonun parametresi A türünden referans olsun, ama biz geçirilen türün D türünden olduğunu bilelim. D türüne aşağıya doğru dönüştürme uygulamak için iki şey yapılabilir:

    1- Göstericiye dönülerek göstericiyle çalışılır.
    2- Referansla işlemlere devam edilir.

    Aslında bu iki işlem içsel olarak neredeyse eşdeğerdir.

    void Sample(A &ra)
    {
    // 1. yontem
    D *pD = static_cast<D *>(&ra);

    // 2. yontem
    D &rD = static_cast<D &>(ra);
    }


    İkili Ağaç Yapıları

    İkili ağaç (binary tree) fiziksel olarak sıralı olmayan elemanların mantıksal bir biçimde sıralı gözükmesi için oluşturulmuş olan, logaritmik aramalara izin veren en önemli ağaç yapılarındandır. İkili ağaçta her düğümün iki göstericisi vardır, göstericilerden biri o düğümden küçük olan elemanı, diğeri ise büyük olan elemanı gösterir. İkili ağaç tipik olarak aşağıdaki gibi bir yapıyla ifade edilebilir:

    template <class T>
    struct BNODE {
    T val;
    BNODE *pLeft;
    BNODE *pRight;
    };

    İkili ağaca yeni bir eleman eleneceği zaman önce eklenecek yer tepe düğümünden hareketle sola ve sağa gidilerek bulunur. Sonra sol ya da sağ düğüm üzerinde güncelleme yapılarak eleman eklenir. Örneğin aşağıdaki sayıların teker teker buraya ekleneceğini düşünelim:

    8 21 7 16 44 3 17 9 28 33







    8

    7

    21

    3

    16

    44

    9

    17

    28

    33

















    İkili ağaçta en tepeden en uzun dala kadar olan eleman sayısına ağacın yüksekliği denir. Farklı dallarda ağacın yüksekliği aynı değilse bu tür ikili ağaçlara dengelenmemiş ikili ağaçlar denir. Son kademe hariç tüm dalların yüksekliği aynıysa böyle ağaçlara dengelenmiş ikili ağaç (balanced binary tree) denir. Eğer son kademede de hiç fazla eleman yoksa ağaç tam dengelenmiştir (complete binary tree). Dengelenmiş bir ağaçta tamamen ikili arama performansına sahiptir, yani en kötü arama sayısı 'dir. Yine dengelenmiş ikili ağaca en kötü olasılıkla logaritmik olarak eleman eklenebilir. Bir dengelenmiş ikili ağaç basit bir kendi kendini çağıran fonksiyonla sıralı olarak dolaşılabilir (binary tree traversing), böylelikle ikili ağaçlar sıralı bir dizi görüntüsü de verebilir.


    İkili Ağaçlarla Bağlı Listelerin Karşılaştırılması

    1- Eleman ekleme bakımından bağlı listeler daha iyidir. Çünkü bağlı listelere eleman ekleme sabit zamanlı bir işlemdir. Halbuki ikili ağaca eleman logaritmik karmaşıklıkta eklenir.
    2- Çift bağlı listelerle ikili ağaç hemen hemen aynı büyüklükte bellek kullanır. Ancak tek bağlı listeler daha az bellek kullanmaktadır.
    3- İkili ağaç arama işlemlerinde bağlı listelerden çok daha iyidir. Dengelenmiş ikili ağaçlarda başarısız aramalarda karmaşıklık biçimindedir. Halbuki bağlı listelerde doğrusal arama söz konusudur.
    4- Sıraya dizme konusunda her zaman ikili ağaç bağlı listeye göre çok daha iyidir. Çünkü basit bir kendi kendini çağıran fonksiyonla ikili ağaç küçükten büyüğe ya da büyükten küçüğe dolaşılabilir.

    Yukarıdaki açıklamalar eşliğinde şunlar söylenebilir: Eğer çok eleman ekleyip az arama işlemi yapılıyorsa bağlı liste, az eleman ekleyip çok arama yapılıyorsa ikili ağaç tercih edilmelidir.




    map ve multimap Sınıfları

    STL'de ikili ağaç oluşturan tipik sınıflar map, multimap ve set, multiset sınıflarıdır. map ve set sınıfları birbirine çok benzer, aralarında küçük farklılıklar vardır (Bu sınıfların dengelenmiş ikili ağaç yöntemi ile oluşturulması zorunlu tutulmamışsa da genel işleyiş mekanizmaları için en uygun veri yapısı dengelenmiş ikili ağaçtır). multimap sınıfının map sınıfından tek farkı aynı elemanın eklenmesine izin vermesidir. map ve multimap yapılarında düğümler pair çiftlerini tutar. Anımsanacağı gibi pair yapısı first ve second isimli iki elemana sahiptir. Bu sınıflar anahtar olarak first bilgisini kullanırlar. Yani ağacı first elemanına göre oluştururlar. Tipik olarak arama işlemi first elemanına göre yapılır. Örneğin first bir kişinin numarası, second ise kimlik bilgileri olabilir. Arama numaraya göre yapılır. Bir kişinin kimlik bilgileri elde edilir. Bu sınıflar aşağıdaki gibi template parametrelerine sahiptir:

    template <class Key, class T, class Compare = less<Key>,
    class Allocator = allocator <pair<const Key, T> > >
    class map{
    //...
    };

    class multimap {
    //...
    };

    Görüldüğü gibi sınıfın en az iki template parametresi belirtilmek zorundadır. Birinci parametre anahtar olarak kullanılacak türü (yani pair yapısının first türünü), ikinci parametre ise tutulacak bilgiyi (yani pair yapısının second elemanını göstermektedir). Örneğin bir kişinin numarasına göre ismini arayabileceğimiz bir map nesnesi şöyle tanımlanır:

    map<int, string> x;

    Sınıfın üçüncü template parametresi anahtar bilgiler ağaca yerleştirilirken hangi operatör ile karşılaştırma yapılacağını belirtir. Burada default olarak less binary predicate'i kullanılmıştır. Yani karşılaştırma sırasında default olarak < operatörü kullanılır. Bu durumun iki önemli sonucu vardır:

    1- Anahtar bilgi bir sınıf ise, sınıfın küçüktür operatör fonksiyonu olmalıdır.
    2- iterator yöntemi ile dolaşım yapılırsa küçükten büyüğe bir sıra görünür. Büyükten küçüğe görünüm elde etmek için reverse_iterator kullanılabilir ya da buradaki predicate sınıf greater olarak alınabilir.

    Sınıfın son template parametresi her sınıfta olduğu gibi allocator sınıfını belirtmektedir.




    map ve multimap Sınıfının Üye Fonksiyonları

    Sınıfın default başlangıç fonksiyonu ve iki iterator arasındaki elemanlardan map yapan başlangıç fonksiyonları vardır. Ayrıca karşılaştırma fonksiyonu içeren bir başlangıç fonksiyonu da içermektedir. Örneğin:

    map<key, elem> x;
    map<key, elem> y(v.begin(), v.end());
    map<key, elem> z(Comp);

    Yine sınıfın klasik olarak size() üye fonksiyonu eleman sayısına geri döner, empty() üye fonksiyonu boş mu diye bakar, clear() üye fonksiyonu tüm elemanları siler, erase() üye fonksiyonu iki iterator aralığını siler. Şüphesiz sınıfın önemli fonksiyonları eleman insert eden ve arayan fonksiyonlardır.


    map ve multimap Sınıflarına Eleman Insert Edilmesi

    Sınıfın insert() üye fonksiyonu elemanı ağaçtaki uygun yere insert eder. Fonksiyonun prototipi şöyledir:

    pair<iterator, bool> insert(const pair<key, elem> &r);

    Görüldüğü gibi insert() fonksiyonu bir pair yapısı alarak insert işlemini yapar. Fonksiyon map sınıfı söz konusuysa daha önce aynı elemandan varsa başarısız olabilir. Ancak multimap sınıfının insert() fonksiyonu başarısız olmaz. multimap sınıfının insert() fonksiyonunun prototipi şöyledir:

    iterator insert(const pair<key, elem> &r);

    Her iki sınıfın insert() fonksiyonunun geri dönüş değerindeki iterator yeni eklenen elemana ilişkin iterator değeridir. Örneğin map sınıfına aşağıdaki gibi eleman insert edilebilir.

    x.insert(pair<int, string>(10, "kaan"));
    x.insert(make_pair(10, string("ali"));

    Birinci insert işleminde geçici bir pair nesnesi oluşturarak pair yapısı elde edilmiştir. İkinci insert işleminde eklenecek pair make_pair() fonksiyonu ile elde edilmiştir. make_pair() fonksiyonu tamamen kolay bir pair nesnesi yaratmak için kullanılır. make_pair() fonksiyonu şöyle yazılmıştır:

    template <class A, class B>
    pair<A, B> make_pair(const A &a, const B &b)
    {
    return pair<A, B>(a, b);
    }

    Görüldüğü gibi make_pair() fonksiyonunu kullanmanın tek avantajı template parametrelerini belirtmemektir. İşlemin başarısı kontrol edilebilir. Bunun için geri dönüş değerinin second elemanına bakmak gerekir. Bu işlem biraz karışık gibi görülebilir.

    if (x.insert(make_pair(15, string("veli"))).second) {
    //...
    }

    map ve multimap sınıfının iteratorleri bidirectional iterator'dür. Yani ileri ve geri yönde hareket edebilir. map ve multimap sınıfları pair yapılarından oluştuğu için iter bu sınıfın bir iterator'ü olmak üzere *iter de pair sınıfı türündendir. Bu durumda elemanların yazdırılması aşağıdaki gibi yapılmalıdır.

    map<int, string> x;
    //...
    //...
    map<int, string>::iterator iter;

    for (iter = x.begin(); iter != x.end(); ++iter)
    cout << (*iter).first << '\t' << (*iter).second);

    Örnek:

    /* map.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <map>
    #include <string>

    using namespace std;

    int main()
    {
    map<int, string> x;
    map<int, string>::iterator iter;

    x.insert(make_pair(1, string("volkan")));
    x.insert(make_pair(5, string("kaan")));
    x.insert(make_pair(3, string("fatih")));
    x.insert(make_pair(10, string("karga")));
    x.insert(make_pair(7, string("murat")));
    x.insert(make_pair(8, string("arda")));

    for (iter = x.begin(); iter != x.end(); ++iter)
    cout << (*iter).first << '\t' << (*iter).second << endl;

    return 0;
    }



    Insert işlemi en kolay [] operatörü ile yapılabilir. [] operatör fonksiyonunun genel yapısı şöyledir:

    elem &operator[](const key &r);

    Fonksiyon index değeri olarak pair yapısının first türünü alır ve ağaçtaki yerleşim yerini bularak eğer belirtilen elemandan yoksa yeni bir eleman insert eder ve o elemana ilişkin second değerinin referansına geri döner. Eğer index olarak verilen eleman ağaçta varsa yeni bir eleman insert etmez doğrudan ağaçta olan elemanın second değerinin referansına geri döner. Bu durumda tipik olarak [] operatörüyle insert şöyle yapılabilir:

    map<int, string> x;

    x[100] = "ali";
    x[300] = "veli";
    x[50] = "sacit";
    //...

    /* map2.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <map>
    #include <string>

    using namespace std;

    int main()
    {
    map<int, string> x;
    map<int, string>::iterator iter;

    x[100] = "volkan";
    x[50] = "kaan";
    x[57] = "falan";
    x[54] = "filen";

    for (iter = x.begin(); iter != x.end(); ++iter)
    cout << (*iter).first << '\t' << (*iter).second << endl;

    return 0;
    }


    map Sınıflarında Arama İşlemleri

    map ve set sınıfları dengelenmiş ikili ağacı kullanarak algoritmik arama yapmakta kullanılır. Arama yapmakta kullanılan üç temel fonksiyon vardır.

    find(key);
    lower_bound(key);
    upper_bound(key);

    find() fonksiyonu bir key değerini parametre olarak alır ve ağaçta o değerin aynısından var mı diye bakar. Arama başarılıysa bulunan elemanın iterator değerine, başarısız ise end iterator değerine geri döner. Örneğin

    /* map3.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <string>
    #include <map>

    using namespace std;

    int main() {

    map<int, string> x;
    map<int, string>::iterator iter;

    x.insert(make_pair(10, string("ali")));
    x.insert(make_pair(20, string("volkan")));
    x.insert(make_pair(30, string("baris")));
    x.insert(make_pair(40, string("emine")));

    iter = x.find(30);
    if (iter == x.end())
    cout << "bulunamadı\n";
    else
    cout << (*iter).first << '\t' << (*iter).second << endl;

    return 0;
    }

    find() fonksiyonu tam uyan elemanı bulmakta kullanılır. Halbuki bazen buna en yakın küçük ya da büyük elemanı bulmak pek çok bakımdan gerekli olabilir. lower_bound() fonksiyonu key <= elem olan ilk elemanı bulur. Bu fonksiyon multimap söz konusu olduğunda aranan elemandan birden fazla olduğunda aranan elemanların ilkini bulacaktır. Örneğin aşağıdaki dizide 8’i arayacak olalım.

    lower_bound(8)


    3 8 8 8 9 10

    Aşağıdaki dizide 5’i arayacak olalım.

    2 4 6 9 10


    lower_bound(5)

    upper_bound() fonksiyonu ilk key < elem koşulunu sağlayan düğümü bulur. Örneğin aşağıdaki dizide bu fonksiyon ile 8’i arayacak olalım.

    upper_bound(8)



    3 8 8 8 9 10

    Aşağıdaki dizide 5 aranacak olsun.


    2 4 6 9 10


    lower_bound(5)

    Görüldüğü gibi aranan eleman bulunamaz ise lower_bound() fonksiyonu ile upper_bound() fonksiyonu arasından bir fark olmaz. Ya da örneğin aranan elmandan bir tane varsa lower_bound() bulunan elemanın iterator değerini, upper_bound() bulunandan bir sonrakinin iterator değerini verir. Özetle elemanın bulunması durumunda lower_bound() öne, upper_bound() arkaya insert için iterator verir.



    /* map4.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <string>
    #include <map>
    #include <vector>

    using namespace std;

    int main()
    {
    vector<pair<int, string> > v;
    multimap<int, string> x;
    multimap<int, string>::iterator iter;

    x.insert(make_pair(100, string("ali")));
    x.insert(make_pair(10, string("volkan")));
    x.insert(make_pair(10, string("baris")));
    x.insert(make_pair(8, string("emine")));

    iter = x.lower_bound(10);

    copy(iter, x.upper_bound(10), back_inserter(v));

    vector<pair<int, string> >::iterator iter2;

    for (iter2 = v.begin(); iter2 != v.end(); ++iter2)
    cout << (*iter2).second;

    return 0;
    }

    map sınıfından silme yapmak için erase() fonksiyonu kullanılabilir. erase() fonksiyonlarının çeşitli versiyonları vardır.

    erase(elem);
    erase(first, last);

    Örneğin:

    #pragma warning(disable:4786)

    #include <iostream>
    #include <string>
    #include <map>

    using namespace std;

    int main()
    {
    multimap<int, string> x;
    multimap<int, string>::iterator iter;

    x.insert(make_pair(100, string("ali")));
    x.insert(make_pair(10, string("volkan")));
    x.insert(make_pair(10, string("baris")));
    x.insert(make_pair(8, string("emine")));

    x.erase(x.lower_bound(10), x.upper_bound(10));

    for (iter = x.lower_bound(10); iter != x.upper_bound(10); ++iter)
    cout << (*iter).second << endl;

    return 0;
    }


    set ve multiset Sınıfları

    set ve multiset sınıfları tamamen map ve multimap sınıfları gibi çalışır. Yani bu sınıflar da dengelenmiş ikili ağaç kurarlar. map ve multimap elemanları pair biçiminde tutarken, bu sınıflar tekil biçimde tutarlar. Dolayısıyla bir anahtar alanları yoktur, tabii yeni elemanın ağaçtaki yere yerleşebilmesi için bir karşılaştırma fonksiyonuna gereksinim vardır. Sınıfın template bildirimi şöyledir:

    template <class T, class Compare = less<T>,
    class Allocator = allocator<T> >
    class set {
    //...
    };

    Görüldüğü gibi nesneyi tanımlarken en az bir türü belirtmek gerekir. Ağaçtaki yer default olarak < operatörü ile bulunur. Ağaçta tutulan bilgi int, float gibi basit bir bilgi ise set map’ten daha kullanışlıdır. Eğer ağaçta tutulacak bilgi yapı ya da sınıf ise, bu yapıya da sınıfın < operatör fonksiyonunu default olarak yazmak gerekir.

    [] operatör fonksiyonu sadece map sınıfı için tanımlıdır. set, multiset ve multimap sınıfları için tanımlı değildir. find(), erase(), insert(), lower_bound() ve upper_bound() fonksiyonları tamamen map ve multimap sınıflarındaki gibidir. Ancak anahtar bir tür ile çalışmazlar, sınıfta tutulan tür ile çalışırlar. Bu sınıflar <set> başlık dosyasındadır.

    /* set1.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <cstdlib>
    #include <set>

    using namespace std;

    int main()
    {
    set<int> x;

    for (int i = 0; i < 100; ++i)
    x.insert(rand() % 1000);

    copy(x.begin(), x.end(), ostream_iterator<int>(cout, "\n"));
    cout << endl << "Toplam eleman = " << x.size() << endl;

    return 0;
    }


    /* set2.cpp */

    #pragma warning(disable:4786)

    #include <iostream>
    #include <string>
    #include <set>

    using namespace std;

    class Person {
    public:
    Person(const char *pName, int no):m_name(pName), m_no(no){}
    friend ostream &operator<<(ostream &r, const Person &per);
    bool operator<(const Person &per) const
    {
    return m_no < per.m_no;
    }
    private:
    string m_name;
    int m_no;
    };

    namespace std { // for visual C++

    ostream &operator<<(ostream &r, const Person &per)
    {
    r << per.m_name << '\t' << per.m_no;
    return r;
    }

    } // off namespace

    int main()
    {
    set<Person> x;

    x.insert(Person("ali", 10));
    x.insert(Person("volkan", 20));
    x.insert(Person("veli", 30));

    copy(x.begin(), x.end(), ostream_iterator<Person>(cout, "\n"));
    cout << endl << "Toplam eleman = " << x.size() << endl;

    return 0;
    }


    -> Operatör Fonksiyonunun Yazımı ve Smart Göstericiler

    -> operatörü sınıfın üye fonksiyonu olarak yazılmak zorundadır. Binary bir operator olmasına karşın sanki unary bir operatörmüş gibi ele alınıp yazılır. Böylesi bir tasarım smart göstericilerin kullanılmasına olanak verdiği için daha faydalı bulunmuştur. a bir sınıf nesnesi olmak üzere a-> işleminin eş değeri a.operator->()-> biçimindedir. Bu durumda a->b nin eş değeri a.operator->()->b biçimindedir. -> operatör fonksiyonunun geri dönüş değerinin bir sınıf ya da yapı adresi olması gerekir. Böylelikle a bir sınıf nesnesi olmak üzere a-> işleminden bir yapı ya da sınıf adresi elde edilmeli, b de elde edilen bu yapı ya da sınıfın elemanı olmalıdır. İşte bir sınıfta -> operatör fonksiyonunu yazarsak o sınıf türünden nesne sanki başka bir sınıf türünden göstericiymiş gibi davranır. Zaten smart gösterici demek gösterici gibi kullanılan sınıf nesnesi demektir. Smart göstericiler sayesinde referans sayma işlemleri gibi işlemler, güvenli gösterici kullanma mümkün hale gelebilir.

    Smart gösterici sistemlerinde bir asıl sınıf vardır, bir de -> operatör fonksiyonu yazılmış asıl sınıf türünden gösterici gibi davranan sınıf vardır. Hatta bazen asıl sınıf tamamen gizlenebilir, asıl sınıfa tamamen smart göstericiler ile erişilebilir.



    Genel amaçlı template bir smart gösterici sınıfı aşağıdakine benzer tanımlanabilir:

    template<class T>
    class Smartptr {
    Public:
    Smartptr(T *p):m_pT(p){}
    ~Smartptr()
    {
    delete m_pT;
    }
    T operator*() { return *m_pT; }
    T *operator->()
    {
    return m_pT;
    }
    private:
    T *m_pT;
    };

    class X {
    public:
    X(int a):m_a(a) {}
    void Disp() { cout << m_a << endl; }
    Private:
    int m_a;
    };

    Smartptr<X> p(new X(10));
    p->Disp(); // p.operator->()->Disp();


    auto_ptr Sınıfı

    auto_ptr sınıfı smart gösterici gibi davranan bir STL sınıfıdır. Ancak sahipliğini devredebilir. Bu sınıf özellikle başlangıç fonksiyonlarında oluşan throw işlemleri için kullanılmaktadır. Bilindiği gibi başlangıç fonksiyonunda throw oluşursa o zamana kadar başlangıç fonksiyonu tamamen bitirilmiş başlangıç fonksiyonları için bitiş fonksiyonu çağırılır. Başlangıç fonksiyonu tam olarak bitirilmemiş sınıflar için ve dinamik tahsis edilmiş sınıf nesneleri için bitiş fonksiyonu çağırılmaz. Örneğin A bir sınıf olsun

    X::X()
    {
    m_p = new A();
    -> throw işlemi yapıldı
    }

    Burada throw işlemi oluştuğunda X için bitiş fonksiyonu çağırılmayacağı gibi A için de çağırılmaz. Çünkü A dinamik olarak tahsis edilmiştir. Başka bir gösterim şöyle olabilir:

    X::X(): m_pA(new A()), m_a(10)
    {
    //...
    }

    Burada m_a’nın içerisinde throw oluşmuşsa hiç bir sınıf için bitiş fonksiyonu çağırılmaz. İşte auto_ptr bu tür durumlarda otomatik bitiş fonksiyonu çağırılsın diye kullanılan smart gösterici sınıfıdır.

    auto_ptr sınıfı bir default başlangıç fonksiyonu bir de template parametreli gösterici olan başlangıç fonksiyonuna sahiptir. Örneğin:

    auto_ptr<int> x;
    auto_ptr<int> y(new int);

    Normal olarak gösterici parametreli başlangıç fonksiyonunda dinamik tahsis edilen alanın başlangıç fonksiyonu olmalıdır. Sınıfın hem atama operator fonksiyonu hem de kopya başlangıç fonksiyonu vardır. Her iki fonksiyon da sahipliği bırakmak için içerisinde tutukları göstericileri atadıktan sonra kendi gösterici elemanlarını NULL değerine çekerler. Böylece bir t anında yalnızca tek bir sınıf tahsis edilen alanı gösterir hale gelir. Bu durumda klasik olarak a = b işleminde b değeri de atamadan etkilenmektedir. Örneğin:

    /* auto_ptr.cpp */

    #include <memory>
    #include <iostream>

    class A {
    public:
    A(int a) : m_a(a){}
    ~A(){}
    void Disp() { std::cout << m_a << std::endl; }
    private:
    int m_a;
    };

    using namespace std;

    void Func(auto_ptr<A> x)
    {
    x->Disp();
    }

    void main(void)
    {
    auto_ptr<A> a(new A(10));
    auto_ptr<A> b;

    a->Disp();
    b = a;
    b->Disp();
    Func(b);
    }

    auto_ptr <memory> başlık dosyasının içerisindedir.


    auto_ptr Sınıfının Yararlı Kullanımları

    Bu sınıf bir smart gösterici sınıfı olarak kullanılabilir. Ancak bu sınıfın asıl amacı sınıfın veri elemanı olan göstericileri nesne yapmaktır. Bazı programcılara göre sınıf gösterici veri elemanı içerecekse bu göstericiler nesne gibi tanımlanmalı, yani auto_ptr kullanılarak smart gösterici biçimine sokulmalıdır. Böylece bellek sızıntısı riski en aza indirilebilecektir. Ancak bu yaklaşım bazen abartılı olabilir. En iyisi bunu programcıya bırakmaktır. Örnek kullanım:

    #include <iostream>
    #include <memory>

    using namespace std;

    class B {
    public:
    B(int b)
    {
    m_b = b;
    throw m_b;
    }
    private:
    int m_b;
    };

    class A {
    public:
    A(int a, int b):m_pi(new int(a)), m_pB(new B(b)) {}
    private:
    auto_ptr<int> m_pi;
    auto_ptr<B> m_pB;
    };

    int main()
    {
    try {
    A a(10, 20);
    }
    catch(...) {
    cout << "exception...\n";
    }
    return 0;
    }

    Sınıfın * ve -> operator fonksiyonları elemana erişimde kullanılır. Doğal türlere ilişkin sınıflarda *, sınıflara ilişkin auto_ptr nesnelerinde -> operatörü kullanılmalıdır.

    #include <iostream>
    #include <memory>

    using namespace std;

    class B {
    public:
    B(int b)
    {
    m_b = b;
    }
    void Disp()
    {
    cout << m_b << endl;
    }
    private:
    int m_b;
    };

    class A {
    public:
    A(int a, int b):m_pi(new int(a)), m_pB(new B(b)) {}
    void Func() throw()
    {
    cout << *m_pi << endl;
    m_pB->Disp();
    }
    private:
    auto_ptr<int> m_pi;
    auto_ptr<B> m_pB;
    };

    int main()
    {
    try {
    A a(10, 20);
    a.Func();
    }
    catch(...) {
    cout << "exception...\n";
    }
    return 0;
    }

    auto_ptr sınıfının release() ve reset() isimli iki üye fonksiyonu vardır. release() sahipliği bırakır, yani geri dönüş değeri olarak göstericiyi verir ve gösterici veri elemanına NULL atar. reset() eskisini delete ederek yeni bir dinamik nesne tutulmasına yol açar.



    Çoklu Türetme


    Bir sınıfın birden fazla taban sınıfı olması durumuna çoklu türetme (multiple inheritance) denir. Çoklu türetme türetilmiş nesnelerin içsel organizasyonu standart olarak belirlenmemiştir. Ancak derleyicilerin çoğu sol kolun tepesinden aşağıda soldan sağa elemanları ardışık dizerler. Örneğin:

    A

    B

    C








    A


    B


    C







    C c; ->


    Farklı kollarda aynı isimli fonksiyonlar bulunabilir. Ancak bu fonksiyonların sınıf ismi ve çözünürlük operatörü olmadan kullanılmaları error oluşturur. Örneğin:

    #include <iostream>
    #include <memory>

    using namespace std;

    class A {
    public:
    void Func()
    {
    cout << "I am A::Func\n";
    }
    };

    class B {
    public:
    void Func()
    {
    cout << "I am B::Func";
    }
    };

    class C : public A, public B {
    };

    int main()
    {
    C c;

    // c.Func(); Ambiguous mistake
    c.A::Func();

    return 0;
    }

    Farklı kollardaki aynı isimli fonksiyonlar arasında overloading işlemi yapılmaz. Çünkü farklı parametreli aynı isimli fonksiyonların bulunması aynı faaliyet alanına özgüdür. Örneğin A sınıfının int parametreli bir Func() fonksiyonu, B sınıfının parametresiz Func() fonksiyonu olsun c.Func() işlemi error’dür.




    Çoklu Türetilmiş Sınıflarda Türemiş Sınıf Taban Sınıf Dönüştürmeleri


    Çoklu türetilmiş bir sınıf nesnesinin adresini her taban sınıfına ilişkin bir göstericiye doğrudan atayabiliriz. Şüphesiz bu durumda çoklu türetilmiş sınıfın ilgili taban sınıf verilerinin bulunduğu bloğun adresi elde edilecektir. Örneğin:

    C c;
    B *pB;
    A *pA;

    pB = &c;
    pA = &c;
    ...
    A





    B




    C

    pA

    pB










    Görüldüğü gibi dönüştürme sonunda adresin sayısal bileşeni değişebilmektedir. Daha sonra ters bir dönüşümün yapılması, yani adresin eski haline getirilmesi mümkün olmayabilir.


    Çoklu Türetme Sınıflarında Sanal Fonksiyonların Kullanılması

    En karışık noktalardan birisi taban sınıfların sanal fonksiyona sahip olduğu durumda çoklu türetme uygulanmış olması durumudur. Bu durumda sanal fonksiyona sahip sınıf sayısı kadar türemiş sınıf içerisinde farklı sanal fonksiyon tablosu bulunması gerekir. Bu tür durumlarda çoklu türetilmiş sınıfın adresini taban sınıflardan birine ilişkin göstericiye aktarıp o gösterici yoluyla o sınıfın sanal fonksiyonunu çağırdığımızda derleyici o göstericinin gösterdiği yerde sanal fonksiyon tablosunu arayacaktır. Bu tasarım ancak çoklu türetilmiş sınıfta birden fazla sanal fonksiyon tablosunun ve sanal fonksiyon tablo göstericisinin bulunmasıyla sağlanır. Örneğin:

    class A {
    public:
    virtual void FuncA() {}
    };

    class B {
    public:
    virtual void FuncB() {}
    };

    class C : public A, public B {
    public:
    void FuncA() {}
    void FuncB() {}
    virtual void FuncC() {}
    };

    C sınıfı türünden bir nesne tanımlandığında sanal fonksiyon tabloları şöyle organize edilecektir:


    A

    B



    C

    //C_A’nın sanal fonksiyon tablosu
    &A::FuncA()
    &C::FuncC()

    //C_B’nın sanal fonksiyon tablosu
    &B::FuncB()













    Şimdi C sınıfı türünden nesnenin adresi B sınıfı türünden bir göstericiye atanarak sanal fonksiyon çağırılsın:

    C c;
    B *pB;
    pB = &c;
    pB->FuncB();

    Derleyici sanal fonksiyonu pB adresinden hareketle arayacaktır. Görüldüğü gibi C’nin sanal fonksiyon tablosu iki parçadan oluşmaktadır. C’nin kendi sanal fonksiyonları için ayrı bir sanal fonksiyon tablosuna gerek yoktur. Derleyici bunu C_A’nın altına yerleştirebilir.

    Çoklu türetmede taban sınıflardaki bir sanal fonksiyonun aşağıya doğru tek bir sonlanan fonksiyonu olmalıdır. Örneğin aşağıdaki gibi bir türetme şeması söz konusu olsun. A sınıfının Func() isimli bir sanal fonksiyonunun olduğunu düşünelim. Bu fonksiyon hem B’de hem E’de yeniden yazılmışsa bu durum error oluşturur. Yalnızca E sınıfında yazılmışsa ya da yalnızca F sınıfında yazılmışsa bu durum geçerlidir.

    A

    B

    C

    D

    E

    F
















    Çoklu türetmelerde taban sınıfın bazen iki kopyası bulunur. Bu durum çoğu kez istenmeyen bir durumdur. Örneğin:

    class A {
    A

    A

    //...
    };

    class B : public A {
    //...
    C

    B

    };

    class C : public A {
    //...
    D

    };

    class D : public B , public C {
    //...
    };

    Burada D sınıfından bir nesne tanımlarsak aşağıdaki gibi bir şekil oluşur:


    A


    B


    A


    C


    D



    Görüldüğü gibi A’dan iki tane bulunmaktadır. Halbuki bir tane bulunması daha istenen bir durumdur. Burada XA, A sınıfının bir elemanı olsun. D.XA erişimi geçersizdir. Çünkü hangi A sınıfının elemanına erişildiği belli değildir. Erişim şöyle yapılmalıdır:

    d.B::XA
    d.C::XA

    Halbuki C++’ın standart iostream sınıf sisteminde buradaki taban sınıftan bir tane bulunur. ios, istream, ostream, iostream.

    ios



    istream



    ostream



    iostream















    Burada istream üzerinde işlem yapıldığında ve bu işlemler ios elemanlarını değiştirdiğinde ostream bu değişiklikleri görür. Bu tür durumlarda taban sınıfı tek yapmak için taban sınıfı sanal tanımlamak gerekir. Derleyiciler sanal taban sınıf tanımlarını birleştirerek şemada bunu tek sınıf biçiminde ifade ederler. Yapılan işlem şöyle anlaşılabilir: Şema sanki sanal taban sınıf yokmuş gibi çizilir. Sonra sanal olarak belirtilmiş taban sınıflar birleştirilir. Örneğin:

    A

    B

    C

    D

    class A {
    //...
    };

    class B : virtual public A {
    //...
    };

    class C : virtual public A {
    //...
    };

    class D : public B , public C {
    //...
    };

    Taban sınıflardan biri sanal bildirilmişse, diğeri normal bildirilmişse birleştirme yapılmaz. Örneğin:


    A

    B

    C

    F

    E

    A

















    deque Sınıfı

    deque sınıfı tamamen vector sınıfı gibidir, ancak vector sınıfı içsel olarak tek bir dizi biçiminde tasarlanmasına karşın deque bloklu bağlı liste tekniği ile tasarlanır.

    .....

    .....

    .....






    vector ile deque arasında şu benzer ve ayrılıklar vardır.

    1- Her iki sınıfta da iterator’ler random access’dir. Yani her iki sınıfın da [] operatör fonksiyonu vardır. Kuşkusuz vector’ün [] operatörü deque’in [] operatöründen daha hızlı çalışacaktır. deque yapısında elemana erişme “amortized constant time” karmaşıklıktadır.
    2- vector sınıfında push_front() ve pop_front() fonksiyonları yoktur. Çünkü bu fonksiyonlar olsaydı koskoca bir vector’ün kaydırılması gerekirdi ki bu fonksiyonların konulmamasının daha anlamlı olduğu düşünülmüştür. Ancak deque’in bloklu yapısı nedeniyle öne yapılan eklemeler ve silmeler yalnızca tek bir bloğu etkilemektedir. Dolayısıyla bu fonksiytonlar etkin çalışabilir.
    3- vector için tahsis edilen alan asla küçültülemez. Yani capacity değeri ancak büyür. vector resize edilse bile yalnızca son elemanının nerede olduğu belirlemesi yapılmaktadır. Halbuki deque’in bloklu yapısı nedeniyle otomatik kapasite küçültmesi yapılabildiği gibi resize() fonksiyonu da capacity değerini düşürebilmektedir.


    Ne Zaman vector, Ne Zaman deque Kullanılmalıdır?

    Random access iterator gereken durumlarda vector ya da deque akla gelmelidir. Eğer ekleme yalnızca sona yapılacaksa tipik olarak vector tercih edilmelidir. Ancak ekleme ya da silme hem başa hem de sona yapılacaksa bu durumda deque tercih edilmelidir. deque’in kullandığı toplam bellek vector’den az olma eğilimindedir. stack adaptör sınıfı da default olarak deque sınıfı kullanılarak yapılmıştır. deque veri yapısı <deque> başlık dosyasında bulunmaktadır.


    queue Sınıfı

    queue tipik olarak FIFO kuyruk sistemidir. Bilindiği gibi gibi LIFO sistemlerine stack denir. queue sınıfı bir adaptör sınıftır ve <queue> başlık dosyasında bulunur. Bildirimi şöyledir.

    template <class T, class Container = deque<T> >
    class queue {
    //...
    };

    Görüldüğü gibi queue sınıfı da default olarak deque sınıfı kullanılarak yapılmıştır. Tipik olarak queue sınıfının push() ve pop() fonksiyonları vardır. push() kuyruğa eleman ekler, pop() sıradaki elemanı kuyruktan siler. Eleman push() ile başa eklenir, front() fonksiyonu ile sıradaki eleman alınır, pop() fonksiyonu ile de silinir.

    #include <iostream>
    #include <queue>

    using namespace std;

    int main()
    {
    queue<int> x;
    for (int i = 0; i < 10; ++i)
    x.push(i);
    for (i = 0; i < 10; ++i) {
    cout << x.front() << endl;
    x.pop();
    }

    return 0;
    }


    C ile C++ Arasındaki Uyumsuzluklar


    C++, C’nin bir üst kümesidir. Pek çok durumda *.c uzantılı bir dosya C++ derleyicisi tarafından başarılı olarak derlenir. Ancak bazı küçük uyumsuzluklar da bulunmaktadır.

    1- C++’daki // yorumlama biçimi 99 standartlarında C’ye de eklenmiştir.
    2- C++ ile eklenen bazı anahtar sözcükler bir C programında kullanılmışsa problem çıkar.
    3- C’de sizeof(‘a’) == sizeof(int)’tir, ancak C++’da sizeof(‘a’) == sizeof(char)’dır.
    4- Stringler C’de ve 96 öncesinde C++’da char * türündendi. Ancak C++’da şimdilik string’in char * türüne atanması kabul edilse de depricated yapılmıştır, gerçek türü
    const char *’dır (bir fonksiyon string ile çağırılmışsa hem char * parametreli hem de const char * parametreli tür varsa, const char * olan seçilir).
    5- C’de yapı içerisinde bir yapı bildirildiğinde içteki yapı tamamen dışarıda yapılmış gibi kabul edilir. Ancak C++’a göre içteki yapı dıştaki yapının faaliyet alanı dışındadır.
    6- Global const değişkenler C++’da (genel olarak const nesneler sabit ifadesi belirttiği için) static kabul edilir. Yani o modüle özgüdür. Bu durum onların başlık dosyasına yerleştirilmesini mümkün hale getirmiştir. C’de böyle bir durum söz konusu değildir.
    7- C++’da main anahtar sözcüktür. Kendi kendini çağıramaz ama C’de anahtar sözcük değildir ve recursive olarak kullanılabilir.
    8- C’de void bir adresin tür dönüştürmesi yapılmadan her hangi bir türe atanması tamamen normal kabul edilmiştir. Ancak bu durum araya bir void gösterici sokarak farklı türler arasındaki göstericilerin birbirine atanması mümkün olmuştur. C++’da bu durum tamamen error olarak değerlendirilmiştir. C’de de bu durumda tür dönüştürmesi uygulanması tavsiye edilmektedir.
    9- C’de const bir değişkenin adresi void türden göstericiye doğrudan atanabilir. Ancak C++’da bu durum yasaklanmıştır, göstericinin const void olması gerekmektedir.
    10- C++’da fonksiyon parametre parantezinin içinin boş bırakılması C’deki gibi parametresi türce ya da sayıca kontrol etme anlamına gelmez. Boş bırakmakta void yazmak aynı anlamdadır.
    11- C++’da fonksiyonun geri dönüş değeri void değilse kesinlikle return yazılmak zorundadır. Halbuki C’de bu en fazla bir uyarıdır.
    12- C++’da yapı ya da sınıf türünden nesne tanımlarken struct ya da class anahtar sözcükleri kullanılmayabilir. C++’da bu nedenle C’de geçerli olan aşağıdaki gibi bir ifade geçerli değildir.

    typedef struct X {
    //...
    } X;

    struct X a;
    int X;

    13- C++’da const değişkenler ilk değer verilerek tanımlanmak zorundadır. C’de bu zorunlu değildir.
    14- C++’da fonksiyon tanımlarken geri dönüş değerinin türü yazılmak zorundadır. Yazılmaz ise int kabul edilir kuralı geçerli değildir.
    15- C’de enum türleri int kabul edilir. C++’da bütün enum türleri özgün türlerdir ve bir enum nesnesine yalnızca enum sabiti atanabilir.
    16- C++’da fonksiyon daha yukarıda tanımlanmadıysa prototip zorunludur.
    17- Aşağıdaki istisna durum C’de normal C++’da error’dür.

    char s[4] = “abcd”;

    C derleyicileri bu durumda ‘\0’ karakteri eklemezler, fakat C++’da bu durum error’dür.
    SON
    Anonim


  6. #6
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb C++ için araçlar

    C++ İÇİN ARAÇLAR

    1- Crystal FLOW for C++ 3.91

    İNDİR

    Flowchart from Source Code for a clear view of the code.
    Rich Call/Caller/File, Class Trees
    Automatic Code/Comment Formatting for improved readability
    Export to BMP/JPEG/Visio, Code Browser
    Editions:
    DataFlow: Object DataFlows, CallFlows, Inline expansion of flowcharts
    Docs: Comprehensive HTML docs with Flowcharts/Trees
    Professional: Code Publisher, Project Flowcharts, Custom symbols in flowcharts, Batch output Flowcharts/Trees

    2- Delphi to C++ Builder 1.5
    İNDİR
    Delphi to C++ Builder can't substitute for your handcraft modification completely, but can save 80% time for you!
    convert Delphi code segment to C++Builder.
    This is a tools that convert Delphi code segment to C++Builder,it can carry out a majority of syntax conversions very simply!(The code in the function or procedure ,from "VAR" to last "EDN" )
    Delphi to C++Builder is so small (547KB) that it only takes your one moment to download it.
    3- C++ Code Export 1.0.0
    İNDİR
    C++ Code Export is a unique and easy to use software to quickly and easily reindent, export (10+ formats supported) and print your C++ documents. Convert your C/C++ documents to PDF, RTF, images and more!
    With the C++ Code Export, you can :
    Format / Indent your C/C++ code
    Convert your C/C++ code to 10+ formats
    Print / Create advanced reports using a state-of-the-art printing engine
    and much more!

    4- Code 128 C++ class 3.0
    İNDİR
    Code 128 C++ class to draw Code 128 barcode

    5- Windows Std Serial Comm Lib for C/C++ 4.1
    İNDİR
    Serial communication component C++ library for serial port communications from a C/C++ or Visual C++ program. Controls multiple ports simultaneously, is fully thread safe, includes 16-bit and 32-bit DLL's, also works with C++ .NET and C#, MFC and C++ Builder. Does not depend on support libraries. Use with any language that can call the Windows API (VB,VB.NET..). Includes 32 functions plus modem control, ANSI emulation, ASCII/XMODEM/YMODEM.

    6- Auto Protocol Builder 2.6
    http://www.marshallsoft.com/ Marshal sitesine bağlanarak gerekli olanı indiriniz.

    Using Auto Protocol Builder, easily and automatically build tcp-based or udp-based network protocol source code for client/server sides. pure platform independent c source code to make sure highly performance and windows,linux and unix platform are all supported.auto protocol struct diagram to help protocol design.platform independent c source code and platform independent network programming library to support all fix or variant length binary protocol.experience now!
    Using Auto Protocol Builder, you will find that network programming suddenly become very easy and comfortable. What you need to do is just give the fields in your protocol, Auto Protocol Builder will generate all head files and source code files for you. It even can generate demo source code, give you a tutorial how to use the generated protocol api. You can directly compile and run your demo to check and testify your protocol. Pure platform independent c source code to make sure your protocol can compile and run under Windows, Linux and Unix platform. Auto Protocol Builder even provide a platform independent network programming library. Those clean, beautiful source code guaranty the highly network communication performance. Auto Protocol Builder can directly compile the generate source code to be dll or static library. At the same time, it even can generate a makefile if your protocol is designed for linux or unix platform. Auto Protocol Builder and it's generated protocol struct diagram can help you design your protocol, those functions are a grate helper to design a good network communication protocol, no matter fix length binary protocol or variant length binary protocol.
    Those functions together, you will find network programming, debug network protocol, tunel network communication protocol performance are not problems any more when using Auto Protocol Builder.

    7- MarshallSoft DUN Dialer for C/C++ 2.1
    İNDİR

    MarshallSoft Dialup Networking (DUN) Component for C/C++. Version 2.1, 6/7/2002.
    Invoke 32-bit Windows Dialup Networking (DUN) from your application code to dial up any installed Internet Service Provider (ISP). Requires 32-bit C/C++ Windows compiler.


    8- Magic C++ v2.0
    İNDİR
    Bring the best of two worlds together!
    No more clumsy vi or command line needed
    Develop C/C++ applications on UNIX and LINUX servers with the familiarity and ease of use of Windows!!
    Write your codes in Windows with our tool and remotely communicate to the Unix/Linux server in real time, supporting CVS and lots of other handy features.
    The most user-friendly Unix/Linux IDE ever!
    Working with UNIX and LINUX servers has never been easier...
    Description:
    Magic C++ is a fully integrated development environment(IDE) designed to meet the requirements of programmers developing on remote Linux/Unix servers using Windows client. By integrating support for FTP, TELNET and our custom Remote Development Protocol(RDP) based on client/server architecture, Magic C++ presents a seamless interface to opening and editing files, and working with compilers and debuggers. While a conventional development involves juggling a PC editor, file transfer utility and terminal window, Magic C++ gives programmers advantages that are immediately apparent. Through an integrated approach to editing, deploying existing remote server-based compilers and debuggers(i.e. gdb or dbx) to compile and debug on Windows PC client, Magic C++ makes it easy to develop software regardless of whether the file is locally on your PC or located on a remote server running any flavor of the Linux/Unix operating system. This allows programmers developing on Linux/Unix platforms to benefit from the many familiar time-saving features provided by Windows while enjoy the stability and performance of Unix based servers.
    Key Features:
    Familiar development environment
    Advanced code editor
    CodeAware(Navigate/Type info/Parameter tips/Auto complete/Auto list)
    Remote text search and replace
    Remote compile and debug
    Projects manager
    CVS support
    Visual File Compare
    Much more and more...

    10- Code Visualizer
    İNDİR
    Code Visualizer makes C++ source code to visual based diagram. Code Visualizer helps you to understand elements of C++ source code. Code Visualizer show C++ elements such as class, struct, template, function to visual based diagrams. Code Visualizer show a method to flow-chart diagrams and NSD(Nassi-Shneiderman Diagrams). Code Visualizer show class relations. Code Visualizer also include analysis and summarize project's statistics.

    11- Easy Way to Build E-Greetings in C++ 1.0
    İNDİR
    Would you like to create your own personalized multi-media greeting cards using Visual C++? Use this C++ source codes to create customized greeting cards for any occasion or recipient. It will be a useful marketing tool for your business.
    The registered version of this package contains the full example source codes for a complete MFC C++ program that shows you how to create your own multi-media greeting card program.

    12- GDI Watch Add-In for Visual Studio 2005
    İNDİR
    This Microsoft Visual Studio 2005 add-in allows you to browse the full content of Windows GDI handles (HDC, HBITMAP, HBRUSH, HFONT, HPALETTE, HPEN (including extended pens), HRGN, HICON, and HCURSOR) while debugging your image processing or UI programs. The add-in looks and behaves like a regular watch window and it works with programs written in C/C++ languages.
    The add-in understands all MFC GDI classes: CDC, CClientDC, CMetaFileDC, CPaintDC, CWindowDC, CGdiObject, CBitmap, CBrush, CFont, CPalette, CPen, and CRgn. It also can extract GDI handles from your own wrapper classes.
    The items that have changed between debugger execution stops are highlighted, so you can quickly identify the effect that your code has on GDI handles. The add-in also gives you access to the device context's painting surface and you are able to watch what each of your painting functions really does.

  7. #7
    Pürheves adas - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2009
    Mesajlar
    205

    Standart

    Bizim okulda c++ branş dersi alternatif olarakda delphi komuşlar( bence alternatifi değil) c++ builder kullanılıyor.dev c diye isminde ki compileri kimseye tavsiye etmiyorum ben onda çalışmıştım kafayı yiyecektim c++ öğrenmek isteyen arkadaşlara şiddetle tavsiyem c++ ya çalıştıkları kadar ingilizceye de çalışssınlar yoksa çok güçlük çekerler.örneğin;STL ( Standart Template Libarary) hakkında türkçe kaynak yok.Standart değişken tanımlama,if döngüsü vb konular ile de hesap makinesi gibi basit programlardan başka bir program yazılmıyor

  8. #8
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Standart

    Dev C normal program parçalarında doğru çalışma doğrulaması verdiği halde ekran görüntüsü sunmaz. Taki Do döngüleri arasında kullanana kadar veya getchar () gibi özel komutlar kullanılana kadar ekran çıktısı vermez verse de kendi kafasına göre sonlandırır.
    Dev C mütahassıs programcılar tarafından tavsiye edildiği için biz tavsiye ettik. Dev c nin kullanımı hakkında az çok bilgi sahbi olmak gerekir ingilizce olduğu kadar türkçe versiyonları da vardır.

  9. #9
    Pürheves adas - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2009
    Mesajlar
    205

    Standart

    c++ builder gibi bir nimet varken tekrar dev c'ye dönebilecemi hiç sanmıyorum yine de teşekkür ederim kardeşim

  10. #10
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb Delphi c++ builder indir 6 ve 2009

    Ve nimet -->Delphi C++ Builder 6.0 indir

    Bu program Delphi 7 ye benzer ama çok daha kapsamlı bir programdır… Delphi 7 firmasının programı olup componentler ve arayüz aynıdır ancak kullanım alanı delphi 7 den daha fazladır. ÖZellikle delphi 7 de oyun geliştirmek isteyenler. C++ builder 6 ile daha fazla verim alabilirler… Bu program Delphinin yaptığı herşeyi yapar ve üstüne daha fazla detayına inebilirsiniz…

    indir:
    Borland C++ 6
    Boyut : 172 MB
    Buyrun Linkler
    http://rapidshare.com/files/163849425/Borland_C__Builder6_By_ScHaO.part1.rar
    http://rapidshare.com/files/163860661/Borland_C__Builder6_By_ScHaO.part2.rar
    Rar Şifresi : DersimScHaO
    Şifre büyük küçük harfe duyarlı olduğundan aynen yukardaki gibi c/p yapınız…
    RAR Pass / Şifresi: http://www.sadecegir.com


    ============================================

    Delphi C++ Builder 2009








    http://rapidshare.com/files/22975821...009.part01.rar
    http://rapidshare.com/files/22976604...009.part02.rar
    http://rapidshare.com/files/22977535...009.part03.rar
    http://rapidshare.com/files/22978154...009.part04.rar
    http://rapidshare.com/files/22983078...009.part05.rar
    http://rapidshare.com/files/22983783...009.part06.rar
    http://rapidshare.com/files/22984516...009.part07.rar
    http://rapidshare.com/files/22985202...009.part08.rar
    http://rapidshare.com/files/22985865...009.part09.rar
    http://rapidshare.com/files/22986510...009.part10.rar
    http://rapidshare.com/files/22987146...009.part11.rar
    http://rapidshare.com/files/22987918...009.part12.rar
    http://rapidshare.com/files/22988640...009.part13.rar
    http://rapidshare.com/files/22989330...009.part14.rar
    http://rapidshare.com/files/22990061...009.part15.rar
    http://rapidshare.com/files/22990716...009.part16.rar
    http://rapidshare.com/files/22991382...009.part17.rar
    http://rapidshare.com/files/22992074...009.part18.rar
    http://rapidshare.com/files/22992774...009.part19.rar
    http://rapidshare.com/files/22992868...009.part20.rar


    Not : bütün partlar 100 mb dır. 20.part 16 mb dır. Linkler denenmiştir. Crack İçindedir.


    RaR Password : Keop[S] Sm

    Rapidsahare otomatik dosya indirici:
    http://www.msxlabs.org/forum/ext.php...ad%2Fsetup.exe
    Konu zeet06 tarafından (02.08.09 Saat 18:49 ) değiştirilmiştir.

+ Konu Cevaplama Paneli

Konu Bilgileri

Users Browsing this Thread

Şu an 1 kullanıcı var. (0 üye ve 1 konuk)

     

Benzer Konular

  1. Hizmette İleri Olmak
    By bEtüL in forum Risale-i Nur Talebeliği
    Cevaplar: 0
    Son Mesaj: 22.11.09, 21:57
  2. Bir İleri, İki Geri
    By Müellif-e in forum Gündem
    Cevaplar: 6
    Son Mesaj: 30.03.09, 08:46
  3. C Programlama
    By zeet06 in forum Bilişim Haberleri ve Bilimsel Makaleler
    Cevaplar: 13
    Son Mesaj: 14.01.09, 14:59
  4. C++ Programlama
    By zeet06 in forum Bilgisayar ve İnternet Sorunları
    Cevaplar: 4
    Son Mesaj: 28.11.08, 15:12

Bu Konudaki Etiketler

Yetkileriniz

  • Konu Acma Yetkiniz Yok
  • Cevap Yazma Yetkiniz Yok
  • Eklenti Yükleme Yetkiniz Yok
  • Mesajınızı Değiştirme Yetkiniz Yok
Yemek Tarifleri ListeNur.de - islami siteler listesi
Google Grupları
RisaleForum grubuna abone ol
E-posta:
Bu grubu ziyaret et

Search Engine Friendly URLs by vBSEO 3.6.0