370 likes | 814 Views
NESNEYE YÖNELİK PROGRAMLAMA KOPYA YAPICI FONKSİYON, STATİK ELEMANLAR, ARKADAŞ SINIF VE FONKSİYONLAR,NESNE DİZİLERİ. Özlem AYDIN Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü.
E N D
NESNEYE YÖNELİK PROGRAMLAMAKOPYA YAPICI FONKSİYON, STATİK ELEMANLAR,ARKADAŞ SINIF VE FONKSİYONLAR,NESNE DİZİLERİ Özlem AYDIN Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü Not: Bu sunumda Doç. Dr. Yılmaz KILIÇASLAN’ın Nesneye Yönelik Programlama dersi sunumlarından faydalanılmıştır.
Sunum Planı • Kopya yapıcı fonksiyonu • Nesnelerin parametre olarak geçirilmesi ve döndürülmesi • Referansların parametre olarak geçirilmesi ve döndürülmesi • Statik veri elemanları ve statik fonksiyonlar • Arkadaş sınıflar ve fonksiyonlar • Nesne dizileri yaratma
ATAMA VE İLK DEĞER ALMA - 1 • C’de atama ve ilk değer alma işlemleri etkileri itibariyle aynıdır. Fakat, C++’da bu işlemlerin farklı etkileri olabilir. İki işlem arasındaki farklar şunlardır: • Atama mevcut bir nesnenin değerinin değişmesi esnasında gerçekleşir; bir nesneye birçok yeni değer atanabilir. • İlk değer alma bir nesnenin bildirimi yapılırken bir başlangıç değeri alması esnasında gerçekleşir; bir nesne yalnızca bir kez ilk değer alır.
ATAMA VE İLK DEĞER ALMA - 2 • Atama ve ilk değer alma işlemleri arasındaki fark, nesnelerle işlem yaparken önemli olabilir. Atama deyimi derleyicinin sınıf için tanımlanmış operator= fonksiyonunu çalıştırmasına neden olurken: String string1(“birinci karakter katari”); String string2; string2 = string1; ilk değer alma aynı fonksiyonu çalıştırmaz: String string1(“birinci karakter katari”); String string2 = string1; operator= fonksiyonu yalnızca daha önce yaratılmış nesneler için çağrılabilir.
KOPYA YAPICI FONKSİYON - 1 • Bir nesnenin yaratıldığı esnada bir başka nesnenin değerini almasını sağlamak için özel bir yapıcı fonksiyon, kopya yapıcı fonksiyon, çağrılır. • Varolan bir nesnedeki verileri yeni oluşturulan nesnenin içine kopyalarlar. • Giriş parametresi olarak aynı sınıftan bir nesneye referans alırlar. • Eğer programda bir kopya yapıcı fonksiyon tanımlı değilse, derleyici standart bir yapıcıyı sınıfa yerleştirir. Standart kopya yapıcı fonksiyon bir nesnenin elemanlarını bire bir yeni nesnenin veri alanlarına kopyalar. İçinde işaretçi olmayan nesneler için yeterlidir.
KOPYA YAPICI FONKSİYON - 2 • Kopya yapıcı bir fonksiyon bir nesnenin ilk değer olarak bir başka nesneyi aldığı durumlarda çalıştırılır. = işareti ile ya da fonksiyon-çağırma sentaksı ile çağrılır: String string2 = string1; String string2( string1 ); • Bir kopya yapıcı fonksiyon olmaması halinde yukarıdaki ifade neticesinde string2 her eleman sahasının ilk değerini string1’in elemanlarından alır. Bu da sınıfın elemanları içinde işaretçilerin bulunması durumunda arzu edilmeyen sonuçlara yol açabilir.
KOPYA YAPICI FONKSİYON - 3 • String sınıfı için kopya yapıcı fonksiyonu şu şekilde yazılabilir: #include <iostream> #include <cstring> Usingnamespacestd; classString { public: String(); String( constchar *s ); String( char c, int n ); String( constString &digeri ); // kopya yapıcı fonksiyon // ... }; String::String( constString &digeri ) { uzunluk = digeri.uzunluk; buf = newchar[uzunluk + 1]; strcpy( buf, digeri.buf ); }
KOPYA YAPICI FONKSİYON - 4 • Kopya başlangıç fonksiyonu derleyici tarafından 3 durumda çağırılır:1. Bir sınıf nesnesi fonksiyona parametre olarak geçirildiğinde.2. Bir fonksiyondan bir nesne döndürüldüğünde. 3. Var olan bir nesnenin kopyası olarak yeni bir nesneye başlangıç ataması yapıldığında .
NESNELERİN PARAMETRE OLARAK GEÇİRİLMESİ • Aşağıdaki fonksiyon bir nesneyi parametre olarak almaktadır: voidtuket( Stringparm ) { // parm nesnesinin kullanımı } voidmain() { String string1(“birinci karakter katari”); tuket(string1); } tuket fonksiyonu değer olarak geçirilmiş bir String nesnesi almıştır. Yani, fonksiyon nesnenin kendisine ait bir kopyasına sahip olmuştur.Aslında, derleyici örtük olarak fonksiyonun parametresine nesneyi ilk değer olarak atamak üzere, kopya yapıcı fonksiyonu çağırmıştır; yaptığı aşağıdakiyle aynıdır: Stringparm( string1 );
NESNE DÖNDÜREN FONKSİYONLAR • Aşağıdaki fonksiyon değer olarak bir nesne döndürmektedir: String f() { StringretDeger(“bir return değeri”); returnretDeger; } voidmain() { String string2; string2 = f(); } f fonksiyonu değer olarak bir String nesnesi döndürmektedir. Derleyici, fonksiyonu çağıranın kapsam alanındaki geçici bir gizli nesneye fonksiyonun return ifadesinde belirtilmiş nesneyi kullanarak ilk değer atamak üzere kopya yapıcı fonksiyonu çağırır. Yani, derleyici aşağıdakilere denk işlemler gerçekleştirir: Stringgecici( retDeger ); string2 = gecici;
NESNE REFERANSLARININ PARAMETRE OLARAK GEÇİRİLMESİ • Bir nesnenin değer olarak bir fonksiyona her gönderilişi, bir miktar “overhead” içerir. Sabit bir nesneye referans geçirerek aynı etki, yapıcı fonksiyon çağırmaktan kaynaklı maliyetten de kurtularak elde edilebilir: voidtuket( constString &parm ) { // parm nesnesinin kullanımı } voidmain() { String string1(“birinci karakter katari”); tuket( string1 ); } Parametre bu şekilde geçirildiğinde, yeni bir nesne yaratılmadığı için, kopya yapıcı fonksiyon çağrılmaz. Derleyici aşağıdaki işlemin aynısını gerçekleştirir: constString &parm = string1; Sonuç olarak, fonksiyon kendisini çağıranla aynı nesneyi kullanmış olur.
NESNE REFERANSLARININ DEĞER OLARAK DÖNDÜRÜLMESİ • Nesnenin referansını değer olarak döndürmek de, nesnenin kendisini döndürmekten daha etkin bir yöntem olabilir. operator=örneğini hatırlayın: String &String::operator=(constString &digeri) { //... return *this; } voidmain() { String string1(“birinci karakter katari”); String string2, string3; string3 = string2 = string1; } Geçici bir nesne yaratılmadığı için fonksiyon değer döndürürken kopya yapıcı fonksiyon çağrılmaz: yalnızca geçici bir referans yaratılır. string2 = string1 ifadesinin değerini string3 nesnesine atarken derleyici aşağıdaki işlemleri yapar: String &geciciRef = string2; string3 = geciciRef;
STATİK VERİ ELEMANLARI - 1 • Bir veri elemanının static olarak tanımlanması mümkündür. • Bir sınıftan üretilen tüm nesnelerin ortak bir veriyi paylaşmasının gerektiği durumlar olabilir. • Bellekte sadece tek kopyasının yaratılması istenen veriler staticolarak tanımlanmalıdırlar • Static olarak tanımlanan veri elemanının sadece bir kopyası vardır ve kendi sınıfının tüm nesneleri o değişkeni paylaşır. • Statik üyeler nesne tanımlanmadan önce bellekte yaratılırlar.
STATİK VERİ ELEMANLARI - 2 • Bir bankadaki tasarruf hesapları için bir TasarrufHesabi sınıfı tanımlayalım. • Sınıfa ait her nesne belirli bir müşterinin hesap ve isim bilgisini taşıyacak olsun. • Ayrıca, sınıf her gün kazanılan faizleri hesaba ekleyecek bir eleman fonksiyona sahip olsun.
STATİK VERİ ELEMANLARI - 3 • Böyle bir sınıf için günlük faiz oranını nasıl gösterebiliriz? • Sürekli değiştiği için sabit değil, değişken olmalıdır. • Sınıfın sıradan bir eleman sahası olması halinde, her nesne kendi kopyasına sahip olacaktır; bu da yalnızca bellek israfına yol açmakla kalmayacak aynı zamanda faiz oranı değiştiğinde her nesneyi güncellemeyi gerektirecektir. • Faiz oranını global bir değişken yapmak isteyebilirsiniz; fakat bu her fonksiyonun değişkeninizin değerini değiştirebileceği anlamına gelecektir. • İhtiyacımız olan belirli bir sınıf için global bir değişkendir.
STATİK VERİ ELEMANLARI - 4 • C++, belirli bir sınıf için global değişken tanımlama imkanını statik eleman sahalar aracılığıyla sağlar. Sıradan bir eleman saha gibi işleme sokulmasına rağmen, bildirimi static yapılmış eleman sahaların, sınıfa ait kaç nesne yaratılırsa yaratılsın, tek bir kopyası oluşturulur. classTasarrufHesabi { public: TasarrufHesabi(); voidfaizEkle() { toplam += faizOrani * toplam; } //... private: char isim[30]; float toplam; staticfloatfaizOrani; //... };
STATİK VERİ ELEMANLARI - 5 • Statik bir eleman sahayı, bildirimini public yapmak suretiyle, program içerisinde erişilebilir kılabilirsiniz: // Eğer faizOranipublic ise voidmain() { TasarrufHesabi hesap1; hesap1.faizOrani = 0.0005; } • Yukarıdaki programda kullanılan sözdizim meşru fakat aldatıcıdır; çünkü, bütün TasarrufHesabi nesnelerinin faiz oranının değiştiriliyor olmasına rağmen, yalnızca hesap1 nesnesinin faiz oranının değiştirildiği izlenimini doğurmaktadır. Daha iyi bir gösterim yolu aşağıdaki olacaktır: TasarrufHesabi::faizOrani = 0.0005; Bu sözdizim değişimin bütün sınıfa uygulanacağı gerçeğini yansıtmaktadır ve hiçbir TasarrufHesabi nesnesi yaratılmamış olsa bile kullanılabilir.
STATİK VERİ ELEMANLARI - 6 • Statik bir eleman sahanın ilk değerini sınıfının yapıcı fonksiyonu içinden alması mümkün değildir; çünkü ilk değer bir kez atanır fakat yapıcı fonksiyon birçok kez çağrılabilir. • Statik bir eleman saha, tıpkı global bir değişken gibi, dosya kapsam alanında ilk değerini almalıdır. Erişim belirteci statik eleman sahalar için ilk değer alma esnasında etkin değildir; dolayısıyla private eleman sahalar ilk değerlerini publiceleman sahalar gibi alırlar: floatTasarrufHesabi::faizOrani = 0.0005; TasarrufHesabi::TasarrufHesabi() { //... } //...
STATİK ELEMAN FONKSİYONLAR - 1 • Eğer yalnızca statik eleman sahalara erişim yapan bir fonksiyonunuz varsa, fonksiyonun bildirimini staticyapabilirsiniz: classTasarrufHesabi { public: TasarrufHesabi(); voidfaizEkle() { toplam += faizOrani * toplam; } staticvoidfaizBelirle( floatyeniDeger ) { faizOrani = yeniDeger; } //... private: char isim[30]; float toplam; staticfloatfaizOrani; //... };
STATİK ELEMAN FONKSİYONLAR - 2 • Statik eleman fonksiyonlar statik eleman sahalara erişim için kullanılan sözdizim ile çağrılabilirler: // Statik eleman fonksiyon çağrımı voidmain() { TasarrufHesabihesap1; hesap1.faizBelirle( 0.0005 ); TasarrufHesabi::faizBelirle( 0.0005 ); } • Statik eleman fonksiyonlar belirli bir nesne üzerinde işlem yapmadıkları için this işaretçisine sahip değillerdir.
STATİK ELEMANLAR • Statik eleman sahalar bütün nesnelerin ihtiyacı olan ortak kaynakları yönetmek ya da nesnelere ilişkin durum bilgisi tutmak için kullanılabilir. Örneğin, herhangi bir anda bir sınıfın kaç nesnesinin bulunduğu bilgisini statik bir eleman saha üzerinde tutabiliriz: classUcak { public: Ucak() { sayac++; } staticintmiktarSoyle() { returnsayac; } ~Ucak(){ sayac--; } private: staticintsayac; } intUcak::sayac = 0;
ARKADAŞ SINIFLAR - 1 • Bazen iki yada daha fazla sınıfın o kadar işbirliği içerisinde çalışması gerekir ki birbirlerinin erişim fonksiyonlarını kullanmak yeterince verimli olmayabilir ve dolayısıyla birbirlerinin özel korumalı (private) eleman sahalarına doğrudan erişmeleri gerekebilir. Bu tür bir imkan friend anahtar sözcüğünü kullanarak elde edilir: class Sinif1 { friendclass Sinif2; // Arkadaş sınıf private: intcokGizli; }; class Sinif2 { public: voiddegistir(Sinif1 nesne1) }; void Sinif2::degistir(Sinif1 nesne1) { nesne1.cokGizli++; }
ARKADAŞ SINIFLAR - 2 • Örnek1 (Lafore 2002): #include <iostream> usingnamespacestd; classalpha { private: int data1; public: alpha() : data1(99) { } //constructor friend class beta; //beta is a friend class };
ARKADAŞ SINIFLAR - 3 // allmemberfunctions can accessprivate // alphadata class beta { public: //accessprivatealpha data void func1(alpha a) { cout << “\ndata1=” << a.data1; } void func2(alpha a) { cout << “\ndata1=” << a.data1; } };
ARKADAŞ SINIFLAR - 4 intmain() { alpha a; beta b; b.func1(a); b.func2(a); cout << endl; return 0; }
ARKADAŞ SINIFLAR - 5 • friend bildirimi public ve private anahtar sözcüklerinden etkilenmez; sınıf bildiriminin herhangi bir yerinde bulunabilir. • Tanımlanan sınıf kendi özel korumalı eleman sahalarına erişebilecek arkadaş sınıflar belirleyebilir; fakat, kendisini bir başka sınıfın arkadaşı ilan edemez. • friendarkadaş sözcüğü tek yönlü erişim imkanı sağlar.
ARKADAŞ SINIFLAR - 6 • Arkadaş mekanizmasını kullanmanız halinde yalıtılmış sınıflarla değil, bir arada düşünülmesi gereken iki yada daha fazla sınıfla uğraşıyorsunuz demektir. Bu nedenle, bu mekanizma elden geldiğince seyrek kullanılmalıdır.
ARKADAŞ FONKSİYONLAR - 1 • Bütün bir sınıfın yerine, tek bir fonksiyon da arkadaş (friend)olarak bildirilebilir. classmyclass { int a, b; public: friendintsum(myclass x); // Arkadaş fonksiyon • Arkadaş fonksiyonları sınıfa ait değildir, ama bu fonksiyonların o sınıfa ait private elemanlara erişim hakkı vardır.
ARKADAŞ FONKSİYONLAR - 1 • Örnek1 (Osborne 1998): #include <iostream> usingnamespacestd; classmyclass { int a, b; public: friendintsum(myclass x); // Arkadaş fonksiyon void set_ab(int i, int j); }; voidmyclass::set_ab(int i, int j) { a = i; b = j; }
ARKADAŞ FONKSİYONLAR - 2 // sum() is not a member function of any class. intsum(myclass x) { /* Because sum() is a friend of myclass, it candirectly access a and b. */ return x.a + x.b; } intmain() { myclass n; n.set_ab(3, 4); cout << sum(n); return 0; }
ARKADAŞ FONKSİYONLAR - 3 • Örnek2 (Osborne 1998): #include <iostream> usingnamespacestd; constint IDLE = 0; constint INUSE = 1; class C2; // forwarddeclaration class C1 { int status; // IDLE if off, INUSE if on screen // ... public: void set_status(intstate); friend int idle(C1 a, C2 b); };
ARKADAŞ FONKSİYONLAR - 4 class C2 { int status; // IDLE if off, INUSE if on screen // ... public: void set_status(intstate); friend int idle(C1 a, C2 b); }; void C1::set_status(intstate) { status = state; } void C2::set_status(intstate) { status = state; }
ARKADAŞ FONKSİYONLAR - 5 int idle(C1 a, C2 b) { if(a.status || b.status) return 0; else return 1; } intmain() { C1 x; C2 y; x.set_status(IDLE); y.set_status(IDLE); if(idle(x, y)) cout << "Screen can be used.\n"; else cout << "In use.\n"; x.set_status(INUSE); if(idle(x, y)) cout << "Screen can be used.\n"; else cout << "In use.\n"; return 0; }
NESNE DİZİLERİ - 1 • Herhangi bir veri tipinde dizi tanımlar gibi nesne dizileri tanımlayabilirsiniz: Tarih dogumGunleri[10]; • Bir nesne dizisi bildirimi yaptığınızda, yapıcı fonksiyon dizideki her eleman için çağrılır. • Eğer ilk değer belirtmeden dizilerinizin bildirimini yapmak istiyorsanız varsayılan yapıcı fonksiyona sahip olmanız gerekir. Yukarıdaki örnekte, dizinin her elemanı Ocak 1, 1 değerini ilk değer olarak alır.
NESNE DİZİLERİ - 2 • Ayrıca, argüman alan yapıcı fonksiyonunuzu açıkça çağırarak da dizideki her elemana ilk değer atayabilirsiniz. Eğer dizinin bütünü için yeterince ilk değer belirtmezseniz, geriye kalan elemanlar için varsayılan yapıcı fonksiyon çağrılır: Tarih dogumGunleri[10] = {Tarih(2,10,1950), Tarih(9,16,1960), Tarih(7,31,1953), Tarih(1,3,1970), Tarih(12,2,1963)};
NESNE DİZİLERİ - 3 • Eğer sınıf tek bir argüman alan yapıcı fonksiyona sahipse, ilk değer olarak yalnızca bu argümanı belirtebilirisiniz. • Ayrıca, farklı ilk değer atama tarzlarını karışık da kullanabilirsiniz: String mesaj[10]={“Mesajin ilk satiri\n”, “ikinci satiri\n”, String(“ucuncu satiri\n”), String(‘-’,25),String()};