Sunum yükleniyor. Lütfen bekleyiniz

Sunum yükleniyor. Lütfen bekleyiniz

SINIFLAR VE DİNAMİK BELLEK YÖNETİMİ Yılmaz Kılıçaslan.

Benzer bir sunumlar


... konulu sunumlar: "SINIFLAR VE DİNAMİK BELLEK YÖNETİMİ Yılmaz Kılıçaslan."— Sunum transkripti:

1 SINIFLAR VE DİNAMİK BELLEK YÖNETİMİ Yılmaz Kılıçaslan

2 GİRİŞ Bu bölümde aşağıdaki konuları inceleyeceğiz: –“Free store” (bkz. Sunum 3) –Atama operatörü –this işaretçisi –copy 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 eleman fonksiyonlar –Arkadaş sınıflar ve fonksiyonlar –Nesne dizileri yaratma

3 “HEAP” C’de çalışma zamanında alınıp kullanılabilen bellek bölgesine “heap” denir. C’de heap’ten bellek istemek için malloc fonksiyonu kullanılır: struct tarih *tarihptr; tarihptr = (struct tarih *)malloc(sizeof(struct tarih)); C++ için malloc fonksiyonu istenmeyen sonuçlar üretebilir. Neden? //Ornek Tarih *tarihPtr; int i; tarihPtr = (Tarih *)malloc(sizeof(Tarih)); i = tarihPtr->aySoyle(); //Tanimsiz ay degeri dondurur

4 “FREE STORE” C++ “free store” olarak adlandırılan bir bellek bölgesinden dinamik olarak nesne yaratmak yada yok etmek için bellek kullanımına izin verir. Tarih *Ptr1, *Ptr2; int i; Ptr1 = new Tarih; //Varsayilan yapici fonksiyon çağrılır i = Ptr1->aySoyle(); //1 (varsayilan deger)dondurur Ptr2 = new Tarih(2,15,1985); //Yapici fonksiyon çağrılır i = Ptr2->aySoyle();//3 dondurur Derleyici new operatörünün döndürdüğü işaretçinin kendisi için bellek ayrılan nesne için olup olmadığını kontrol eder: void *Ptr; Ptr = new Tarih;

5 “DELETE” (1) Nasıl malloc fonksiyonunun eşleniği olan bir free fonksiyonu varsa, new operatörünün de eşleniği olan bir delete operatörü vardır. delete operatörü ayrılan bellek bölgelerini daha sonra kullanılabilmek üzere free store bölgesine iade eder: Tarih *Ptr1; İnt i; Ptr1 = new Tarih(3, 15, 1985); İ = Ptr1->aySoyle(); Delete Ptr1; delete operatörü belleği geri vermeden önce otomatik olarak yıkıcı fonksiyonu çağırır. delete operatörünü yalnızca new ile döndürülen işaretçilere ve yalnızca bir kez uygulamalısınız.

6 “DELETE” (2) new ve delete operatörlerini yalnızca sınıflarla değil diğer derleyici ile birlikte gelen veri tipleri ile de kullanabilirsiniz: // Ornek 1 int *ip; ip = new int; //... delete ip; // Ornek 2 int length; char *cp; // length degiskenine bir deger atanir cp = new char[length]; //... delete [] cp;

7 “DELETE” (3) // Ornek 3 int (*matrix)[10]; int size; // size degiskenine bir deger atanir matrix = new int[size][10]; //... delete [] matrix;

8 İŞARETÇİ ELEMANLI SINIFLAR - 1 new ve delete operatörlerini bir sınıfın eleman fonksiyonları içinde kullanabilirsiniz. Her nesnesinin bir karakter katarı içereceği bir String sınıfı tanımlamak istediğimizi varsayalım: #include ; class String { public: String(); String( const char *s ); String( char c, int n ); void belirle( int index, char yeniK ); char soyle( int index ) const; int uzunlukSoyle() const { return uzunluk; } void goruntule() const { cout << buf; } ~String(); private: int uzunluk; char *buf; }

9 İŞARETÇİ ELEMANLI SINIFLAR - 2 // Varsayilan Yapici Fonksiyon String::String() { buf = 0; uzunluk = 0; } // const char * alan Yapici Fonksiyon String::String( const char * s ) { uzunluk = strlen( s ); buf = new char[uzunluk + 1]; strcpy( buf, s ); } // char ve int alan Yapici Fonksiyon String::String( char c, int n ) { uzunluk = n; buf = new char[uzunluk + 1]; memset( buf, c, uzunluk ); buf[uzunluk] = ‘\n’; }

10 İŞARETÇİ ELEMANLI SINIFLAR - 3 // String içinde bir karakterin belirlenmesi void String::belirle( int index, char yeniK) { if( (index >0) && (index <= uzunluk) ) buf[index - 1] = yeniK ; } // String içinden bir karakterin okunmasi char String::soyle( int index ) const { if( (index >0) && (index <= uzunluk) ) return buf[index - 1]; else return 0; } // Yikici Fonksiyon String::~String() { delete [] buf; } int main() { String string1(“birinci Karakter Katari”); string1.belirle(1, ‘B’); return 0; }

11 İŞARETÇİ ELEMANLI SINIFLAR - 4 Her String nesnesi, birisi uzunluk ve buf eleman sahalarını içeren ve diğeri karakterlerin kendilerini depolayan iki bloktan oluşur. Nesnelerin içeriğini dinamik olarak değiştirmek mümkündür: void String::ekle( const char *ek ) { char *temp; uzunluk += strlen( ek ); temp = new char[uzunluk + 1]; strcpy( temp, buf ); strcat( temp, ek ); delete [] buf; buf = temp; } int main() { String string1(“birinci karakter katari”); string1.ekle(“ ve devami”); return 0; }

12 İŞARETÇİ ELEMANLI SINIFLAR - 5 Bir String nesnesi kapsam alanının dışına çıkınca, uzunluk ve buf değerlerinin içeren bellek bloğu otomatik olarak serbest bırakılır. Fakat, karakter bloğu new ile alındığı için, açıkça serbest bırakılmalıdır. delete operatörünü kullanan yıkıcı fonksiyon bu işlevi yerine getirir. Yine de String sınıfı bazı problemlere potansiyel olarak açıktır. main fonksiyonuna aşağıdaki kod parçasını eklediğimizi varsayalım: String string2(“ikinci karakter katari”); string2 = string1; Bir nesnenin bir diğerine atanmasını istediğimizde derleyici eleman bazında atama yapar. Yani, yukarıdaki atama deyimi aşağıdaki koda denktir: string2.uzunluk = string1.uzunluk; string2.buf = string1.buf; Böyle bir atama işlemi sonucunda doğabilecek problemleri belirleyin.

13 İŞARETÇİ ELEMANLI SINIFLAR - 6 Bir String nesnesini diğerine doğrudan atamamız neticesinde karşılaşacağımız problemler şunlardır: –String nesnelerinden herhangi birine yapılacak bir değişiklik diğerini de etkileyecektir; bu muhtemelen arzu edilir bir durum olmayacaktır. –string1 ve string2 nesnelerinin buf işaretçisi yıkıcı fonksiyonları üzerinden ayrı ayrı silinecektir. İşaretçiler aynı değeri taşıdığı için bu tahmin edilemeyen sorunlara yol açabilir. –“ikinci karakter katari” değerini taşıyan string2 nesnesinin orijinal ‘buffer’ bloğu serbest bırakılmadan kaybolmuştur.

14 ATAMA OPERATÖRÜ - 1 Nasıl fonksiyon isimleri aşırı yüklenebiliyorsa (gerçekte birer fonksiyon olan) operatör isimleri de aşırı yüklenebilir. Aşağıda atama operatörünün aşırı yüklenmesini görmekteyiz: #include class String { public: String(); String( const char *s ); String( char c, int n ); void operator=( const String &digeri ); //... }; void String::operator=(const String &digeri) { uzunluk = digeri.uzunluk; delete [] buf; buf = new char[uzunluk+1]; strcpy( buf, digeri.buf ); }

15 ATAMA OPERATÖRÜ - 2 int main() { String string1(“birinci karakter katari” ); string1.goruntule(); cout << ‘\n’; String string2(“ikinci karakter katari” ); string2.goruntule(); cout << ‘\n’; string2 = string1; string2.goruntule(); cout << ‘\n’; return 0; } Program çıktısını belirleyiniz. Derleyici string2 = string1; deyimini aşağıdaki fonksiyon çağrımı gibi yorumlayacaktır: string2.operator=( string1 );

16 ATAMA OPERATÖRÜ - 3 Programcının yanlışlıkla String sınıfından bir nesneyi kendisine atadığını varsayalım: string1 = string1; Elbette çok az programcı böyle bir ifade yazacaktır. Fakat kendi kendine atama işlemi daha dolaylı yollardan da gerçekleştirilebilir: String *stringPtr = &string1; //... string1 = *stringPtr; operator= fonksiyonunun böyle bir atama işlemi esnasında ne yapacağını belirleyin.

17 this İŞARETÇİSİ - 1 this işaretçisi statik eleman fonksiyonlar haricindeki eleman fonksiyonların erişebildiği özel bir işaretçidir: Eleman fonksiyonu çağıran nesneye işaret eder. Daha açıkçası bir eleman fonksiyonu bir nesne için nesne için çağırdığınız zaman, derleyici önce bu nesnenin adresini this işaretçisine atar ve ardından fonksiyonu çağırır. Eleman fonksiyon sınıfın eleman sahalarına her erişiminde örtük olarak this işaretçisini kullanır. Örneğin, void Tarih::ayBelirle( int mn ) { ay = mn; } //... tarih1.ayBelirle( 3 ); biçimindeki C++ kod parçasının C karşılığı şöyle olacaktır: void ayBelirle(Tarih *const this, int mn ) { this->ay = mn; } //... ayBelirle( &tarih1, 3 );

18 this İŞARETÇİSİ - 2 Bir eleman fonksiyonu yazarken herhangi bir eleman sahaya erişmek için this işaretçisini açıkça kullanmak yada fonksiyonu çağıran nesneye referansta bulunmak için *this ifadesini kullanmak legal yöntemlerdir. Aşağıdaki örnekteki üç deyim birbirine denktir: void Tarih::ay_goruntule() { cout << ay; cout ay; cout << (*this).ay; }

19 this İŞARETÇİSİ - 3 this işaretçisi bir eleman fonksiyonu çağıran nesne ile bu fonksiyona parametre olarak gönderilen nesnenin aynı nesneler olup olmadığını anlamak için kullanılabilir ve bu şekilde örneğin nesneyi kendisine değer olarak atama probleminden kaçınılabilir: void String::operator=(const String &digeri) { if( &digeri == this ) return; delete [] buf; uzunluk = digeri.uzunluk; buf = new char[uzunluk+1]; strcpy( buf, digeri.buf ); }

20 this İŞARETÇİSİ - 4 Hem C’de hem C++’da bir atama deyimi atananı değer olarak alan bir ifade gibi düşünülebilir. Örneğin, i = 3; değeri 3 olan bir ifadedir. Bu durumun bir neticesi birden fazla atama deyiminin zincirleme olarak birbirine bağlanabilmesidir: a = b = c; Atama operatörü sağdan birleştirmeli olduğundan yukarıdaki ifade aşağıdakiyle denktir: a = (b = c);

21 this İŞARETÇİSİ - 5 Bu zincirleme atama özelliğini aşırı yüklenmiş nesne atama fonksiyonunuzun da kullanmasını istiyorsanız, fonksiyonun atama sonucunu değer olarak döndürmesini sağlamalısınız: String &String::operator=(const String &digeri) { if( &digeri == this ) return *this; delete [] buf; uzunluk = digeri.uzunluk; buf = new char[uzunluk+1]; strcpy( buf, digeri.buf ); return *this;} Artık cout ifadelerinin de nasıl birden fazla çıktı değeri aldığını açıklayabiliriz: cout << a << b << c; Aşırı yüklenmiş sola kaydırma operatörü *this değerini cout nesnesi olarak döndürmektedir.

22 ATAMA VE İLK DEĞER ALMA 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. İki işlem arasındaki bu ayrım 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.

23 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. 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 yada fonksiyonu-çağırma sentaksı ile çağrılır: 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.

24 KOPYA YAPICI FONKSİYON - 2 String sınıfı için kopya yapıcı fonksiyonu şu şekilde yazılabilir: #include class String { public: String(); String( const char *s ); String( char c, int n ); String( const String &digeri ); //... }; String::String( const String &digeri ) { uzunluk = digeri.uzunluk; buf = new char[uzunluk + 1]; strcpy( buf, digeri.buf ); }

25 NESNELERİN PARAMETRE OLARAK GEÇİRİLMESİ Aşağıdaki fonksiyon bir nesneyi parametre olarak almaktadır: void tuket( String parm ) { // parm nesnesinin kullanımı } void main() { 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 fonksiyon parametre nesneyi ilk değer olarak atamak üzere kopya yapıcı fonksiyonu çağırmıştır; yaptığı aşağıdakiyle aynıdır: String parm( string1 );

26 NESNE DÖNDÜREN FONKSİYONLAR Aşağıdaki fonksiyon değer olarak bir nesne döndürmektedir: String f() { String retDeger(“bir return değeri”); return retDeger;} void main() { 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: String gecici( retDeger ); string2 = gecici;

27 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: void tuket( const String &parm ) { // parm nesnesinin kullanımı } void main() { 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: const String &parm = string1; Sonuç olarak, fonksiyon kendisini çağıranla aynı nesneyi kullanmış olur. Kopya yapıcı fonksiyonun parametre olarak bir nesne değil nesneye referans aldığına dikkat ediniz. Aksi halde ne olacağını belirtiniz.

28 NESNE REFERANSLARININ DEĞER OLARAK DÖNDÜRÜLMESİ Nesneye referansı 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=(const String &digeri) { //... return *this; } void main() { 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;

29 STATİK ELEMAN SAHALAR - 1 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. 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.

30 STATİK ELEMAN SAHALAR - 2 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. Örnek: class TasarrufHesabi { public: TasarrufHesabi(); void faizEkle() { toplam += faizOrani * toplam; } //... private: char isim[30]; float toplam; static float faizOrani; //... };

31 STATİK ELEMAN SAHALAR - 3 Statik bir eleman sahayı, bildirimini public yapmak suretiyle, program içerisinde erişilebilir kılabilirsiniz: // Eğer faizOrani public ise void main() { TasarrufHesabi hesap1; hesap1.faizOrani = ; } Yukarıdaki programda kullanılan söz dizim legal 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 = ; Bu söz dizim 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.

32 STATİK ELEMAN SAHALAR - 4 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ğillerdir; dolayısıyla private eleman sahalar ilk değerlerini public eleman sahalar gibi alırlar: float TasarrufHesabi::faizOrani = ; TasarrufHesabi::TasarrufHesabi() { //... } //...

33 STATİK ELEMAN FONKSİYONLAR - 1 Eğer yalnızca statik eleman sahalara erişim yapan bir fonksiyonunuz varsa, fonksiyonun bildirimini static yapabilirsiniz: class TasarrufHesabi { public: TasarrufHesabi(); void faizEkle() { toplam += faizOrani * toplam; } static void faizBelirle( float yeniDeger ) { faizOrani = yeniDeger; } //... private: char isim[30]; float toplam; static float faizOrani; //... };

34 STATİK ELEMAN FONKSİYONLAR - 2 Statik eleman fonksiyonlar statik eleman sahalara erişim için kullanılan söz dizim ile çağrılabilirler: // Statik eleman fonksiyon çağrımı void main() {TasarrufHesabi hesap1; hesap1.faizBelirle( ); TasarrufHesabi::faizBelirle( ); } Statik eleman fonksiyonlar belirli bir nesne üzerinde işlem yapmadıkları için this işaretçisine sahip değillerdir.

35 STATİK ELEMANLAR Statik eleman sahalar bütün nesnelerin ihtiyacı olan ortak kaynakları yönetmek yada 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: class Ucak { public: Ucak() { sayac++; } static int miktarSoyle() { return sayac; } ~Ucak(){ sayac--; } private: static int sayac; } int Ucak::sayac = 0;

36 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 { friend class Sinif2; private: int cokGizli; }; class Sinif2 { public: void degistir( Sinif1 nesne1 ) }; void Sinif2::degistir( Sinif1 nesne1 ) { nesne1.cokGizli++; } friend bildirimi public ve private anahtar sözcüklerinden etkilenmez; sınıf bildiriminin herhangi bir yerinde bulunabilir.

37 ARKADAŞ SINIFLAR - 2 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. friend arkadaş sözcüğü tek yönlü erişim imkanı sağlar. 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.

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

39 NESNE DİZİLERİ - 1 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()};


"SINIFLAR VE DİNAMİK BELLEK YÖNETİMİ Yılmaz Kılıçaslan." indir ppt

Benzer bir sunumlar


Google Reklamları