You are on page 1of 361

1

Ders Hedefi
Nesneye Yönelik Programlama (NYP), uyarlanabilir ve
yeniden kullanılabilir bilgisayar programları geliştirmek
için ortaya atılmış yeni bir yaklaşım biçimidir. NYP, yazılım
tasarımı ve programlama için, geleneksel programlama
yöntemlerinden farklı bir yol izlemektedir. Uyarlanabilir ve
yeniden kullanabilme yönüyle diğer programlama
tekniklerinden, örneğin yapısal programlama veya
yordamsal programlama tekniklerinden farklıdır.

Nesneye Yönelik Programların çalışma biçimi ve doğal


olarak bu dersin konusu olan C++ programlama dili, diğer
geleneksel dillerden daha değişik bir yaklaşım biçimidir.
Çünkü bu sistemin temelini nesneler oluşturmaktadır. Bu
nesneler, birbirleriyle haberleşebilen unsurlardır.

Bilgisayar Programlama II dersi, nesneye yönelik


programlama tekniklerine bir giriş yapmakta ve bu
çerçevede C++ dilini adım adım öğretmektedir. Bu ders,
herhangi bir programlama dili deneyimi olmayan
öğrencilerin de izleyebilmesini sağlayacak düzeyde
hazırlanmıştır.

NESNEYE YÖNELİK PROGRAMLAMA

Nesneye yönelik programlama, kullanıcıya sistem


içerisindeki mevcut nesnelerin ve bunların değişik
durumlardaki davranışlarının modellenmesi yönünde
bir yaklaşımla program yapma imkanı sağlamıştır.

Eğer bir dil, programcıya sorunu parçalara ayırma


hakkı tanıyorsa ve program parçalara ayrıldığında her
bir parçanın ayrı ve özgün davranışları
tanımlanabiliyor ve hatta kendi durumları söz konusu
olabiliyorsa o zaman programlama dilinin nesneye
yönelik bir dil olduğunu söyleyebiliriz. Nesneye yönelik
bir program, birbiriyle ilişkili bağımsız modüllerden
oluşur.

Bu bölümde Nesneye Yönelik Programlama'ya giriş


yapılmakta, onunla ilişkili olan kavramlar
tanımlanmaktadır.

2
1.1. Nesneye Yönelik Programlama
Nesneye Yönelik Programlama (NYP), uyarlanabilir ve yeniden kullanılabilir bilgisayar
programları geliştirmek için ortaya atılmış yeni bir yaklaşım biçimidir. NYP, yazılım
tasarımı ve programlama için, geleneksel programlama yöntemlerinden farklı bir yol
izlemektedir. Uyarlanabilir ve yeniden kullanabilme yönüyle diğer programlama
tekniklerinden, örneğin yapısal programlama veya yordamsal programlama
tekniklerinden farklıdır.

Nesneye yönelik programlama, adından da anlaşılacağı gibi, nesneleri temel olarak kabul
eder. Nesneye Yönelik Programlama, üstünlüğünü ve işlevselliğini; nesneler hakkındaki
bilgiyi ve nesneden beklenen davranış hakkındaki bilgiyi içeren ayrık sınıfların varlığı
sayesinde kazanmaktadır. Sınıflar, Nesneye Yönelik Programlamanın en önemli
kavramlarından biridir.

1.1.1. Nesneye Yönelik Yaklaşıma Bir Örnek


Bu duruma iyi bir örnek olarak bir kişisel bilgisayarı gösterebiliriz. Bu bir programlama
örneği olmamasına rağmen, nesneye yönelim için bir örnek olarak kabul edilebilir. Her
kişisel bilgisayar, birbirleriyle ilişkisi olmayan firmaların ürettiği bileşenlerden
oluşmaktadır. Örneğin; ana kart, bellek, ekran, fare, klavye, CD sürücü ve bunun gibi
bileşenler sayılabilir.

Tüm bileşenler, bilgi ve davranışını içeren tanımlara göre kurulmuştur. Örneğin bir CD
sürücüden, işletim sistemi onu talep ettiğinde CD'den gelen verileri okuması beklenir.
Kişisel bilgisayar üreticileri CD nin iç yapısıyla ve çalışmasıyla ilgilenmezler. Sadece ondan
beklenildiği gibi davranmasını isterler.

3
Kişisel bilgisayar, CD sürücüsüne mesaj gönderir ve CD sürücüsü bu mesaja uygun
hareket eder.

1.1.2. Nesneye Yönelik Programlama ve C++


Nesneye Yönelik Programlar da kişisel bilgisayar örneğinde olduğu gibi çalışır. Kişisel
bilgisayarın parçaları, yani nesneleri ile olan haberleşmesi ve nesnenin buna göre
davranması, nesneye yönelik programlarda da aynen gerçekleşir. Doğal olarak, burada
ana program kişisel bilgisayarın kendisidir ve ilişkide olduğu nesneler de birer program
parçasıdır. Nesneye yönelik program, kullanacağı bir nesnenin tüm ayrıntılarını bilmeden
onu çağırabilir. Program basit biçimde o nesne yardımıyla bilgi edinilmesini ya da belirli
bir işi yerine getirmesini bekler.

Nesne yönelim kavramları, tıpkı insan anlayışının temellerini oluşturduğu gibi, C++'ın da
kalbini oluşturur. Bu kavramların programlara nasıl dönüştürüldüğünü anlamamız
önemlidir. Göreceğiniz gibi, nesne yönelimli programlama; her büyük yazılım projesinin
hayat döngüsüne eşlik eden düşünce, büyüme ve yaşlanma gibi küçümsenmeyecek
değişikliklerle, ayakta kalan programlar oluşturmak için güçlü ve doğal bir görüş açısı
getirmektedir. Örneğin, bir kere iyi tanımlanmış nesneleriniz ve bunların açık, güvenilir
arabirimleri oldu mu, eski bir sistemin parçalarını korkmadan ve istediğiniz biçimde
değiştirebilir ya da görevden alabilirsiniz.

Nesneye Yönelik Programların çalışma biçimi ve doğal olarak bu dersin konusu olan C++
programlama dili, diğer geleneksel dillerden farklıdır çünkü, bu sistemin temelini nesneler
oluşturmaktadır. Bu nesneler, birbirleriyle haberleşebilen unsurlardır. Nesneye yönelik
programlarda, bir nesne diğerine bir mesaj gönderir ve diğer nesneden bir mesaj ya da
davranış bekler.

4
1.2. Nesneye Dayalı Programlama Aşamaları
Nesneye dayalı programlama belirli bir sıraya göre yapılır. Bu sıra içinde nesneler
tasarlanır, yaratılır ve ana program içinde bu nesneler kullanılır.

• Problemi oluşturan nesneler belirlenir. Bu nesneler somut ya da soyut varlıklar


olabilir.
• Bu nesneler kodlanarak tanımlanır.
• Ana programda bu nesneler kullanılır. Nesne, ana programda aldığı mesaja göre
davranır. Ya kendisi bir mesaj üretir ya da bir başka mesaj gelinceye dek bekler.

1.3. Nesneye Dayalı Programlamanın Unsurları


Nesneye Yönelik Programlamada uygulamayı oluşturan varlıkları modellemek için, veriler
ve onlar üzerinde işlem yapan yordamların bir arada bulunduğu "nesneler" kullanılır.
Buna veri soyutlama denir. Nesneye Yönelik Programlamanın çeşitli temel unsurları
vardır. Bu unsurlar şu şekilde sıralanabilir:

5
1. Kapsülleme (Encapsulation)
2. Kalıtım (Inheritance)
3. Çok biçimlilik (Polymorphism)

Nesnelerle ilgili olarak aşağıdaki temel özellikler sıralanabilir:

1. Nesneler, sınıf (class) adı verilen bir veri yapısı ile oluşturulur. Nesne, kapsülleme
yapılan yerdir. Sınıfı kullanan programda nesneler tanımlanır ve bu nesnelere
mesajlar gönderilir. Mesajlar karşısında bir davranışta bulunmak sınıfın işidir.
2. Veri soyutlama ve veri gizleme modüller arası bağımlılığı azaltır. Değişiklikler
modül içinde kalır, tüm sistemi etkilemez.
3. Kalıtım (inheritance) özelliği kullanılarak bir sınıf başka bir sınıftan türetilebilir.
4. Yordamların çok şekillilik (polymorphism) özelliği vardır.

1.4. Nesneye Yönelik Programlamada Nesneler


Nesneye yönelik programlar yüzlerce ve hatta binlerce nesneden oluşabilir. Bu nesneler
birinden diğerine mesaj göndermek suretiyle haberleşirler.

Nesneye yönelik program, birbirleriyle haberleşen nesnelerin bir grubu olarak da


düşünülebilir.

Gönderilen mesajlara göre, yeni bir nesneye gereksinim duyulduğunda bu nesne yaratılır.
Gereksinim olmadığında ise silinebilir. Nesneler, gönderilen mesajlara göre kendilerinden
beklenen eylemleri yerine getirirler. Mesajlar farklı iki nesne arasında olabileceği
gibi, bir nesne kendisi ile de mesajlaşabilir.

6
Nesneler diğer nesnelere ya da kendi kendilerine mesaj gönderirler.

1.5. Gerçek Dünyada Soyutlama


Nesneye yönelik programlamanın gerekli bir unsuru, soyutlamadır (abstraction).
İnsanoğlu, karmaşıklığı soyutlama yoluyla idare eder. Örneğin, insanlar bir otomobili on
bin ayrı parçadan oluşan bir küme gibi düşünmezler. Onu, kendine özgü eşsiz davranışları
olan, iyi tanımlanmış bir nesne olarak düşünürler. Bu soyutlama sayesinde arabayı, onu
oluşturan parçaların karmaşıklığı altında ezilmeden, işyerine gitmek için kullanabilirler.
Motorun, fren sistemlerinin ve araba vitesinin nasıl çalıştığı gibi ayrıntıları göz ardı
ederler. Bunun yerine nesneden bir bütün olarak yararlanırlar.

Çoğu kişinin otomobillerden anlaması bu soyutlama sayesinde olmaktadır. Birçok kişi


otomobil motorunun ne yaptığını bilir. Ancak, her bir parçasının ne iş yaptığını bilmez.
Her bir parçası farklı farklı işlemleri yerine getirir. Aslında arabayı kullanabilmesi için bu
tür ayrıntıları bilmesine de gerek yoktur. Böyle bir soyutlama yardımıyla, bu parçaların
oluşturduğu motorun ne amaca hizmet ettiğini bilir.

Buradan şu sonuç çıkmaktadır: Gerçek dünyadaki nesneler, tüm ayrıntıları ile değil
soyut halleri ile anlamlı ve kullanışlı olmaktadır.

Soyutlamanın güçlü yanlarından birisi, hiyerarşik sınıflandırmadır. Bu size karmaşık


sistemleri anlamlı katmanlara ayırmanıza ve daha yönetilebilir parçalara indirgemenize
imkan verir. Dışardan, araba tek bir nesnedir. İçine girdiğinizde ise arabanın bazı alt
sistemlerden oluştuğunu görürsünüz: Direksiyon, frenler, ses sistemi, emniyet kemerleri,
ısıtma ve diğerleri. Bu alt sistemler de daha özelleşmiş birimlerden oluşur. Mesela ses
sistemi; bir radyo, CD ve kaset için ayrı bölümlerden oluşur. Asıl önemli olan şudur: Siz
arabanın karmaşıklığını ya da herhangi diğer sistemin karmaşıklığını hiyararşik
soyutlamalarla idare edersiniz.

7
Otomobil kullanmak için otomobilin her parçası hakkında bilgiye gereksinim yoktur.

1.6. Nesnenin Davranış Biçimi


Nesne teknolojisi sistemleri nesnelere ayrıştırır. Tüm sistem, birbirlerinden bağımsız, veri
ve davranışta bulunma özelliği olan nesnelerden oluşur. Bu nesneler kendi aralarında ya
da bunun dışındakilerle etkileşim halindedir. Sistemin tüm işlemleri, nesne işlemleri
tarafından yerine getirilir.

Farklı nesneler doğal olarak farklı işlemleri yerine getirmektedir. Örneğin, bir bankanın
ATM cihazı kart okuma, para çekme, para yatırma, makbuz verme gibi işlemleri yerine
getirir. Bir başka örnek verilecek olursa, kalem nesnesi yazma işlemini yerine getirir.

Her bir nesne için kendinize şöyle bir soru sorun: "Bu nesne benim için ne yapar?" Bir
diğer deyişle, bu nesne sistem içindeki diğer nesneler için nasıl bir hizmet sunar? Bu
soruyu yanıtlamak için, nesnenin sistem içinde nasıl kullanıldığı konusunda bir görüşe
sahip olmamız gerekmektedir. Ancak her bir nesne için yüzlerce işlem tanımlamamız söz
konusu olabilir. Bu durum bizi tatmin etmeyecektir.

Altın kural, nesnenin sistem içinde nasıl kullanılacağını anlamak ve sadece nesnenin bu
durumda kullanılışıyla ilgili işlemleri belirlemektir.

Bir otomobil nesnesini göz önüne alalım. Otomobil, kaporta, dişli çarklar, motor, aynalar,
vites kutusu, akü, fren sistemi, soğutma sistemi, silecekler, hidrolik sistem, yakıt
besleme sistemi, tekerlekler vb. gibi nesnelerden oluşur. Bu parçaların görevi farklıdır.
Örneğin, motor arabayı hareket ettirir, ama parçaların bir arada durmasını sağlamaz.
Parçaların bir arada durmasını sağlamak kaportanın görevidir.

Motorun çalışması, arabanın hareket etmesi, fren yapılınca durması, gaz pedalına
basılınca hızlanması ise davranışlardır. Gaz pedalına basılınca arabanın hızlanması, gaz
pedalı nesnesinin bir davranışıdır. Araba üreten bir firma, otomobilin tüm parçalarını
kendisi üretmek zorunda değildir. Çoğu parçayı diğer firmalardan sağlar ve kendi
otomobilini bu şekilde üretir. Nesneye dayalı programlarda da programcı kendi

8
modüllerini tümüyle kendisi yazmayabilir. Hazır
modülleri kullanarak kendi programını oluşturur.

Her bir nesne sistemde belli bir işlemi gerçekleştirir.


(Örnek: Kalem bir nesnedir, kalem yazma işlemini
gerçekleştirir.)

1.7. Nesneler Değişkenlere Benzer


C programlarında değişkenlerin ne anlama geldiğini ve
nasıl kullanıldığını biliyorsunuz. Değişkenler, kendilerine
atanan verileri içeren bellek alanları olarak
düşünülebilir. Nesneye Yönelik Programlamada nesneler değişkenlere benzerler. Ancak
ondan farklı olarak, sadece verileri değil, bir davranış biçimini de içermektedirler. Yani
nesne bazı verileri içerebilir, ancak nesnenin nasıl davranacağına ilişkin bilgileri de
içeririr. Bir nesnenin nasıl davranacağına programcı karar verir.

Nesneler yaratılırken, değişkenlerde olduğu gibi, tek bir satır veya komut ile
oluşturulmaz. Aynen bir program gibi nesne kodları oluşturulur. Nesnenin kendi
değişkenleri, fonksiyonları ve denetim deyimleri bulunacaktır.

Nesne başlı başına ayrı bir program gibi tasarlanır ve kodlanır.

Nasıl davranacağı fonksiyonlar biçiminde, nesne içinde


ayrıca tanımlanır. Bu nesnelerin birleşiminden ana
program oluşturulur. Bir program çok sayıda nesne
içerebilir. Her nesne program içinde bağımsız bir yapıya
sahiptir. Nesnenin nasıl bir davranış sergileyeceği ana
program tarafından değil, nesnenin içindeki kodlar
tarafından tanımlanır.

Nesnenin kendi değişkenleri, fonksiyonları ve denetim deyimleri bulunur.

1.8. Nesnelerin Barındırdığı Bilgiler


Her bir nesne, kendisi hakkında ve yerine getireceği işlem yani eylem hakkında bilgi
barındırır. Sistemin gereksinim duyduğu tüm veriler bir nesnenin özellikleri içinde
yerleşmiştir. Bazı nesnelerin az verisi vardır; hatta bazılarının hiç yoktur. Bazıları ise
büyük miktarda veriye sahiptir. Bu olay nesnenin gerçekleştireceği işleme bağlıdır.

İşlemlere bağlı olarak, farklı nesneler farklı özellikleri saklarlar. Örneğin,


bankanın ATM nesnesi içerdeki para, kart tanıma, ATM kodu, ve bunun gibi özelliklere
sahip olacaktır. Kalem nesnesi ise kalan mürekkep, ucun kalınlığı, kalem gövdesinin

9
uzunluğu vb. özelliklere sahiptir. Bir otomobil nesnesi ise daha farklı özelliklere sahip
olacaktır. Örneğin, otomobilin hızı, ağırlığı, yakıt tüketimi vb. sayılabilir.

! İlgili nesneyi ve nesnenin özelliklerini görmek için resme tıklayınız.

1.9. Nesne Hiyerarşisi


Nesneler bir başka nesnelerin bir araya getirilmesi ile oluşturulabilir veya bir nesne bir
diğer nesnenin bir alt parçası olabilir.

Benzer biçimde, bir banka nesne olarak kabul edilebilir. Bu bankanın sahip olduğu ve
para yatırma ve çekme işlemlerinde kullanılmak üzere hizmete sunduğu ATM makinaları
(Otomatik para çekme makinası) da birer nesnedir. Bu nesneler bankanın bir parçası
olarak değerlendirilir. ATM makinası da birtakım alt nesnelerden oluşmuştur. Örneğin
klavye, kard okuyucu ve para haznesi sayılabilir.

Karmaşık sistemlerin hiyerarşik soyutlamaları, bilgisayar programlarına da uygulanabilir.


Geleneksel bir işlem-yönelimli program verileri, soyutlama yoluyla bileşenlerine
dönüştürülebilir. Bir dizi işlem aşaması, bu nesnelerin arasındaki mesaj koleksiyonu
haline gelebilir. Böylece her bir nesne kendi özgün hareketini tanımlar. Bu nesnelere, bir
şey yapılması gerektiğini söylediğinizde, cevap veren somut varlıklar olarak
davranabilirsiniz. Bu, nesne yönelimli programlamanın özüdür.

1. Banka bir nesnedir.


2. Bir banka bir ATM nesnesine sahiptir.

10
3. Bir ATM, klavye, kart okuyucu, para haznesi nesnelerinden oluşur.

1.10. Kapsülleme

Kapsülleme, yönettiği kod ve veriyi birbirine bağlayan ve bu ikisini dış kaynaklı karıştırma
ve yanlış kullanımlardan koruyan bir mekanizmadır.

Kapsüllemeyi, veri ve kodu, ambalajı dışında tanımlanmış başka bir kodun rasgele
erişiminden koruyan bir ambalaj olarak da düşünebiliriz. Ambalajın içindeki veri ve koda
erişim, bir arabirim tarafından sıkıca kontrol edilir. Nesneye dayalı dillerde kod ve veri, bir
"kara kutu" oluşturacak biçimde bir araya getirilir. Bir başka deyişle nesne, kapsülleme
yapılan yerdir.

Bunu gerçek dünyayla ilişkilendirmek için arabanızdaki otomatik vites mekanizmasını ele
alalım. Ne kadar ivmelendiğiniz, üzerinde bulunduğunuz yüzeyin asfalt durumu, vites
kolunun pozisyonu gibi, motorunuz hakkında yüzlerce bitlik bilgiyi kapsüller. Siz kullanıcı
olarak bu karmaşık sistemi sadece bir metotla değiştirebilirsiniz: Vites değiştirme kolunu
hareket ettirerek... Mekanizmayı, dönüş sinyalini ya da ön cam sileceklerini kullanarak
etkileyemezsiniz. Yani vites değiştirme kolu, vites değiştirmek için iyi tanımlanmış bir
arabirimdir. Üstelik mekanizmanın içerisinde ne olduğu, dışarıdaki nesneleri etkilemez.
Mesela vites değiştirme, farları yakmaz. Çünkü otomatik vites değiştirme kapsüllenmiştir.
Düzinelerce farklı otomobil üreticisi bu mekanizmayı istedikleri gibi gerçekleştirebilirler.
Fakat sürücünün bakış açısından hepsi aynı çalışır.

Bu fikrin aynısı programlamaya da uygulanabilir. Kapsüllenmiş kodun gücü, herkesin ona


nasıl ulaşacağını ve böylece gerçekleşme ayrıntılarından bağımsız olarak ve beklenmedik
yan etki korkusu olmadan kullanabilmelerinden kaynaklanır.

1.11. Sınıflar
Kapsüllemenin temeli sınıflardır. Sınıf (class), birbiriyle ilişkili verilerin ve bu verileri
kullanmayı sağlayacak yordamların bir arada bulundurulmasını sağlayan bir yapıdır. Sınıf,
nesneleri tanımlamak için kullanılan tür tanımıdır. Ortak özelliklere sahip nesnelere ait
veri ve yordamlar bir sınıfın içinde toplanırlar. Bu sınıf yapısı kullanılarak program içinde
nesneler tanımlanır.

Sınıf bir grup nesne tarafından paylaşılacak yapı ve davranışları (veri kod) tanımlar. Belli
bir sınıfın her nesnesi, sanki o sınıfın bir kalıbıyla damgalanmış gibi sınıf tarafından
tanımlanan yapı ve davranışları gösterir. Bu nedenle, nesneler bazen sınıf örnekleri
(instance) olarak da tanımlanır. Böylece, sınıfın mantıksal bir yapısı varken nesnenin
fiziksel bir gerçekliği vardır.

Bir sınıf oluşturduğunuz zaman, o sınıfı oluşturan kod ve verileri de belirtirsiniz. Topluca
bu unsurlara sınıfın üyeleri (member) denir. Ayrıca sınıf tarafından tanımlanan veri, üye

11
değişkenler (member variable) ya da örnek değişkenler (instance variable) olarak
bilinir. Bu veri üzerinde iş gören koda üye yöntemler (member method) yada sadece
yöntem adı verilmektedir. Burada söz edilen yöntem, C/C++ programlarında fonksiyon
olarak bilinmektedir.

Sınıfın karmaşık bir kapsüllemesi olduğu için, sınıf içinde uygulamanın karmaşıklığını
gizlemek için bazı mekanizmalar vardır. Bir sınıfın genel (public) arabirimi, sınıfın dış
kullanıcılarının bilmesi gereken ya da bilebileceği herşeyi temsil eder.

Sınıfın özel (private) yöntemlerine ya da verilerine, sadece o sınıfın üyesi olan kodlar
erişebilir.

Bu yüzden o sınıfın üyesi olmayan hiçbir kod sınıfın özel veri ya da metodlarına
erişemez. Programın diğer parçaları tarafından bir sınıfın özel üyelerine erişim, ancak
sınıfın genel metodları yoluyla olabildiği için kural dışı hiçbir eylem
gerçekleşmeyeceğinden emin olabilirsiniz. Bu da tabi ki genel arabirimin, sınıfın iç
çalışmalarını ortaya çıkarmaması için dikkatli tasarlanmasını gerektirir.

Sonuç olarak:

• Bir sınıf, nesneler için bir şablondur.


• Bir sınıf, bir nesnenin tüm durumlarıyla ilgili
işlemleri ve özellikleri tanımlar.

Bir kalem nesnesinin yaratılması söz konusu


olduğunda, onunla ilgili işlemleri ya da özellikleri
tanımlamak zorunda değiliz. Bu nesnenin ait olduğu
sınıfı belirtmemiz yeterlidir.

1.12. Kalıtım-Inheritance
Bir Kangal cinsi köpeği göz önüne alalım. "Kangal köpeği" bir nesnedir. "Kangal köpeği"
bir köpek olduğu için, diğer köpekler gibi, köpek türünün tüm özelliklerini taşır. Ayrıca
kendine özgü bazı ilave özellikleri de vardır. Örneğin "Kangal köpeği" diğer köpeklere
göre sahibine daha fazla bağlıdır. Burada şöyle bir durum söz konusudur: "Kangal
köpeği" ile ilgili bir işlem tanımlarken, bu köpeğin tüm özelliklerini tanımlamaya gerek var
mı? Nesneye Yönelik yaklaşımda buna gerek yoktur. Çünkü "Kangal köpeği" nin ilgili
olduğu "Köpek" nesnesi ve onun da bağlı olduğu "Hayvan" nesnesinin ortak özellikleri
zaten bilinmektedir. Bunlar bir sınıf olarak tanımlanırsa, bu sınıf ile ilgili tüm nesneler o
sınıfa bağlanarak, ortak olan tüm özellikleri kullanılabilir.

12
Kangal köpeği ait olduğu üst-sınıfların tüm özelliklerine sahiptir.

Kalıtım (inheritance), bir nesnenin diğer bir nesnenin özelliklerini kazanması işlemidir.
Bu önemlidir, çünkü hiyerarşik sınıflandırmayı destekler. Birçok bilgi hiyerarşik (yani
ast-üst) sınıflandırma ile yönetilebilir hale gelir.

Hiyerarşiler kullanılmaz ise, her nesnenin özelliklerinin açıkça belirtilmesi gerekir. Fakat
kalıtım kullanımıyla, bir nesnenin, onu sadece kendi sınıfının içinde eşsiz yapan
özelliklerini belirtmemiz yeterlidir. Genel niteliklerini ebeveyninden alabilir. Böylece bir
nesneyi daha genel bir durumun özel bir örneği yapan şey kalıtım mekanizmasıdır.

1.13. Çok Şekillilik-Polimorfizm


Köpekle ilgili örneğimizi yeniden göz önüne alalım. Bunu genişleterek, köpeğin koklama
duyusunu yorumlamak istiyoruz. Köpek bir kedinin kokusunu aldığında havlar ve onu
kovalar. Köpek, eğer yiyecek kokusu alırsa salya salgılar ve yiyecek kabına koşar. Her iki
durumda da aynı koku duyusu iş başındadır. Farklı olan, kokusu alınan şeydir; yani
köpeğin burnu tarafından işlenen veri türüdür.

Çok biçimlilik (polimorfizm), genel anlamda bir adın, birbirleriyle ilişkili fakat teknik
açıdan farklı iki veya daha fazla amaç için kullanılabilmesi yeteneğidir.

Nesneye Yönelik Programlama kavramı içinde ise çok biçimlilik, bir adın genel bir iş
(action) sınıfını belirlemesine olanak sağlar. Genel bir iş sınıfı içerisinde, yapılacak iş,
veri tipi tarafından belirlenir. Örneğin çok biçimliliği pek fazla desteklemeyen C'de, mutlak
değer bulma işi üç ayrı fonksiyon adı gerektirir. abs(), labs() ve fabs(). Bu fonksiyonlar
sırasıyla bir tamsayının, bir uzun tamsayının (long integer) ve bir gerçel (reel) sayının
mutlak değerini hesaplar. Fakat çok biçimliliği destekleyen C++'da bu fonksiyonlar, abs()
gibi tek bir isimle adlandırılırlar. Fonksiyonu çağırmak için kullanılan veri tipi, gerçekte
hangi fonksiyonun çalışacağını belirler. Böylece bir fonksiyon adının birkaç farklı amaç için
kullanılması mümkündür. Buna fonksiyonların aşırı yüklenmesi (function overloading)
adı verilmektedir.

13
Nesneye Yönelik Olmayan Programlamada, mutlak değeri alınacak olan sayı türüne göre
ayrı ayrı fonksiyonlar yaratılırken, Nesneye Yönelik programlamada bu işi tek bir
fonksiyon yapar.

Çok biçimlilik işleçlere de uygulanabilir. Gerçekte her programlama dili, aritmetik


işleçlerle ilgili olduğundan bir parça çok biçimlilik içerir. Örneğin, C'de + işleci tamsayı,
uzun tamsayı ve gerçel tipdeki sayıları ve karakterleri toplamak için kullanılır. Bu
durumların her birinde derleyici otomatik olarak ne tür bir aritmetik işlem yapacağını bilir.
C++'da bu tanımladığınız diğer veri türlerine de uygulanabilir. Çok biçimliliğin bu çeşidine
işleçlerin aşırı yüklenmesi (operator overloading) adı verilir.

Daha genel olarak düşünürsek, çok biçimlilik, genel bir arabirimin, birbirleriyle ilişkili
birtakım işler için kullanılması anlamına gelen "bir arabirime birden fazla yöntem"
fikri ile açıklanmaktadır. Çok biçimliliğin üstünlüğü, bir arabirimin genel bir iş sınıfını
belirlemek için kullanılmasına imkan vererek karmaşıklığı azaltmasıdır. Duruma uygun
işin seçilmesi derleyicinin görevidir. Siz programcı olarak kendiniz bu seçimi elle yapmak
zorunda değilsiniz. Sadece genel arabirimi hatırlamanız ve kullanmanız yeterlidir.
Yukarıda verdiğimiz örnekte mutlak değer fonksiyonu için bir yerine üç ad kullanılması,
bir sayının mutlak değerini bulan genel işi gerçekte olduğundan daha karmaşık hale
getirir.

Bölüm Özeti
Bu bölümde,

• Nesneye Yönelik Programlamanın tanımını


• Nesneye Yönelik Programlamanın temellerini
• Soyutlama kavramını
• Nesnelerin davranış biçimini
• Nesne hiyerarşisini
• Kapsüllemeyi
• Sınıf kavramını
• Kalıtımı
• Çok biçimliliği

öğrendik.

14
15
16
17
C++ TEMELLERİ
C++, nesneye yönelik programlama tekniğinin
uygulanabilmesi için C'nin genişletilmiş bir biçimidir.
Nesneye yönelik programlama tekniği(NYP) özellikle büyük
kodların üstesinden gelebilmek amacıyla tasarlanmıştır.
Tasarımı C++ üzerinde yapılmış olmasına karşın bugün pek
çok yüksek seviyeli programlama dili bu tekniği
desteklemektedir. C++ ve nesneye yönelik programlama
tekniğinin en belirgin uygulama alanlarından birisi
WINDOWS altında programlamadır. WINDOWS karmaşık
ve yüksek seviyeli bir işletim sistemidir. WINDOWS altında
program geliştirebilmek için uzun kodlar yazmak gerekir.
Bu nedenle WINDOWS altında C ile değil C++ ile ve NYP
tekniğini kullanarak program yazmak daha etkin bir
çözümdür.

2.1. C++ Programlarının Yapısı


C++ programları C++ programlama dilinde olduğu gibi, belirli bir yapıya sahiptir ve
kendine özgü bazı bileşenleri bulunmaktadır. İlk bilinmesi gereken nokta:

Bir C++ programı bir veya daha fazla fonksiyondan oluşmakta ve her bir fonksiyon bir
veya daha fazla sayıda deyim içermektedir.

C++'de bir fonksiyon, altyordam (subroutine) olarak da adlandırılır ve bunlar programın


bir başka yerinden isimleri kullanılarak çağrılabilir.

Şekil 1'de bir C++ programını görüyoruz. Bu program çok basittir ve çalıştırıldığında
ekrana "Merhaba Türkiye" mesajını yazmaktadır. Programın içerdiği çoğu satırların ne
anlama geldiğini biliyoruz. C programlama dilini anlatırken bunları ele alarak incelemiştik.

Fonksiyonlar C++ bloklarından oluşur. Fonksiyon içindeki her bir deyim, programın
çalıştırılması durumunda, belirli bir eylemi yerine getirir. O halde deyimler, programın
amacına ulaşması için gereken işlemleri yerine getiren komutlar olarak değerlendirilir.
Bunun dışındaki ifadeler de birer C++ deyimi olarak değerlendirilir.

Tüm C++ deyimleri noktalı virgül (;) işareti ile son bulur. Bu işaret program içindeki
herhangi bir satırın sonunu belirlemez. Sadece deyimin sonunu belirler. O halde, bir
satırın sonunda (;) işareti yer almıyorsa, bu satırdaki deyimin bir alt satırda da devam
ettiği anlaşılır. Örneğin, C++ programı içinde,

cout << "Merhaba Turkiye .." << "\n";

satırının yer aldığını varsayalım. Bu satırın sonunda (;) işareti yer aldığına göre, deyimin
tanımlanması bu satır üzerinde tamamlanmıştır. Bir C++ satırının üzerinde, deyim sonları
(;) işareti ile belirlenmek koşuluyla, birden fazla deyime de yer verilebilir.

18
Her bir deyimin sonunda mutlaka (;) işaretinin yer alması gerektiğini belirttik. Ancak bazı
durumlarda bileşik deyim, deyim bloğu veya sadece bloklardan söz edilir. Bloklar içinde
yer alan her bir deyimin sonuna (;) işareti konulması gerekmez. Söz konusu blok { }
işaretleri arasına yerleştirilir. Ancak, deyim bloğunun sonunu belirleyen } işaretinin
yanına (;) işareti eklenmez. Şekil 2'deki program bir main() fonksiyonu içindeki bloğu
gösteriyor.

#include <iostream>
using namespace std;

// ilk C++ programınız


int main()
{
cout << "Merhaba"
<<"Turkiye"
<< "\n";
return 0;
}

Şekil 1: Ekrana "Merhaba Türkiye.." mesajını yazan bir C programı.

#include <iostream>
using namespace std;

//Bu program bir mesaj


//görüntüler

int main()
{
cout << "Bu bir C++";
cout <<"programidir.."
<< "\n";
return 0;
}

Şekil 2: Main fonksiyonunun içinde bir deyim bloğu var.Bu program ekrana "Bu bir C++
programidir.." yazar.

19
Bölüm Hedefi

Bu bölümü bitirdiğinizde,

• C++ programlarının yapısını,


• Fonksiyonları,
• C++ önişlemcisini,
• Program açıklama satırlarını,
• Değişkenleri,
• Değişmezleri,
• Basit veri giriş ve çıkışlarını,
• İşleçleri,

öğrenmiş olacaksınız.

2.1.1. Programların Derlenmesi ve Çalıştırılması


C++ kaynak programları oluşturulduktan sonra bir isim verilerek kaydedilir. Bu tür
programların kaynak dosya isimleri ".cpp" ile bitmelidir. Böylece derleyici programın bir
C++ programı olduğunu algılar.

Bir C++ programının kaynak kodu, yukarıda gösterildiği biçimde hazırlandıktan sonra
derlenir. Derleme sonucunda herhangi bir hata ile karşılaşılmaz ise ".obj" dosyası
yaratılır. Derlenen program ".exe" biçiminde bir uygulama dosyasına dönüştürülmelidir.
Bunun için build işlemi yapılır. Böyle bir işlem sonucunda yaratılan dosya çalıştırılarak
programdan beklenen sonuçlar elde edilir.

20
2.2. Fonksiyonlar
Fonksiyonlar, C++ programlama dilinin temel yapı taşlarıdır. C++ programları
fonksiyonlardan oluşur.

C++ programlarındaki karmaşıklığı azaltmak ve programı modüler bir yapıya


kavuşturmak için fonksiyonlardan yararlanılır. Fonksiyon, programcının tekrarlanan
kodlar yazmasını önler. Fonksiyon, belirli bir adı olan program parçasıdır. Fonksiyonlar bu
isimlerle çağrılarak kullanılır.

Fonksiyonlar, bir değer veya bir sonuç üretmek üzere tasarlanır. Bu sonuç göz
önüne alınarak, fonksiyonların bir veri türüne sahip olması gerektiği söylenebilir. Örneğin
fonksiyon tamsayı değer üretecek ise veri türü int olarak tanımlanır. Bu durumda,
fonksiyondan sonuç döndürme işlemini gerçekleştirmek üzere, ilgili fonksiyon içinde
return deyimi kullanılır. Ancak, herhangi bir değer döndürülmeyecek ise, fonksiyonun
veri türü olarak void tanımı yapılır.

21
Fonksiyonun genel yapısı gösterilmiştir. Her fonksiyon bir değer veya sonuç üretir.

2.2.1. main() Fonksiyonu


Bir C++ programının bir veya daha fazla fonksiyondan oluşmakta olduğunu biliyoruz.
Önceki verdiğimiz örnekte yer alan main() bir fonksiyonudur. Bir C++ programının içinde
çok sayıda fonksiyon yer alabilir, ancak mutlaka bir main() fonksiyonu bulunmalıdır.
Program yürütülmeye başladığında, öncelikle programın içinde bağlanmış bulunan bir
başlangıç yordamının çalışması beklenir. Bu yordam da main() fonksiyonunu çağırır.

C++'da programcı tarafından tanımlanan ve isimlendirilen fonksiyonlar


kullanılabilmektedir. Bu tür fonksiyonların nasıl oluşturulabileceği öğreneceğiz.

C++' da programcı tarafından tanımlanan fonksiyonlar dışında, hazır kitaplık fonksiyonları


da bulunmaktadır. Bu fonksiyonların, verilen görevi nasıl yerine getirdikleri belirtilmez.
C++ programı içinde sadece isimleri kullanılarak istenilen sonuçlara ulaşılması sağlanır.

! Aşağıdaki program örneğinin sonucunu görmek için "Sonuç" düğmesine tıklayınız.

#include <iostream>
using namespace std;

int main()
{
int num;
num=100;
cout << "Sayi:"
<< num
<<"\n";
return 0;
}

22
2.2.2. Fonksiyon Prototipi
Fonksiyonların kullanılabilmesi için, main() dışında, ilgili fonksiyonların yapısını programın
başında tanımlamak gerekmektedir. Bu tanım, fonksiyonun parametrelerini ve veri
türlerini içerecektir.

! Aşağıdaki program örneğinin sonucunu görmek için "Sonuç" düğmesine tıklayınız.

23
main() fonksiyonu içinden us() isimli fonksiyon çağrılarak çalıştırılacaktır. Söz konusu
us() isimli fonksiyonu kullanabilmek için, programın başında aşağıda görüldüğü biçimde
tanımlanır. Bu program 25 işleminin sonucunu bulmaktadır.

#include <iostream>
using namespace std;

// Prototip tanımlanıyor..
int us(int sayi, int ussu);

void main ()
{
cout << "Islem sonucu :"
<< us(5,2)
<< "\n";
}
int us(int sayi, int ussu)
{
int sonuc=1;
int i;
for(i = 0; i < ussu; i++)
sonuc *= sayi;
return(sonuc);
}

24
2.3. C++ Önişlemcisi
C++ programları kendi derleyicisi ile de ilişki halinde olacaktır. Bu ilişki C++ önişlemcisi
yardımıyla sağlanır. Önişlemciler çeşitli emirlerden oluşabilir. Bu emirler, C++
derleyicisinin kaynak kodunu denetlemekte kullanılır. Önişlemci emirleri C++ programı
içinde (# ) işareti ile başlar. C++'nin en çok kullanılan önişlemci emirleri #include ve
#define ile tanımlanmaktadır. Önişlemci emirleri (;) işareti ile sonlandırılmaz.

#include <iostream>
#define PI_SAYISI 3.14
...
...
...
...

C++'ın en çok kullanılan önişlemci emirleri #include ve #define'dir.

2.3.1. Kitaplık Fonksiyonlarının Programa Dahil Edilmesi -


#include Emri
En önemli önişlemci emirleri arasında #include sayılabilir.

25
Bu önişlemci emri, bir kaynak dosyasının bu programa dahil edilmesini sağlar.

C++ programına dahil edilen dosyalar, başlık dosyası olarak isimlendirilir.

C++'da standart kitaplık fonksiyonları hakkındaki bilgiler, bu tür başlık dosyaları içinde
yer alır. Standart fonksiyonları kullanmak için, söz konusu dosyaları C++ programına
dahil etmek gerekir. Örneğin iostream dosyasının C++ programına katılması için şekil-
1'de görüldüğü biçimde programa dahil edilir.

Şekil-1'deki C++ programında main() fonksiyonu içinde bir mesajı ekrana yazdırabilmek
için "<<" işleci kullanılmıştır. Bu işleci kullanabilmek için C++ programına <iostream>
dosyasının dahil edilmesi gerekmektedir. Aksi takdirde program bu işleci
yorumlayamayacaktır.

#include <iostream>
using namespace std;

int main()
{
cout << "Bu bir C++ ";
cout << "programidir.";
cout<<" \n";
return 0;
}

Şekil 1: iostream dosyasının C++ programına katılması

2.3.2. Sembolik Değişmezlerin Tanımlanması-#define emri


Program içinde kullanılacak değişmezlerin tanımlanması için önişlemci emri kullanılabilir.
Söz konusu önişlemci emri bir isim ve bir değer ile birlikte tanımlanır. İsme bir karakter
dizisinin atanması amacıyla #define önişlemci emri kullanılır.

C++ programı içinde SON isimli bir değişmezin kullanılacağını varsayalım. #define
önişlemcisi ile birlikte kullanılacak değişmez isminin büyük harfle yazılması, genel bir
alışkanlık olarak kabul edilmektedir. Büyük harflerle kaydetmek zorunlu değildir. Söz
konusu değişmezin program içindeki değerinin 50 olması gerektiğini varsayalım. Böyle bir
amaca uygun C++ programının yapısı şu şekilde olabilir:

26
#include <iostream>
# define SON 50
using namespace std;

int main()
{
cout << "Sayi:" << SON << "\n";
return 0;
}

Bu program çalıştırıldığında ekran üzerinde aşağıdaki sonuç görüntülenir:

2.4. Açıklama Satırları


C++ programının bazı bölümlerine açıklamalar eklemek gerekebilir. Özellikle uzun
programlarda, program bölümlerinin hangi işlemi yerine getirdiğini program satırları
arasına kaydetmek yararlı olacaktır. Bu sayede, programı inceleyen kişiler, kodların hangi
amaçla düzenlendiği hakkında fikir sahibi olurlar.

C++ programının herhangi bir yerine açıklama satırları eklemek için, açıklamanın
başına (//) eklenir. (//Açıklamalar)

Bu işaretlerle başlayan ifadeler C++ derleyicisi tarafından "yok" sayılır. Yani herhangi bir
işleme tabi tutulmaz.

Aşağıda bir C++ programına yer veriyoruz. Programın başında programla ilgili açıklama
satırları ekliyoruz.

27
#include <iostream>
using namespace std;

// Bu program klavyeden
// girilen bir degeri okuyarak
// ekranda goruntuler..

int main()
{
int sayi;
cout << "Sayi giriniz:";
cin >> sayi;
cout << "Sayi:" << sayi << "\n";
return 0;
}

"//" ile başlayan ifadeler C++ derleyicisi tarafından "yok" sayılır. Yani herhangi bir işleme
tabi tutulmaz.

2.5. Temel Veri Türleri ve Değişkenler


Değişken, program içinde kullanılan değerlere bellek üzerinde açılan alanlardır. Bu
alanlar bir değişken ismi ile anılır. C++'de tüm değişkenler kullanılmadan önce programa
bildirilmelidir. Bu bildirim esnasında, değişkenin veri türü belirlenir.

C++'de birçok veri türü bulunmaktadır. Bunlardan en belli başlı olanı char'dır. Sözü
edilen veri türü, karakter içeren değerlerin ifade edilmesinde kullanılır. Aşağıdaki tablo
üzerinde C++ temel veri türleri yer almaktadır.

! Veri türleriyle ilgili açıklamaları görmek için üzerine tıklayınız.

28
Yukarıdaki tablo en temel C++ veri türlerini içermektedir. Bunların dışında başka veri
türleri de bulunmaktadır. Tabloda int ile tanımlanan tamsayı değişkenler tüm ondalıklı
olmayan tüm işaretli sayıları kapsamaktadır. Bu tür bir değişken, genellikle -32768 ile
32767 arasındaki tamsayıları içerebilir. Tabloda belirtilenler dışında başka tamsayı veri
türleri de tanımlanabilmektedir. Ayrıca işaretli tamsayılar için signed, işaretsizler için
unsigned tanımları kullanılabilir.

Float, double ve long double gibi tek ve çift duyarlıklı sayısal değerler ise kesirli
sayılardır. Çift duyarlıklı sayılar, adından da anlaşılabileceği gibi, tek duyarlıklı sayılara
oranla iki kat daha hassas bir değerlerdir. Bu iki veri türü büyük sayısal değerlerin ifade
edilmesinde kullanılır.

2.5.1. Değişken Veri Türünün Bildirimi


C++ programı içinde kullanılacak bir değişkenin veri türünü bildirmek için şu şekilde bir
tanım yapılır:

veri_türü değişken_adı;

Örneğin, C++ programı içinde sayma işlemlerini yerine getirmek için sayac isimli bir
değişkenin kullanılması gerektiğini varsayalım. Bu değişken Şekil1'deki gibi bildirilir.
Yapılan bu bildirime göre, sayac isimli değişken program içinde tamsayı değerler
içerecektir. Bildirim satırları da aynen diğer C++ deyimleri gibi (;) işareti ile son
bulmalıdır.

Şekil2'deki C++ programını göz önüne alalım. Bu program içinde for deyimi ile bir döngü
tanımlanmaktadır. Bu deyimin ne anlama geldiğini sonraki bölümde ele alarak
inceleyeceğiz.

29
#include <iostream>
using namespace std;

/// Bu program 0 ile 50


// arasindaki tüm tamsayıları
// ekranda görüntüler..

int main()
{
int i;
for (i=0;i<=50;i++)
cout << i << "\n";
return 0;
}

2.5.2. Değişkenlere Başlangıç Değeri Atama


Bir değişkene programın içinde herhangi bir yerde belirli bir değer atayarak içeriğini
değiştirmek mümkündür. Ancak çoğu uygulamada, değişkenin veri türü daha başlangıçta
belirlenirken, bir değere de sahip olması istenir.

! Aşağıdaki programı inceleyiniz ve sonucunu görmek için "Sonuç" düğmesine tıklayınız.

30
Şekil 1: Blok içinde enaz ve encok isimli iki adet değişken tanımlanmıştır. Bunların her
ikisi de int sözcüğü kullanılarak tanımlandığı için birer tamsayıdır. Veri türü tanımlarının
yapıldığı satırlar üzerinde enaz değişkenine 10 değerinin, ençok değişkenine ise 100
değerinin yerleştirildiği görülmektedir.

#include <iostream>
using namespace std;

int main()
{
int enaz=10;
int encok=100;
cout << "En kucuk sayi:" << enaz << "\n";
cout << "En buyuk sayi:" << encok << "\n";
return 0;
}

31
2.5.3. Değişken Türleri
C++ programları içinde farklı amaçlara yönelik değişken tanımları yapılabilir. Değişken
türlerini şu şekilde sıralayabiliriz:

• Yerel değişkenler
• Küresel değişkenler
• extern değişkenler
• static değişkenler
• auto değişkenler
• register değişkenler

1-) Yerel Değişkenler:

Değişkenler için veri türü bildirimleri fonksiyonların neresinde yapılacaktır? Bu sorunun


yanıtını şöyle verebiliriz:

Değişken veri türü bildirimleri bir fonksiyonun içinde ya da dışında yapılabilir. Bu iki farklı
tanım farklı sonuçlara neden olacaktır.

Veri türünün fonksiyon içindeki veya dışındaki bildirimi farklı sonuçlara neden olacaktır.
Çünkü, fonksiyon içinde bildirimi yapılan bir değişken, sadece o fonksiyon için geçerlidir.
Yerel değişkenler, program yürütüldüğünde aktif hale geçerek kendisi için ayrılan bellek
alanlarını kullanır. Ancak yer aldıkları blok sona erdiğinde bu bellek alanları iptal olur ve
değişken içeriği tamamen yok olur. Aynı blok daha sonra tekrar başlasa bile, yerel
değişkenler eski değerlerini alamaz.

Program içinde birden fazla fonksiyon varsa, sadece tanımlandığı fonksiyonda geçerli
olabilecek değişkenlere yerel değişken (local variable) adı verilir.

32
#include <iostream>

using namespace std;

int main()

int durum;

durum=1;

...

return 0;

Şekil 1: main fonksiyonu içinde yerel değişken bildirimi.

Şekil1'de durum isimli yerel bir değişken tanımlanmakta ve ona bir değer atanmaktadır.

! Yerel değişkenlerle ilgili örneği görmek için tıklayınız.

Aşağıdaki C++ programını göz önüne alalım. Burada iki adet fonksiyon yer almaktadır.
Birinci main() fonksiyonu içinde bir i değişkeni tanımlanmıştır. Bu fonksiyon içinde
fonk1() isimli bir başka fonksiyon çağrılmaktadır. Söz konusu fonk1() fonksiyonu da i
’nin değerini görüntüleyecektir.

#include <iostream>
using namespace std;
int fonk1();
int main()
{
int i=5;
cout << "sayi degeri 1:" <<i <<"\n";
fonk1();
return 0;
}
int fonk1()
{
cout << "sayi degeri 2:" <<i <<"\n";
return 0;
}

Bu program derlendiğinde hata verecektir. Çünkü i değişkeni bir yerel değişken olarak
main() içinde tanımlanmıştır. Bu değişkenin değeri bir başka fonksiyon içinde
kullanılamayacaktır. Böyle bir amaca ulaşabilmek için küresel değişkenlerin tanımlanması
söz konusu olacaktır.

33
2-) Küresel Değişkenler:

Eğer bir değişkenin program içindeki tüm fonksiyonlar için geçerli olması söz konusu ise,
bu kez fonksiyonların dışında bir yerde bildirimi yapılır. Şekil2'de gösterildiği biçimde
durum değişkeni tanımlanacak olursa, bu tanım sadece main() fonksiyonunda değil,
program içindeki tüm fonksiyonlar için geçerli olacaktır. Bu tür değişkenlere küresel
değişken (global variable) adı verilir.

#include <iostream>

using namespace std;

durum=1;

main()

...

Şekil 2: Küresel değişken bildirimi.

! Küresel değişkenlerle ilgili örneği görmek için tıklayınız.

Aşağıdaki C++ programında i değişkeni tüm fonksiyonların dışında tanımlamıştır. Bu


durumda i=5 değeri tüm fonksiyonlar için geçerlidir. Bu program yürütüldüğünde, her iki
fonksiyonun 5 değerini ekran üzerine yazdırdığı görülür.

#include <iostream>
using namespace std;
int fonk1();
int i=5;
int main()
{
cout << "sayi degeri 1:" <<i <<"\n";
fonk1();
return 0;
}
int fonk1()
{
cout << "sayi degeri 2:" <<i <<"\n";
return 0;
}

34
Program çalıştırıldığında aşağıdaki sonuç görüntülenir:

3-)extern Değişkenler:

Küresel değişkenlerin içerdiği değerlerin, programın sonuna kadar tüm fonksiyonları


kapsayacak biçimde geçerli olduğunu biliyoruz. Bazı durumlarda bir program dosyası
içinde tanımlanan küresel değişkenlerin, bir başka programda da geçerli olması
istenilebilir. Böyle durumlarda extern değişkenler kullanılır.

4-)static Değişkenler:

Bir değişken, yerel değişken olarak tanımlanmış ise, bu değişkenin yer aldığı fonksiyonun
yürütülmesi sona erdiğinde değeri yok oluyordu. Bu durumu önleyerek, değişken
değerinin sadece ilgili fonksiyon içinde değil, tüm program boyunca geçerli olması
sağlanabilir. Böyle bir amaca ulaşmak için static değişkenler tanımlanır.

Örneğin, derece isimli değişken değerinin, fonksiyonun çalışması ardından yok olmasını
istemediğimizi varsayalım. Bu durumda Şekil3'te belirtilen tanım yapılır.

Yukarıda yapılan tanımlar, static ile küresel değişkenlerin birbirine çok benzediğini
göstermektedir. Burada şu farka dikkat etmek gerekiyor: static değişkenler, fonksiyonun
yürütülmesi sonunda değerini kaybetmez; ancak bu değer diğer fonksiyonlar tarafından
da kullanılamaz. Aynı fonksiyon yeniden yürütüldüğünde, daha önceki değerin
kullanılmasını sağlar. O halde derece isimli değişkenin değeri, fonksiyon yeniden
yürütüldüğünde, önceki değer göz önüne alınarak işlemlere devam edilir.

int main()

static int derece;

...

Şekil 3: derece isimli static değişkenin bildirimi.

35
5-) register Değişkenler:

Yukarıda incelediğimiz değişkenlerin tümü bellek üzerindeki alanları kullanır. Bazı


uygulamalarda, değişkenlere çok daha hızlı biçimde erişmek söz konusu olabilir. Böyle
durumlarda, register değişkenleri tanımlanarak bellek alanları yerine, makinenin hızlı
bellek yazmaçları kullanılabilir. Ancak bu alanların miktarı sınırlı olduğundan, fazla sayıda
register değişken tanımlanırsa amacına ulaşmaz. Derleyici fazla bulduklarını normal
bellek alanlarına yerleştirir.

2.6. Değişmezler
Değişmez ya da bir başka deyişle sabit, değeri değişmeyen program bileşenleridir. C++
programlarında aşağıda belirtilen veri türlerine sahip değişmezler yer alabilir:

• Tamsayı değişmezler
• Kayan noktalı değişmezler
• Karakter değişmezler
• Karakter dizisi değişmezler

Bir değişmez C++ programı içinde const sözcüğü ile tanımlanır. Bu sözcük şu şekilde
kullanılmaktadır:

const sabit_adı = değeri;

Program içinde tamsayıları belirtmek için int, karakterler için char ve kayan noktalı
değerler içeren değişmezleri tanımlamak için float sözcüğü kullanılır. Bir değişmez
tanımlandıktan sonra, programın herhangi bir yerinde değerini değiştirmek olanaksızdır.

Aşağıda iki adet değişmez tanımı veriyoruz. Bunlardan birincisi bir tamsayı değişmezi,
ikincisi bir karakter değişmezi tanımlamaktadır.

#include <iostream>
using namespace std;

int const yasi = 15;


char const cins = 'E';

int main()
{
cout << "Yas:" << yasi <<"\n";
cout << "Cinsiyeti:" << cins <<"\n";
return 0;
}

36
2.7. Basit Veri Giriş Çıkışları
Bir C++ programı içinde değişkenlere değerler atayarak, bu değerler üzerinde çeşitli
işlemler yapılabilir. Ancak bu tür veriler, program hazırlanırken program içine gömülen
değerlerdir.

Programın yürütülmesi, yani çalıştırılması esnasında da programa veri aktarmak


gerekebilir. C++'da bu amaca uygun komutlar vardır. C++’da standart giriş-çıkış
nesnesine yönlendirme yapmak için “<<“ işleci kullanılır. Yönlendirme esnasında, bir satır
yazdırıldıktan sonra bir sonraki satırın başına atlamak için “\n“ tanımı kullanılır. Ekran
nesnesine karşılık gelmek üzere cout sözcüğü kullanılır.

Eğer bir klavyeden veri girişi söz konusu ise, bu kez “>>“ işleci kullanılır. Klavye nesnesi
için cin sözcüğü kullanılır. Söz konusu bilgi giriş çıkış işlemlerini yapabilmek için, ilgili
kitaplıkları içeren iostream dosyasının aşağıda gösterildiği biçimde programa dahil
edilmesi gerekmektedir:

#include <iostream>
using namespace std;

C++’da giriş-çıkış işlemleri akımlar yoluyla gerçekleştirilir. Bu aşamada standart giriş


çıkış işlemlerinden kısaca söz ettik. Ayrıntılı açıklamaları ayrı bir bölüm halinde sunacağız.

cout << “Merhaba Turkiye“;

Bu deyim çalıştığında ekran üzerinde “Merhaba Turkiye“ mesajı görüntülenir.

cin >> sayi;

Klavyeden girilen bir sayısal değeri C++ programına aktarmak için, yukarıdaki gibi bir
tanım yapılır.

2.8. İşleçler
Tüm programlama dillerinde, aritmetik işlemler başta olmak üzere, büyüklüklerin
karşılaştırılması ve mantıksal karşılaştırmaların yapılmasını sağlamak için işleç

37
(operatör) adı verilen simgelere başvurulur. C++ programlarında en çok aşağıda
belirtilen işleçler kullanılmaktadır:

• Aritmetik İşleçler
• Karşılaştırma İşleçleri
• Mantıksal İşleçler

2.8.1. Aritmetik İşleçler


Toplama, çıkarma, çarpma ve bölme işlemlerini yapmaya yönelik işleçler aritmetik işleçler
olarak bilinir. Bu işleçler belirli bir sıraya göre işlem görürler. Şekil1'deki tablo üzerinde
aritmetik işleçleri tanımlıyoruz.

Aritmetik işleçler yardımıyla aritmetik ifadeler belirlenir. Bölme işlemlerinde paydanın


sıfır değere sahip olmamasına dikkat etmek gerekir. Bu tür hatalı bir durum
derleyici tarafından algılanmaz. Ancak programın yürütülmesi esnasında, bir hata mesaj
görüntülenerek programın çalışması sona erer.

Şekil 1: Aritmetik işleçlerin tanımı.

C++ programı içinde 120 değerine sahip sayi1 ve 300 değerine sahip sayi2
değişkenlerinin içerdiği değerleri toplamak istiyoruz. Amacımıza uygun program şu
şekilde olacaktır:

38
#include <iostream>
using namespace std;

// Bu program iki sayısal değeri


// toplayarak sonucu görüntüler..

int main()
{
int sayi1=120;
int sayi2=300;
int toplam;
toplam = sayi1 + sayi2;
cout << "Sonuc:" <<toplam<<"\n";
return 0;
}

Bu program yürütüldüğünde iki sayısal değer toplanarak ekranda aşağıdaki mesaj


görüntülenir:

Artırma ve azaltma işleçleri:


C++'da (++) artırma ve (--) azaltma işleçlerinin özel bir önemi vardır. (++) işleci
yanındaki değişkenin otomatik olarak bir artmasını sağlar. (--) ise bu işlemin tersini
gerçekleştirerek, yanındaki değişkenin değerini bir azaltır. Bu işleçler değişkenin önünde
yada arkasında yer alabilir. Bu durumda anlamı değişir ancak her iki durumda da aynı
değer elde edilir.

a tamsayısının 50, b tamsayısının ise 30 değerine sahip olduğunu varsayalım. Bunlardan


a'nın değerini 1 artırmak; b'ninkini ise 1 azaltmak istiyoruz. Geleneksel yol ile bu işlemi
yerine getirecek olursak, a'yı 1 artırmak için,

a=a+1

b'yi 1 azaltmak için,

b=b-1

yazılabilir. Bu satırlar ile ulaşılmak istenilen amaca,

a++

39
b--

biçiminde tanımlar yapılarak da ulaşılabilir. Bu tanımları kullanarak hazırladığımız C++


programını aşağıda sunuyoruz.

#include <iostream>
using namespace std;

// Artırma işleçlerinin kullanılması..


int main()
{
int a=50;
int b=30;
cout <<a++<<"\n";
cout <<++b<<"\n";
return 0;
}

Bu program yürütüldüğünde iki sayısal değer toplanarak ekranda aşağıdaki mesaj


görüntülenir:

Aritmetik işleçlerin atama işleciyle birlikte kullanımı:


Artırma ve azaltma işleçleri tek başlarına kullanıldığında, ilgili değişkenin değeri bir
artıyor veya azalıyordu. Bu işleçler atama deyimi ile birlikte kullanıldığında, değişkenin
önünde ya da arkasında olması durumuna göre farklı sonuçlara neden olmaktadır.
Örneğin,

a=sayac++

deyimi şöyle çalışacaktır: Önce sayac isimli değişkenin değeri a değişkeni içine atanacak,
ardından sayac'ın değeri 1 artacaktır. Örneğin sayac değişkeninin değeri 5 ise, bu işlem
sonucunda a 'nın değeri de 5 olacaktır. Ancak sayac değişkeninin değeri 6 olacaktır. Buna
karşılık,

a=++sayac

40
biçimindeki bir tanım farklı bir sonuç yaratacaktır. Bu durumda sayac değişkeninin değeri
1 artırılır ve bulunan değer a değişkenine aktarılır. Söz konusu sayac değişkeninin değeri
5 ise, bu atama sonucunda a 'nın değeri 6 olacaktır.

! Aritmetik işleçlerin atama işleciyle birlikte kullanımıyla ilgili örneği görmek için tıklayınız.

Aşağıdaki C++ rogramını gözönüne alalım. Programda yer alan a tamsayısının 50, b
tamsayısının ise 30 değerine sahip olduğunu varsayalım.

#include <iostream>
using namespace std;
int main()

{
int a=50;
int b=30;
int x,y;
x=a++;
y=++b;
cout <<x<<"\n";
cout <<y<<"\n";
return 0;
}

Bu program yürütüldüğünde aşağıdaki görüntü oluşur:

2.8.2. Karşılaştırma İşleçleri


Bu işleçler iki sayısal değeri yada iki karakteri karşılaştırmak amacıyla kullanılır. Söz
konusu bu işleçler çoğunlukla program içindeki karşılaştırma deyimleri ile birlikte
kullanılır. Karşılaştırma işleçlerini Şekil1'deki tablo üzerinde sunuyoruz.

İki değerin karşılaştırılması amacıyla if deyimi kullanılır. Bu deyimin ne anlama geldiğini


sonraki bölümde ayrıntılı olarak ele alarak inceleyeceğiz. Bu aşamada sadece şunu
söyleyebiliriz: Bir değeri bir başka değerle karşılaştırmak için if deyimi kullanılır. Eğer

41
karşılaştırmanın sonucu "doğru" ise bu deyimin ardından gelen satır işlem görür. Eğer bu
koşul doğru değil ise else deyimi ardından gelen satır işlem görecektir.

Şekil 1: Karşılaştırma işleçleri

! Karşılaştırma işleçleriyle ilgili program örneğini görmek için tıklayınız.

Klavye yardımıyla girilen bir sayısal değerin pozitif olup olmadığına karar vermek
istiyoruz. Amacımıza uygun C++ programı aşağıdaki gibi düzenlenebilir.

#include <iostream>
using namespace std;
// Karşılaştırma işleçlerinin
// kullanılması..
int main()
{
int sayi;
cout << "Bir sayi giriniz:";
cin >> sayi;
if (sayi < 0)
cout << "Negatif sayi girdiniz :"
<< sayi << "\n";
else
cout << "Pozitif sayi girdiniz :"
<< sayi << "\n";
return 0;
}

42
Bu program yürütüldüğünde, ekranda bir mesaj görüntülenerek bir sayı girilmesi beklenir.
Sayı girildikten sonra enter tuşuna basılırsa, girilen bu değer sayi isimli değişkene atanır. if
deyimi içinde karşılaştırma yapılır. Girilen sayı -10 olsun. Bu durumda if deyiminin hemen
ardından gelen deyim işlem görecek ve ekranda aşağidaki mesaj görüntülenecektir:

2.8.3. Mantıksal İşleçler


Mantıksal işleçler, iki veya daha fazla sayıdaki koşulun birlikte sınanması amacıyla
kullanılırlar. Bu işleçleri Şekil1'deki tablo üzerinde veriyoruz.

Klavye yardımıyla girilen iki sayısal değeri karşılaştırarak, durumlarına göre bir mesaj
görüntülemek istiyoruz. Eğer her iki sayısal değer pozitif ise "Her iki sayi pozitif.."
biçiminde, en az biri negatif ise "Sayilardan en az biri negatif.." mesajı
görüntülenecektir.

Şekil 1: Mantıksal işleçler

43
#include <iostream>
using namespace std;

// Mantıksal işleçlerin kullanılması..


int main()
{
int sayi1,sayi2;
cout << "Birinci sayiyi giriniz:";
cin >> sayi1;
cout << "İkinci sayiyi giriniz:";
cin >> sayi2;

if (sayi1 > 0 && sayi2>0)


cout << "Her iki sayi pozitif..";
else
cout << "Sayilardan en az biri negatif..";
return 0;
}

Bu program yütütülmeye başladıktan sonra, klavye yardımıyla -5 ve 15 sayısal


değerlerini girecek olursak "Sayilardan en az biri negatif.." mesajı görüntülenir.

44
Bölüm Özeti
Bu bölümde,

• C++ programlarının yapısını


• Fonksiyonları
• C++ önişlemcisini
• Program açıklama satırlarını
• Değişkenleri
• Değişmezleri
• Basit veri giriş ve çıkışlarını
• İşleçleri

öğrendik.

45
46
47
48
49
50
51
C++ PROGRAMLARININ DENETİMİ

Bu bölümde,

• Program denetimi,
• Karşılaştırma deyimleri,
• Döngüler,
• Koşullu döngüler,

konuları ele alınarak incelenecektir.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Program Denetimini,
• Karşılaştırma işlemlerini,
• Döngüleri,

öğrenmiş olacaksınız.

3.1. Program Denetimi


Bir program, belirli deyim satırlarından oluşur ve genellikle bu deyimler sırayla işlem
görür. Programın işlem görme sırasını değiştirmek mümkündür. Bu sayede, program
deyimleri kontrol altına alınır.

Denetim deyimleri program satırlarının işleyiş sırasını kontrol altında tutmak amacıyla
kullanılır. Bir C++ programında denetim deyimleri, aşağıda belirtilen işlemlerde kullanılır:

• Karşılaştırma işlemleri
• Döngüler

52
Deyimler sırayla işlem görür; ancak denetim deyimleri kullanılarak deyimlerin işlem
görme sırası değiştirilebilir.

3.2. Karşılaştırma İşlemleri


C++ programlarının birçok yerinde iki ifadenin karşılaştırılması işlemine başvurmak
gerekecektir. Karşılaştırma işlemlerinde ise, önceki bölümde ele alarak incelediğimiz
karşılaştırma işleçleri kullanılır.

Bu işleçler yardımıyla aşağıda belirtildiği biçimde karşılaştırma ifadeleri tanımlayabiliriz:

! Karşılaştırma ifadelerinin açıklamalarını görmek için üzerlerine tıklayınız.

53
Karşılaştırma işlemlerinde karşılaştırma işleçlerinin yanısıra mantıksal işleçler de yer
alabilir. Bu amaçla mantıksal işleçler kullanılabilir. Karşılaştırma ifadesi içinde mantıksal
işleçler, aşağıdaki örnekte belirtildiği biçimde kullanılabilir:

! Mantıksal işleçlerle oluşturulan karşılaştırma ifadelerinin açıklamalarını görmek için


üzerlerine tıklayınız.

3.2.1. if Deyimi İle Karşılaştırma İşlemi


Bir C++ programında karşılaştırma işlemi sonucunda bir eylemin yapılması söz konusu
ise, yani belirli deyimlerin çalıştırılması gerekiyorsa if deyimine başvurulur. if deyimi
Şekil1'deki gibi tanımlanır.

Bu tanıma göre, ifade içinde belirtilen koşulun "doğru" olması halinde, if içinde belirtilen
deyim çalışır. Eğer koşulun doğruluk değeri "yanlış" ise if içinde tanımlanan deyim işlem
görmez.

Karşılaştırma işlemi sonucunda sadece bir deyim işlem görmeyebilir. Bazı uygulamalarda
birden fazla deyimin çalışması söz konusu olabilir. Bu durumlarda söz konusu deyimleri
gruplamak gerekir. Gruplama işlemlerinde { } simgeleri kullanılır

54
Şekil 1: if deyiminin tanımı

Şekil 2: Karşılaştırma işlemlerinde gruplama "{}" kullanımı.

! if deyiminin çalışma prensibi ile ilgili animasyonu incelemek için tıklayınız.

55
Örnek 1:
Program içinde yer alan bir değişkenin değerini görüntülemek için aşağıdaki C++
programını hazırlıyoruz.

#include <iostream>
using namespace std;

// Karşılaştırma işlemi..
int sayi=5;

int main()
{
if (sayi<10)
cout << "Kosul dogru"<< "\n";
return 0;
}

Bu C++ programı çalıştırıldığında aşağıdaki sonuç elde edilir:

Kosul dogru

Bu program içinde yer alan,

if (sayi<10)
cout << "Kosul dogru";

56
deyimleri şu şekilde yorumlanır: Eğer sayi değişkeninin değeri 10 'dan küçük ise ekrana
"Koşul doğru" ifadesini yaz. Programın başında sayi=5 tanımı yapıldığı için, if deyimi
içindeki sayi<10 ifadesi "doğru" değerini üretir. O halde if deyimi içinde yer cout deyimi
işlem görecektir.

Örnek2:
Bu kez iki ayrı mesajı ekranda görüntülemek söz konusudur. Her bir değeri yazdırmak
için cout deyimi iki kez kullanılıyor.

#include <iostream>
using namespace std;

int sayi=5;
int main()
{
if (sayi<10)
{
cout << "Kosul dogru ";
cout << "Tebrikler.."<< "\n";
}
return 0;
}

Bu C++ programı çalıştırıldığında ekranda aşağıdaki sonuç elde edilir:

Kosul dogru Tebrikler..

3.2.2. Aksi Takdirde..


Karşılaştırma işlemlerinde koşul gerçekleştiğinde, yani “doğru” değerini aldığında if içinde
tanımlanan deyimlerin işlem gördüğünü öğrenmiş bulunuyoruz. Peki bu koşul
gerçekleşmediğinde nasıl bir yol izlenecektir? Bir koşulun gerçekleşmemesi durumunda
yerine getirilecek eylemleri belirlemek için else sözcüğü kullanılır. Bu sözcük if deyimi
içinde Şekil1'de gösterildiği gibi yer alacaktır.

Bu tanıma göre, ifade içinde belirtilen koşulun "doğru" olması halinde, if içinde belirtilen
deyim 1 çalışır. Aksi durumda, yani koşulun doğruluk değeri "yanlış" ise deyim2 deyimi
işlem görür.

Program içinde else sözcüğünün kullanılması durumunda deyim gruplarına yer verilebilir.
Deyim grupları { } işaretleri arasında tanımlanmalıdır. Bu durumda ifade'nin doğruluk
değeri "doğru" ise bu ifadeyi izleyen deyim bloğu; aksi durumda else sözcüğünü izleyen
deyim bloğu çalışır.

57
Şekil 1: if else yapısı

! "Doğru" veya "Yanlış" butonlarına tıklayarak if else


yapısının çalışmasını inceleyin.

! Karşılaştırma işlemiyle ilgili örnekleri görmek için tıklayınız.

Örnek 1:
Aşağıdaki program bir karşılaştırma işlemini tanımlamaktadır. sayi isimli değişkenin
değerinin 10'dan küçük olup olmamasına göre bir mesaj görüntüleyecektir. Programın
başında sayi isimli değişkenin değer 55 olarak tanımlanmıştır.

58
#include <iostream>
using namespace std;

int sayi=55;
int main()
{
if (sayi<10)
cout << "Kosul dogru"<< "\n";
else
cout << "Kosul yanlis"<< "\n";
return 0;
}

Bu program çalıştırıldığında ekranda şu sonuç görüntülenir:

Kosul yanlis

Örnek 2:
Klavyeden girilen bir sayısal değerin pozitif, negatif veya sıfır olup olmadığını test etmek
üzere aşağıdaki C++ programını düzenliyoruz.

59
#include <iostream>
using namespace std;
int sayi;
int main()
{
cout << "Sayi :";
cin >> sayi;
if (sayi>0)
cout << "Pozitif";
else if (sayi<0)
cout << "Negatif";
else
cout << "Sifir" <<"\n";
return 0;
}

Bu program çalıştırıldığında, önce bir sayısal değerin girilmesi beklenir. Bu sayısal değer
girildikten sonra enter tuşuna basılarak sonuç elde edilir. Örneğin -39 sayısal değeri
girildiğinde şu şekilde bir sonuç görüntülenir:

Sayi :-39
Negatif

3.3. Döngüler
Programın belirli bölümlerinin defalarca işlenmesi söz konusu olabilir. Bunu sağlamak
üzere döngülerden yararlanılır. C++ programlarında döngü işlemleri farklı biçimlerde
gerçekleşebilmektedir:

• for döngüsü
• while döngüleri
• do while döngüleri

Döngü bloğu içindeki deyimler defalarca işlenebilir.

3.3.1. Belirli Sayıda Tekrarlar: for Döngüsü


C++ programı içinde bir ya da daha fazla sayıda deyimin belirli bir koşulun
gerçekleşmesine dek tekrarlanması söz konusu ise for deyimi kullanılır. Bu deyim şu
şekilde tanımlanmaktadır:

60
for(sayaç ; koşul ; artma)
deyimler;

Bu deyimden şu anlaşılmaktadır: Döngü bir sayaca göre yapılacaktır. Yani her bir döngü
işlemi sayılacaktır. Döngü işlemi koşul gerçekleşinceye dek devam edecektir. Koşul
gerçekleştiğinde, yani doğruluk değeri "yanlış" olduğunda döngü terkedilerek bir sonraki
deyim işlem görmeye başlar. for içinde sayacın nasıl artacağı da ayrıca tanımlanır.

for döngüsünün işleyişi

! for döngüsünü kullanarak işlem yapan örnekleri görmek için tıklayınız.

Örnek1:
Ekran üzerinde 1 ile 10 arasında kalan tüm tamsayıları görüntülemek istiyoruz.
Amacımıza uygun C++ programı şu şekilde düzenlenebilir:

#include <iostream>
using namespace std;

// for döngüsü için örnek..


int i;

int main()
{
for (i=1;i<=10;i++)
cout << i << "\n";
return 0;
}

Bu program çalıştırıldığında, ekran üzerinde aşağıda belirtilen sonuç görüntülenir:

61
Örnek2 :
Klavye yardımıyla girilen iki tamsayı arasında kalan tüm tüm sayıların toplamını bulmak
istiyoruz. Başlangıç sayısı ve son sayı bu toplama dahil edilmeyecektir. Amacımıza uygun
C++ programı şu şekilde olabilir:

#include <iostream>
using namespace std;

// Bu program girilen iki tamsayi


// arasinda kalan tüm tamsayilarin
// toplamlarini hesaplar..

62
int main()
{
int i,baslangic,son;
int toplam=0;

cout << "Baslangic deger:";


cin >> baslangic;
cout << "Son deger:";
cin >> son;

// İki değer arasındaki toplam hesaplanıyor..


for (i=baslangic+1;i<son;i++)
toplam+=i;

cout << "Sonuc:" << toplam << "\n";

return 0;
}

Bu program çalıştırılıp klavyeden başlangıç değer için 1, son değer için 5 verildiğinde
aşağıdaki sonuçlar elde edilir:

Gerçekten, 1 ile 5 arasındaki sayıların (2,3,4) toplamı 9'dur.

Örnek3:
Verilen bir sayısal değerin faktöriyelini bulmak istiyoruz. n bir tamsayı olmak izere, n'in
faktöriyeli,

n!=1x2x3x...n

biçiminde hesaplanır. Bu tanıma uygun olarak, aşağıdaki C++ programını hazırlıyoruz.

63
#include <iostream>
using namespace std;

// Bu program verilen bir sayisal


// degerin faktoriyelini hesaplar

int main()
{
int i,sayi;
int faktoriyel=1;

cout << "Sayi:";


cin >> sayi;

// Verilen sayının faktöriyeli hesaplanıyor..


for (i=1;i<=sayi;i++)
faktoriyel*=i;

cout << "Sonuc:" << faktoriyel


<< "\n";
return 0;
}

Bu C++ programı çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç görüntülenir.


Örnek olarak 5 sayısının faktöriyelini hesaplıyoruz:

64
3.3.2. Koşullu Döngüler
Belirli bir koşulun gerçekleşmesi durumunda bazı deyimlerin çalıştırılması söz konusu ise
koşullu döngü deyimlerinden yararlanılır. Bu döngüler iki türlüdür. Birincisi, koşulun
döngü başlangıcında tanımlandığı durumdur. Bu şekildeki döngüler while döngüleri
olarak bilinirler. Eğer koşul döngünün sonunda test ediliyorsa, bu kez do..while
döngüleri kullanılır.

Döngü başında denetim: while döngüleri:


Bir koşulun gerçekleşmesi durumunda belirli işlemlerin tekrarlanması söz konusu ise
while döngülerinden yararlanılır. Bu döngü Şekil1'de görüldüğü biçimde çalışmaktadır.
Koşul sağlanmadığı sürece döngü içindeki hiçbir deyim çalışmayacaktır. Bu denetim
döngü bloğunun başında yapılır.

Döngü sonunda denetim-do..while döngüleri:


Yukarıda açıklanan while döngülerinde koşul, bu deyimin tanımlandığı noktadan itibaren
test edilmektedir. Yani daha başlangıçta bu koşul geçerli değil ise hiç bir deyim işlem
görmemektedir. Buna karşılık, koşulun döngü bloğunun son satırında yapılması söz
konusu olabilir. Böyle durumlarda do while deyimleri kullanılır.

Şekil 1: while ve do while döngülerinin tanımları.

65
Şekil 2: while ve do while döngülerinin çalışma şekilleri.

! while ve do while döngüleriyle ilgili program örneklerini görmek için tıklayınız.

Örnek1:
5 sayısal değerinden daha küçük olan tüm tamsayıları listelemek istiyoruz. Bu amaçla
aşağıda görüldüğü biçimde, while deyimini kullanarak bir C++ programı hazırlayabiliriz.

#include <iostream>
using namespace std;

/* Bir sayısal değerden


daha küçük olanları listeler */

int i;
int n=5;

int main()
{
while(i<n)
cout << i++ << "\n";
return 0;
}

Bu program çalıştırıldığında ekran üzerinde aşağıdaki sonuç listelenir:

66
Örnek2:
Aşağıdaki iki C++ programını göz önüne alalım. Burada while ve do..while döngülerinin
çalışmaları arasındaki farkları ortaya koyacağız.

#include <iostream>
using namespace std;

int i;
int n=0;

int main()
{
while(n!=i)
cout << i++ << "\n";
return 0;
}

Bu program çalıştırıldığında, ekran üzerinde herhangi bir şey görüntülenmez. Çünkü


programın başında n=0 olarak tanımlanmıştır. Dolayısıyla, while deyimi içinde yer alan
koşul hiçbir zaman gerçekleşmeyeceği için, bu döngü içindeki cout deyimi çalışmaz. Bu
kez while yerine do deyimini ekliyoruz ve while deyimini döngü sonuna yerleştiriyoruz.

67
#include <iostream>
using namespace std;

int i;
int n=0;

int main()
{
do
{
cout << i++ << "\n";
}while(n!=i);
return 0;
}

Bu program çalıştığında, ekranda 1 den başlamak üzere artan sorada sonsuz sayıda
tamsayı görüntülenir. Bu sonsuz döngüye girilmesinin nedenini şöyle açıklayabiliriz:do
döngüsü içinde yer alan deyimlerin işlenmesi için bir koşul yoktur. Koşul, bu deyimler bir
kez çalıştıktan sonra geçerli olacaktır. Ancak i tamsayısı bir arttığı için, yani i=1 olduğu
için while içindeki koşul hiçbir zaman geçerli olmaz ve sonsuz döngüye girilir.

3.3.3. Döngülerden Çıkış ve Devam


Bazı uygulamalarda, döngü işlemi tamamlanmadan döngünün sona erdirilmesi söz
konusu olabilir. Bu gibi durumlarda break deyimi kullanılır. Bu deyim,

break;

biçiminde tanımlanır. Döngü içinde bu deyime sıra geldiğinde, break ardından döngü
sonuna kadar olan tüm deyimler atlanır ve döngüye bir sonraki adımdan itibaren devam
edilir.

Bir döngüyü terketmeden bir adımının atlanması söz konusu ise,

continue;

deyimi kullanılır. Bu deyim döngünün işlemesini sona erdirmez, sadece bir sonraki döngü
adımına geçilmesini sağlar. Eğer for döngüsü kullanılıyorsa, işlem sırası bu deyime
geldiğinde, bu deyimden döngü sonuna kadar olan deyimler çalışmaz, döngü bir
artırılarak sonraki döngüye geçilir. Eğer while döngüsü kullanılıyorsa, continue deyimine
sıra geldiğinde, döngü içinde bu deyimden sonraki tüm deyimler atlanır ve koşul
sağlandığı sürece döngüye devam edilir.

68
break deyiminin for döngüsünde kullanımı.

continue deyiminin for döngüsünde kullanımı.

continue deyiminin while döngüsünde kullanımı.

! break ve continue deyimleriyle ilgili program örneklerini görmek için tıklayınız.

69
Örnek1:
1 den başlayarak sırasıyla tamsayıları listeleyeceğiz. Ancak sıra 5 sayısına gelince, işlem
sona erdirilecektir. Amacımıza uygun C++ programı şu şekilde olabilir:

// break deyimi için örnek..


#include <iostream>
using namespace std;

int i;
void main()
{
for (i=1;i<100;i++)
{
cout << i << "\n";
if (i==5)
break;
}
}

Bu C++ programı çalıştırıldığında şöyle bir sonuç elde edilir:

Örnek2:
1 ile 15 arasındaki tamsayıları listeleyeceğiz. Ancak bu listeye 8 ile 12 arasındaki
tamsayılar dahil olmayacaktır. Amacımıza uygun C++ programı şu şekilde olacaktır:

70
// continue deyimi için örnek..
#include <iostream>
using namespace std;

int i;
void main()
{
for (i=1;i<15;i++)
{
if (i>8 && i<12)
continue;
cout << i << "\n";
}
}

Bu program çalıştığında şu şekilde bir sonuç elde edilecektir:

3.3.4. switch Deyimi


Bir değişkenin aldığı değerlere bağlı olarak, belirli işlemlerin yaptırılması söz konusu
olabilir. Aslında bu tür işlemleri if deyimi yardımıyla yerine getirebiliriz. Ancak bazı
uygulamalarda if deyiminin kullanılması programın karmaşıklığını artırabilir. Eğer bir
değişkenin değeri belirli sabitlerle karşılaştırılacak ve bunun sonucunda farklı işlemler
yapılacak ise if deyimi yerine switch deyimi kullanılabilir. Bu deyim şu şekilde
tanımlanmaktadır:

71
Bu deyim şu şekilde yorumlanacaktır:

Bir değişkenin değeri sabit1' e eşit ise sadece ilgili case bloğundaki deyimler çalışır.
Benzer biçimde değişkenin değeri sabit2' ye eşit ise bunu izleyen deyimler işlem
görecektir. Eğer değişkenin değeri herhangi bir case içinde tanımlı bir sabite eşit değil ise
default başlıklı blok içinde yer alan deyimler çalışır.

! switch deyimleriyle ilgili program örneğini görmek için tıklayınız.

Klavye yardımıyla il kodlarından birinin girilmesi beklenmektedir. Bir trafik kodu


girildiğinde, o koda karşılık gelen il adı görüntülenecektir. Amacımıza uygun C++
programı şu şekilde olacaktır:

72
// switch deyimi için örnek..
#include <iostream>
using namespace std;

void main()
{
int kod;
cout << "Il trafik kodu :";
cin >> kod;

cout << "Il adi:";


switch(kod)
{
case 6:
cout << "Ankara";
break;
case 34:
cout << "Istanbul";
break;
case 35:
cout << "Izmir";
break;
default:
cout <<"Diger";
}
cout << "\n";
}

Bu C++ programı çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilebilir:

73
Bölüm Özeti
Bu bölümde,

• Program Denetimini
• Karşılaştırma İşlemlerini
• Döngüleri

öğrendik.

74
75
76
77
78
79
DİZİLER, GÖSTERGELER, FONKSİYONLAR VE
YAPILAR

Bu bölümde diziler, göstergeler, fonksiyonlar ve


yapılarla ilgili temel bilgiler verilmekte ve bunların
işlevleri açıklanmaktadır.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Dizileri,
• Çok Boyutlu Dizileri,
• Katar Dizilerini,
• Göstergeyi,
• Fonksiyonların Kullanımını,
• Fonksiyon Çağırma Yöntemlerini,
• Yapıları,
• Birlikleri,

öğrenmiş olacaksınız.

4.1. Diziler
C++ programlama dilinde, aynı türden bilgilerin saklandığı ve dizi adı verilen veri yapıları
kullanılır.

Dizi, belirli sayıda verinin bellekte saklandığı değişken listesidir.

Bu liste tek bir isimle program içinde kullanılır. Örneğin, a[1], a[2], a[3], a[4] ve a[5]
ifadelerinin herbiri bir değişkeni göstermektedir. Bu değişkenler listesini a[i] ismiyle
program içinde ortak biçimde kullanabiliriz. Söz konusu listeyi dizi olarak tanımlıyoruz.

80
4.1.1. Bir Boyutlu Diziler
Bir grup sayısal değer yada karakter veriyi içeren diziler bir boyutlu diziler olarak
tanımlanır. Bir boyutlu diziler, C++ programlarında aşağıda gösterildiği biçimde
tanımlanır:

81
Örnek olarak, program içinde 15 adet tamsayıdan oluşan bir dizi için şu şekilde bir yer
ayrılabilir:

int a[15];

Diziler indeksleri yardımıyla kullanılır. İndeks, bir dizinin her bir elemanına sırayla verilen
bir numaradır. İndeksler, sıfırdan başlayarak oluşturulur. Buna göre dizinin birinci
elemanının indeksi 0'dır. İkinci elemanının indeksi ise 1'dir.

82
4.1.2. Dizilere Başlangıç Değeri Atama
Bir dizi, doğal olarak bazı veriler içerecektir. Bu değerler çoğunlukla program içinde belirli
hesaplamalar sonucunda elde edilir. Ancak bazı durumlarda, atama yoluyla dizinin içi
doldurulur.

Bir diziye başlangıç değerleri vermek için, ilgili değişkene o değer doğrudan atanır.
Örneğin,

a[0]=2;

biçiminde bir tanım yapılırsa, dizinin ilk elemanı olarak 2 değerinin belirlendiği anlaşılır.
Diziye aynı anda birden fazla değer atanabilir. Bunun için söz konusu değerler { }
işaretleri arasında yazılır. Örneğin,

a[5]={2,7,6,3,9}

biçiminde yazılarak diziye başlangıç değerleri verilebilir.

Diziye 2,7,6,3,9 değerleri atanıyor.

! Dizilere başlangıç değeri atamakla ilgili program örneğini görmek için tıklayınız.

2,7,0,3,9 değerlerini bir dizi içine yerleştirdikten sonra, tek tek ekranda görüntülemek
istiyoruz. Kullanacağımız dizinin adı a[] biçiminde olsun. Bu diziye, bellekte 10 elemanlık
bir yer ayırıyoruz. Daha sonra dizinin beş elemanına ayrı ayrı 5 adet sayı yerleştiriyoruz.

83
// Dizilerin kullanımı..
#include <iostream>
using namespace std;
// Dizi tanımlanıyor..
int a[10];
void main()
{
a[0]=2;
a[1]=7;
a[2]=0;
a[3]=3;
a[4]=9;
cout << a[0] << "\n";
cout << a[1] << "\n";
cout << a[2] << "\n";
cout << a[3] << "\n";
cout << a[4] << "\n";
}

Bu C++ programı çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilebilir:

Bu C++ programında dizinin her bir elemanına ayrı ayrı değerler atanmıştır. Bunun
yerine, aşağıda görüldüğü biçimde atama yapılarak işlem kısaltılabilir:

84
#include <iostream>
using namespace std;

// Dizi değerleri tanımlanıyor..


int a[5]={2,7,0,3,9};

void main()
{
cout << a[0] << "\n";
cout << a[1] << "\n";
cout << a[2] << "\n";
cout << a[3] << "\n";
cout << a[4] << "\n";
}

Bu şekildeki bir değer atamanın daha pratik olduğu görülmektedir.

4.1.3. İndekslerin Kullanımı


Bir programda, dizi elemanlarının herbirine nasıl başlangıç değerler verilebildiğini gördük.
İndeksler gözönüne alınarak, diziye değerler yerleştirilmesi ya da yazdırılması işlemleri
tanımlanabilir. Örneğin, beş adet dizi elemanının ekran üzerinde yazdırılması işleminde
indeksleri for deyimiyle birlikte kullanabiliriz.

Dizinin elemanlarını for döngüsüyle ekrana yazdırmak.

! İndeksleri kullanmakla ilgili program örneğini görmek için tıklayınız.

En son örnekteki programı, indeksler için bir i değişkenini kullanarak daha kolay ve etkin
biçimde oluşturabiliriz. Bu programda, dizi elemanlarının herbirinin tek tek kullanılması
yerine, indeksleri göz önüne alınarak for döngüsü içinde yazdırılması sağlanmıştır.

85
#include <iostream>
using namespace std;

// Dizi değerleri tanımlanıyor..


int a[5]={2,7,0,3,9};
int i;

void main()
{
for (i=0;i<=4;i++)
cout << a[i] << "\n";
}

Bu program çalıştırıldığında, önceki örneğin ürettiği sonuç aynen elde edilir:

4.1.4. Çok Boyutlu Diziler


Tek boyutlu dizileri bir vektör olarak düşünebiliriz. C++ içinde tek boyutlu diziler dışında
çok boyutlu dizileri tanımlayarak kullanmamız mümkündür. İki boyutlu bir dizi bir tablo
olarak değerlendirilebilir. Bu durumda iki farklı indeksin kullanılması söz konusu olacaktır.
Birinci indeks iki boyutlu tablonun satır elemanlarını, ikinci indeks ise sütun elemanlarını
gösterecektir.

Örnek: İki boyutlu bir diziyi tanımladıktan sonra için bazı verilerle doldurduktan sonra
görüntülemek istiyoruz. Bu amaçla aşağıdaki C++ programı hazırlanabilir.

86
#include <iostream>
using namespace std;
// Dizi değerleri tanımlanıyor..
int dizi[5][2] = {{1,2}, {7,9}, {3,0}, {5,1}, {1,1}};
int main()
{
cout << "Dizi elemanlari:" << "\n";
for (int i = 0; i<5; i++)
for (int j=0; j<2; j++) {
cout << "[" << i << "][" << j << "]: ";
cout << dizi[i][j]<< "\n";
}
return 0;
}

Bu program çalıştırıldığında şöyle bir sonuç görüntülenir:

87
4.1.5. Katar Dizileri
Bazı programlama dillerinde katarlar (strings), ayrı bir veri türü olarak
tanımlanabilmektedir. C'de ise bu mümkün değildir. Onun yerine, katarlar, karakterlerden
oluşan normal bir boyutlu dizi olarak değerlendirilir. Bir katar, boşluk (NULL) ile veya bir
başka deyişle '\0' karakteri ile son bulan bir karakter dizisi olarak kabul edilir. C++ de
NULL değer olarak sıfır kabul edilebilir.

Katar dizilerini C++ programına tanıtmak için, şekil 1'deki gibi bir bildirim yapılır.

Katar boyutları tanımlanırken dikkatli olmak gerekir. Çünkü katarın en son karakteri
NULL olacaktır. O halde, n boyutlu katarlara n+1 boyutluk yer ayırmakta yarar vardır.

C++'da katarların bildirimi ve kullanımı diğer dillerden farklıdır. Katarlarla ilgili bazı
işlemler işleçler yerine özel fonksiyonlarla yerine getirilir. C++'ın standart
kütüphanesinde yer alan bu fonksiyonları aşağıda ele alarak inceleyeceğiz.

Katar dizilerinin bildirimi.

4.1.5.1. Klavyeden Katar Okumak


Bir katarı klavyeden okumak için C++'ın gets() fonksiyonu kullanılır. Bu fonksiyon
herhangi bir indeks tanımlamadan karakter katarlarının okunmasını sağlar. Söz konusu
fonksiyon, klavye üzerindeki return tuşuna basılıncaya dek girilen tüm karakterleri okur.
Okuduğu katarın sonuna satır sonu işaretini değil, NULL değerini yerleştirir. Fonksiyon
Şekil1'deki gibi tanımlanır.

gets() fonksiyonu, klavyeden girilen katarları, herhangi bir ek tanımlamaya gerek


duymadan bir dizi içine yerleştirir. Dizinin herbir karakteri dizinin farklı bir hücresi içine
yerleşir.

Şekil 1: gets() fonksiyonunun tanımı.

88
! gets() fonksiyonunun işleyişini görmek için "Klavyeden
Değer Gir" düğmesine tıklayınız.

! Örneği görmek için tıklayınız.

Aşağıdaki C++ programı klavyeden girilen katara ait tüm karakterleri tek tek görüntüler.

#include <iostream>
using namespace std;

char katar[50];
int i;

void main()
{
gets(katar);
for (i=0;katar[i];i++)
cout<< katar[i] << "\n";
}

Bu program çalıştırıldıktan sonra ekran üzerinde, bir değer girilmesi beklenir. Klavyeden
Kazakistan yazdıktan sonra yine klavyenin return tuşuna basıyoruz. Sonuç olarak, yazılan
katar bir dizi içine yerleştirilir ve dizinin herbir elemanı tek tek yazdırılır:

89
4.1.5.2. Katar Kopyalama
Bir dizgi içine C++ programı içinde belirli bir katar yerleştirilmesi işlemini geleneksel
yollarla yapamayız. Örneğin, şu şekilde bir tanım yapıldığını varsayalım:

char katar[50];
katar="abcde";

Bu şekilde bir tanım hatalıdır. Çünkü bu atama göstergeye yapılan atamadır.


Göstergelerin ne olduğunu bir sonraki bölümde ayrıntılı olarak ele alarak inceleyeceğiz.
Atamanın bir katara yapılabilmesi için, C++'ın strcpy() fonksiyonu kullanılır. Bu
fonksiyon şu şekilde tanımlanır:

strcpy() fonksiyonu, program içinde tırnak işaretleri arasında tanımlanmış katarın herbir
karakterini, bir dizinin hücreleri içine tek tek yerleştirir.

90
Bir katar[] katarına program içinde bir atama yapmak istiyoruz. Örneğin, katar[] katarına
"Kazakistan" ifadesini atayarak bu katarın herbir karakterini tek tek görüntülemek
istiyoruz.

#include <iostream>
using namespace std;

char katar[50];
int i;

void main()
{
strcpy(katar,"Kazakistan");
for(i=0;katar[i];i++)
cout<< katar[i] << "\n";
}

Bu program çalıştırıldıktan sonra ekran üzerinde, bir değer girilmesi beklenir. Klavyeden
Kazakistan yazdıktan sonra yine klavyenin return tuşuna basıyoruz. Sonuç olarak, yazılan
katar bir dizi içine yerleştirilir ve dizinin her bir elemanı tek tek yazdırılır:

4.1.5.3. Katarların Birleştirilmesi


İki katarın birleştirilerek tek bir katar haline dönüştürülmesi söz konusu ise C'nin
strcat() fonksiyonu kullanılır. Bu fonksiyon Şekil1'deki gibi tanımlanmaktadır.

Bu fonksiyon, var olan bir katarın sonuna bir başka katarı ekleyecektir. Tanıma göre,
katar1 bir başka katar2 'nin sonuna eklenecektir. Örneğin "abc" katarının sonuna "def"
katarı strcat() fonksiyonu kullanılarak eklenebilir.

91
Şekil 1: strcat() fonksiyonunun tanımı.

Şekil 2: strcat() fonksiyonu, "abc" katarının sonuna "def"


katarını ekliyor.

! Örneği görmek için tıklayınız.

******************KALINAN YER***********************

Örnek

İki katarı birleştirerek bir katar elde etmek istiyoruz. Katarlardan birincisi "Kazakistan"
diğeri ise " ve Turkiye" biçiminde olsun. Bu iki katarı birleştirmek için aşağıda görüldüğü
biçimde bir C++ programı düzenlenebilir:

92
Bu program çalıştırıldığında, şu şekilde bir sonuç elde edilir:

Bu sonucu elde edebilmek için, cout ile dizinin her bir elemanını tek tek yazmaya gerek
yoktur. Söz konusu cout deyimi aşağıda gösterildiği biçimde kullanılarak aynı sonuç elde
edilebilir:

93
Şekil 1: strcat() fonksiyonunun tanımı.

Şekil 2: strcat() fonksiyonu, "abc" katarının sonuna "def" katarını ekliyor.

4.1.5.4. Katarların Karşılaştırılması


İki katarın birbirleriyle karşılaştırılarak, içerdiği karakterlerin aynı olup olmadıkları test
edilebilir. Bu amaçla strcmp() fonksiyonu kullanılır. Bu fonksiyon Şekil1'de gösterildiği
biçimde tanımlanıyor.

Bu karşılaştırma sonucunda, her iki katar birbirinin aynı ise "0"; birbirinden farklı ise "1"
değeri üretilir. Elde edilen bu değer kullanılarak programın akışı yönlendirilebilir.

Şekil 1: strcmp() fonksiyonunun tanımı

94
! Örneği görmek için tıklayınız.

Örnek

İki katarı karşılaştırarak sonucunu değerlendirmek istiyoruz. Eğer iki katar birbirine eşit ise
"Katarlar birbirinin aynı", farklı ise "Katarlar birbirinden farklı" mesajı görüntülenecektir.
Amacımıza uygun C++ programı şöyle olabilir:

Bu program çalıştırıldığında ekranda aşağıdaki sonuç görüntülenir:

95
4.1.5.5. Katarların Uzunluğunu Bulmak
Bazı uygulamalarda bir katarın uzunluğunu bulmak söz konusu olabilir. Bir katarın
uzunluğunu, yani kaç karakter içerdiğini elde etmek için C'nin standart strlen()
fonksiyonu kullanılır. Bu fonksiyon aşağıdaki şekilde tanımlanmaktadır:

! strlen() fonksiyonunun işleyişini görmek için "strlen();" düğmesine tıklayınız.

! Katarların uzunluğunu bulmakla ilgili program örneğini görmek için tıklayınız.

Örnek

Aşağıdaki C++ programı bir katarın uzunluğunu bulmak amacıyla düzenlenmiştir:

96
Bu program çalıştırıldığında, katar isimli dizinin uzunluğu, yani kaç karakter içerdiği
bulunur.

4.1.5.6. Katar Dizilere Başlangıç Değeri Atamak


Bir katar dizisine programın başında bir başlangıç değeri atanarak bu değerler
kullanılabilir. Örneğin, a[] isimli bir katara "Burak" dizgisini atamak için şöyle bir tanım
yapılabilir:

char a[]={'B','u','r','a','k'} ;

Bunun yerine,

char a[]="Burak";

biçiminde bir tanım da yapılabilir.

Örnek: Bir dizi içine haftanın gün isimlerini yerleştirmek ve ekran üzerinde görüntülemek
istiyoruz. Bu amaçla aşağıdaki C++ programını hazırlıyoruz:

97
Bu program çalıştırıldığında aşağıdaki sonuç elde edilir:

4.2. Gösterge Kavramı


Gösterge (pointer) C++ programlarında önemli yere sahip bir kavramdır. Gösterge, bir
değişkenin bellekteki adresini tutan bir değişken olarak düşünülür. Örneğin, b değişkenin
bellekteki konumunu, yani adresini gözönüne alalım. Bu adresi bir başka a değişkeni içine
yerleştirelim. Bu durumda "a, b'nin göstergesidir" ya da "a, b'ye işaret etmektedir" denir.

O halde gösterge, bir değişkenin değerini değil söz konusu değişkenin bellek üzerindeki
adresini içermektedir.

98
a değişkeni, b değişkeninin bellekteki adresi olan 13affff6 değerini içermektedir. Demek
ki a, b'nin göstergesidir.

4.2.1. Göstergelerin Bildirimi


Göstergelerin C++ programlarında kullanılabilmesi için, herşeyden önce bildiriminin
yapılması gerekmektedir.

Tür, göstergenin tipini belirler. Örneğin tamsayı bir gösterge değişken için int bildirimi
yapılır. Değişken isimlerinin başında "*" işlecine yer verilir. Bu işleç, gösterge
değişkenlerinin tanımlanmasında kullanılır.

Şu şekilde bir gösterge tanımı yapılabilir:

int *a;

Bu ifade, tamsayı türündeki bir a göstergesini tanımlamaktadır.

99
Göstergelerin bildirimi için yukarıdaki genel tanım kullanılır.

4.2.2. Değişkenlerin Adresi


Bildirimi yapılmış bir gösterge değişken herhangi bir şey ifade etmez. Bu göstergenin
kullanılabilmesi için, söz konusu gösterge değişkene bir başka değişkenin adresini
yerleştirmek gerekmektedir.

O halde, öncelikle bir değişkenin adresinin nasıl elde edilebileceğini bilmek söz
konusudur. Bir değişkenin bellek üzerindeki adresini öğrenmek için "&" işlecinden
yararlanılır. Örneğin, a değişkenininin bellek üzerindeki adresini,

&a

biçiminde gösterebiliriz.

! Bir değişkenin içeriğini ve bellek adresini veren program örneğini görmek için tıklayınız.

Örnek

Bir a değişkenin içeriğini ve bellek adresini öğrenmek istiyoruz. Bu amacımıza ulaşmak için
şöyle bir yol izlenebilir:

100
Bu program çalıştırıldığında şu şekilde bir sonuç görüntülenir:

a, bir değişken ise &a, a değişkeninin adresini ifade eder.

4.2.3. Göstergeye Adres Atama


Göstergelerin kullanılabilmesi için şu işlemler yapılır:

1. Gösterge değişkenin bildirimi yapılır. Bunun için "*" işlecinden yararlanılır.


2. Gösterge değişkene bir değişken adresi atanır. Değişkenin adresini bulmak için "&"
işleci kullanılır.

*, gösterge türünden değişken tanımlar.

&, değişkenin adresini belirler.

Örnek:
Bir a değişkeninin 500 değerini içerdiğini varsayalım. Bu değişkenin bellek üzerindeki
adresini bir gösterge içine yerleştirmek ve ardından bu gösterge değerini kullanarak a
değişkeninin değerini görüntülemek istiyoruz. Bunun için aşağıdaki C++ programını
düzenliyoruz:

101
Bu C++ programı çalıştırıldığında, a değişkenin adresi şu şekilde görüntülenir:

4.3. Fonksiyonların Kullanılması


Karmaşık yapılı C++ programlarındaki bu karmaşıklığı azaltmak ve programı modüler bir
yapıya kavuşturmak için fonksiyonlardan yararlanılır. Fonksiyon, programcının
tekrarlanan kodlar yazmasını önler. Fonksiyon belirli bir adı olan program parçalarıdır.
Fonksiyonların çalışabilmesi için bir başka fonksiyondan adı ile çağrılması gerekmektedir.

Aslında C++ programları fonksiyonlardan oluşur. Şu ana dek kullandığımız main() de bir
fonksiyondur. Ancak bu fonksiyonun bir başka fonksiyon içinden çağrılmasına gerek
yoktur. Her C++ programında bir main() fonksiyonun yer alması gerekmektedir. main()
fonksiyonu, program çalıştırıldığında otomatik olarak çağrılan bir fonksiyondur. Bir main()
fonksiyonu içinden bir başka fonksiyon çağrılabildiği gibi, herhangi bir fonksiyon içinden
bir başka fonksiyon çağrılabilir. Örneğin, Şekil1'deki gibi fonksiyon1() isimli fonksiyondan
fonksiyon2() isimli bir başka fonksiyon çağrılabilir.

Şekil 1: fonksiyon2, fonksiyon1'in içinden çağrılıyor.

102
4.3.1. Fonksiyon Nasıl Çalışır ?
Bir fonksiyon çalıştırıldığında, doğal olarak bir işi yerine getirmesi beklenecektir.
Fonksiyonun döndürdüğü (ürettiği) değer onu çağıran fonksiyona aktarılarak bu değer
işlenmeye devam edebilir. Veya fonksiyon bir değer üretir, ancak bu değer ana programa
aktarılmaz. Bir fonksiyon çalıştıktan sonra, kontrol yeniden onu çağıran programa geçer.
Ana programın bir alt satırından itibaren diğer deyimler çalışmaya devam eder.

fonksiyon2, bir sonuç üretir; ardından onu çağıran


fonksiyon kaldığı satırdan çalışmaya devam eder.

4.3.2. Fonksiyonların Tanımlanması


Fonksiyonların kullanılabilmesi için belirli bir biçimde tanımlanması gerekmektedir. Bu
yapı aşağıda belirtildiği biçimde olacaktır. Herşeyden önce bir fonksiyonun mutlaka bir adı
olmalıdır.

tür
fonksiyon_adı(parametre_listesi)
{
deyimler
}

Bu tanıma göre fonksiyondan elde edilecek değerin türü belirlenebilir. Örneğin, test isimli
bir fonksiyon tamsayı sonuç döndürecek ise int test() biçiminde bir tanım yapılabilir.

Örnek:
Bir sayi() fonksiyonunun tamsayı değer döndürececeğini varsayalım. Bu durumda,

int sayi();

biçiminde tanımlanabilir. Bu fonksiyon herhangi bir parametre listesi içermediği için,

int sayi(void);

biçiminde de ifade edilebilir. Tamsayı türünde bir x parametresine sahip ise,

int sayi(int x);

tanımı yapılır. Eğer sayi() isimli fonksiyon herhangi bir değer döndürmeyecek ise,

void sayi();

103
biçiminde tanımı yapılabilir. Ancak bu fonksiyon herhangi bir değer döndürmeyecek ve
tamsayı türünde bir x parametresini kullanacak ise,

void sayi(int x);

tanımı yapılabilir.

! Bir ana fonksiyondan başka bir fonksiyonu çağırarak işlem yapan program örneğini
görmek için tıklayınız.

Örnek

Bir ana fonksiyondan bir başka fonksiyon çağırarak işlem yapmak istiyoruz. Örneğin
çağırmak istediğimiz fonksiyonun adı mesaj() olsun ve bu fonksiyon sadece bir mesaj
görüntüleme işlemini yerine getirsin.

Bu program çalıştırıldığında, program içinde tanımlanan mesaj() isimli foksiyona bağlı


olarak aşağıdaki sonuç görüntülenir:

hos geldiniz..

4.3.3. Fonksiyonun Değer Döndürmesi


Bir fonksiyon çalıştığında, doğal olarak bir sonuç üretecektir. Bu sonucun, kendisini
çağıran fonksiyona gönderilmesi veya bir başka deyişle döndürülmesi söz konusu ise,
fonksiyon içinde return deyimine yer vermek gerekecektir.

104
! Fonksiyonların değer döndürmesiyle ilgili örneği görmek için tıklayınız.

Örnek

Bir ana fonksiyondan bir başka sayilar() isimli fonksiyon çağrılmaktadır. Söz konusu
sayilar() fonksiyonunda sayi değişkeninin değeri 5 olarak belirlenmiştir. Bu değeri ana
programa döndürmek istiyoruz.

Bu program çalıştırıldığında ise aşağıdaki sonuç elde edilir:

105
4.3.4. Fonksiyonlar Arasında Parametre Geçirme
Her fonksiyon bir diğerinden bağımsız olarak düzenlenir. Bir fonksiyon içinde yer alan bir
değişkeni bir başka fonksiyon içinde aynen kullanamayız. Bunun için farklı yöntemlere
başvurulur. Bunlardan birincisi, fonksiyonlar arasında parametre geçirilmesidir.
Parametre geçirilmesini sağlamak için fonksiyonlar şu şekilde tanımlanır:

tür fonksiyon_adı (parametre_listesi)


{

deyimler

Hiç parametresi olmayan fonksiyonlar da olabilir. Bu gibi durumlarda parametre listesi


boş bırakılabileceği gibi void de yazılabilir.

Örnek:
Bir fonksiyon yardımıyla üs alma işlemlerini yerine getirmek istiyoruz. Bu işlem, x ve y
verildiğinde ifadesinin değerini hesaplayacaktır. Amacımıza uygun C++ programını
fonksiyonlardan yararlanmak suretiyle şu şekilde düzenleyebiliriz.

106
Bu program çalıştırıldığında aşağıdaki sonuç elde edilir:

4.3.5. Küresel Değişkenler Yardımıyla Fonksiyonlar


Arasında Değer Taşımak
Fonksiyonlar arasında parametre geçirerek veri aktarılması işleminin nasıl
gerçekleştirilebildiğini gördük. Benzer bir sonuca ulaşmak için küresel değişkenlerden de
yararlanabiliriz. Küresel değişkenleri dersimizin başlarında ele alarak incelemiştik. Küresel
değişkenler, programın çalışması boyunca, fonksiyonlar dahil, değeri geçerli olan
değişkenlerdir.

Küresel değişkenler fonksiyonların içinde değil, ondan önce tanımlanır. Bir küresel
değişken tanımından sonra yer alan tüm fonksiyonlar için bu değişkenin değeri geçerlidir.

107
4.3.6. static Değişkeninin Fonksiyonlarda Kullanımı
Dersimizin ilk konularında static değişkenlerinin ne anlama geldiğinden söz etmiştik. Bu
tür değişkenler, fonksiyon terkedildiğinde değerlerini kaybetmezler. Aynı fonksiyon bir
daha çalıştırıldığında, daha önceki değer yeniden kullanılabilir.

Program içindeki bir fonksiyonun ya da küresel değişkenin başına static tanımı yapıldığı
takdirde, bu fonksiyon veya değişken diğer kaynak program dosyalarına karşı erişilmez
olacaktır. Bunun anlamı, diğer dosyalardan yapılacak erişimlere karşı "gizli" kalacaktır.

fonksiyon1, iki defa çağrılıyor. İkinci çağrılışta static x değişkeni, ilk çağrılışında aldığı
değeri korur; tekrar sıfıra eşitlenmez.

108
4.3.7. Fonksiyon Çağırma Yöntemleri
C++ programlama dilinde fonksiyonların çağrılması iki farklı yöntemle
gerçekleştirilmektedir:

• Değer ile çağırma


• Referans ile çağırma

Değer ile çağırma

Şu ana kadar ele alarak incelediğimiz fonksiyon çağırma biçimi değer ile çağırma olarak
bilinmektedir. Bu çağırma biçiminde; çağıran fonksiyondan çağrılan fonksiyona aktarılan
değişken değerlerinde bir değişiklik olsa bile, bu değişiklik çağıran programdaki değişken
değerlerine etki etmez. Bu değişiklikler çağrılan fonksiyon içinde parametrelerin kopyaları
üzerinde gerçekleşir.

Referans ile çağırma

Bir fonksiyonu değer ile çağırarak ona nasıl veri aktardığımızı ve veri döndürdüğümüzü
biliyoruz. Bunun yerine, verilerin bellek adreslerini kullanarak da fonksiyonları
çağırabiliriz. Bu durumda, çağrılan fonksiyon içinde parametre değerleri üzerinde
yapılacak değişikliklerin, onu çağıran fonksiyon üzerinde de etkili olduğu görülecektir.

! Değer ile çağırma" ya da "Referans ile çağırma" düğmelerine tıklayarak sonucu


gözleyiniz.

4.3.8. Hazır Fonksiyon Nedir ?


Bu derste C++ programlama dilinin hazır fonksiyonları üzerinde duracağız. Aslında bu
fonksiyonların bir kısmını daha önceki derslerde ele alarak incelemiştik. Örneğin en son
konuda inclediğimiz dosyalarla ilgili tüm işlemlerin fonksiyonlarla yapıldığını biliyoruz.

Bildiğiniz gibi, C++'da iki fonksiyon türü ile karşılaşıyoruz. Bunlardan birincisi C++'nın
hazır fonksiyonları, diğer ise kullanıcı tarafından yaratılan fonksiyonlardır. C++'nın hazır
fonksiyonları, adından da anlaşılabileceği gibi, programcıya hazır olarak sunulan

109
fonksiyonlardır. Bu fonksiyonlar bir C++ kitaplığında yer alır. Bu kitaplık dosyası
programın başında #include deyimi ile programa dahil edilir. Böylece program bu
kitaplık içinde yer alan tüm fonksiyonları doğrudan kullanabilir.

Örnek:
Program içinde C++'nın cos() matematiksel fonksiyonu kullanmak istiyoruz. Bu
fonksiyonun kullanılabilmesi için, programın başında matematik kitaplığı programa dahil
edilmelidir:

#include<math>

4.4. Yapılar
C++ programlama dilinde birbirleriyle ilişkili veri gruplarına yapı (struct) adı
verilmektedir. Yapı, farklı veri türlerine sahip değişkenlerin bir grup olarak
değerlendirilmesi ve bu grubun bir isimle kullanılması amacıyla tercih edilir. Yapılar şu
şekilde tanımlanıyor:

struct yapı-adı
{
alanlar
} değişken listesi;

Yapının içinde yer alan her bir veri tür tanımına yapının üyeleri yada alanları adı
verilmektedir. Yapıların, birçok programlama dilinde yer alan kayıt kavramı ile aynı
olduğunu söyleyebiliriz.

110
Örnek: Bir kuruluştaki personelin verileri farklı değişkenlerle ifade edilebilir. Örneğin,
numara ile personel numarası gösterilebilir. Bu değişkenin veri türü tamsayı olabilir.
Benzer biçimde personel adı karakter olarak adi isimli değişken içinde saklanabilir. Son
olarak, ücreti çift duyarlıklı bir sayı olarak, double veri türü biçiminde ve ucret adıyla
ifade edilebilir.

Sayılan bu değişkenlerin tümü bir personel ile ilişkilidir. O halde bu değişkenleri personel
isimli bir yapı biçiminde gruplayarak tanımlayabiliriz.

struct personel
{
int numara;
char adi;
double ucret;
} p;

Bu tanımdan şu anlaşılmakatadır:

personel Yapının adı. Bu adın tanımlanması zorunlu değildir.

numara, adi, ucret Yapının içerdiği alanlar.

p Yapı değişkeni.

Personel verileri gruplanarak personel isminde bir yapı


tanımlanabilir.

4.4.1. Yapı Değişkenlerinin Kullanımı


Yapının her bir alanına tek tek ulaşılabilir. Bunun için yapı değişkenleri aşağıda belirtildiği
biçimde kullanılır:

111
yapı_değişkeni.alan_adı

Örneğin, Şekil1'de gösterildiği biçimde bir yapı tanımlandığını varsayalım:

Bu yapının herbir alanına C++ programının herhangi bir yerinde erişilebilir. Bunun için,
yapı değişkeni ve alanı birbirinden (.) işleci ile ayrılmak suretiyle kullanılır. Yapının x
alanını,

test.x

biçiminde kullanabiliriz.

! Örneği görmek için tıklayınız.

Örnek

Personel ile ilgili çeşitli alanların yer aldığı personel isimli bir yapı tanımlayalım. Bu
yapının her bir alanına bir değer atadıktan sonra ekranda görüntülenmesini istiyoruz.
Amacımıza şu şekilde ulaşabiliriz:

Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

112
Şekil 1: testYapi adında bir yapı tanımı.

4.4.2. Birden Fazla Yapı Değişkeninin Kullanımı


Önceki kısımlarda bir yapı ile birlikte sadece bir değişken kullandık. Bazı uygulamalarda,
aynı yapıyı birden fazla değişkenin kullanması istenilebilir. Bu amaçla yapı içinde birden
fazla değişken tanımlanabilir. Örneğin soldaki gibi bir tanım, aynı yapının p1 ve p2 adıyla
farklı iki değişken tarafından kullanılabileceğini göstermektedir.

p1 ve p2 aynı yapıyı kullanan yapı değişkenleridir.

4.4.3. Alan Uzunluklarının Belirlenmesi


Yapı içinde yer alan alanların bellekte işgal edecekleri alanları belirleyerek yapı ile birlikte
tanımlamak mümkündür. Böylece belleğin daha etkin kullanılması sağlanabilir. Örneğin,
ders_adi isimli bir alanın uzunluğunun en fazla 20 karakterden oluşması söz konusu ise,
söz konusu alan yapı içinde Şekil1'deki gibi tanımlanabilir.

Şekil 1: ders_adi adindaki char dizisine 20 karakterlik yer


ayrılıyor.

113
4.4.4. Alanlara Başlangıç Değeri Vermek
Yapı içinde yer alan her bir alana bir başlangıç değeri atamak mümkündür. Bu tür bir
atama yapılırken dikkat edilecek en önemli nokta, alanların veri türü ile atanan veri
türünün uygun olmasıdır.

4.4.5. Fonksiyonlarla Kullanımı


Bir yapı, bir fonksiyona aynen diğer veri türlerinde olduğu gibi parametre biçiminde
geçirilebilir. Ayrıca bir fonksiyon bir yapıyı kendisini çağıran fonksiyona döndürebilir.

Bir yapı tanımlandıktan sonra, bir fonksiyon bu yapının herbir elemanını kullanıyorsa,
yapının elemanları tek tek fonksiyona geçirilmez. Bunun yerine, yapının doğrudan
fonksiyona geçirilmesi yeterlidir.

fonksiyon1 çağrılırken yapı isminde struct personel türünden bir yapı parametre olarak
veriliyor; fonksiyon2 struct personel türünden bir yapı döndürüyor.

4.4.6. Yapılara Gösterge Bildirimi


Yapılara erişimin bir diğer yolu, göstergelerin kullanılmasıdır. Bir yapı değişkeni diğer
değişken türlerinde olduğu gibi, göstergelerle de tanımlanabilir. Örneğin aşağıdaki yapı
bir per değişkeni ile ifade edilebildiği gibi, aynı yapı *p göstergesi ile de ifade
edilebilmektedir.

Program içinde yapı değişkeni ile her bir alanı (.) işleci ile birbirinden ayrılarak
kullanılabiliyordu. Örneğin, numara alanı,

per.numara

114
biçiminde ifade edilebilmektedir. Göstergelerin kullanımında ise daha farklı bir yol izlenir.
Nokta işleci yerine (->) ok işleci kullanılır. Örneğin, numara alanını,

p->numara

biçiminde ifade edebiliriz.

per, yapı cinsinde bir değişken, p ise yapıyı gösteren göstergedir.

4.5. Birlikler
Yapılara benzeyen bir diğer kavram, birlik adıyla bilinir. Yapılar, belirli bir grup alanın tek
bir değişken biçiminde ifade edilmesini sağlıyordu. Değişkenin her bir alanı için bellekte
ayrı bir yer ayrılıyordu. Dolayısıyla her bir alan gerek duyulduğunda tek tek
kullanılabiliyordu.

Birliklerde ise, birliğin tümü için bellekte tek bir yer ayrılır. Bu yer, birliğin en büyük alanı
kadardır. Birlikler şu şekilde tanımlanmaktadır:

union birlik_adı
{

alanlar

} değişken_listesi;

Örnek olarak aşağıdaki birlik tanımını gözönüne alalım:

union test
{
int x;
char y[10];
} p;

Bu tanıma göre x tamsayısı için bellekte 4 baytlık yer ayrılmaktadır. Birlik içinde yer alan
y değişkeni için 10 baytlık yer ayrılmıştır. Bu durumda, birlik için en fazla 10 baytlık bir
yer ayrılmıştır. Ayrılan bu alan, birliğin her bir alanı tarafından ortak kullanılır.

! Birliklerle ilgili program örneğini görmek için tıklayınız.

115
Yapılarda ele alarak incelediğimiz örneği aynen kullanmak istiyoruz. Diğerinden tek farkı,
yapı yerine birlik tanımı yapılmıştır.

Bu program çalıştığında aşağıdaki sonuç elde edilir.

Bu sonuçtan şu anlaşılmaktadır: Personel sicil numarasını gösteren numara ve personel


adını gösteren adi alanlarına değerler yerleştirilmesine rağmen görüntülenmemiştir.
Çünkü birlik içindeki en son hangi alana veri yerleştirilirse, daha öncekilerin yerini alır.

116
Birlik, en büyük alanı kadar bellekte yer işgal eder.

Bölüm Özeti
Bu bölümde,

• Dizileri
• Çok boyutlu dizileri
• Katar dizilerini
• Göstergeyi
• Fonksiyonların kullanımını
• Fonksiyon çağırma yöntemlerini
• Yapıları
• Birlikleri

öğrendik.

117
118
119
120
121
122
123
SINIF VE NESNELER

Bu bölümde,

• Sınıflara giriş yapılacak;


• Sınıf oluşturma yöntemleri incelenecek;

• Nesnelerle ilgili işlemler açıklanacaktır.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Sınıfların oluşturulmasını,
• struct ile sınıf oluşturmayı,
• class ile sınıf oluşturmayı,
• Kurucu ve yok edici fonksiyonları,
• Bir nesnenin diğer nesneye nasıl atandığını,
• Fonksiyonlara nesne atamayı,
• Fonksiyonlardan nesne döndürmeyi,
• Arkadaş fonksiyonları,

öğrenmiş olacaksınız.

5.1. Sınıflara Giriş


C++ nesneye yönelik bir programlama dilidir. O halde program içinde nesnelerin
tanımlanması önem taşımaktadır. Nesnelerin yaratılması amacıyla sınıflar (class)
kullanılır. Sınıf, progaramlarda gerçek nesneleri yaratmak için kullanılan bir yapıdır. Sınıf,
veri üyeleri (data members) ve üye fonksiyonlarını (member functions) tanımlayarak
nesnenin yapısını ve davranış biçimini ortaya koyar.

C++'da sınıfları farklı yollardan oluşturmak mümkündür:

124
5.2. struct ile Sınıf Oluşturma
Bir yapının nasıl oluşturulduğunu önceki derslerimizden biliyoruz. Bu aşamada bir yapının
bir sınıf oluşturmada nasıl kullanılabileceği üzerinde duracağız. Önceki anlattıklarımızdan
farklı olarak, struct ile bir sınıf oluşturulurken, bu yapının hem veri hem de kodları
içerdiğini göreceksiniz. Bunun anlamı, struct yapısı içinde veri tanımları yanısıra,
fonksiyon tanımlarına da yer verilecektir. Bu fonksiyonlar sınıflar için üye fonksiyonlar
olarak değerlendirilir.

Bu şekilde bir sınıf oluşturulması, gerçek nesneler için uygun bir yol değildir. C++'da
struct bildirimi içindeki üye verileri public'tir. Burada public olan kısmı, veri ve
fonksiyonların topluluk dışında kalan kısmıdır. Böyle olunca nesneleri struct ile
tanımlamanın eksikliği ortaya çıkar. Bu yapı nesneleri veriyi gizleyemez. Halbuki, veri
saklanması gerçek nesnelerin tanımlanması için bir ön koşuldur. struct nesneleri veriyi
koruyup gizlemediğinden, main() fonksiyonunun kodu nesnenin içinde tanımlanmış
herşeye erişebilir.

struct sınıf_adı
{
veri tanımları;
üye fonksiyonlar();
}nesne listesi;

! struct yapısını kullanarak sınıf oluşturan program örneğini görmek için tıklayınız.

Örnek

125
Bir küre nesnesi oluşturmak ve onu kullanmak istiyoruz. Bu nesneyi struct yapısı içinde
oluşturacağız. Söz konusu küre nesnesi aşağıda sıralanan verileri içerecektir:

x, y, z : Kürenin Koordinatları
R : Kürenin Yarıçapı

Kürenin alanı ve hacmi aşağıda belirtilen formüller aracılığıyla hesaplanmaktadır:

Aşağıda hazırlanan C++ programı, kürenin koordinatlarını, alanını ve hacmini ekran


üzerinde görüntüleyecektir.

126
Bu program çalıştırıldığında, ekran üzerinde şu şekilde bir sonuç görüntülenir:

127
struct ile oluşturulan nesneler, verileri koruyup
saklayamaz; bu verilere başka fonksiyonlar tarafından erişilebilir.

5.3. class ile Sınıf Oluşturma


C++'da gerçek nesnelerin yaratılması işlemi class ile olur. Sınıf nesneler, yaratmak için
kullanılan yöntemdir. Sınıfları class ile şu şekilde bildirimi yapılır:

class sınıf_adı
{
private veri ve fonksiyonlar;

public:
public veri ve fonksiyonlar;

}nesne listesi;

Bir sınıfın bazı bölümlerini programın diğer bölümleri tarafından erişilebilir hale getirmek
söz konusu ise, public anahtar kelimesi ile bu belirtilir. Herhangi bir tanım yapılmaz ise,
sınıf içinde belirtilen tüm bölgelerin özel bölge olduğu, yani private kabul edildiği
varsayılır. Bu durumda söz konusu bu bölgelerin tümüyle sınıfın üyesi bir fonksiyon
tarafından kullanılabileceği anlaşılır.

128
Sınıfın private bölümlerine programın diğer kısımlarının
erişim hakkı yoktur.

5.3.1. Sınıf İçinde Yerel Fonksiyonların Tanımlanması


Sınıfın sahip olduğu fonksiyonları, class ile başlayan sınıf tanımı içine yerleştirebiliriz. Bu
tür fonksiyonlara yerel fonksiyonlar (in-line functions) adı verilmektedir. Yerel
fonksiyonlar genel fonksiyonlara oranla daha hızlı çalışır. O halde bu fonksiyonlar C++
programlarının verimini artıran olanaklar olarak kabul edilebilir.

Örnek:
Bir kürenin konumuna ilişkin koordinatları, alanını ve hacmini görüntüleyen C++
programını struct kullanarak yapmıştık. Aynı örneği bu kez class anahtar kelimesini
kullanarak yapmak istiyoruz.

129
Bu program çalıştırıldığında, ekran üzerinde şu şekilde bir sonuç görüntülenir:

130
5.3.2. Sınıf İçinde Genel Fonksiyonların Tanımlanması
Gerek duyulduğunda, fonksiyon tanımları sınıfın bildiriminin dışına taşınarak genel
fonksiyonlar biçimine dönüştürülebilir. Bu durumda, sınıf tanımı içinde ilgili fonksiyonların
prototiplerine yer vermek gerekecektir.

Üye fonksiyonların, sınıf dışında tanımlanması durumunda, bu fonksiyonun hangi sınıfın


bir üyesi olduğunu da belirtmek gerekmektedir. Bunun için (::) işlevinden yararlanılır.
Tanım şu şekilde olacaktır:

sınıf_adı :: fonksiyon_adı

Örnek:
Önceki örnekte alan() ve hacim() fonksiyonlarını Kure sınıfının içinde tanımlamıştık. Bu
kez ilgili fonksiyonları sınıf dışına taşıyoruz ve sınıf içinde bu fonksiyonların prototiplerine
yer veriyoruz. Bu program çalıştırıldığında önceki örnekte elde edilen sonucun aynısı
bulunur.

131
Bu program çalıştırıldığında, ekran üzerinde şu şekilde bir sonuç görüntülenir:

132
5.3.3. Erişimin Sınırlandırılması
Bir sınıfın veri veya fonksiyonlarından oluşan üyelerine erişim kısıtlanabilir.

Bir kısım sınıf üyelerine sadece o sınıf fonksiyonlarının erişmesi sağlanabilir.

Böylece bu verilerin istenilmeyen değerler alması önlenmiş olur. Bu durumda söz konusu
üyeler özel olarak tanımlanır. Özel üyeler private anahtar kelimesi ile tanıtımı yapılır.

Eğer sınıfın üyesi olmayan fonksiyonların sınıf verilerine erişmesi söz konusu ise, bu
veriler sınıf içinde açık olarak tanımlanır. Kullanıma açık olan üyeler public anahtar
kelimesi ile tanıtılır. Sayılan her iki anahtar kelimeden herhangi birisi kullanılmaz ise, sınıf
içindeki tüm üyelerin özel, yani private olduğu varsayılır.

! private alanlara sınıf dışından erişilememesiyle ilgili program örneğini görmek için
tıklayınız.

Örnek

Küre ile ilgili sınıfın verilerini ve fonksiyonların özel, yani private olarak tanımlamak
istiyoruz. Bunun için public tanımının yapılmamaması yeterlidir. İstenirse private anahtar
kelimesi kullanılabilir.

Bu şekilde bir tanım yapıldığında, söz


konusu veri ve fonksiyonları sadece Kure isimli nesne tarafından kullanabilecektir. O
halde, main() fonksiyonu bu verileri ya da işlevleri aşağıda belirtildiği biçimde
kullanmaya kalkarsa program doğru biçimde derlenmeyerek hata verecektir.

133
5.4. Kurucu ve Yok Edici Fonksiyonlar
Program içinde yer alan bir değişkeni aşağıda belirtildiği biçimde tanımlayarak, ona belirli
bir değer atayabiliriz:

int en;
en = 10;

Bunun yerine her iki tanımı birleştirerek, hem verinin türünü hem de değerini bir tek satır
üzerinde ortaya koyabiliyoruz.

int en = 10;

134
Bu şekilde değişkenlere başlangıç değerleri atayabiliyoruz. Bir sınıf içinde yer alan bir
değişkene başlangıç değeri atanması söz konusu ise daha farklı bir yol izlenir. Böyle bir
amaca ulaşmak için kurucu fonksiyonlar kullanılabilir.

Bir nesne oluşturulduğunda, o nesnenin kurucu fonksiyonu otomatik olarak çağrılır.

Bu nesnenin bildirimi işletildiğinde çağrılacağı anlamındadır. C++'da değişken bildirimleri


programın çalıştırılması anında etkin olmaktadır. Bunun nedeni, bir nesne bildiriminin bir
kurucu fonksiyonu çağırması olasılığıdır.

Nesnenin yok edilmesi esnasında ise yok edici fonksiyon çalıştırılır. Bu fonksiyon nesnenin
işgal ettiği alanı boşaltarak belleğin etkin kullanılmasına yardım eder. Yok edici
fonksiyonlar da ilişkili olduğu sınıf ile aynı isme sahiptir. Ancak fonksiyon adının başına
"~" işaret kaydedilir.

fonksiyon_adı();
//kurucu fonksiyon nesne yaratıldığı
//anda çağrılarak nesne içindeki
// değişkenlere ilk değer atar.

~fonksiyon_adı();
//yok edici fonksiyon nesneyi yok ederek
//nesnenin bellekte işgal ettiği alanı boşaltır.

Kurucu fonksiyon ait olduğu sınıf ile aynı ismi taşır. Bu fonksiyon herhangi bir
değer döndürmez. Bunun anlamı, kurucu fonksiyon return sözcüğü içermez.

! Kurucu ve yok edici fonksiyonlarla ilgili örneği görmek için tıklayınız.

Örnek

Program içinde testSinif isimli bir sınıfımızın var olduğunu varsayalım. Bu sınıf, sınıf adı
ile aynı ismi içeren bir kurucu ve bir yok edici fonksiyona sahiptir. Kurucu fonksiyon, a ve
b değişkenlerine iki değer atamaktadır. Yok edici fonksiyon ise bu değerleri yok
etmektedir.

135
Bu program çalıştırıldığında aşağıdaki sonuç elde edilir.

136
5.4.1. Parametre Alan Kurucular
Kurucu fonksiyonlara, diğer fonksiyonlarda olduğu gibi parametre geçirmek mümkündür.
Bu parametreler genellikle bir nesne oluşturulduğunda, onu ilk kullanıma hazırlamak
amacıyla kullanabiliriz.

! Parametre alan kurucu fonksiyonlarla ilgili örneği görmek için tıklayınız.

Örnek

testSinif isimli sınıfa ait bir kurucu fonksiyona parametre geçirerek, kurucu fonksiyon
içindeki bir değişkene başlangıç değer atamak istiyoruz. Amacımıza ulaşmak için aşağıda
belirtilen yolu izliyoruz.

137
Bu program çalıştırıldığında aşağıdaki sonuç elde edilir:

138
Parametre alan bir kurucu fonksiyon.

5.4.2. Birden Fazla Parametreli Kurucu Fonksiyonlar


Bir kurucuya her zaman sadece bir parametre geçirilmez. Uygulamada çoğunlukla birden
fazla parametre geçirilir. Birden fazla parametre geçirilmesinde de aynı yol izlenir.

! Birden fazla parametre alan kurucu fonksiyonlarla ilgili örneği görmek için tıklayınız.

Örnek

Bu kez birden fazla parametreli kurucu fonksiyon tanımlamak istiyoruz. Amacımıza uygun
program şu şekilde düzenlenebilir:

139
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilir:

140
5.4.3. Kurucu Fonksiyona Değişkenlerin Geçirilmesi
Önceki örneklerde gördüğümüz gibi, bir kurucu fonksiyona bir ya da daha fazla
parametre geçirilebiliyordu. Bu parametreler birer sabit olarak ele alınmıştır. Bu olanağın
yanısıra, gerek duyulduğunda kurucu fonksiyona değişkenler de geçirilebilir.

! Kurucu fonksiyona parametre olarak değişken geçiren program örneğini görmek için
tıklayınız.

Örnek

Klavyeden girilen iki adet tamsayı değeri okuyarak x ve y gibi iki değişkene aktaran ve bu
değişkenleri kurucu fonksiyona geçiren program aşağıda görüldüğü biçimde düzenlenebilir.

141
Bu program çalıştırıldıktan sonra klavye yardımıyla 3 ve 9 gibi iki adet tamsayı giriyoruz.
Sonuç olarak aşağıdaki görüntü elde edilir.

142
Kurucu fonksiyona parametre olarak sabit veya değişken
geçirilebilir.

5.5. Bir Nesneyi Diğer Nesneye Atamak


Aynı türdeki bir nesne bir diğer nesneye atanabilir. Bunun anlamı, bir nesnenin kopyası
oluşturulabilir. Nesne bir diğer nesneye atandığında, tüm veri üyelerinin bit bit kopyası
oluşturulur.

! Nesnelerin kopyalanmasını incelemek için "nesne'yi KOPYALA" düğmesine tıklayınız.

Nesneler kopyalandığında kopyalanan nesneye ait veriler aynen kopya nesnelere geçirilir.

143
! Nesnelerin birbirine atanması ile ilgili örneği görmek için tıklayınız.

Örnek

Program içinde oluşturulan n1 ve n2 nesnelerini göz önüne alalım. Bunlardan n1


nesnesine 1324 değerini atadıktan sonra, onu n2 nesnesine kopyalamak istiyoruz. Bunun
için aşağıda belirtildiği biçimde bir yol izlenir.

Bu program çalıştırıldığında, n1 nesnesinin aldığı değer n2 nesnesi üzerine kopyalanır.

144
5.6. Fonksiyonlara Nesne Atamak
Diğer veri türlerinde olduğu gibi, nesneler de parametreler biçiminde fonksiyonlara
aktarılabilir. Fonksiyonun parametresi sınıf tanımı içinde bildirilir. Fonksiyon her
çağrıldığında bu sınıfa ait bir nesne parametre olarak kullanılır. Fonksiyona, diğer veri
türlerinde olduğu gibi varsayılan olarak nesnelerin değerleri aktarılır.

! Fonksiyonlara nesne atanması ile ilgili örneği görmek için tıklayınız.

Örnek

Fonksiyonlara nesne atama yoluyla sayısal değerlerin karesini bulacağız. Aşağıdaki


program sayi tamsayısını içeren testSinif isimli bir sınıf oluşturmaktadır. Program içinde
yer alan kare() fonksiyonunun testSinif türünde bir parametresi vardır. Bu fonksiyon
nesneye ait sayi değerinin karesini döndürür. Bu yöntem ile 8 ile 11 sayısal değerlerinin
karelerini hesaplayacağız.

145
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilir.

146
Parametre olarak testSinif türünden nesne alan bir
fonksiyon.

5.7. Fonksiyonlardan Nesne Döndürmek


Fonksiyonlara nesneleri nasıl gönderebildiğimizi artık biliyoruz. Benzer biçimde
fonksiyonlardan nesne döndürmemiz mümkündür. Döndürme işlemi return deyimi ile
yerine getirilir.

! Fonksiyonlardan nesne döndürmek ile ilgili program örneğini görmek için tıklayınız.

Örnek

Aşağıdaki program bir fonksiyondan nasıl nesne döndürülebileceğini ortaya koymak


amacıyla düzenlenmiştir.

147
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilir:

148
5.8. Arkadaş Fonksiyonlar
Bir fonksiyonun üyesi olmadığı bir sınıfa ait özel (private) üyelere erişim hakkı olması
istenebilir. Bu durumda arkadaş (friend) fonksiyonlardan yararlanmak söz konusu
olacaktır. Arkadaş fonksiyonlar, sınıfın üye fonksiyonları değildir. Ancak o sınıfa ilişkin
özel elemanlara erişim hakkı vardır. Örneğin bir arkadaş fonksiyon, testSinif sınıfı için şu
şekilde tanımlanabilir:

class testSinif
{
özel üyeler;
...
public:
friend void arkadasFonk();

};

Burada prototipi verilen arkadasFonk() isimli fonksiyon, programın daha sonraki


satırlarında tanımlanacaktır. Bu tanım içinde, public tanımından önce yer alan sınıfın özel
üyelerine erişim hakkı vardır. Arkadaş fonksiyonlar, farklı türdeki iki sınıfın
karşılaştırılmasında kullanılabilir. Bu fonksiyonlar, özellikle iki sınıfın aynı fonksiyonu
paylaşması amacıyla kullanılır.

Arkadaş fonksiyonların sınıfın özel (private) üyelerine erişim hakkı vardır!

! Arkadaş fonksiyonlarla ilgili program örneğini görmek için tıklayınız.

Örnek

149
Aşağıdaki program, testSinif isimli bir arkadaş fonksiyonu tanımlamaktadır. Bu fonksiyon
testSinif sınıfının bir üye fonksiyonu değildir. Söz konusu arkadaş fonksiyonu, testSinif
sınıfının özel kullanıma açık; yani private olarak tanımlanmış üyelerini kullanabilmektedir.
Bu özel üyelerin içerdiği değerleri karşılaştırarak, birbirlerine eşit olup olmadıkları
konusunda bir sonucun üretilmesine yardımcı olmaktadır.

Bu programda testSinif sınıfının a ve b elemanları 10 ve 20 değerlerine sahip olduğuna


göre aşağıdaki sonuç görüntülenecektir:

150
Arkadaş fonksiyonlar sınıfın özel(private) üyelerine erişebilirler.

Bölüm Özeti
Bu bölümde,

• Sınıfların oluşturulmasını
• struct ile sınıf oluşturmayı
• class ile sınıf oluşturmayı
• Kurucu ve yok edici fonksiyonları
• Bir nesnenin diğer nesneye nasıl atandığını
• Fonksiyonlara nesne atamayı
• Fonksiyonlardan nesne döndürmeyi
• Arkadaş fonksiyonları

öğrendik.

151
152
153
154
NESNE DİZİLERİ,
GÖSTERGELER,BAŞVURULAR VE BELLEK
YÖNETİMİ

Bu bölümde nesne dizisi, gösterge, başvuru


kavramları tanımlanmakta; programcılıkta
önemli bir unsur olan etkin bellek yönetimi,
örneklerle açıklanmaktadır.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Nesne dizilerini,
• Nesne göstergelerini,
• Başvuruları,
• Dinamik bellek yönetimini,

öğrenmiş olacaksınız.

6.1. Nesne Dizileri


Dizilerin ne anlama geldiğini önceki bölüm içinde ele alarak incelemiştik. Bu bölümde
nesne dizilerinden söz edeceğiz. Aslında nesne dizileri, aynen değişken dizileri gibi
kullanılır. Nesne dizisinin bildirimi yapılır ve bu bildirime bağlı olarak program içinde
kullanılır.

! Nesne dizileriyle ilgili program örneğini görmek için tıklayınız.

Örnek

Bir diziye 0 ile 4 arasındaki tüm tamsayıları bir nesne dizisine yerleştirdikten sonra bu dizi
içeriğini görüntülemek istiyoruz. Amacımıza uygun C++ programı şu şekilde olabilir:

155
Bu program çalıştırıldığında şöyle bir sonuç görüntülenir:

156
5 adet nesneden oluşan testSinifi türünden nesne dizisi gösterilmiştir.

6.1.1. Nesne Dizilerine Başlangıç Değeri Atamak


Nesne dizilerine başlangıç değerleri yerleştirmek için kuruculardan yararlanılır. Kurucu
fonksiyonların ne olduğunu biliyorsunuz. Böylece nesne dizisinin herbir hücresine
istediğimiz bir değeri atama şansı elde edilmiş olacaktır.

Örnek:
Bir dizi nesnesi oluşturduktan sonra bu diziye beş adet sayısal değer yüklemek ve daha
sonra bunları görüntülemek istiyoruz.

157
Bu program çalıştırıldığında, söz konusu dizi isimli nesneye 2,7,0,3,9 değerleri yüklenir.
Programın sonundaki döngü yardımıyla bu dizinin her bir elemanı görüntülenir. Sonuç şu
şekilde olacaktır:

6.1.2. Çok Boyutlu Nesne Dizileri


Birden fazla boyuta sahip dizilerin tanımlanabileceğini biliyoruz. Benzer biçimde çok
boyutlu nesne dizilerini hazırlamak mümkündür. Bu durumda, boyut sayısı kadar indeks
tanımlamak söz konusu olacaktır.

! Çok boyutlu nesne dizileriyle ilgili program örneğini görmek için tıklayınız.

158
Örnek

İki boyutlu bir dizinin içini sayısal değerlerle doldurduktan sonra, bu dizinin her bir
elemanını görüntülemek istiyoruz. Amacımıza uygun C++ programı şu şekilde olabilir:

Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

159
dizi isimli, testSinifi türünden, 5x3'lük, çok boyutlu bir nesne dizisi gösterilmiştir.
Şekildeki her hücre bir nesne barındırır.

6.2. Nesne Göstergeleri


Gösterge, bir değişkenin bellekteki adresini tutan bir değişken olarak düşünülür. İkinci
bölümde ele alarak incelediğimiz göstergeleri, bir değişken için değil bir nesne için de
belirleyebiliriz. O halde bir nesneye, ona işaret eden bir gösterge yardımıyla da
erişilebilir. Burada dikkat edilecek nokta, nesnelerin üyelerine yapılan başvuruların "->"
işleci ile yapılmasıdır.

! Nesne göstergeleriyle ilgili program örneğini görmek için tıklayınız.

Örnek

Beş adet sayısal değeri bir nesne dizisi içine yerleştirdikten sonra, bu nesne dizisine bir
gösterge atamak ve bu gösterge yardımıyla dizinin tüm elemanlarını görüntülemek
istiyoruz.

160
Sonuç olarak aşağıdaki görüntü elde edilir:

161
Şekil 1: gosterge_adi isminde, testSinif türünde bir nesne göstericinin bildirimi.

6.3. Nesneler İçin this Göstergesi


Bu bölümde C++ için yeni bir gösterge tanımlayacağız. Söz konusu gösterge this adını
taşımaktadır. Bir üye fonksiyon her çağrıldığında, kendisini çağıran nesneye işaret eden
bir göstergeye otomatik olarak aktarılır. Bu göstergeye this anahtar kelimesi ile
ulaşabiliriz. Örneğin aşağıda belirtilen nesne tanımını göz önüne alalım:

class testSinif
{
int a;
..
};

Bir üye fonksiyon a değişkenine bir başlangıç değeri atayabilir:

a=100;

Bu ifade aslında şu şekildeki bir ifadenin kısa gösterimidir:

this -> a=100;

162
! this göstergesiyle ilgili program örneğini görmek için tıklayınız.

Örnek

350 ve 150 gibi değerleri nesne değişkenlerine atadıktan sonra, bunların toplamını
hesaplayarak görüntüleyeceğiz. Bunu yaparken this göstergesinden yararlanacağız.

163
Bu program çalıştırıldığında şu şekilde bir sonuç elde edilecektir:

Program içinde yer alan this göstergesi kullanılmadan da aynı sonuç elde edilecektir.
Burada sadece nesne değişkenleri yerine this göstergesinin nasıl kullanılabileceğini
gösterdik. Sözü edilen this göstergesinin, işleçlerin aşırı yüklenmesi konusunda özel bir
önemi olacaktır.

164
6.4. Başvurular
C++ programlama dilinde bir fonksiyona parametrelerin nasıl geçirileceğini biliyoruz. Bir
fonksiyonu parametreleri kullanarak iki farklı şekilde çağırmak mümkündür:

• Değer ile çağırma


• Başvuru ile çağırma

6.4.1. Değer İle Çağırma


Değer ile çağırma yönteminde çağıran fonksiyondan çağrılan fonksiyona aktarılan
değişken değerlerinde bir değişiklik olsa bile, bu değişiklik çağıran programdaki değişken
değerlerine etki etmez. Bu değişiklikler çağrılan fonksiyon içinde parametrelerin kopyaları
üzerinde gerçekleşir. Bunun anlamı, fonksiyona değişkenlerin kendileri değil, içerdikleri
değerler aktarılmaktadır. Örneğin,

int x=8, y=2, z;


z=topla(x,y);

biçiminde bir tanım yapılacak olursa, topla fonksiyonuna x ve y değişkenleri değil; 8 ve 2


değerleri geçirilecektir.

Bu fonksiyonun çağrıldığı yerde ise, a=8 ve b=2 değerlerini alacaktır. Fakat fonksiyon
içinde a ve b değişikliğe uğrarsa, x ve y bundan etkilenmez. Çünkü fonksiyona x ve y
değil sadece onların değerleri aktarılmıştır.

! Değer ile çağırmayla ilgili program örneğini görmek için tıklayınız.

Örnek

Aşağıdaki programı ele alarak incelemek istiyoruz. Bu programda, çağıran fonksiyon içinde
sayi değişkenine 4 değerini yerleştiriyoruz. Ardından fonk1() isimli fonksiyonu çağırarak,
sayi değişkeninin değerini bu fonksiyona aktarıyoruz. Çağrılan fonksiyonun içinde sayi
değişkenin değerini 15 arttırıyoruz.

165
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

Sonuc 1: 19
Sonuc 2: 4

Buradan şu sonuç çıkmaktadır: Fonksiyonlar değere göre çağrıldığında; çağrılan


fonksiyon içinde parametrenin değeri değiştirilse bile bu değişiklik çağıran programda
aynı isimli değişkenin değerine etki yapmaz.

Değer ile çağırma yönteminde çağrılan fonksiyona sadece değişkenlerin değeri gönderilir,
değişkenler gönderilmez.

6.4.2. Başvuru İle Çağırma


Bir fonksiyonu değer ile çağırarak ona nasıl veri aktardığımızı ve veri döndürdüğümüzü
biliyoruz. Bunun yerine, verilerin bellek adreslerini kullanarak da fonksiyonları
çağırabiliriz. Bu durumda, çağrılan fonksiyon içinde parametre değerleri üzerinde
yapılacak değişikliklerin, onu çağıran fonksiyon üzerinde de etkili olduğu görülecektir.
Böyle bir durumda, fonksiyon içinde değişken değerleri değiştirildiğinde, bu yeni değerler
fonksiyon dışında da geçerli olur.

C'de başvuru yöntemi göstergeler kullanılarak yerine getirilmektedir. Bu durum C++' da


da geçerlidir. Ancak C++'da, bunun yerine doğrudan başvurular (referanslar) kullanılır.

166
Aslında başvuru, bir gösterge gibi hareket eden bir tür kapalı gösterge olarak da
değerlendirilebilir.

Bir değişkene başvuru vermek için C++ 'da "&" işareti kullanılır. Örneğin &x veya & x
biçiminde bir başvuru tanımlanabilir.

! Başvuru ile çağırmayla ilgili program örneğini görmek için tıklayınız.

Örnekler

Örnek1:
Aşağıdaki C++ programını göz önüne alalım. Burada gösterge kullanmak suretiyle, bir
fonksiyona sayi isimli değişkenin değeri atanmaktadır. Söz konusu değişkene main()
içinde 4 değeri yerleştirildikten sonra fonk1() fonksiyonu yardımıyla, bu değişkenin
göstergesi kullanılarak 15 değeri ekleniyor.

Yukarıdaki program çalıştırıldığında şu şekilde bir sonuç elde edilir:

Aynı programı bu kez göstergeleri kullanmadan, başvurular yardımıyla yazmak istiyoruz.


Amacımıza uygun C++ programı şu şekilde olabilir:

167
Örnek2:
Başvuruları açıklamak üzere aşağıdaki programı düzenliyoruz. Bu programda,
onceSonra fonksiyonuna değişkenlerin başvuruları geçirilmektedir.

Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

168
Başvuru ile çağırma yönteminde çağrılan fonksiyona değişkenlerin bellek adresleri
aktarılır. Böylece çağrılan fonksiyon içinde bu değişkenlerin değerleri değiştirilebilir.

6.4.3. Nesnelerin Başvuru Biçiminde Fonksiyonlara


Geçirilmesi
Bir nesnenin bir fonksiyona nasıl geçirildiğini biliyoruz. Böyle bir durumda, ilgili nesnenin
bir kopyası oluşturularak, bu kopyası fonksiyona aktarılıyordu. Bunun yerine, nesnenin bir
kopyasını oluşturmadan, bir referans biçiminde fonksiyona geçirilmesi söz konusu olabilir.
Bu durumda, & işareti yardımıyla nesne bir başvuru olarak fonksiyona geçirilir.

! Nesnelerin başvuru biçiminde fonksiyonlara geçirilmesiyle ilgili program örneğini


görmek için tıklayınız.

Örnekler

Aşağıdaki C++ programını göz önüne alalım. Bu programda, testSinif isimli nesneyi
kare() fonksiyonuna bir başvuru ile geçirmek istiyoruz.

169
Bu program çalıştırıldığında, aşağıda belirtilen sonuç elde edilir. Bu programda

kare(testSinif &d)

deyimi ile d başvurusu testSinif isimli nesneye geçiriliyor.

/*BURADAKİ İFADEDE BİR HATA VAR,testSinif isminde bir nesne yok,sanırım o d olacak
HOCAYA SOR*/

170
Başvuru biçiminde nesne alan bir fonksiyonun bildirimi.

6.5. Dinamik Bellek Yönetimi


Şu ana kadar incelediğimiz tüm konularda oldukça küçük örnekleri ele alarak inceledik.
Bu örneklerde yer alan değişkenler küçük boyutlu oldukları için, çalıştırılmaları esnasında
bellekte yer ile ilgili herhangi bir sorun yaşanmadı. Bazı uygulamalarda bellekte yer
sorunu yaşanabilir. Böyle durumlarda belleğin etkin biçimde yönetilmesi söz konusu
olacaktır.

Standart C 'de bellek yönetimi amacıyla iki fonksiyondan yararlanılır. Bunlardan birincisi
malloc(), diğeri ise free() fonksiyonudur. Bellekte bir yer ayırma söz konusu ise
malloc(); ayrılan yerin iptal edilmesi gerektiğinde ise free() fonksiyonu kullanılır.

C++ 'da bu iki fonksiyonun kullanılabilmesi mümkün olmasına karşılık, belleği dinamik
biçimde yönetmek üzere, onların yerine new ve delete işleçleri kullanılır. C++'da
bellekte yer ayırmak için new, ayrılan yeri iptal etmek için delete işleci kullanılır.

Dinamik bellek yönetimi sayesinde daha az bellek


kullanan programlar yaratmak mümkündür.

171
6.5.1. Bellekte Yer Ayırmak
Bellekte dinamik olarak bir yer ayırmak gerektiğinde new işlecine başvurulur. Bu işleci şu
şekilde kullanıyoruz :

gösterge = new tür;

Tanımdan görüldüğü gibi, new işlecini bir tür tanımı takip etmektedir. Tür, bellekte yer
ayrılacak nesnenin türüdür. Bu işleç, türü belli olan nesneyi taşıyacak kadar genişliği
olan, dinamik olarak ayrılmış belleğe bir gösterge döndürür.

Bellekte yer ayırma işleci olan new, belirtilen nesnenin türüne göre bellekte yeterli yer
ayırır. O nedenle ne kadar yer gerekli olduğunu anlamak için sizeof deyimini kullanmaya
gerek yoktur. C'de malloc() kullanılırken böyle bir yol izleniyordu. Dinamik olarak ayrılan
bir nesneye başlangıç değeri atamak söz konusu ise,

gösterge = new tür (başlangıç değer);

biçiminde bir tanım yapılır.

Örnek :
Bir tamsayı için bellekte yer ayırmak istiyoruz. Bunun için aşağıda belirtilen kodlar
yazılabilir :

int *g;
g=new int;

! new işlecinin işleyişini görmek için "new" düğmesine tıklayınız.

172
6.5.1.1. Diziler İçin Yer Ayırma
Nesne çok sayıda elemandan ibaret ise, yani bir dizi söz konusu ise,

gösterge = new tür [elemanlar]

biçiminde bir tanım yapılır.

Örnek:
Aşağıda belirtildiği biçimde bellekte yer ayırdığımızı varsayalım.

int *g;
g = new int [5];

Birinci satırda türü belli olan bir elemanın belleğe yerleştirileceğini belirtmektedir. İkinci
satırda ise, 5 elemanlı bir dizinin bu belleğe atanmasını sağlar. Bu durumda, program
çalıştırıldığında bilgisayarınızın işletim sistemi bellekte türü int olan 5 elemanlık bir yer
ayırır ve g olarak atanmış olan göstergenin başlangıcına gösterge döndürür. Bu durumda,
g beş tamsayı eleman için geçerli bellek bloklarının başlangıcını gösterecektir.

g göstergesi, beş tamsayı elemanı için ayrılmış


olan bellek bloklarının başlangıcını gösterir.

6.5.1.2. Yer Ayırma Hatası


Dinamik bellek yönetimi işletim sistemi aracılığıyla gerçekleşir. Çok görevli (multitask)
işletim sistemlerinde aynı anda çok sayıda farklı program çalışabilmektedir. Böyle olunca,
bellek üzerinde ayrılacak alanlarla ilgili olarak problemler çıkabilir. Bu gibi durumları
denetlemek üzere C++ programında bazı kodlar yazılmalıdır. Aşağıda if deyimi ile
denetleme işlemini yapıyoruz:

! Yer ayırma denetimi yapan program örneğini görmek için tıklayınız.

173
Bir tamsayı için bellekte yer ayırmak istiyoruz. Eğer bellekte yer ayırma ile ilgili bir sorun
çıkarsa bir uyarı görüntülenecektir.

6.5.2. Belleği Serbest Bırakmak


Bellekte dinamik olarak new işleci ile ayrılan yeri iptal etmek veya bir başka deyişle
serbest bırakmak için delete işleci kullanılır. Bu işleç, bellekte ayrılan bir alanı iptal
etmek için,

delete gösterge;

biçiminde tanımlanır. Eğer bir dizi için ayrılan bellek alanının serbest bırakılması söz
konusu ise,

delete [ ] gösterge;

biçiminde kullanılır.

! Ayrılan bellek alanının serbest bırakılmasıyla ilgili program örneğini görmek için
tıklayınız.

Örnek1:
Önceki kısımda ele alarak inclediğimiz örnekte bir tamsayı değişken için dinamik
bellek alanı alanı ayırmıştık. Bu kez ayrılan alanı serbest bırakıyoruz.

174
Örnek2:
Bir dizi içine belirlenen sayıda değeri yerleştirdikten sonra görüntüleyeceğiz. Diziye
bellekte dinamik yer ayıracağız ve görüntüleme ardından bellekte ilgili alanı serbest
bırakacağız.

175
Bu fonksiyon içinde yer alan atol() fonksiyonu, katarı long int veri türüne çevirmek için
kullanıldı. Yukarıdaki program çalıştırıldığında, klavyeden belirtilen sayıda tamsayı
girilmesi beklenir. Bu tamsayıla bir dizi içine kaydedilir ve ardından dizinin her bir elemanı
görüntülenir.

! delete işlecinin işlevini görmek için "delete" düğmesine tıklayınız.

Bölüm Özeti
Bu bölümde,

• Nesne dizilerini
• Nesne göstergelerini
• Başvuruları
• Dinamik bellek yönetimini

öğrendik.

176
177
178
179
180
FONKSİYONLARA AŞIRI YÜKLEME

Fonksiyonlara aşırı yükleme, Nesneye Yönelik


Programlamanın temel özelliklerinden biridir. Bu
bölümde fonksiyonlara aşırı yükleme, örneklerle
açıklanmaktadır.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Fonksiyonlara yeni görevler yüklemeyi,


• Fonksiyonlara aşırı yükleme nasıl yapıldığını,
• Varsayılan argümanları,
• Kurucu fonksiyonlara aşırı yüklemeyi,
• Kurucu fonksiyona varsayılan argüman
geçirmeyi,

öğrenmiş olacaksınız.

7.1. Fonksiyonlara Yeni Görevler Yükleme


Çok biçimlilik (polimorfizm), genel anlamda bir adın, örneğin bir fonksiyon adının
birbirleriyle ilişkili, fakat teknik açıdan farklı iki veya daha fazla amaç için kullanabilmesi
yeteneğidir. Nesneye Yönelik Programlama kavramı içinde ise çok biçimlilik, bir adın
genel bir iş (action) sınıfını belirlemesine olanak sağlar. C++'da çok biçimlilik özelliği,
fonksiyonlara yeni görev yükleme veya bir başka değişle fonksiyonlara aşırı yükleme
(function overloading) ile sağlanabilir.

C programlama dilinde bir sayısal değerin mutlak değerini almak söz konusu olduğunda
karşımıza abs(), labs() ve fabs() gibi birtakım farklı kitaplık fonksiyonlar çıkmaktadır. Bu
kitaplık fonksiyonlarından birincisi bir tamsayının, ikincisi bir uzun tamsayının ve
üçüncüsü ise bir gerçel sayının mutlak değerini bulmaktadır. Tüm bunların yerine sadece

181
bir fonksiyonun tanımlanması, bizi daha etkin programlar yazmaya itecektir. O halde bu
üç fonksiyon yerine sadece bir abs() fonksiyonun bulunması işimizi kolaylaştıracaktır.

C++'da sayılan bu üç fonksiyonun yerine sadece bir abs() fonksiyonun kullanılması


mümkündür. Yani üç fonksiyon adı yerine, program içinde sadece bir fonksiyon adı
kullanılabilir. Fonksiyonun hangi amaç için kullanıldığına ise derleyici karar versin. C+
+'da bu olanak fonksiyonlara aşırı yükleme yapılarak elde edilir.

Bu durumda şöyle bir soru sorulabilir:

• Farklı işlemleri yapan fonksiyonlar aynı isimle çağrılırsa, bir karışıklık olmayacak
mıdır?
• Derleyici hangi fonksiyonu çağırdığını nasıl anlayacaktır ?

Fonksiyonu çağırmak için kullanılan veri tipi, gerçekte hangi fonksiyonun


çalışacağını belirler. Bir fonksiyona aşırı yükleme yapmak için, o fonksiyonun her farklı
durumu için ayrı ayrı bildirimi yapılır. Derleyici parametre sayısını ve veri türüne bakarak
hangi fonksiyonu kullanacağına karar verir. Böylece bir fonksiyon adının birkaç farklı
amaç için kullanılması mümkündür. Fonksiyonlara aşırı yükleme yapılarak bu tür bir çok
biçimlilik sağlanabilir.

Mutlak değer döndüren abs() fonksiyonu, her parametre türü için ayrı ayrı bildirilerek
aşırı yüklenmiştir. Derleyici bu fonksiyonu çağıran veri türüne göre uygun bildirimi çağırır.

7.2. Fonksiyonlara Aşırı Yükleme Nasıl Yapılır?


Kullanıcı tarafından tanımlanan fonksiyonlara aşırı yükleme yapmak için şöyle bir yol
izlenir:

182
! Mutlak değer bulan bir fonksiyonun aşırı yüklenmesiyle ilgili program örneğini görmek
için tıklayınız.

Tamsayı yada tek duyarlıklı bir sayısal değerin mutlak değerini bulmak istiyoruz. Normal
olarak, böyle bir işlem için, farklı veri türleri olduğu için iki ayrı isme sahip fonksiyon
tanımlarız. Ancak biz aynı bir fonksiyon ismine iki fonksiyon görevini yüklemek istiyoruz.
Bunun için, mutlakDeger() isimli bir fonksiyon prototipi belirledikten sonra, her bir
prototip için ayrı ayrı iki fonksiyon tanımlıyoruz. Ancak her iki fonksiyonun adı
mutlakDeger() olacaktır.

183
#include <iostream>
using namespace std;
// Fonksiyon prototipleri..
// mutlakDeger fonksiyonuna iki kere yükleme yapılacak..
int mutlakDeger(int i);
float mutlakDeger(float x);
// Tamsayının mutlak değerini hesaplar..
int mutlakDeger(int tamSayi) {
if (tamSayi < 0)
return (tamSayi * -1);
else
return (tamSayi);
}

184
// Tek duyarlıklı sayının mutlak değerini hesaplar..
float mutlakDeger(float tekDuyarlikli) {
if (tekDuyarlikli < 0.0)
return (tekDuyarlikli * -1.0);
else
return (tekDuyarlikli);
}
void main()
{
int tamCevap;
float tekCevap;
int tamSayi = -50;
float tekDuyarlikli = -3.426;
// Tam sayının mutlak değeri..
tamCevap = mutlakDeger(tamSayi);
cout << tamSayi << " Sayisinin mutlak degeri=";
cout << tamCevap << "\n";
// Tek duyarlıklı sayının mutlak değeri..
tekCevap = mutlakDeger(tekDuyarlikli);
cout << tekDuyarlikli << " Sayisinin mutlak degeri=";
cout << tekCevap << "\n";
}

Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç görüntülenir:

! Tarih bilgisini ekrana yazan bir fonksiyonun aşırı yüklenmesiyle ilgili program örneğini
görmek için tıklayınız.

Fonksiyonlara aşırı yükleme işlemine en fazla gereksinim duyulan durumlardan birisi,


tarih verileriyle yapılan işlemlerdir. Örneğin tarih bilgisi katar biçiminde belirtilebileceği
gibi, üç ayrı tamsayı biçiminde de belirtilebilir. Bu iki farklı görevi tek bir fonksiyon ismine
yükleyen C++ programı şu şekilde olabilir:

185
#include <iostream>
using namespace std;

// tarih fonksiyonuna iki kere yükleme yapılacak..


void tarih(char *tarih);
void tarih(int gun, int ay, int yil);

// Katar girişi için tarih


void tarih(char *tarih) {
cout << "Tarih: " << tarih;
cout << "\n";
}

// Tamsayı değerlerle tarih


void tarih(int gun, int ay, int yil) {
cout << "Tarih: ";
cout << gun << "/" << ay << "/" << yil;
cout << "\n";
}

int main()

186
{
tarih("16/11/2002");
tarih(16,11,2002);
return 0;
}
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç görüntülenir:

7.3. Varsayılan Argümanlar


C++'da ‚ bir fonksiyona aşırı yüklemenin nasıl yapılabildiğini gördük. Bunun için
fonksiyonun değişik biçimlerini birer prototip biçiminde tanımlayarak, program içinde bu
isme bağlı olarak farklı biçimlerde kullanıyorduk.

Fonksiyonların aşırı yüklenmesine benzer bir diğer olanak, varsayılan argümanlardır.


Aslında varsayılan argümanlar, fonksiyonların aşırı yüklenmesine benzer. Fonksiyonlar
için başlangıç parametreleri belirlenir. Program içinde bu fonksiyon herhangi bir argüman
tanımlamadan kullanılırsa, varsayılan (default) argümanların kabul edildiği anlaşılır.

! Bir fonksiyona başlangıç argümanı atamakla ilgili program örneğini görmek için
tıklayınız.

Program içinde mesaj() isimli bir fonksiyonu tanımlayarak kullanacağız. Bu mesaj için bir
başlangıç ifadesi tanımlanmıştır. Bu mesaj fonksiyonuna başka bir mesaj atayarak
kullanabiliriz. Ancak bu durumda başlangıçta atanan mesaj değişmez.

187
#include <iostream>
using namespace std;

// Fonksiyon prototipi ve başlangıç değer ..


void mesaj(char m[]="mesaj 1..");

// Fonksiyon tanımlanıyor..
void mesaj(char m[]) {
cout << m;
cout << "\n";
}
void main()
{
mesaj(); //Varsayılan argüman kullanılıyor..
mesaj("mesaj 2.."); //Diğer argüman geçiriliyor..
mesaj(); //Varsayılan argüman kullanılıyor..
}
Bu program çalıştırıldığında şu şekilde bir sonuç elde edilir :

! Bir fonksiyona birden fazla başlangıç argümanı atamakla ilgili program örneğini görmek
için tıklayınız.

188
Bir fonksiyona birden fazla argüman atandıktan sonra, bu argümanları fonksiyonlara
geçirmek mümkündür. Bunun için önce prototip içinde başlangıç değerler atanır ve
bunların varsayılan argüman olması sağlanır. Ardından değişik argümanlar atanarak buna
bağlı olarak sonuçlar elde edilir. Argüman listesi içinde herhangi bir argümana atama
yapılmaz ise, varsayılan argümanın geçerli olduğu görülür.

#include <iostream>
using namespace std;

// Fonksiyon prototipi ve başlangıç değer ..


void fonk(int i=5, long j=55678, float x=10.34,
char c='Z', double d=4.533539);

// Fonksiyon tanımlanıyor..
void fonk(int i, long j, float x, char c, double d)
{
cout << i<< "," << j << ",";
cout <<x << "," << ch<<","<<d;
cout << "\n";
}

void main()
{
fonk();
fonk(2);
fonk(2,19032);

189
fonk(3,25400,32.63);
fonk(8,29321,55.89,'A');
fonk(1,45422,66.99,'B',1.934290);
}

Bu program çalıştırıldığında aşağıdaki sonuç elde edilir :

Başlangıç parametreleri atanmış bir fonksiyonun çalışma algoritması.

7.4. Kurucu Fonksiyonları Aşırı Yükleme


Bir fonksiyona aşırı yüklemenin nasıl yapılacağını biliyoruz. Bu noktadan hareketle,
kurucu fonksiyonlara aşırı yükleme yapılıp yapılamayacağını öğrenmek istiyoruz. Kurucu
fonksiyonları aşırı yüklemek mümkündür. Üstelik C++ programlarında sıkça yapılan bir
işlemdir. Kurucu fonksiyona aşırı yükleme yapılarak, belirli bir nesnenin başlangıç
durumunu seçmekte esnek bir yapı kazandırılabilir.

! Kurucu fonksiyonları aşırı yüklemeyle ilgili program örneğini görmek için tıklayınız.

190
Bir kurucu fonksiyona aşırı yükleme yapmak istiyoruz. Bu amaçla aşağıdaki programı
hazırlıyoruz.

#include <iostream>
using namespace std;

class testSinif {
float deger;
public:
// Kurucu için aşırı yüklemeler tanımlanıyor..
testSinif(int n) {
deger=n;
}
testSinif(int n, float m) {
deger=n+m;
}
testSinif(int n, float m, long r) {

191
deger=(n+m)/r;
}
float okuDeger() {
return deger;
}
};
void main()
{
// Kurucu için yüklemeler yapılıyor..
testSinif s1(10);
testSinif s2(10, 12.25);
testSinif s3(25, 425, 50000);

cout << "Sayi 1=" << s1.okuDeger() << "\n";


cout << "Sayi 2=" << s2.okuDeger() << "\n";
cout << "Sayi 3=" << s3.okuDeger() << "\n";
}
Bu program çalıştırılırsa şu şekilde bir sonuç elde edilir :

Kurucu fonksiyonları aşırı yüklenmiş bir sınıf.

7.5. Kurucu Fonksiyona Varsayılan Argüman Geçirmek

192
Varsayılan argümanlar, fonksiyonlar için başlangıç parametrelerini belirlemekte
kullanılıyordu. Bu tür argümanları kurucu fonksiyonlar için de tanımlayarak kullanmak
mümkündür. Kurucu fonksiyon için önce bir başlangıç değeri tanımlanır. Fonksiyon
program içinde argümansız olarak kullanılırsa, bu başlangıç değerinin argüman olarak
atandığı varsayılır. Birçok durumda, bir veya daha fazla sayıda varsayılan argüman
tanımlanarak, bir kurucu fonksiyon aşırı yüklenmekten kurtarılabilir.

Örnek: Aşağıdaki programda bir kurucu fonksiyon tanımlanmış, bu fonksiyon için bir
varsayılan argüman tanımlanmıştır. Varsayılan argüman deger=5 olarak belirlenmiştir.
Bunun anlamı, söz konusu fonksiyon çağrıldığında, eğer herhangi bir argüman
geçirilmemiş ise, deger=5 argümanı varsayılan olan kabul edilecektir.

#include <iostream>
using namespace std;

class testSinif {
int deger;
public:
// Kurucu fonksiyon için
// varsayılan argüman tanımlanıyor..
testSinif(int n=5) {
deger=n;
}
int okuDeger() {
return deger;

193
}
};
int main()
{
testSinif s1; //Varsayılan argüman kullanılıyor..
testSinif s2(10);
cout << "Sayi 1=" << s1.okuDeger() << "\n";
cout << "Sayi 2=" << s2.okuDeger() << "\n";
cout << "Sayi 1=" << s1.okuDeger() << "\n";
return 0;
}
Bu fonksiyonda,

testSinif(int n=5)

biçimindeki bir tanım, kurucu fonksiyon için n=5 olmak üzere bir varsayılan argüman
belirlemektedir. Böyle bir durumda,

testSinif s1;

satırı kurucu fonksiyonu varsayılan değere göre çalıştıracaktır. Bunun bir sonucu olarak,

cout << "Sayi 1=" << s1.okuDeger() << "\n";

satırının çalışması sonucunda,

Sayi 1=5

sonuç satırı görüntülenir. Buna karşılık,

testSinif s2(10);

satırının çalışması sonucunda argüman olarak 10 değeri kurucu fonksiyona aktarılır ve

cout << "Sayi 2=" << s2.okuDeger() << "\n";

ile bu yeni argümana bağlı olarak sonuç görüntülenir. Son olarak yeniden,

cout << "Sayi 1=" << s1.okuDeger() << "\n";

sartırı ile varsayılan argüman yeniden kullanılır. Buradaki amaç, varsayılan argümana
bağlı olarak kurucu fonksiyonun döndürdüğü değerin değişmediğini göstermektir.
Programın çalışması sonucunda aşağıdaki görüntü elde edilir:

194
Bölüm Özeti
Bu bölümde,

• Fonksiyonlara yeni görevler yüklemeyi


• Fonksiyonlara aşırı yüklemenin nasıl yapıldığını
• Varsayılan argümanları
• Kurucu fonksiyonları aşırı yüklemeyi
• Kurucu fonksiyona varsayılan argüman
geçirmeyi

öğrendik.

195
196
197
198
İŞLEÇLERE AŞIRI YÜKLEME

C++'da fonksiyonlar gibi işleçler de aşırı yüklenebilir.


Böylece bir işleç birden fazla işlevi yerine getirebilir.
Bu bölümde işleçlere aşırı yükleme yapılışı örneklerle
anlatılmaktadır

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Üye işleç fonksiyonlarını,


• Aritmetik işleçleri aşırı yüklemeyi,
• Karşılaştırma işleçlerini ve mantıksal işleçleri aşırı
yüklemeyi,
• ++ ve - işleçlerini aşırı yüklemeyi,

öğrenmiş olacaksınız.

8.1. Üye İşleç Fonksiyonları


İşleçlere bilinen özellikleri dışında başka özellikler kazandırmak mümkündür. C++'da bu
işleme işleçlerin aşırı yüklenmesi adı verilmektedir. İşleçler aşırı yüklendiğinde asıl
anlamlarını kaybetmezler. Aşırı yükleme onlara ilave anlamlar kazandırır.

Aslında şu ana kadar olan derslerimizde iki aşırı yüklenmiş işleci sık sık kullandık. Bu
işleçler << ve >> işleçleridir. Bu işleçler normal olarak kaydırma işleci olarak kullanılır.
Ancak C++'da bu işleçlere başka bir anlam daha yüklenmiştir. Bu işleçler, bildiğiniz gibi
giriş-çıkış işlemlerini gerçekleştirmekte de kullanılabilmektedir.

199
<< işlecinin asıl anlamı ve yüklenen anlamı.

İşleçleri aşırı yüklemek için öncelikle bir işleç fonksiyonu oluşturmak gerekmektedir. İşleç
fonksiyonları genellikle tanımlandıkları sınıfa üyedirler. Üye işleç fonksiyonları şu şekilde
tanımlanmaktadır:

türü sınıf_adı :: operator işleç (argüman listesi)


{
// Fonksiyonun gerçekleştireceği işlemler

}

Tanımda görüldüğü gibi, operatör anahtar kelimesinin hemen ardından, aşırı yüklenecek
işleç tanımlanmaktadır. Örneğin, + işlecini aşırı yüklemek için, operator+ biçiminde bir
tanım yapılır. Bazı işleçlere aşırı yükleme yapılamaz. Aşırı yükleme yapılamayan işleçler
şunlardır:

. :: ?

Ayrıca önişlemci işleçlerini de aşırı yükleyemeyiz!

8.2. Aritmetik İşleçlere Aşırı Yükleme


İşleçlere aşırı yükleme işlemini gerçekleştirebilmek için, öncelikle operatör anahtar
sözcüğüyle başlayan bir fonksiyonun tanımlanması gerektiğini biliyoruz. Örneğin aritmetik
anlamda kullanılan + işaretine, toplama özelliği yanısıra bir başka görev yüklenebilir.
Bunun için, operator+ fonksiyonunun tanımlanması gerekir. Benzer biçimde -, / ve *
işleçlerine de normal görevleri yanısıra başka görevler yüklenebilir.

"+" işlecini aşırı yüklemek için "İşleci aşırı yükle" düğmesine tıklayınız.

200
! + işlecinin aşırı yüklenmesiyle ilgili örneği görmek için tıklayınız.

İki nokta koordinatlarının a(X1,Y1) ve b(X2,Y2) olduğunu varsayalım. Bu iki noktanın


koordinatlarının toplamı, karşılıklı elemanlarının toplamı olarak elde edilebilir. Yani,
toplama sonucunda 5+9=14, 7+3=10 değerleri hesaplanır. İki noktanın koordinatları
verildiğinde, bunları karşılıklı olarak toplayan bir işleç tanımlamak istiyoruz. Bu işleç
kullanıldığında, koordinat değerleri karşılıklı olarak toplanacaktır.

Aşağıdaki programda operator+ fonksiyonu, X koordinatlarının toplamını x'de, Y


koordinatlarının toplamını ise y'de bulunduran testSınif isimli bir nesne döndürmektedir.
Elde edilen sonuç, gecici isimli bir nesne içinde saklanmaktadır.

201
#include <iostream>
using namespace std;

// Sınıf tanımlanıyor..
class testSinif {
public:
int x,y;
testSinif () {x=0;y=0;}
testSinif (int,int);
testSinif operator+ (testSinif);
};

testSinif::testSinif (int a, int b) {

202
x = a;
y = b;
}
// + işleci testSinif sınıfı için aşırı yükleniyor..
testSinif testSinif::operator+ (testSinif param) {
testSinif gecici;
gecici.x = x + param.x;
gecici.y = y + param.y;
return (gecici);
}
int main ()
{
testSinif a (5,7);
testSinif b (9,3);
testSinif c;
// İki nesne toplanıyor..
// + işleci operator+() işlevini çağırır..
c = a + b;
cout << c.x << "\n";
cout<< c.y << "\n";
return 0;
}

Yukarıdaki program çalıştırıldığında şu şekilde bir sonuç elde edilir:

8.3. Karşılaştırma İşleçlerini ve Mantıksal İşleçleri Aşırı


Yükleme
Aritmetik işlem işaretlerine aşırı yüklemenin nasıl yapılabildiğini gördük. Benzer biçimde
karşılaştırma işleçlerine ve mantıksal işleçlere aşırı yükleme yapılabilir. Yükleme
yapabileceğimiz karşılaştırma işleçleri ve mantıksal işleçler aşağıda yer almaktadır. Bu
işleçleri 2. bölümde ele alarak incelemiştik.

203
İşleç Anlamı İşleç Anlamı

== Eşit && Ve

!= Eşit Değil || Veya

> Büyük ! Değil

< Küçük

>= Büyük veya Eşit

<= Küçük veya Eşit

! Karşılaştırma işleclerinin aşırı yüklenmesiyle ilgili program örneğini görmek için


tıklayınız.

Önceki örnekte olduğu gibi, iki noktanın koordinatlarını göz önüne alalım. Bu
koordinatların a(X1,Y1) ve b(X2,Y2) olduğunu varsayalım. İki noktanın koordinatlarını
karşılıklı olarak karşılaştırmak istiyoruz. İki noktanın karşılıklı değerlerinin birbirlerinden
büyük veya küçük olup olmadıklarını test etmek istiyoruz.

204
#include <iostream>
using namespace std;

205
// Sınıf tanımlanıyor..
class testSinif {
public:
int x,y;
testSinif () {x=0;y=0;}
testSinif (int,int);
int operator< (testSinif param);
int operator> (testSinif param);
};
testSinif::testSinif (int a, int b) {
x = a;
y = b;
}
// > işleci testSinif sınıfı için aşırı yükleniyor..
int testSinif::operator> (testSinif param) {
return x > param.x && y > param.y;
}
// < işleci testSinif sınıfı için aşırı yükleniyor..
int testSinif::operator< (testSinif param) {
return x < param.x && y < param.y;
}
void main ()
{
testSinif a (5,1);
testSinif b (9,3);
// Yüklenmiş işlecin kullanımı…
if (a > b) {
cout << "a nin her iki degeri b den buyuk";
cout << "\n";
}
else {
cout << "a nin her iki degeri b den buyuk degil";
cout << "\n";
}
if (a < b) {
cout << "a nin her iki degeri b den kucuk";
cout <<"\n";
}
else {
cout << "a nin her iki degeri b den kucuk degil";
cout << "\n";
}
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

206
8.4. ++ Artırma ve -- Eksiltme İşleçlerini Aşırı Yükleme
Artma ve eksiltme işlemlerinde kullanılan ++ ve -- işleçleri, aynen aritmetik işleçlerde
olduğu gibi aşırı yüklenebilirler. Ancak aritmetik işleçlerin iki terim arasında
kullanılabilmesine karşılık; artırma ve eksiltme işleçleri tek terim için kullanılır. Bu
işleçlere ilişkin üye fonksiyon herhangi bir parametre almaz.

Örnek:
Bir noktanın koordinatını gözönüne alalım. ++ işlecine bir başka yetenek kazandırarak;
koordinatın x ve y değerini birlikte 1 artırmak istiyoruz. Böyle bir amaca ulaşmak için
aşağıdaki programı hazırlıyoruz.

207
#include <iostream>
using namespace std;

// Sınıf tanımlanıyor..
class testSinif {
public:
int x,y;
testSinif () {x=0;y=0;}
testSinif (int,int);
testSinif operator++ ();
void oku (int &d1,int &d2){
d1=x;

208
d2=y;
};
};
testSinif::testSinif (int a, int b) {
x = a;
y = b;
}
// ++ işleci testSinif sınıfı için aşırı yükleniyor..
testSinif testSinif::operator++ () {
x++;
y++;
return *this;
}
int main ()
{
testSinif ts(5,7);
int k1,k2;
// Nesne değeri bir artırılıyor..
++ts;
ts.oku(k1,k2);
cout << "(" << k1 << "," << k2 << ")";
cout<< "\n";
return 0;
}
Program çalıştırılırsa aşağıda belirtilen sonuç elde edilir:

Bölüm Özeti
Bu bölümde,

• Üye işleç fonksiyonlarını


• Aritmetik işleçlere aşırı yüklemeyi
• Karşılaştırma işleçlerini ve mantıksal işleçleri aşırı
yüklemeyi
• ++ ve - işleçlerini aşırı yüklemeyi

öğrendik.

209
210
211
212
KALITIM

NYP'nin en kullanışlı özelliklerinden birisi de kalıtım


kavramıdır. Kalıtım, benzer özellikler taşıyan sınıflarda
aynı özelliklerin tekrar tekrar yazılmasını önler.
Varolan bir sınıf yapısına sadece birkaç özellik daha
ekleyerek yeni sınıf yapısını elde etmiş ve aynı şeyleri
tekrar yazmaktan kurtulmuş olursunuz.

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• Kalıtımın ne olduğunu,
• Hiyerarşi dışındaki erişimleri engellemeyi,
• Türetilmiş sınıfların tanımlanmasını,
• Kurucu ve yok edici fonksiyonların kalıtım
işlemlerinde kullanılmasını,
• Çoklu kalıtımı,

öğrenmiş olacaksınız.

9.1. Kalıtım Nedir?


Kalıtım (inheritance), bir nesnenin diğer bir nesnenin özelliklerini kazanması işlemidir.
Bu önemlidir, çünkü hiyerarşik sınıflandırmayı destekler. Birçok bilgi, hiyerarşik (yani
ast-üst) sınıflandırma ile yönetilebilir hale gelir.

213
Hiyerarşiler kullanılmaz ise, her nesnenin özelliklerinin açıkça belirtilmesi gerekir. Fakat
kalıtım kullanımıyla, bir nesnenin, onu sadece kendi sınıfının içinde eşsiz yapan
özelliklerini belirtmemiz yeterlidir. Genel niteliklerini ebeveyninden alabilir. Böylece, bir
nesneyi, daha genel bir durumun özel bir örneği yapan şey kalıtım mekanizmasıdır.

Kalıtım, nesneye yönelik programlamanın temel özelliklerinden biridir. C++'da kalıtım, bir
sınıf tanımı içine bir başka sınıfın dahil edilmesini sağlar. Böylece yukarıdan aşağı doğru
bir sınıflar hiyerarşisi oluşturulur. Bu hiyerarşide en başta gelen sınıf temel sınıf olarak
değerlendirilir. Temel sınıf en genel sınıfı tanımlamaktadır. Bu temel sınıftan diğer sınıflar
türetilir. Bu tür sınıflara türetilmiş sınıf adını veriyoruz.

Türetilmiş sınıf, temel sınıfın tüm özelliklerini taşır. Ayrıca kendine özgü bazı özellikler
ekleyebilir.

9.2. Hiyerarşi Dışındaki Erişimi Engelleme


C++'da sınıfları anlatırken, public ve private anahtar kelimelerinden söz etmiştik. Bir
sınıfın bazı bölümlerini programın diğer bölümleri tarafından erişilebilir hale getirmek söz
konusu ise, public anahtar kelimesi ile bu belirtilir. Herhangi bir tanım yapılmaz ise, sınıf
içinde belirtilen tüm bölgelerin özel bölge olduğu, yani private kabul edildiği varsayılır. Bu
durumda söz konusu bu bölgelerin tümüyle sınıfın üyesi bir fonksiyon tarafından
kullanılabileceği anlaşılır.

Buradan şu anlaşılmaktadır: Bir public üyeye, program içindeki diğer tüm fonksiyonlar
tarafından erişilebilir. Bir private üyeye sadece sınıfın üye fonksiyonları tarafından ya da
friend fonksiyonları tarafından erişilebilir. Sayılanlar dışında yeni bir üye türünden söz
etmek istiyoruz. Bu üye protected adıyla bilinir. Bu durumda bir sınıf Şekil1'deki gibi
tanımlanmaktadır.

214
Şekil 1: protected veri ve fonksiyonların tanımı.

Bir protected üye private üyeye benzer. Ancak kalıtım ile ilgili olarak ilave bir özelliği
bulunmaktadır. Türetilmiş bir sınıfı gözönüne alalım. Bu sınıf temel sınıftan bazı verileri
kalıtım yoluyla kazanacaktır. Temel sınıfın tüm public üyeleri türetilmiş sınıfın public
üyeleri haline gelir. Bu nedenle artık türetilmiş sınıf tarafından da erişilebilir durumdadır.

Ancak temel sınıfın private üyelerine türetilmiş sınıf tarafından erişilemez. Bu durumda,
bir üyeyi hem private olarak tutmak, hem de sadece türetilmiş sınıf tarafından
kullanılmasını sağlamak söz konusu ise protected üyelere başvurulur. Böylece, bir üyeye
sınıf hiyerarşisi içinden erişebilmesini; ancak hiyararşi dışından erişimin engellemesini
sağlamak üzere protected üye tanımı yapılır.

Örnek:
Şekil2'de yer alan temel sınıf tanımını gözönüne alalım. Burada yasi ve agirlik tanımları
hayvan temel sınıfından türetilecek tüm sınıflarda kullanılabilir. Ancak türetilen sınıflar
dışında herhangi bir yerden erişmek mümkün değildir.

Şekil 2: Temel sınıf tanımı.

215
9.3. Türetilmiş Sınıfların Tanımlanması
C++ ile ilgili kaynaklarda, temel sınıfa ebeveyn sınıf (parent class) veya üst sınıf (super
class) adı da verilmektedir. Türetilmiş sınıflara ise yavru sınıf (child class) veya alt sınıf
(subclass) isimleri kullanılabilmektedir.

Bir sınıf bir diğer sınıftan kalıtım yoluyla oluşturulurken, şu şekilde bir tanım yapılır:

class türetilmiş_sınıf : erişim_türü temel_sınıf {



}

Erişim türü olarak public veya private veya protected seçilebilir. Erişim türü
belirtilmediği takdirde private seçildiği varsayılır.

! Temel sınıflardan yavru sınıflar türetmek için "+" simgelerine tıklayınız.

! Türetilmiş sınıfların tanımlanmasıyla ilgili program örneğini görmek için tıklayınız.

Bir temel sınıftan bir türetilmiş sınıfı yaratmak için aşağıda belirtilen yol izlenir. Örneğin,
hayvan temel sınıfından kopek isimli bir sınıfı şu şekilde türetebiliriz:

216
// Temel sınıf..
class hayvan
{
public:
// Kurucu fonksiyonlar
hayvan();
~hayvan();

int yasBelirle();
int agirlikBelirle();

protected:
int yasi;
int agirlik;
};

// Türetilmiş sınıf tanımlanıyor..


class kopek : public hayvan
{
public:
kopek();
};

9.3.1. public İle Kalıtım


Eğer üyeler public olarak tanıtılmış ise, temel sınıfın tüm public üyeleri türetilmiş sınıfın
public üyeleri haline gelir. Bu durumda söz konusu üyelere hem temel sınıf, hem de

217
türetilmiş sınıf tarafından erişilebilir. Bir sınıf bir diğer sınıftan public kalıtımla türetilirken
şu şekilde bir tanım yapılır:

class türetilmiş_sınıf : public temel_sınıf {



}

Temel sınıfın bir public üyesinin, public olarak kalıtımı olmuşsa, türetilmiş sınıfın bir
public üyesi haline gelmiş olur. Türetilmiş sınıf public olarak nitelendirildiği için,
türetilmiş sınıfın dışında herhangi bir yerde kullanılabilir.

! public ile sınıf türetmekle ilgili program örneğini görmek için tıklayınız.

Aşağıdaki C++ programını göz önüne alalım. Burada sinifA isimli temel sınıfın public
olan tüm üyelerine, bu sınıftan türetilen sinifB isimli sınıftan erişilebilmesini istiyoruz.
Amacımıza uygun program şu şekilde olabilir:

218
#include <iostream>
using namespace std;

class sinifA {
int degerA;
public:
int pub() {
return degerA=567;
}
};

// public türü kalıtımla türetilmiş

219
// sınıf tanımlanıyor..
class sinifB : public sinifA {
int b;
public:
int f1() {
return b=123;
}
};

int main() {
int aa, bb;
sinifB objB; //Sınıfın bir nesnesi tanımlanıyor.
aa = objB.pub(); //Temel sınıfa erişiliyor..
bb = objB.f1(); //Türetilmiş sınıfa erişiliyor..

cout << "Temel siniftan gelen =";


cout << aa << "\n";

cout << "Turetilmis siniftan gelen =";


cout <<bb << "\n";

return 0;
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

220
public ile kalıtımda türetilmiş sınıf, temel sınıfın tüm public üyelerine erişebilir.

9.3.2. private İle Kalıtım


Türetilmiş sınıfın nesnelerinin, temel sınıfın public üye fonksiyonlarına erişimi
engellenmek isteniliyorsa, private kalıtım tercih edilir. Temel sınıfın bir public üyesinin,
private olarak kalıtımı olmuşsa, türetilmiş sınıfın bir private üyesi haline gelmiş olur.
Bunun anlamı, türetilmiş sınıfa katılan temel sınıf üyelerine kalıtım yoluyla erişim
olanaksız hale gelmiştir. Bu üyelere ancak türetilmiş sınıf içinde erişilebilir. Açıkça
söylemek gerekirse, türetilmiş sınıf dışında, sınıfın üyelerine nokta işlecini kullanarak
ulaşmak mümkün değildir.

Bir sınıf bir diğer sınıftan private kalıtımla oluşturulurken şu şekilde bir tanım yapılır:

class türetilmiş_sınıf : private temel_sınıf {



}

! private türü kalıtımla türetilmiş sınıftan temel sınıfa erişilememesiyle ilgili program
örneğini görmek için tıklayınız.

public ile kalıtım konusunda verdiğimiz örneği yeniden göz önüne alalım. Bu programda
türetilmiş sınıf tanımında public yerine private yazalım.

221
#include <iostream>
using namespace std;

class sinifA {
int degerA;
public:
int pub() {
return degerA=567;
}
};

// public türü kalıtımla türetilmiş


// sınıf tanımlanıyor..

222
class sinifB : private sinifA {
int b;
public:
int f1() {
return b=123;
}
};

int main() {
int aa, bb;
sinifB objB; //Sınıfın bir nesnesi tanımlanıyor.
aa = objB.pub(); //HATA : Temel sınıfa erişilemez.
bb = objB.f1(); //Türetilmiş sınıfa erişiliyor..

cout << "Temel siniftan gelen =" ;


cout << aa << "\n";

cout << "Turetilmis siniftan gelen =";


cout <<bb << "\n";

return 0;
}

Bu program derlenmeye çalışıldığında, hatalı durumla karşılaşılacak ve derleme işlemi


gerçekleşmeyecektir. Derleme esnasında,

aa = objB.pub(); // HATA : Temel sınıfa erişilemez.

satırındaki tanımların hatalı olduğu bildirilecektir. Çünkü private olarak tanımlanmış


türetilmiş bir sınıf, temel sınıfın üyelerini de private olarak bünyesine alacaktır. Bu
durumda belirtilen satırda, yani türetilmiş sınıfın dışındaki herhangi bir yerde kalıtım
yoluyla temel sınıfın bir üyesine erişme olanağı ortadan kalkmıştır. Böyle bir sorun nasıl
çözülecektir? Bunu bir sonraki örnekte ele alarak inceleyeceğiz.

! private türü kalıtımla türetilmiş sınıftan temel sınıfa erişebilen program örneğini görmek
için tıklayınız.

Temel sınıfın pub() isimli bir üyesini private olarak tanımlı bir türetilmiş sınıf aracılığıyla
kullanmak istiyoruz. Bunun için türetilmiş sınıf içinde temel sınıfın üyesini bir başka üyeye
aktarıyoruz. Bu üye public olduğundan, türetilmiş sınıf yardımıyla kullanılabilir.

223
#include <iostream>
using namespace std;

class sinifA {
int degerA;
public:
int pub() {
return degerA=567;
}
};
// private türü kalıtımla türetilmiş
// sınıf tanımlanıyor..
class sinifB : private sinifA {

224
int b;
public:
int f1() {
return b=123;
}
//Temel sınıf üyesi kullanılıyor..
int f2() {
int c=pub();
return c;
}
};
int main() {
int aa, bb;
sinifB objB; //Sınıfın bir nesnesi tanımlanıyor.
aa = objB.f2(); //Temel sınıfa erişiliyor..
bb = objB.f1(); //Türetilmiş sınıfa erişiliyor..

cout << "Temel siniftan gelen =" ;


cout << aa << "\n";
cout << "Turetilmis siniftan gelen =";
cout <<bb << "\n";
return 0;
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir :

225
private ile kalıtımda, türetilmiş sınıfın, temel sınıfın public üyelerine erişimi engellenir.

9.3.3. protected İle Kalıtım


Bir temel sınıf için bazı üyelerin mutlaka private kalması gerekiyorsa ve bu üyelere
türetilmiş sınıf içinden erişilmesi söz konusu ise, protected erişimin tercih edilmesi
gerekir. Biliyorsunuz, temel sınıf içinde yer alan ve private olarak bildirilmiş bir üyeye
türetilmiş sınıf dahil, bir başka yerden erişilemiyordu.

Bu sorunu çözmek üzere şöyle bir çözüm geliştirilebilir:


Bazı üyelerin hem temel sınıfta private olarak değerlendirilmesini, hem de kalıtımı
sağlamak üzere, türetilmiş sınıflar tarafından erişilmesini sağlamak için ilgili üyeler temel
sınıf içinde protected olarak tanımlanır.

! protected ile kalıtımla ilgili program örneğini görmek için tıklayınız.


Örnek

Temel sınıf içinde pub() isimli üyenin mutlaka private olarak değerlendirilmesi
gerektiğini varsayalım. Bu durumda, sinifB() isimli türetilmiş sınıf içinde bu üyeleri
kullanabilmek için, temel sınıf içinde ilgili üyeleri private olarak değil, protected olarak
tanımlıyoruz.

226
#include <iostream>
using namespace std;
class sinifA {
int degerA;
protected:
int pub() {
return degerA=567;
}
};
// public türü kalıtımla türetilmiş
// sınıf tanımlanıyor..
class sinifB : public sinifA {
int b;

227
public:
int f1() {
return b=123;
}
//Temel sınıf üyesi kullanılıyor..
int f2() {
int c=pub();
return c;
}
};
int main() {
int aa, bb;
sinifB objB; //Sınıfın bir nesnesi tanımlanıyor.
aa = objB.f2(); //Temel sınıfa erişiliyor..
bb = objB.f1(); //Türetilmiş sınıfa erişiliyor..

cout << "Temel siniftan gelen =" ;


cout << aa << "\n";
cout << "Turetilmis siniftan gelen =";
cout <<bb << "\n";
return 0;
}
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç görüntülenir.

9.4. Kurucu ve Yok Edici Fonksiyonların Kalıtım


İşlemlerinde Kullanımı
Temel sınıf ve ondan türetilmiş sınıflarda kurucu ve yok edici fonksiyonları kullanabiliriz.
Ancak kurucu fonksiyonların temel ve türetilmiş sınıflar içindeki çalışma önceliği
önemlidir. Bir temel sınıf kurucu fonksiyon içeriyorsa, bu kurucu fonksiyon türetilmiş
kurucu fonksiyondan önce çalışır. Yok edici fonksiyonlarda ise bunun tersi sıra geçerlidir.
Önce türetilmiş sınıfın içindeki yok edici fonksiyon çalışır.

! İlgili program örneğini görmek için tıklayınız.

Kurucu fonksiyona sahip bir sınıfı ve bir başka kurucu fonksiyona sahip türetilmiş sınıfları
kullanarak; sinifA isimli temel sınıfın public olan tüm üyelerine, bu sınıftan türetilen
sinifB isimli sınıftan erişilebilmesini istiyoruz. Amacımıza uygun program şu şekilde
olabilir:

228
#include <iostream>
using namespace std;

class sinifA {
public:
int degerA;
sinifA();
};
// public türü kalıtımla türetilmiş
// sınıf tanımlanıyor..
class sinifB : public sinifA {
public:

229
int degerB;
sinifB ();
};
// Temel sınıf için kurucu fonksiyon..
sinifA::sinifA() {
degerA=567;
}
// Türetilmiş sınıf için kurucu fonksiyon..
sinifB::sinifB() {
degerB=123;
}
int main() {
int aa, bb;

sinifB objB; //Sınıfın bir nesnesi tanımlanıyor.


aa = objB.degerA; //Temel sınıfa erişiliyor.
bb = objB.degerB; //Türetilmiş sınıfa erişiliyor.

cout << "Temel siniftan gelen =" ;


cout << aa << "\n";
cout << "Turetilmis siniftan gelen =";
cout <<bb << "\n";
return 0;
}
Bu program çalıştırıldığında aşağıdaki sonuç görüntülenir :

230
! Kurucu ve yok edici fonksiyonlarının işleyiş sırasını görmek için simgelere tıklayınız.

9.5. Çoklu Kalıtım


Bir sınıf, birden fazla temel sınıftan türetilebilir. Bu olaya çoklu kalıtım adını veriyoruz.
Çoklu kalıtımda, temel sınıf ve türetilmiş sınıflar aşağıda belirtildiği biçimde tanımlanır.

// Birinci temel sınıf..


class sinifA {
..
};
// İkinci temel sınıf..
class sinifB {
..
};
// Türetilmiş sınıf .
class sinifC : public sinifA,public sinifB

231
{
..
};

sinifC, iki temel sınıftan (sinifA ve sinifB) türetilmiş.

! İki temel sınıfı kullanarak bir türetilmiş sınıfın elde esilmesi ile ilgili örneği incelemek için
tıklayınız.

İki temel sınıfı kullanarak bir türetilmiş sınıf elde etmek istiyoruz. Bunun için aşağıda
belirtilen yol izlenebilir.

232
#include <iostream>
using namespace std;
class sinifA {
int degerA;
public:
int f1() {
return degerA=567;
}
};
class sinifB {
int degerB;
public:
int f2() {
return degerB=888;
}
};
// public türü kalıtımla türetilmiş
// sınıf tanımlanıyor..
class sinifC : public sinifA, public sinifB {
int b;
public:
int f3() {
return b=123;
}
};
int main() {
int aa, bb, cc;
sinifC objC; //Sınıfın bir nesnesi tanımlanıyor.
aa = objC.f1(); //1. Temel sınıfa erişiliyor..
bb = objC.f2(); //2. Temel sınıfa erişiliyor..
cc = objC.f3(); //Türetilmiş sınıfa erişiliyor..
cout << "1. Temel siniftan gelen =" ;
cout << aa << "\n";
cout << "2. Temel siniftan gelen =";
cout <<bb << "\n";
cout << "Turetilmis siniftan gelen =";
cout <<cc << "\n";
return 0;
}

Bu program çalıştırıldığında aşağıda belirtildiği biçimde bir sonuç elde edilir:

233
9.5.1. Çoklu Kalıtımda Belirsizlik
Bir temel sınıftan birden fazla türetilmiş sınıf elde ediliyorsa, bir karışıklığın ortaya
çıkması beklenebilir. Örneğin sinifA isimli bir temel sınıftan sinifA ve sinifB gibi iki
türetilmiş sınıf ve sinifA ile sinifB'den sinifD gibi bir başka türetilmiş sınıf elde edildiğini
varsayalım. Bu durumda, sinifA temel sınıfının özellikleri sinifD tarafından iki defa
kalıtım yoluyla alındığı anlaşılacaktır. Böyle bir kalıtım olayı tanımlandığında C++ temel
sınıfın iki kopyasını oluşturarak, kalıtım işlemlerini bu kopyalar üzerinde gerçekleştirir.

Temel sınıfın iki kopyası olduğuna göre, sinifD türetilmiş sınıfı kalıtım ile ilgili özellikleri
sinifB üzerinden mi yoksa sinifC üzerinden mi gerçekleştirecektir ? C++ bu durumu
yorumlayamayacak ve bir belirsizlik ortaya çıkacaktır.

Eğer temel sınıfın iki kopya yerine bir kopyası olsaydı bu tür bir sorun yaşanmayacaktır.
Bu durumda temel sınıfın tek bir sanal kopyası oluşturulur ve bu sanal sınıftan kalıtım
işlemleri yürütülür. Bir sanal sınıf oluşturmak için virtual anahtar kelimesi kullanılır.

! Belirsizlik yüzünden hata veren program örneğini görmek için tıklayınız.

Örnek

sinifA isimli bir temel sınıftan sinifA ve sinifB gibi iki türetilmiş sınıf ve sinifA ile
sinifB'den sinifD gibi bir başka türetilmiş sınıf elde edildiğini varsayalım. Bu durumda,
sanal temel sınıfı yaratmadan, normal biçimde kalıtım işlemlerini gerçekleştirmek
istiyoruz.

234
235
#include <iostream>
using namespace std;
class sinifA {
int degerA;
public:
int f1() {
return degerA=567;
}
};
// 1. Türetilmiş sınıf...
class sinifB : public sinifA {
int degerB;
public:
int f2() {
return degerB=888;
}
};
// 2. Türetilmiş sınıf...
class sinifC : public sinifA {
int degerB;
public:
int f3() {
return degerB=888;
}
};
// 3. Türetilmiş sınıf
// Çoklu kalıtım için türetilmiş sınıf...
class sinifD : public sinifB, public sinifC {
int b;
public:
int f4() {
return b=123;
}
};
int main() {
int aa, bb, cc, dd;
sinifD objD; //Sınıfın bir nesnesi tanımlanıyor.
aa = objD.f1(); //1. Temel sınıfa erişiliyor..
bb = objD.f2(); //2. Temel sınıfa erişiliyor..
cc = objD.f3(); //3. Türetilmiş sınıfa erişiliyor..

236
dd = objD.f4(); //Türetilmiş sınıfa erişiliyor..
cout << "Temel siniftan gelen =" ;
cout << aa << "\n";
cout << "1. Turetilmis siniftan gelen =";
cout <<bb << "\n";
cout << "2. Turetilmis siniftan gelen =";
cout <<cc << "\n";
cout << "3. Turetilmis siniftan gelen =";
cout <<dd << "\n";
return 0;
}

Bu program derlendiğinde, bir hata mesajı ile karşılaşılır. C++ derleyicisi programcıya bir
belirsizlik olduğunu bildirir. Sorun çoklu kalıtım esnasında yaşanan belirsizlikten
kaynaklanmaktadır. Bir sonraki örnekte bu sorunun nasıl çözüme kavuşturulduğu
anlatılmaktadır.

! virtual anahtar kelimesini kullanan program örneğini görmek için tıklayınız.

Önceki örnekte olduğu gibi, sinifA isimli bir temel sınıftan sinifA ve sinifB gibi iki
türetilmiş sınıf ve sinifA ile sinifB'den sinifD gibi bir başka türetilmiş sınıf elde edildiğini
varsayıyoruz. Ancak çoklu kalıtım nedeniyle yaşanabilecek sorunları önlemek üzere,
virtual anahtar kelimesini kullanarak bir sanal temel sınıf yaratıyoruz ve kalıtım
işlemlerini bu sanal sınıf üzerinden gerçekleştiriyoruz.

237
238
#include <iostream>
using namespace std;
class sinifA {
int degerA;
public:
int f1() {
return degerA=567;
}
};
// 1. Türetilmiş sınıf...
class sinifB : virtual public sinifA {
int degerB;
public:
int f2() {
return degerB=888;
}
};
// 2. Türetilmiş sınıf...
class sinifC : virtual public sinifA {
int degerB;
public:
int f3() {
return degerB=333;
}
};
// 3. Türetilmiş sınıf
// Çoklu kalıtım için türetilmiş sınıf...
class sinifD : public sinifB, public sinifC {
int b;
public:
int f4() {
return b=123;
}
};
int main() {

239
int aa, bb, cc, dd;
sinifD objD; //Sınıfın bir nesnesi tanımlanıyor.
aa = objD.f1(); //1. Temel sınıfa erişiliyor..
bb = objD.f2(); //2. Temel sınıfa erişiliyor..
cc = objD.f3(); //3. Türetilmiş sınıfa erişiliyor.
dd = objD.f4(); //Türetilmiş sınıfa erişiliyor..
cout << "Temel siniftan gelen =" ;
cout << aa << "\n";
cout << "1. Turetilmis siniftan gelen =";
cout <<bb << "\n";
cout << "2. Turetilmis siniftan gelen =";
cout <<cc << "\n";
cout << "3. Turetilmis siniftan gelen =";
cout <<dd << "\n";
return 0;
}
Bu program derlendiğinde herhangi bir hata ile karşılaşılmaz. Program çalıştırıldığında ise
aşağıdaki sonuç görüntülenir :

240
Bölüm Özeti
Bu bölümde,

• Kalıtım ne olduğunu,
• Hiyerarşi dışındaki erişimleri engellemeyi,
• Türetilmiş sınıfların tanımlanmasını,
• Kurucu ve yok edici fonksiyonların kalıtım
işlemlerinde kullanılmasını,
• Çoklu kalıtımı,

öğrendik.

241
242
243
244
245
246
10. C++ GİRİŞ/ÇIKIŞ SİSTEMİ

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• C++ akımlarını,
• ios sınıfını ve biçimlendirilmiş
giriş çıkışları,
• Manipülatörlerin kullanımını,
• Dosya giriş ve çıkış işlemlerini,
• Disk dosyalarına biçimlendirilmiş
veri kaydetmeyi,
• İkili dosyalara veri okuma ve
yazma işlemlerini,
• Rasgele erişimli dosyaları,
• Yazıcı çıkışlarını,

öğrenmiş olacaksınız.

10.1. Akımlar
C++ 'da verilerin girişi yada çıkışı gibi işlemler akım (stream) adı verilen nesneler
yardımıyla yerine getirilir. Akım, bir sınıfın bir nesnesi olarak değerlendirilir. Akım, C++ 'ın
bilgisayarın fiziksel aygıtlarını kullanabilmesini sağlayan olanaktır. Araç ne olursa olsun C++
bu aygıtlarla akım nesneleri yoluyla iletişim kurar.

Önceki bölümlerde sık sık kullandığımız cout ve cin birer akım nesnesidir. Bu nesnelerden
cout, bildiğiniz gibi, çıkışları ekrana yönlendirerek görüntülenmelerini sağlamaktadır. Buna
karşılık, cin nesnesi klavyeden bilgi girişlerini bekleyen bir nesne olarak karşımıza çıkmıştır.

C++'da akım nesnelerine veri yönlendirmek için "<<" veya ">>" işleçlerini kullandık. Bu

247
işleçlere ilave bazı özellikler katarak aşırı yükleme yapabileceğimizi de biliyoruz.

Akım nesneleri sadece ekrana bilgi yönlendirmek ya da klavyeden girilen değerleri okumak
için kullanılmaz. Akım nesneleri disk dosyalarına veri yöneltmek; yani veri yazmak ya da
okumak amacıyla da kullanılabilir. Sözü edilen işlemlerin nasıl olduğunu bu bölümde ele
alarak inceleyeceğiz.

Bir C++ programı çalışmaya başladığında, cout, cin, cerr ve clog akım nesneleri
kendiliğinden açılır. Bu akımların varsayılan aygıtlarını aşağıdaki tabloda listeliyoruz:

AKIM VARSAYILAN AYGIT ANLAMI

cout Ekran Standart Çıkış

cin Klavye Standart Giriş

cerr Ekran Standart Hata

clog Ekran Ön Bellekli Standart Hata

! Akımlarla ilgili program örneğini görmek için tıklayınız.

Klavyeden girilen bir sayısal değeri ekranda aynen görüntüleyen bir C++ programını şu
şekilde düzenleyebiliriz. Bu programda klavyeden girilen verileri okumak üzere cin
nesnesi kullanılıyor. Klavyeden girilen değeri ekrana yazdırmak üzere cout nesnesini
kullanıyoruz.

#include <iostream>
using namespace std;

248
// Bu program klavyeden
// girilen bir degeri okuyarak
// ekranda goruntuler..

int main()
{
int sayi;
cout << "Sayi giriniz:";
cin >> sayi;
cout << "Sayi:" << sayi << "\n";
return 0;
}
Bu program çalıştırılıp 23 sayısı girildiğinde şöyle bir sonuç elde edilir :

Bilgisayarın ekran, klavye gibi fiziksel aygıtlarıyla iletişimi akım nesneleriyle gerçekleşir.

10.2. ios Sınıfı İle Biçimlendirilmiş Giriş/Çıkışlar


Akım sınıfları hiyerarşik bir yapıya sahiptir. Bu yapının en üstünde ios sınıfı yer
almaktadır. C++'da akımları çalıştırabilmek için gerekli özelliklerin önemli bir kısmını bu
sınıf sağlar. Örneğin, giriş ve çıkışların biçimlendirilmesi ile ilgili özellikler bu sınıf
tarafından sağlanır. Sözü edilen ios sınıfı önemli akım işlemlerini denetleyen ve çok sıkça

249
kullanılan çeşitli üye fonksiyonlar ve değişkenler içerir. C++ programı <iostream>
içeriyorsa ios sınıfına erişim hakkı doğacaktır.

Klavyeden programa giden giriş ve programdan ekrana giden çıkış ios sınıfı ile
biçimlendirilir.

10.2.1. Biçimlendirme Kelimelerinin Kullanımı


Giriş ve çıkış işlemlerinde çoğunlukla veriyi biçimlendirerek kullanmak söz konusu
olacaktır. Biçimlendirme işlemlerinde bazı özel kelimeler kullanılarak tanım yapılır. Giriş
ve çıkış işlemlerinde kullanılabilecek bazı biçimlendirme anahtar kelimelerine aşağıda yer
veriyoruz:

Biçimlendirme kelimeleri ios sınıfının birer üyesi olduğu için, kullanılarken ios sınıfına da
yer verilir. Örneğin, left biçimlendirme kelimesi ios::left biçiminde kullanılarak, çıktının
sola dayalı olarak görüntülenmesi sağlanır.

250
Bu aşamada iki ios 'un iki üye fonksiyonu üzerinde durmak istiyoruz. Bunlardan birincisi
setf() fonksiyonu olarak bilinir. Bu fonksiyon, biçimlendirme kelimelerinin programa
tanıtılması amacıyla kullanılır. Söz konusu setf() fonksiyonu program içinde şu şekilde
kullanılır:

akım.setf(ios::biçimlendirme kelimesi)

Örneğin, ekran üzerine yapılacak çıkışlar için scientific biçimlendirme kelimesini


kullanabilmek için şu şekilde bir tanım yapılır:

cout.setf(ios::scientific);

Yukarıda belirtildiği biçimde setf() fonksiyonu ile tanımlanan biçimlendirmeyi iptal etmek
üzere unsetf() fonksiyonu kullanılır.

! Biçimlendirme işlemleriyle ilgili program örneğini görmek için tıklayınız.

Üç adet sayısal değeri üstel olarak, yani scientific olarak ekran üzerinde görüntülemek
istiyoruz. Bunun için aşağıda belirtilen yol izlenebilir.

#include <iostream>
using namespace std;

int main()
{
// Sayısal değerler tanımlanıyor..

251
double s1=12.34;
double s2=2.764;
double s3=839.5;

//Çıkışlar biçimlendiriliyor..
cout.setf(ios::scientific);
cout << s1 <<"\n";
cout << s2 <<"\n";
cout << s3 <<"\n";

// Biçimlendirme sona erdiriliyor..


cout.unsetf(ios::scientific);

return 0;
}
Bu program çalıştıırldığında şöyle bir sonuç elde edilir:

10.2.2. Fonksiyonların Kullanımı


Sözünü ettiğimiz ios sınıfı, biçimlendirme işlemlerini gerçekleştirmek üzere bazı
fonksiyonlara da sahiptir. Bu fonksiyonların bir kısmını aşağıdaki tablo üzerinde yer
almaktadır. Bu fonksiyonlar arasında yer alan unsetf() ve unsetf() fonksiyonlarından
daha önce söz etmiştik.

Bu fonksiyonlar akış nesnesi adından sonra nokta işareti konularak tanımlanırlar.


Örneğin, alan genişliğini 20 karaktere dönüştürmek için,

cout.width(20);

biçiminde bir tanım yapılır.

252
! Fonksiyonları kullaranak biçimlendirme yapan program örneklerini görmek için
tıklayınız.

Program içinde tanımlanmış üç adet sayısal değeri biçimlendirerek ekran üzerinde


görüntülemek amacındayız. Bu sayılardan birincisini üç basamaklı olacak biçimde
görüntülemek istiyoruz. Basamak sayısını belirtmek için precision() fonksiyonunu
kullanıyoruz. İkinci sayısal değerin 20 karakterlik bir alana yerleşmesini istiyoruz. Bunun
için width() fonksiyonunu kullanıyoruz. Bu fonksiyon, sayısal değerin belirlenen alanda
sağa dayalı olarak görüntülenmesini sağlayacaktır. Üçüncü sayısal değere de width()
fonksiyonu ile 20 karakterlik yer ayırıyoruz. Ayrıca, bu sayısal değerin belirlenen alanda
sola dayalı ve sabit noktalı yerleşmesini istiyoruz. Sola dayalı yerleşmesi için left, sabit
noktalı olmasını sağlamak için fixed biçimlendirme kelimesini kullanıyoruz. Amacımıza
uygun C++ programı şu şekilde olabilir:

#include <iostream>
using namespace std;
int main()
{
// Sayısal değerler tanımlanıyor..
double s1=134;
double s2=2764;
double s3=83.525;
//Çıkışlar biçimlendiriliyor..
// s1 için biçimlendirme..
cout.precision(3);
cout << s1 << "\n";

253
// s2 için biçimlendirme..
cout.width(20);
cout << s2 << "\n";
// s3 için biçimlendirme..
cout.width(20);
cout.setf(ios::left | ios::fixed);
cout << s3 << "\n";

return 0;
}
Yukarıdaki program çalıştırıldığında şöyle bir sonuç elde edilir:

10.2.3. Manipülatörlerin Kullanımı


Bir akım içine giriş ve çıkışların biçimlendirilerek yönlendirilmesinde kullanılan bir diğer
yöntem, "manipülatör" yoluyla yerine getirilir. Manipülatörler, belirli biçimlendirme
kelimelerinden oluşmaktadır. Bu kelimeler doğrudan doğruya ilgili çıktıya yönlendirilerek,
biçimlendirilmesi sağlanır. Giriş-çıkış manipülatörleri, birer özel biçimlendirme
fonksiyonlarıdır. Bu tür manipülatörleri kulanabilmek için <iomanip> kütüphanesinin
programa dahil edilmesi gerekmektedir.

Bazı manipülatörler argüman alarak görevlerini yerine getirir. Bazıları ise herhangi bir
argüman almazlar. Argüman almayan bir kısım manipülatörleri aşağıdaki tablo üzerinde
veriyoruz.

! Manipülatörlerin anlamlarını görmek için üzerlerine tıklayınız.

254
Argüman alan bir kısım ios manipülatörleri ise aşağıdaki tabloda yer almaktadır:

! Argüman almayan manipülatörlerle ilgili program örneğini görmek için tıklayınız.

Üç adet sayısal değeri ekran üzerinde görüntülemek istiyoruz. Sayısal değerler ardarda
gelen satırlara kaydedilecektir. Böyle bir sonuca "\n" ifadesini cout nesnesine yönelterek
elde edebiliyoruz. Ancak aynı sonuca ulaşmak için endl manipülatörünü kullanabiliriz.

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{

255
// Sayısal değerler tanımlanıyor..
double s1=134;
double s2=2764;
double s3=835;

//Çıkışlar biçimlendiriliyor..
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
return 0;
}

Bu program çalıştırıldığında şu şekilde bir sonuç elde edilir:

! Argüman alan manipülatörlerle ilgili program örneğini görmek için tıklayınız.

Üç sayısal değerin ekran üzerinde biçimlendirilmiş olarak görüntüsünü elde etmek


istiyoruz. Birinci sayısal değer 2 ondalık basamaklı olacaktır. Ayrıca sabit noktalı olması
istenilmektedir. İkinci sayısal değer için 20 karakterlik alan ayrılması ve sayısal değerin
başına "*" işaretlerinin yerleştirilmesi söz konusudur. Üçüncü sayısal değer için de 20
karakterlik alan ayrılacak ve sayısal değer bu alanın soluna yerleştirilecektir. Amacımıza
uygun program şu şekilde olabilir:

256
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
// Sayısal değerler tanımlanıyor..
double s1=134;
double s2=2764;
double s3=83.525;
//Çıkışlar biçimlendiriliyor..
// s1 için biçimlendirme..
cout << setprecision(2);
cout << fixed << s1 << endl;
// s2 için biçimlendirme..
cout << setw(20);
cout << setfill('*') << s2
<< endl;
// s3 için biçimlendirme..
cout << setw(20);
cout << left;
cout << s3 << endl;
return 0;
}

257
Bu program çalıştırıldığında aşağıda görüldüğü biçimde bir sonuç elde edilir:

10.2.3.1. Özel Manipülatörlerin Tanımlanması


C++ 'ın kendi manipülatörlerinin kullanılması dışında, özel manipülatörleri de
tanımlayarak kullanmak mümkündür. Manipülatörler bazı özel fonksiyonlar biçiminde
düzenlenerek kullanılabilir. Bu tür fonksiyonlar parametresiz olabileceği gibi, parametreli
de olabilir. Parametresiz manipülatör fonksiyonları aşağıda belirtilen yapıya sahiptir:

ostream &manipülatör adı(ostream &stream) {


//Fonksiyon satırları ..
return stream;
}

Eğer parametresiz manipülatör kullanılacak ise ise aşağıda belirtildiği biçimde tanımlanır:

istream &manipülatör adı(ostream &stream) {


//Fonksiyon satırları ..
return stream;
}

! Özel manipülatör tanımlamakla ilgili program örneğini görmek için tıklayınız.

Üç ayrı sayısal değeri 20 karakterlik bir alana yerleştireceğiz ve baş kısmına "*"
işaretlerini yerleştireceğiz. Sayısal biçimlendirme işlemlerini man1 isimli bir manipülatör
tanımlayarak yerine getirmek istiyoruz. Amacımıza uygun program şu şekilde olabilir:

258
#include <iostream>
using namespace std;
//Özel manipülatörler tanımlanıyor.
ostream &man1(ostream &stream) {
stream.width(20);
stream.fill('*');
return stream;
}
int main()
{
// Sayısal değerler tanımlanıyor..
double s1=134;
double s2=2764;
double s3=83.525;
//Çıkışlar biçimlendiriliyor..
// s1 için biçimlendirme..
cout << man1 << s1 << endl;
// s2 için biçimlendirme..
cout << man1 << s2 << endl;
// s3 için biçimlendirme..
cout << man1 << s3 << endl;

return 0;

259
}
Bu program çalıştırıldığında şu şekilde bir sonuç elde edilir:

10.3. Dosya Giriş/Çıkış İşlemleri


C programlarında disk giriş/çıkış işlemlerinin nasıl yapıldığını biliyorsunuz. C++'da disk
dosyalarının yaratılması ve onlara erişim C dilinden oldukça farklıdır. Aslında C'nin
fread() ve fwrite() deyimleri C++'da da çalışır. Ancak bu yöntem tercih edilmez. Bunun
yerine aşağıda açıklanan yöntemler kullanılır.

Disk dosyalarıyla çalışmak için C++'ın bazı sınıflarını kullanmak gerekiyor. Girdi işlemleri
için ifstream; çıktı işlemleri için ofstream ve hem girdi hem de çıktı işlemleri için
fstream sınıflarına başvurulur. Bu sınıflar ios sınıfından türetilmiştir.

O halde disk giriş çıkış işlemlerini yerine getirmek için öncelikle <fstream> in programa
dahil edilmiş olması gerekmektedir. Bu işlem şu şekilde olacaktır:

#include <fstream>

Bir dosya açıldıktan sonra işlemler yapılır ve ardından dosya kapatılır. Ancak C++'da
akımları açıkça kapatmaya gerek yoktur. Çünkü akımlar geçerli oldukları alanın dışına
çıkıldığında, akımların yok edici fonksiyonları çalışarak, dosyanın otomatik olarak
kapanmasına neden olur. Eğer bir programda dosyanın veri kaydetmek ve veri okumak
için ayrı ayrı iki kez açılması gerekiyorsa, birinci işlemin tamamlanması ardından yeniden
açılabilmesi için kapatılması gerekecektir. Kapatma işlemi şu şekilde gerçekleşir:

akım.close();

260
Diske dosya kaydetmek ya da diskteki dosyayı okumak için akımlar(stream) kullanılır.

10.3.1. Disk Dosyasına Biçimlendirilmiş Veri Kaydetme


Disk üzerinde bir dosyayı yaratmak ve bu dosyaya verileri kaydetmek için belirli bir yol
izlenir. Disk üzerine verileri yazmak için ofstream nesnesi şu şekilde açılmaktadır:

ofstream akım adı("dosya_adı");

Bu şekildeki bir tanım ile bir disk dosyası açılmaktadır. Bu dosya açıldığına göre, artık
üzerine veriler kaydedilebilir. Dosya üzerine veri kaydetmek için doğrudan akım nesnesi
kullanılır. Örneğin, akım adı dosya ise, bu akımın açılması ve bir değerlerin kaydedilmesi,
"<<" işleci kullanılarak şu şekilde olacaktır:

dosya << no << ' ' << adi << ' ' << cins << ' ' << yas << ' ' << bolum;

Bu şekilde yapılan bir tanım, dosya akımına ilişkin dosyaya no, adi, cins, yas ve bölüm
değişkenlerinin aralarına birer boşluk yerleştirerek kaydedilmesini sağlar. Bu şekilde
yapılan kayıt işlemleri sonucunda, sayısal değerler dahil tüm değişken değerleri disk
üzerine karakter olarak kaydedilir.

261
! Disk üzerinde dosya yaratıp üzerine veri kaydeden program örneğini görmek için
tıklayınız.

personel.txt isimli bir dosyanın yaratılarak içine personel verilerinin kaydedilmesini


istiyoruz. Amacımıza uygun program şu şekilde olacaktır:

262
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int no=123;
string adi="BURAK";
char cins='E';
int yas=25;
string bolum="EKONOMI";
char bosluk=' ';
int main()
{
// Çıkış dosyası tanımlanıyor..
ofstream dosya("personel.txt");
// Dosyaya yazdırma işlemi..
dosya << no
<< bosluk
<< adi
<< bosluk
<< cins
<< bosluk

263
<< yas
<< bosluk
<< bolum;
cout << "Bilgiler kaydedildi.. \n";
return 0;
}
Bu program çalıştırıldığında söz konusu dosyanın yaratıldığı görülür. Dosyanın içeriğini
Notepad ile şu şekilde görüntülüyoruz.

10.3.2. Diskten Biçimlendirilmiş Veri Okumak


Disk üzerinde yaratılmış bir dosyadan veri okumak istiyoruz. Bu dosya, önceki kısımda
açıklanan metin dosyası biçiminde olacaktır. Dosyadan okunan katar değerler program
içinde diğer türlere dönüştürülür. Katarları içeren bir dosyadan veri okumak için,

ifstream akım adı("dosya adı");

biçiminde bir ifstream nesnesinin oluşturulması gerekmektedir. Bu tanım ile, akım


üzerinden okuma işlemleri gerçekleştirilir. Örneğin, veri okumak için dosya isimli bir akım
şu şekilde tanımlanabilir:

ifstream dosya ("personel.txt");

Bu akım tanımlandıktan sonra, okuma işlemleri aşağıda görüldüğü biçimde ">>" işleçleri
kullanılarak şu şekilde yönlendirilir:

dosya >> no >> adi >> cins >> yas >> bolum;

264
! Disk üzerindeki bir dosyadan veri okuyan program örneğini görmek için tıklayınız.

Daha önce yarattığımız personel.txt isimli dosyanın içerdiği verileri okumak istiyoruz.
Amacımıza uygun program şu şekilde olabilir:

265
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int no;
string adi;
char cins;
int yas;
string bolum;
char bosluk=' ';
int main()
{
// Okunacak dosya tanımlanıyor..
ifstream dosya ("personel.txt");
// Dosyadan okuma işlemi..
dosya >> no

266
>> adi
>> cins
>> yas
>> bolum;
// Okunan veriler görüntüleniyor..
cout << no
<< bosluk
<< adi
<< bosluk
<< cins
<< bosluk
<< yas
<< bosluk
<< bolum << endl;
return 0;
}

Bu program çalıştırıldığında personel.txt dosyası açılır ve içerdiği veriler okunur. Okunan


değerler ise cout üzerine yönlendirilerek ekranda görüntülenir.

! Diske verileri kaydetme ve diskten okuma işlemlerini aynı program üzerinde


gerçekleştiren program örneğini görmek için tıklayınız.

Diske verileri kaydetme ve diskten okuma işlemlerini aynı program üzerinde


gerçekleştirmek istiyoruz.

267
#include <iostream>

268
#include <fstream>
#include <string>
using namespace std;
int no=123;
string adi="BURAK";
char cins='E';
int yas=25;
string bolum="EKONOMI";
char bosluk=' ';
int main()
{
// Çıkış dosyası tanımlanıyor..
ofstream dosya("personel.txt");
// Dosyaya yazdırma işlemi..
dosya << no
<< bosluk
<< adi
<< bosluk
<< cins
<< bosluk
<< yas
<< bosluk
<< bolum;
// Dosya kapatılıyor..
dosya.close();
// Okunacak dosya tanımlanıyor..
ifstream dosya1 ("personel.txt");
// Dosyadan okuma işlemi..
dosya1 >> no
>> adi
>> cins
>> yas
>> bolum;
// Okunan veriler görüntüleniyor..
cout << no
<< bosluk
<< adi
<< bosluk
<< cins
<< bosluk
<< yas
<< bosluk
<< bolum << endl;
return 0;
}

Bu program çalıştırıldığında, önce veriler bir dosyaya kaydedilir. Ardından bu dosya ile ilgili
akım kapatılır ve çıkış yapmak üzere yeniden açılır. Sonuç olarak aşağıdaki görüntü
oluşur.

269
10.3.3. Dosyaların Açılması İle İlgili Hata Denetimi
Dosyaların yazdırılmak üzere açılması yada okunması esnasında bir hata ile
karşılaşılabilir. Örneğin, okuma işlemi yapılacak ise ilgili dosyanın disk üzerinde mevcut
olması gerekmektedir. Eğer bu dosya mevcut değil ise bir hata durumu ortaya çıkacaktır.
Örneğin, dosya isimli bir akımın açılması ile ilgili bir sorun var ise, aşağıda gösterildiği
biçimde bir hata denetimi yapılabilir.

if (!dosya) {
cout << "HATA:Dosya acilamadi.." << endl;
return 1;
}

Bu tanıma göre dosya akımında bir sorun var ise, yani erişilemiyorsa bir hata mesajı ile
programcı uyarılacaktır. Yukarıdaki tanıma göre, dosya ile ilgili herhangi bir sorun varsa,
dosya isimli akım 0 değerini üretir. Böyle bir durumda bir hata olduğu belirlenir; ancak
hatanın türü ortaya konulmaz.

270
! Hata denetimiyle ilgili program örneğini görmek için tıklayınız.

Diskte var olan personel.txt isimli bir dosyayı okumak istiyoruz. Bu okuma işlemi
gerçekleşmez ise bir mesaj ile kullanıcı uyarılacaktır.

#include <iostream>

271
#include <fstream>
#include <string>
using namespace std;

int no;
string adi;
char cins;
int yas;
string bolum;
char bosluk=' ';

int main()
{
// Okunacak dosya tanımlanıyor..
ifstream dosya ("personel.txt");

// Hata durumu belirleniyor..


if (!dosya) {
cout << "HATA:Dosya acilamadi.."
<< endl;
return 1;
}

// Dosyadan okuma işlemi..


dosya >> no
>> adi
>> cins
>> yas
>> bolum;

// Okunan veriler görüntüleniyor..


cout << no
<< bosluk
<< adi
<< bosluk
<< cins
<< bosluk
<< yas
<< bosluk
<< bolum << endl;
return 0;
}

Eğer personel.txt isimli dosya diskte yok ise, doğal olarak hatalı bir durumla karşılaşılır.
Bu durumda aşağıda görüldüğü biçimde bir sonuç elde edilir.

272
10.3.3.1. Hatalarla İlgili Ayrıntılı Bilgi Edinmek
Dosya giriş/çıkışları hakkında daha ayrıntılı bilgiler edinerek hata hakkında daha sağlıklı
yorumlar yapılabilir. Giriş/çıkış durum bilgisini elde etmek için iki yol bulunmaktadır.
Bunlardan birincisi, rdstate() fonksiyonunun kullanılmasıdır. Bu fonksiyon ios'un bir üye
fonksiyonudur. Bu fonksiyon şu şekilde kullanılır:

akım.rdstate()

Bir diğer yol bad(), eof(), fail() ve good() fonksiyonlarından bir veya birkaçının
kullanılmasıdır. Dosya sonunu belirlemek amacıyla eof() kullanılır. Bu fonksiyon, eğer
dosya sonu ise 1; aksi durumda 0 değerini döndürür.

Hataların türleriyle ilgili bilgi almak mümkündür.

! Hatanın türüyle ilgili bilgi veren program örneğini görmek için tıklayınız.

Aşağıdaki programı göz önüne alalım. Burada personel.txt isimli bir dosya açılmaktadır.
Bu dosyanın disk üzerinde henüz yer almadığını varsayalım. Bu durumda hatayı
belirlemek istiyoruz.

273
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int no;
string adi;
char cins;
int yas;
string bolum;
char bosluk=' ';
int main()
{
// Okunacak dosya tanımlanıyor..
ifstream dosya ("personel.txt");
// Hata durumu belirleniyor..
if (!dosya)
cout << "HATA:Dosya acilamadi.."
<< endl;
else
cout << "Dosya hatasız acildi.."

274
<< endl;
cout << "Hata durumu = "
<< dosya.rdstate() << endl;
cout << "Dosya sonu = "
<< dosya.eof() << endl;
return 0;
}
Bu program çalıştırıldığında aşağıdaki sonuç görüntülenir:

10.3.4. Satırlar Biçiminde Okuma


Bir disk dosyasına verilerin katarlar biçiminde kaydedildiğini biliyoruz. Bir katar içinde ve
kelimeler arasında boşluklar yer alabilir. Bu verilerin kaydedilmesinde bir sorun olmaz.
Ancak okunması sırasında sorunlarla karşılaşılacaktır. Çünkü bir değişken, sadece bir
kelimenin okunmasına neden olacaktır. Bunun yerine, dosya içindeki bir satırın tümüyle
okunarak bir değişken içine yerleştirilmesi gerekebilir. Böyle durumlarda getline()
fonksiyonuna başvurulur. Bu fonksiyon, dosya içinde "\n" yani yeni satır karakterine
ulaşıncaya dek tüm karakterleri okur. Söz konusu fonksiyon şu şekilde tanımlanır:

dosya.getline(tampon, tampon genişliği);

Yukarıdaki tanıma göre getline() fonksiyonu, okuduğu satırı bir tampon alan üzerine
yerleştirir. Bu alanın genişliği fonksiyon içinde belirtilir.

275
! Satırlar biçiminde okumakla ilgili program örneğini görmek için tıklayınız.

Önce katarlardan oluşan bir disk dosyası yaratacağız. Bu dosya üç satırdan oluşacaktır.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;
string satir1="Ahmet Yesevi Uluslararasi "
"Turk Kazak Universitesi";
string satir2="Uzaktan Egitim Fakultesi";
string satir3="Bilgisayar Muhendisligi Bolumu";
int main()

276
{
// Yazdırılacak dosya tanımlanıyor..
ofstream dosya ("test.txt");
// Disk dosyasına yazdırma işlemi..
dosya << satir1 << endl;
dosya << satir2 << endl;
dosya << satir3 << endl;

return 0;
}
Bu program çalıştırıldığında aşağıda belirtilen sonuç elde edilir.

10.3.5. Karakter Giriş/Çıkışları


Karakter katarları yanısıra, gerektiğinde tek tek karakter giriş/çıkışları da
gerçekleştirilebilir. Bu tür uygulamalar ostream ve istream 'ın üyeleri olan put() ve
get() fonksiyonları yardımıyla gerçekleştirilir. Doğal olarak put() fonksiyonu
karakterlerin kaydedilmesi, get() ise karakterlerin okunması işlemlerinde kullanılır.

! put() ve get() fonksiyonlarının işleyişini görmek için ilgili simgeye tıklayınız.

277
! get() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Daha önce yarattığımız test.txt isimli dosyanın içeriği şu şekilde idi:

Bu dosyadan verileri karakter karakter okuyarak bir değişken içine yerleştirmek ve daha
sonra bu değeri görüntülemek istiyoruz. Program şu şekilde olabilir:

278
#include <string>
using namespace std;

char karakter;

int main()
{
// Okunacak dosya tanımlanıyor..
ifstream dosya ("test.txt");

// Dosyadan tüm satırlar okunuyor..


while (dosya) {
dosya.get(karakter);
cout << karakter;
}
cout << endl;
return 0;
}
Bu program çalıştırıldığında şöyle bir sonuç görüntülenir:

279
! put() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir dosyaya verileri karakter karakter kaydetmek için put() fonksiyonu kullanılır.
Aşağıdaki program verilen bir katarı, karakter karakter ilgili dosyaya yazmaktadır.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

string katar = "Ahmet Yesevi Uluslararasi "


"Turk Kazak Universitesi, Kazakistan in "
"en sevilen universitesidir..";
int main()
{
// Yazdırılacak dosya tanımlanıyor..

280
ofstream dosya ("test.txt");

// Yazdırma işlemi..
for (int i=0;i<katar.size();i++)
dosya.put(katar[i]);
cout <<"Yazma islemi tamamlandi..";
cout << endl;
return 0;
}
Bu program çalıştırıldığında, test.txt isimli dosya içine yazma işlemi gerçekleştirilir. Bu
dosyanın içeriği şu şekildedir:

10.3.6. İkili Giriş/Çıkışlar


Önceki kısımlarda anlattığımız konular, bir disk dosyasına verilerin karakterler biçiminde
kaydedilmesiyle ilgiliydi. Bu tür dosyalar, biçimlendirilmiş metin dosyaları olarak
değerlendirilir. Ancak küçük boyutlu dosyalarda bu mümkün olmasına karşılık, büyük
boyutlu dosyalarda bu yol tercih edilmez. Onun yerine ikili (binary) dosyalar kullanılır.

Verileri ikili düzende kaydetmek için ofstream'ın write() fonksiyonu kullanılır. Eğer ikili
verileri okumak söz konusu ise bu kez ifstream'ın read() fonksiyonu kullanılır. Bu iki
fonksiyon aşağıda belirtildiği biçimde tanımlanır:

dosya.write(tampon, tampon genişliği);


dosya.read(tampon, tampon genişliği);

Bu fonksiyonlardan write() 'ın görevlerini yerine getirebilmesi için ofstream 'ın şu


şekilde tanımlanması gerekiyor:

ofstream akım ("dosya adı", ios::out | ios::binary);

Eğer okuma işlemi gereçekleşecek ise read() fonksiyonu için aşağıdaki tanım yapılır:

ifstream akım ("dosya adı", ios::in | ios::binary);

281
Tampon alana alınan verilerin karakter türünde olması gerekiyor. Eğer okunan ya da
yazılan değerler karakter türü değil ise bunları karaktere çevirmek gerekir. Örneğin, sayi
isimli değişkenin veri türü double ise, bu değişkeni tampona yerleştirmek için ((char
*)&sayi,sizeof(double)) biçiminde bir tanım yapmak gerekecektir. Bu tür bir tanım
hem write() hem de read() içinde yapılmalıdır.

! write() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir katarı ve bir adet sayısal değeri disk dosyasına ikili olarak kaydetmek istiyoruz.
Amacımıza uygun C++ programı şu şekilde olabilir:

282
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// Yazdırılacak veriler tanımlanıyor..


char katar[] = "Ahmet Yesevi Uluslararasi "
"Turk Kazak Universitesi";
int ogrenci_sayi = 15000;

int main()
{
// Yazdırılacak dosya tanımlanıyor..
ofstream dosya ("test.txt", ios::out | ios::binary);

// Dosyanın durumu denetleniyor..


if(!dosya) {
cout << "HATA:Dosya açilamadi..";
return 1;
}

283
// Disk dosyasına yazdırma işlemi..
dosya.write(katar, strlen(katar));
dosya.write((char *) &ogrenci_sayi, sizeof(int));

cout <<"Yazma islemi tamamlandi.." << endl;


cout << endl;
return 0;
}

! read() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Önceki örnekte yaratılan dosyayı bu kez okumak istiyoruz. İkili dosyanın okunması
aşağıda belirtildiği biçimde olur:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

284
int ogrenci_sayi;
const MAX=49;
char katar[MAX];

int main()
{
// Okunacak dosya tanımlanıyor..
ifstream dosya ("test.txt", ios::in | ios::binary);

// Dosyanın durumu denetleniyor..


if(!dosya) {
cout << "HATA:Dosya açilamadi..";
return 1;
}

// Okuma işlemi..
dosya.read(katar, MAX);
dosya.read((char *) &ogrenci_sayi, sizeof(int));

cout << katar << endl;


cout << "Ogrenci sayisi: "
<< ogrenci_sayi << endl;
return 0;
}
Bu program çalıştırıldığında test.txt isimli dosya açılarak okunur. Okunan veriler aşağıda
belirtildiği biçimde görüntülenir:

10.3.7. Rastgele Erişimli Dosyalar


İkili verilere sahip bir dosyada verilere sırayla nasıl erişilebildiğini biliyoruz. Bazı
durumlarda ilgili verilere doğrudan erişmek söz konusu olabilir. Böyle durumlarda
rastgele erişim söz konusudur.

Her dosya, okuma göstergesi ve yazma göstergesi adı verilen iki tamsayı değer ile
ilişkilendirilmiştir. Bu değerler dosya içinde yazma ve okuma işleminin yerine
getirilebileceği bayt sayısı olarak belirlenir.

C++'da giriş ve çıkış akımlarının birer üyesi olan seekg() ve seekp() fonksiyonları
kullanılarak rastgele erişim yapılır. Bunlardan seekg() fonksiyonu, okuma göstergesini
belirlenen bir noktadan itibaren öteleme kadar hareket ettirir. Buna karşılık seekp()
fonksiyonu yazma göstergesini belirlenen noktadan itibaren öteleme kadar hareket ettirir.

285
Rastgele aramalarda kullanılan seekg() fonksiyonu iki biçimde uygulanabilir. Bunlardan
birincisi tek argüman aldığı durumdur. Eğer tek argümanı varsa, bu argüman dosyanın
başından itibaren pozisyonunu gösterir. Eğer iki parametre varsa, birinci argüman
dosyanın içinde belirli bir konumdan itibaren bir ötelemeyi belirtir. İkinci argüman ise
ötelemenin başlangıç konumunu belirtir. Ötelemenin başlangıç noktası için üç seçenek
vardır:

beg Dosyanın başı

cur Şu andaki konum

end Dosyanın sonu

Örneğin bir dosyanın başından itibaren 20. konumdan itibaren yazma işlemi yapılacak ise
şu şekilde bir tanım yapılır:

seekp(20, ios::beg)

! seekp() fonksiyonunun kullanımıyla ilgili program örneğini görmek için tıklayınız.

Önceki örneklerde ikili olarak yarattığımız test.tst dosyasını gözönüne alalım. Bu


dosyada 49. konumdan itibaren 15000 değerinin yer aldığını biliyoruz. Bu değeri rasgele
erişim yöntemiyle 20000 olarak değiştirmek istiyoruz. Bu amaçla aşağıda belirtilen
işlemler yapılır.

286
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int ogrenci_sayi =20000;

int main()
{
// Okunacak dosya tanımlanıyor..
fstream dosya ("test.txt",ios::in|ios::out|ios::binary);

// Dosya denetleniyor..
if(!dosya) {
cout << "HATA:Dosya açilamadi..";
return 1;
}

// Dosya göstergesi 49. pozisyona getiriliyor..


dosya.seekp(49, ios::beg);

// Bu pozisyondan itibaren yazılıyor..


dosya.write((char *) &ogrenci_sayi, sizeof(int));

287
return 0;
}

Bu program çalıştırıldığında, ilgili dosyanın 49. konumundan itibaren 20000 değeri


yerleştirilir.

! seekg() fonksiyonunun kullanımıyla ilgili program örneğini görmek için tıklayınız.

Bu kez önceki örnekte yapılan değişikliği yine rasgele erişim yöntemiyle okumak
istiyoruz. Değişliği dosyanın 49. konumundan itibaren yapmıştık ve bu konuma 20000
değerini yerleştirmiştik. Şimdi bu değeri görüntülemek istiyoruz.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int ogrenci_sayi;
int main()
{
// Okunacak dosya tanımlanıyor..

288
fstream dosya ("test.txt",ios::in|ios::out|ios::binary);

// Dosya denetleniyor..
if(!dosya) {
cout << "HATA:Dosya açilamadi..";
return 1;
}
// Dosya göstergesi 49. pozisyona getiriliyor..
dosya.seekg(49, ios::beg);

// Bu pozisyondan itibaren okuma yapılıyor..


dosya.read((char *) &ogrenci_sayi, sizeof(int));

// Okunan değer görüntüleniyor..


cout << "Ogrenci sayisi="
<< ogrenci_sayi
<< endl;

return 0;
}
Bu program çalıştırıldığında aşağıdaki sonuç görüntülenir:

10.3.8. Nesne Giriş/Çıkışları


Normal olarak tanımlanan verilerin disk dosyalarına yazılması ve okunmasını öğrendik.
Sınıf biçiminde ortaya konulan nesnelerin de disk dosyalarına benzer biçimde yazılması ya
da okunması mümkündür. Söz konusu dosyalar birer metin dosyası olabileceği gibi, ikili
dosya da olabilir. Çoğunlukla ikili dosya olması tercih edilir. Yazma işlemlerinde write(),
okuma işlemlerinde ise read() üye fonksiyonu kullanılır.

! Nesnelerin disk dosyalarına yazılmasıyla ilgili program örneğini görmek için tıklayınız.

289
Öğrenci numaraları ve isimlerinden oluşan bir dosya yaratacağız. Bu dosyaya veri
girişlerini nesneler yardımıyla yerine getireceğiz. Veri giriş işlemlerini yerine getirmek
üzere aşağıdaki programı hazırlıyoruz:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// ogrenci isimli sınıf tanımlanıyor…


class ogrenci {
protected:
short numarasi;
char adi[20];

290
public:
void veriOku() {
cout << "Numarasi :";
cin >> numarasi;
cout << "Adi :";
cin >> adi;
}
};

int main()
{
// ogrenci sınıfının os isimli bir
// nesnesi tanımlanıyor..
ogrenci os;
os.veriOku();

// Yazdırılacak dosya tanımlanıyor..


ofstream dosya("testOgrenci.txt",ios::binary);

// Bu dosyaya yazma işlemi gerçekleştiriliyor..


dosya.write((char *) &os, sizeof(os));

return 0;
}

Bu program çalıştırıldıktan sonra, açılan pencerede öğrenci numarası olarak 123 ve


öğrenci adı olarak "BURAK" değerini kaydediyoruz. Bu veriler disk dosyası üzerine
kaydedililir.

! Nesnelerin disk dosyalarından okunmasıyla ilgili program örneğini görmek için tıklayınız.

Nesneler ile veri girişlerini bir önceki örnekte gerçekleştirdik. Şimdi bu dosyadan yine
nesneler yardımıyla verileri okumak istiyoruz.

291
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// ogrenci isimli sınıf tanımlanıyor…


class ogrenci {
protected:
short numarasi;
char adi[20];
public:
void veriGoster() {

292
cout << "Numarasi :"
<< numarasi
<< endl;
cout << "Adi :"
<< adi
<< endl;
}
};
int main()
{
// ogrenci sınıfının os isimli
// bir nesnesi tanımlanıyor..
ogrenci os;
// Yazdırılacak dosya tanımlanıyor..
ifstream dosya ("testOgrenci.txt",ios::binary);

// Bu dosyaya yazma işlemi gerçekleştiriliyor..


dosya.read((char *) &os, sizeof(os));

// Sonuçlar görüntüleniyor..
os.veriGoster();

return 0;
}
Bu program çalıştırıldığında, testOgrenci isimli dosyanın içerdiği veriler okunur.

10.4. Yazıcı Çıkışları


Ekran ve disk üzerine veri yönlendirmesi yapılarak, verilerin ekranda görüntülenmesi ya
da diske yazdırılması sağlanabilir. Bunlara ek olarak, verileri bilgisayarımızın diğer
aygıtlarına yönlendirebiliriz. Bu aygıtları aşağıdaki tabloda listeliyoruz.

TANIM AYGIT

Com1 veya aux Birinci seri port

Com2 İkinci seri port

293
Lpt1 veya prn Birinci paralel port

Lpt2 İkinci paralel port

Lpt3 Üçüncü paralel port

con Konsol (klavye ve ekran)

Bu tabloda görüldüğü gibi, prn veya lpt1 üzerine yönlendirme yapılarak, verilerin
yazıcıdan aynen çıkması sağlanabilir.

! Yazıcıya veri yönlendirmesi yapan program örneğini görmek için tıklayınız.

Program içinde belirlenen bir katarı yazıcıdan dökmek istiyoruz. Bunun için aşağıdaki
program hazırlanır.

294
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

string katar = "Ahmet Yesevi Universitesi ";

int main()
{
// Akım tanımlanıyor..
ofstream dosya;

// Yazdırma işlemi..
dosya.open("PRN");
dosya << katar
<< endl;

return 0;
}
Bu program çalıştırıldığında yazıcı üzerinde aşağıdaki ifadenin yazdırıldığı görülür:

Ahmet Yesevi Universitesi

295
Bölüm Özeti
Bu bölümde,

• C++ akımlarını,
• ios sınıfını ve biçimlendirilmiş giriş çıkışları,
• Manipülatörlerin kullanımını,
• Dosya giriş ve çıkış işlemlerini,
• Disk dosyalarına biçimlendirilmiş veri
kaydetmeyi,
• İkili dosyalara veri okuma ve yazma
işlemlerini,
• Rasgele erişimli dosyaları,
• Yazıcı çıkışlarını,

öğrendik.

296
297
298
299
300
301
302
303
304
305
306
307
308
309
C++ Standart Şablon Kütüphanesi

Bölüm Hedefi
Bu bölümü bitirdiğinizde,

• C++ Standart şablon kütüphanesini,


• Algoritmaları,
• Bazı temel algoritmaları,
• Kabları,
• Dinamik dizileri,
• Listeleri,
• Yığıtları,
• Kuyrukları,
• Yineleyicileri,

öğrenmiş olacaksınız.

11.1. C++ Standart Şablon Kütüphanesi ile Veri Yapıları


Veri yapıları, verileri bellekte saklamak için kullanılan çeşitli yöntemleri ifade eder.
Örneğin, dizi, liste ve yığıt, kuyruk kavramları birer veri yapısı olarak değerlendirilir. C+
+'da veri yapılarını oluşturmak ve onların üzerinde işlem yapmak oldukça kolaydır. Böyle
bir amaçla C++'ın Standart Şablon Kütüphanesi 'ne başvurulur.

Standart Şablon Kütüphanesi, bazı temel bileşenlere sahiptir:

• Kablar (Container)
• Algoritmalar
• Yineleyiciler (Iterator)

Bu bileşenler, birçok yazılımda birbirleriyle ilişkili olarak görev yaparlar. Kablar, verileri
barındıran nesnelerdir. Bu nesneler verilerin bellekte saklanma biçimlerini ortaya koyar.
Algoritmalar, kablar üzerinde etkilidir. Bazı algoritmalar kabların içinde uygulanacak

310
işlemi tanımlar. Örneğin sıralama, arama ve dönüştürme işlemleri algoritma olarak kabul
edilir. Yineleyici ise, göstergelere benzer. Yineleyiciler, kabın içeriği üzerinde gezinmeyi
sağlayan olanaklardır.

11.2. Algoritmalar
Bir kab içindeki verilere bir işlem uygulanacak ise, algoritmalardan söz etmek
gerekecektir. Algoritma, kab içindeki verilere uygulanacak işlemleri tanımlar.
Algoritmalar, kab sınıflarının bir üye fonksiyonu değildir. Bunlar kendi başlarına bağımsız
birer şablon fonksiyonudur.

Standart Şablon Kütüphanesinin birçok algoritma fonksiyonu bulunmaktadır. Aşağıdaki


tabloda bunların belli başlıları tanıtılmaktadır.

Daha sonraki derslerimizde, bu algoritmaların en önemlileri üzerinde biraz daha


duracağız. Bu algoritmalar kablar ile birlikte çalıştırmak üzere tasarlanmıştır. Ancak, bu

311
algoritmaları normal dizlere de uygulama olanağımız vardır. Algoritmaları önce bildiğimiz
normal diziler üzerine uygulayarak, ne işe yaradıklarını ortaya koymak istiyoruz.

11.2.1. find() Arama Algoritması


Bir dizi yada bir kab içindeki belirli bir değeri aramak için find() algoritması kullanılır. Bu
algoritma, verilen değere karşılık gelen ilk değeri döndürür. Bu algoritma şu şekilde
tanımlanıyor:

find(başlangıç, bitiş, aranan_değer)

Bu algoritma, başlangıç ve bitiş noktalarından oluşan bir aralık içinde belirli bir değeri
bulmaktadır.

! find() algoritmasıyla ilgili program örneğini görmek için tıklayınız.

Bir basit dizi içinde yer alan değerler arasında 11 değerini aramak istiyoruz. Bu değerin
dizinin kaçıncı elemanı olduğunu bulmak üzere aşağıdaki program hazırlanabilir.

312
#include <iostream>
#include <algorithm>
using namespace std;

// Dizi tanımlanıyor..
int dizi[] = {25,43,11,56,77,81,13,90,123,900};
int main()
{
int * p;
// Arama işlemi yapılıyor..
p = find(dizi,dizi+10,11);
// Dizi görüntüleniyor..
cout << "Dizi:";
for (int i=0;i<10;i++)
cout <<dizi[i] << ' ';
cout << endl;
// Aranan elemanın konumu görüntüleniyor..
cout <<"Aranan elemanin konumu:"
<< (p-dizi)
<< endl;
return 0;
}

Bu program çalıştırıldığında, şu şekilde bir sonuç elde edilir:

313
Bu şekilde bir sonuç bulunmasının nedeni, dizilerde birinci elemanın 0 indisine sahip
olmasındadır. Yani dizinin ikinci elemanı, 43 değil 11'dir. Program içinde kullanılan find()
algoritmasının ilk parametresi, dizinin ilk değerinin göstergesini verecektir. Bu değere 10
ekleyerek arama işleminin yapılacağı aralık tanımlanmıştır. Yani 1. eleman ile 10. eleman
arasında arama işlemi yapılacaktır.

11.2.2. sort() Sıralama Algoritması


Bir dizi ya da bir kab içinde yer alan verileri belirli bir sırada elde edilmek istenebilir.
Böyle bir amaca ulaşabilmek için standart şablon kütüphanesinin sort() algoritması
kullanılır. Söz konusu algoritma şu şekilde tanımlanır:

sort(başlangıç, bitiş)

Bu algoritma, başlangıç ve bitiş noktalarından oluşan bir aralık içindeki değerleri


sıralamaktadır.

! sort() algoritmasıyla ilgili program örneğini görmek için tıklayınız.


Bir grup sayısal değeri küçükten büyüğe doğru sıralamak istiyoruz. Bu amaçla aşağıda
belirtilen programı hazırlıyoruz.

314
#include <iostream>
#include <algorithm>
using namespace std;

// Dizi tanımlanıyor..
int dizi[] = {892,210,34,-56,77,91,13,25,1,62};

int main()
{
// Sıralama işlemi yapılıyor..
sort(dizi,dizi+10);

// Sonuç görüntüleniyor..
for (int i=0;i<10;i++)
cout <<dizi[i] << ' ';
cout << endl;

return 0;
}
Program çalıştırıldığında, verilen dizi sıralı olarak elde edilir.

315
11.2.3. count() Sayma Algoritması
Bir dizi ya da bir kab içinde yer alan verileri saymak gerekebilir. Bu
amaçla standart şablon kütüphanesinin count() algoritması
kullanılır. Belirli bir aralıktaki verileri sayan algoritma şu şekilde
tanımlanır:

count(başlangıç, bitiş)

! count() algoritmasıyla ilgili program örneğini görmek için tıklayınız.


Bir dizinin içerdiği verileri sayarak sonucunu görüntüleyen program şu şekilde olabilir:

#include <iostream>
#include <algorithm>
using namespace std;

// Dizi tanımlanıyor..

316
int dizi[] = {48,25,25,43,81,81,25,90,25,85};

int main()
{
int n;
int aranan=25;

// Sayma işlemi yapılıyor..


n = count(dizi,dizi+10,aranan);
// Dizi görüntüleniyor..
cout << "Dizi:";
for (int i=0;i<10;i++)
cout <<dizi[i] << ' ';
cout << endl;
// Sayma sonucu görüntüleniyor..
cout <<aranan
<< " degerinden "
<< n
<< " adet var.."
<< endl;
return 0;
}
Program çalıştırıldığında aşağıdaki sonuç elde edilir:

11.2.4. search() Araştırma Algoritması


Bir dizi ya da bir kab içinde bir başka dizi ya da kabın araştırılması gerekebilir. Böyle bir
amaca ulaşmak için standart şablon kütüphanesinin search() algoritması kullanılır. Belirli
bir aralıktaki verileri bir başka aralıkta arayan algoritma şu şekilde tanımlanır:

search(başlangıç1, bitiş1, başlangıç2, bitiş2)

317
! search() algoritmasıyla ilgili program örneğini görmek için tıklayınız.

Program içinde tanımlanan dizi1 isimli dizi içinde dizi2 dizisinin içeriğini aramak ve hangi
konumda bulunduğunu belirlemek istiyoruz. Amacımıza uygun program şu şekilde
olabilir:

318
#include <iostream>
#include <algorithm>
using namespace std;

// Dizi tanımlanıyor..
int dizi1[] = {48,25,25,43,81,81,25,90,25,85};
int dizi2[] = {43,81,81};

int main()
{
int * p;

// Araştırma işlemi yapılıyor..

319
p = search(dizi1,dizi1+10,dizi2,dizi2+3);

// Diziler görüntüleniyor..
cout << "Ana dizi:";
for (int i=0;i<10;i++)
cout <<dizi1[i] << ' ';
cout << endl;

cout << "Aranan dizi:";


for (int j=0;j<3;j++)
cout <<dizi2[j] << ' ';
cout << endl;

// Araştırma sonucu görüntüleniyor..


if (p == dizi1+10)
cout <<"Aranan dizi bulunamadı..";
else
cout << "Eslesmenin basladigi konum:"
<< (p-dizi1)
<< endl;
return 0;
}
Bu program çalıştırıldığında şu şekilde bir sonuç elde edilir:

11.2.5. transform() Hesaplama Algoritması


Bir dizi ya da bir kab içinde bir başka dizi ya da kabın içerdiği verilere bir işlem
uygulayarak, sonuçlarını aynı kabın ya da bir başka kabın içine yerleştirilmesi gerekebilir.
Böyle durumlarda standart şablon kütüphanesinin transform() algoritması kullanılır.
Belirli bir aralıktaki verileri bir başka aralıkta arayan algoritma şu şekilde tanımlanır:

transform(başlangıç, bitiş, sonuc, işlem)

Bu algoritma, belirli bir aralıktaki verilere işlem isimli fonksiyondaki işlemleri uygular ve
sonucunu sonuc isimli dizi içine kaydeder.

320
! transform() algoritmasıyla ilgili program örneğini görmek için tıklayınız.

dizi1 isimli dizi içindeki verilerin iki katını hesaplayarak sonuçlarını dizi2 isimli dizi içine
kaydetmek istiyoruz. Bu amaçla aşağıda belirtilen yol izlenir.

321
#include <iostream>
#include <algorithm>
using namespace std;

// Dizi tanımlanıyor..
int dizi1[] = {48,25,25,43,81,81,25,90,25,85};
int dizi2[10];

int iki_kati(int);

int main()
{
// Araştırma işlemi yapılıyor..

322
transform(dizi1,dizi1+10,dizi2,iki_kati);

// Temel dizi görüntüleniyor..


cout << "Ana dizi:";
for (int i=0;i<10;i++)
cout <<dizi1[i] << ' ';
cout << endl;

// Hesaplanan dizi görüntüleniyor..


cout << "Hesaplanan dizi:";
for (int j=0;j<10;j++)
cout <<dizi2[j] << ' ';
cout << endl;

return 0;
}

// Değerlerin iki katını hesaplıyor..


int iki_kati(int x) {
return (2*x);
}
Bu program çalıştırıldığında aşağıdaki sonuç elde edilir:

11.3.Kablar
Kab, verileri bellekte saklama biçimini belirler. Örneğin, en çok kullanılan veri yapıları
arasında yer alan diziler, listeler, yığıtlar ve kuyruklar birer kab olarak değerlendirilir. C+
+'da temel yedi adet kab bulunmaktadır. Bunların dışında üç adet daha türetilmiş kab
vardır. Ayrıca kullanıcı isterse kendi kablarını tasarlayabilmektedir.

Her kab sınıfı, bu kaba uygulanan bir grup fonksiyonu içermektedir. Bir kabı programa
dahil edebilmek için öncelikle başlığın programlara dahil edilmesi gerekmektedir.
Aşağıdaki tabloda önemli bazı kablara yer verilmektedir. Her bir kabın adı, görevi ve
başlık tanımını aşağıdaki tabloda açıklıyoruz.

323
Her kabın kendisi için bir yer ayırıcısı (allocator) bulunmaktadır. Söz konusu yer
ayırıcılar bellekte kablar için yer ayrıma işini yerine getirir. Varsayılan yer ayırıcı
allocator sınıfından bir nesnedir.

11.3.1. Kablar İçin Üye Fonksiyonlar


Sıralama ve arama işlemlerini yürüten algoritmalar Standart Şablon Kütüphanesi'nin
en önemli bileşenleridir. Bu işlemlerin uygulandığı kablara diğer bazı basit işlemleri
uygulayabilmek için üye fonksiyonları kullanmak gerekebilecektir. Aşağıdaki tablo, tüm
kablarda kullanabilen ortak üye fonksiyonları sunmaktadır.

Kablarda kullanabilen bazı ortak fonksiyonlar.

11.3.2. Vektörler: Dinamik Diziler


Diziler, veri yapıları arasında önemli bir yere sahiptir. Dizi, verilerin bellekte ardarda
sıralanmış biçimidir. Örneğin işletmenin bölüm isimleri bir dizi oluşturabilir. Bu şekildeki
bir dizi tek boyutlu olarak değerlendirilir. Bazı durumlarda, aynen tablolara benzer
biçimde iki boyutlu veya daha fazla boyuta sahip diziler oluşturulabilir. Dizideki her bir
elemana, dizinin indeksi yardımıyla ulaşılır.

C++'da dizilerin nasıl yaratıldığı ve kullanıldığını biliyoruz. Dizilere esnek bir yaklaşım,
standart şablon kütüphanesinin vektör adı verilen olanakları yardımıyla yapılmaktadır.
Vektörleri dinamik diziler olarak da isimlendirmek mümkün. Bu diziler, duruma göre
genişleyebilen ya da daralabilen dizilerdir. Bu genişleme ve daralma işlemleri bizim
adımıza otomatik olarak gerçekleştirilir.

324
Standart şablon kütüphanesinin vector sınıfı dinamik dizilerin oluşturulması amacıyla
kullanılır. Bu sınıfın üye fonksiyonlarının bir kısmını biliyoruz. 9.4.1.'de söz konusu üye
fonksiyonları tablo halinde sıralamıştık. Bunlar dışında, bu sınıf ile birlikte aşağıda
sıralanan fonksiyonlar kullanılabilir.

Yukarıda sayılan insert() ve erase() üye fonksiyonları, genel olarak bir kab içindeki
elemanlar arasına rasgele bir eleman eklemek ya da çıkarmak için kullanılır. Ancak,
vektörlerde araya veri ekleme kullanışlı ve tercih edilen bir yöntem değildir. Araya bir
eleman eklemek için, yani insert() kullanılırken, önce bu konumdan sonraki tüm
elemanlar bir kaydırılır; ardından sözü edilen eleman o konuma eklenir. Silme işleminde,
yani erase() kullanırken de benzer bir yol izlenir. Silinen elemanın işgal ettiği konumu
kapatmak için tüm elemanlar bir geri kaydırılır.

Vektörlerin, ya da bir başka deyişle dinamik dizilerin kullanılabilmesi için, öncekle


programa <vector> başlığının şu şekilde dahil edilmiş olması beklenir:

#include <vector>

Bir vektörü kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:

vector<veri türü> vektör adı

Örnek:
Tamsayı değerler içeren tVector isimli bir dinamik vektör şu şekilde tanımlanır:

vector<int> tVector

11.3.2.1. Vektörün Sonuna Eleman Ekleme

325
Bir vektörün sonuna eleman ekleme söz konusu olduğunda push_back() üye
fonksiyonundan yararlanılır. Bir vektör tanımlandıktan sonra, ilgili elemanlarına erişimi
sağlamak için [ ] işleçlerinden yararlanılır. Vektörün sonuna eleman eklemek için
push_back() fonksiyonu şu şekilde kullanılır:

vektör adı.push_back(veri);

! Vektörün sonuna eleman eklemek için push_back() simgesine tıklayınız.

! push_back() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir vektöre 5 adet eleman eklemek istiyoruz. Bunun için aşağıda belirtildiği biçimde bir
yol izlenebilir:

326
#include <iostream>
#include <vector>
using namespace std;

int main()
{
// Vektör tanımlanıyor...
vector<int> d;

// Vektöre eleman ekleniyor..


d.push_back(190);
d.push_back(200);
d.push_back(345);
d.push_back(450);
d.push_back(560);

// Vektör görüntüleniyor..
cout << "Dizi:";
for (int i=0;i<d.size();i++)
cout <<d[i] << ' ';
cout << endl;

return 0;
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

! back() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir vektörün en son elemanını görüntülemek üzere back() üye fonksiyonu aşağıda
gösterildiği biçimde kullanılabilir:

327
#include <iostream>
#include <vector>
using namespace std;

int main()
{
// Vektör tanımlanıyor...
vector<int> d;

// Vektöre eleman ekleniyor..


d.push_back(190);
d.push_back(200);
d.push_back(345);
d.push_back(450);
d.push_back(560);

// Son elemanın görüntülenmesi..


cout << "Son eleman:";
cout << d.back();
cout << endl;

return 0;
}

328
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

11.3.2.2. Vektörün Sonundan Eleman Çıkarmak


Bir vektörün sonuna eleman ekleme söz konusu olduğunda push_back() üye
fonksiyonunu kullandık. Vektörün sonundaki bir elemanı çıkarmak söz konusu ise, bu kez
pop_back() fonksiyonu kullanılır. Bu fonksiyon aşağıda belirtildiği biçimde kullanılabilir:

vektör adı.pop_back(veri);

! Vektörün sonundan eleman çıkarmak için pop_back() düğmesine tıklayınız.

! pop_back() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir vektörün en sonundaki bir elemanı vektörden çıkarmak istiyoruz. Böyle bir sonuca
ulaşmak için pop_back() fonksiyonunu içeren bir program yazıyoruz.

329
#include <iostream>
#include <vector>
using namespace std;

int main()
{
// Vektör tanımlanıyor...
vector<int> d;
// Vektöre eleman ekleniyor..
d.push_back(190);
d.push_back(200);
d.push_back(345);
d.push_back(450);
d.push_back(560);

//Son elemanın vektörden //çıkarılması..


d.pop_back();
// Vektör görüntüleniyor..
cout << "Dizi:";
for (int i=0;i<d.size();i++)
cout <<d[i] << ' ';
cout << endl;
return 0;
}

330
Program çalıştırıldığında 5 adet eleman vektör içine yerleştirilmektedir. Ardından bu
vektörün son elemanı pop_back() ile silinmektedir. Bu program çalıştırıldığında şöyle bir
sonuç elde edilir:

11.3.2.3. Dizi Değerlerinin Bir Vektöre Aktarılması


Dinamik olmayan bir dizinin içerdiği elemanların tümünü ya da bir kısmını, gerektiğinde
bir vektöre yüklemek gerekebilir. Bu tür bir ekleme işlemi aşağıda belirtildiği biçimde
gerçekleştirilir.

! Dizi değerlerinin vektöre aktarılmasıyla ilgili program örneğini görmek için tıklayınız.

Program içinde tanımlanan ve başlangıç değerlerine sahip dizi isimli bir dizinin ilk 5
elemanını bir vektöre aktarmak istiyoruz. Bunun için aşağıda belirtilen yol izlenir:

331
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Başlangıç değerlere sahip bir dizi..
int dizi[] = {48,25,25,43,81,81,25,90,25,85};
// İlk 5 eleman seçilerek vektöre aktarılıyor...
vector<int> d(dizi,dizi+5);
// Vektöre eleman ekleniyor..

d.push_back(190);
d.push_back(200);
d.push_back(345);
d.push_back(450);
d.push_back(560);

// Vektör görüntüleniyor..
cout << "Dizi:";
for (int i=0;i<d.size();i++)
cout <<d[i] << ' ';
cout << endl;
return 0;
}

332
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

11.3.3. Listeler
Vektörlerin başına ve sonuna kolayca kayıt edebiliyorduk. Ancak araya kayıt girilmesi söz
konusu olduğunda, vektörler bu amaç için kullanışlı olmuyordu. Çünkü araya kayıt
girebilmek için, ilgili konumdan sonraki tüm elemanları bir kaydırarak yer açmak
gerekiyordu. Silme işleminde de benzer bir sorun yaşanıyordu. Bu sorunları önlemek
üzere, vektörler yerine listelerin kullanılması mümkündür.

Vektör elemanlarına doğrudan, yani rastgele erişebiliyorduk. Vektörlerin aksine liste


elemanlarına sırayla erişilebilir. Listeler iki yönlüdür. Bu nedenle elemanlara önden
arkaya veya arkadan öne doğru erişim söz konusu olabilir. Listeler için rastgele erişim
kullanılmaz. Aşağıdaki tablo listelerde kullanılabilecek bazı üye fonksiyonlara yer
vermektedir.

Listelerin C++ içinde standart şablon kütüphanesi yardımıyla kullanılabilmesi için,


öncelikle programa <list> başlığının şu şekilde dahil edilmiş olması beklenir:

#include <list>

Bir listeyi kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:

list<veri türü> liste adı

333
11.3.3.1. Listelere Eleman Ekleme ve Çıkarma
Bir listeye baş tarafından eleman eklemek için push_front, sonuna eleman eklemek için
push_back() fonksiyonları kullanılır. Bu fonksiyonlar soldaki şekilde tanımlanmaktadır.

! Listelere eleman eklemek ve çıkarmakla ilgili program örneğini görmek için tıklayınız.

Bir listenin başına ve sonuna birer elaman ekleyeceğiz. Bunu sağlamak için aşağıda
belirtilen yol izlenebilir:

334
#include <iostream>
#include <list>
using namespace std;

int main()
{
// Liste tanımlanıyor...
list<int> ls;
// Liste elemanları yerleştiriliyor..
ls.push_back(190);
ls.push_back(200);
ls.push_back(345);
ls.push_back(450);
ls.push_back(560);
// Listenin başına bir eleman ekleniyor..
ls.push_front(300);
// Listenin sonuna bir eleman ekleniyor..
ls.push_back(999);
// İlk elemanın görüntülenmesi..
cout << "Ilk eleman:";

335
cout << ls.front();
cout << endl;
// Son elemanın görüntülenmesi..
cout << "Son eleman:";
cout << ls.back();
cout << endl;

return 0;
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

11.3.3.2. Listelerin Sıralanması


Listelerin içerdikleri verileri sıralamak söz konusu olduğunda sort() üye fonksiyonundan
yararlanılır. Bu fonksiyon şu şekilde kullanılır:

liste adı.sort();

! sort() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.

Bir dizinin içerdiği verileri bir liste içine ekleyerek, bu listeyi sıralamak ve sonuçlarını
görüntülemek istiyoruz.

336
#include <iostream>
#include <list>
using namespace std;
int main()
{
// Başlangıç değerlere sahip bir dizi..
int dizi[] = {56,11,-14,67,88,67,91,90,25,85};

// Liste tanımlanıyor...
// Dizideki ilk 8 eleman seçilerek
// listeye aktarılıyor...
list<int> ls(dizi,dizi+8);
// Liste elemanları sıralanıyor..
ls.sort();
// Liste elemanlarının görüntülenmesi..
cout << "Liste:";
while (!ls.empty()) {
cout <<ls.front() << ' ';
ls.pop_front();
}
cout << endl;

return 0;

337
}
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:

11.3.4. Yığıtlar
Listelere benzer bir diğer yapı yığıtlardır (stacks). Ancak yığıtlara veri ekleme ve çıkarma
işlemleri ancak yığıtın sonunda yapılabilir. Doğal olarak yığıtlar "ilk giren son çıkar"
prensibine göre işlem görür.

Yığıtların standart şablon kütüphanesi yardımıyla oluşturulabilmesi ve kullanılabilmesi


için, öncekle programa <stack> başlığının şu şekilde dahil edilmiş olması beklenir:

#include <stack>

Bir yığıtı kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:

stack<veri türü> yığıt adı

! Yığıtlarla ilgili program örneğini görmek için tıklayınız.

Bir yığıt yaratmak ve içine beş adet veri yerleştirmek istiyoruz. Yığıt oluşturulduktan
sonra son elemanı görüntülenecektir.

338
#include <iostream>
#include <stack>
using namespace std;
int main()
{
// Yığıt tanımlanıyor..
stack<int> y;
// Yığıta beş eleman ekleniyor..
y.push(190);
y.push(200);
y.push(345);
y.push(450);
y.push(560);
// Yığıtın son elemanı görüntüleniyor..
cout << "Son eleman:";
cout <<y.top();
cout << endl;
return 0;
}
Bu program çalıştırıldığında aşağıda belirtildiği biçimde bir sonuç elde ediler.

339
11.3.5. Kuyruklar
Yığıtlarda veri ekleme ve çıkarma işlemi listenin sadece sonundan yapılabiliyordu. Buna
karşılık, kuyruk (queue) adı verilen veri yapılarında ise ekleme işlemleri listenin bir
ucundan, çıkarma işlemleri ise diğer ucundan yapılır. Kuyruklar "ilk giren ilk çıkar"
prensibine göre işlem görür.

Kuyrukların standart şablon kütüphanesi yardımıyla oluşturulabilmesi ve kullanılabilmesi


için, öncelikle programa < queue > başlığının şu şekilde dahil edilmiş olması beklenir:

#include <queue>

Bir kuyruğu kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:

queue<veri türü> queue adı

! Kuyruklarla ilgili program örneğini görmek için tıklayınız.

Bir kuyruğa beş adet sayısal değer ekledikten sonra, bir elemanını silmek istiyoruz.
Kuyruklarda ekleme işlemleri kuyruğun sonuna yapılır. Silme işlemi ise kuyruğun
başındaki elemandan başlayabilir. Amacımıza uygun program şu şekilde olabilir:

340
#include <iostream>
#include <queue>
using namespace std;

int main()
{
// Kuyruk tanımlanıyor..
queue<int> k;
// Kuyruğa 5 eleman ekleniyor..
k.push(190);
k.push(200);
k.push(345);
k.push(450);
k.push(560);
// Kuyruktan bir eleman çıkarılıyor..
k.pop();
// Kuyruğun ilk elemanı görüntüleniyor..
cout << "Ilk eleman:";
cout <<k.front();
cout << endl;
// Kuyruğun son elemanı görüntüleniyor..
cout << "Son eleman:";

341
cout << k.back();
cout << endl;
return 0;
}
Bu program çalıştırıldığında, kuyruğa beş eleman eklenir ve bir eleman silinir. Ekleme
işlemlerinin kuyruğun sonuna yapıldığı, silme işleminin ise kuyruğun başından itibaren
gerçekleştirildiği görülüyor.

11.3.5.1. Çift Uçlu Kuyruklar


Kuyruklarda ekleme işlemleri kuyruğun sonuna yapılabiliyordu. Silme işlemi ise kuyruğun
başındaki elemandan başlayabiliyordu. Bu kısıtlamalardan kurtularak daha esnek bir
kuyruk yapısına ulaşmak için, standart şablon kütüphanesine çift uçlu kuyruklar
eklenmiştir. Çift uçlu kuyruklar bazı yönlerden vektörlere, bazı yönlerden listelere benzer.
Bir vektör gibi [ ] işleci yardımıyla rasgele erişimi destekler. Çift uçlu kuyruklara önden
ve arkadan erişilebilir. Kısacası, çift uçlu kuyruk, push_front() ve pop_front()
fonksiyonlarını da destekleyen bir tür vektördür. Bildiğiniz gibi, bu fonksiyonları vektörler
desteklemez.

Çift uçlu kuyrukların standart şablon kütüphanesi yardımıyla oluşturulabilmesi ve


kullanılabilmesi için, öncelikle programa <deque> başlığının şu şekilde dahil edilmiş
olması beklenir:

#include <deque>

Çift uçlu kuyrukları kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:

deque<veri türü> kuyruk adı

342
! Çift uçlu kuyruklarla ilgili program örneğini görmek için tıklayınız.

Çift uçlu bir kuyruk yarrattıktan sonra, bu kuyruğun 4. elemanını rasgele erişerek
değiştirmek istiyoruz. Bu amaçla aşağıdaki programı hazırlıyoruz.

#include <iostream>

343
#include <deque>
using namespace std;

int main()
{
// Kuyruk tanımlanıyor..
deque<int> cuk;
// Kuyruğa 5 eleman ekleniyor..
cuk.push_back(190);
cuk.push_back(200);
cuk.push_back(345);
cuk.push_back(450);
cuk.push_back(560);

// Kuyruktaki 4. eleman değiştiriliyor..


// İndis 0 dan başladığı için 4 yazılıyor..
cuk[3]=25;
// Kuyruk görüntüleniyor..
cout << "Kuyruk:";
for (int i=0;i<cuk.size();i++)
cout <<cuk[i] << ' ';
cout << endl;

return 0;
}
Bu program çalıştırıldığında aşağıda gösterildiği biçimde bir sonuç elde edilir :

11.4. Yineleyiciler
Yineleyiciler, bir kab içindeki elemanlara tek tek erişmeyi sağlayan mekanizmadır.
Yineleyiciler, hangi algoritmanın hangi kab ile kullanılabileceğini belirlerler. Yineleyiciler,
bir algoritma ile bir kabı birbirine bağlayan mekanizmadır.

Yineleyiciler, kab içinde genellikle bir elemandan diğerine sırayla erişime neden olurlar.
Yineleyiciler ++ işleci ile artırılarak sonraki elemanlara; -- ile eksiltilerek önceki
elemanlara erişim sağlanabilir. Yineleyiciler bildiğimiz göstergelere benzerler.
Yineleyicinin işaret ettiği bir elemanın değeri * işleci kullanılarak öğrenilebilir.

344
11.4.1. Erişim Yönünden Yineleyiciler
Yineleyiciler bir kabın içindeki elemanlara erişimi sağlar. O halde bunlar verilerin
bellekteki konumlarına işaret eder. Yineleyicilerin elemanlara erişim yönünden üç farklı
sınıfından söz edilebilir:

• İleri yineleyicisi
• İki yönlü yineleyici
• Rasgele erişimli yineleyici

Birincisi, yani ileri yineleyicisi tercih edildiğinde, ++ işleci yardımıyla kab içinde sadece
ileri doğru ve birer birer hareket edebilir. Eğer bir algoritma ileriye doğru adımlarken
kabdan okuma yapabilir ve yazma işlemi yapabilirse böyle bir algoritma ileri yineleyicisi
kullanmalıdır. Geriye doğru hareket etmesi, ya da herhangi bir konuma doğrudan
erişmesi mümkün değildir.

Bir algoritma bir kab içinde hem ileri hem de geri adımlayabiliyorsa, böyle bir algoritma
iki yönlü yineleyici kullanacaktır. Bu tür bir hareketi ++ veya -- işleçleri yardımıyla birer
birer ileri ya da geri doğru hareket ederek gerçekleştirir.

Bir algoritmanın kab içindeki herhangi bir elemana doğrudan erişmesi için rastgele erişim
yineleyicisi kullanılmalıdır. Rastgele erişim yineleyicisi ileri yineleyicisi ve iki yönlü
yineleyiciden farklı bir davranış biçimi sergiler. Bu yineleyici, kab içindeki elemanlara ileri
ve geri doğru hareket ederek erişebildiği gibi, gerektiğinde herhangi bir konuma
doğrudan da erişebilir. Rastgele erişim yineleyicisi, verilere erişim açısından dizilere
benzer. Aritmetik işlemlerde sadece rastgele erişim yineleyicilerinin kullanılabileceği
unutulmamalıdır. Örneğin,

yineleyici = yineleyici +1

biçimindeki bir işlem sadece rastgele erişim yineleyicileri için geçerlidir.

345
11.4.2. Giriş ve Çıkış Yönünden Yineleyiciler
Yineleyicileri erişim yönünden sınıflandırabileceğimiz gibi, giriş ve çıkış yönünden de
sınıflandırabiliriz. Giriş yineleyicisi, art arda gelen verileri bir kabın içine okumak için
kullanılır. Bu yineleyici bir giriş aygıtına, örneğin klavye, işaret edebilir. Çıkış yineleyicisi
ise bir çıkış aygıtına, örneğin ekran, işaret etmesi söz konusu olabilir. Saydığımız bu iki
yineleyicinin, giriş çıkış aygıtlarına işaret ettiği anlaşılmaktadır.

Eğer bir algoritmanın, bir kab boyunca elemanları kaba yazmadan tek tek okuyarak
ilerlemesi söz konusu ise, bu algoritma kendisini kaba bağlamak için giriş yineleyicisini
kullanmak zorundadır. Giriş yineleyicileri, * işlecini atama işlemlerinin sağ tarafında
destekler. Örneğin,

deger = * iterasyon;

yazılabilir. Buna karşılık, bir kabın içinde ileri yönde adım adım hareket ediliyorsa ve bu
hareket esnasında okuma değil yazma işlemi yapılıyorsa, bu algoritma çıkış yineleyicisini
kullanacaktır. Çıkış yineleyicisi * işaretini, atama ifadesinin sol yanında kabul edebilir.
Örneğin,

* iterasyon = deger;

! Giriş/Çıkış yineleyicilerinin işleyişini görmek için simgelere tıklayınız.

346
Aşağıdaki tabloda Ğiriş-Çıkış yönünden yineleyiciler verilmektedir:

Yineleyici Erişim Yön

Giriş Sıralı Sadece ileri

Çıkış Sıralı Sadece ileri

11.4.3. Kabların ve Algoritmaların Yineleyici Yardımıyla


Eşleştirilmesi
Yineleyiciler, bir algoritma ile bir kabı birbirine bağlayan mekanizmadır. Ancak her
algoritma her kaba uygulanamaz. Algoritmaların kablarla nasıl kullanılabilmesi için,
kablarla yineleyicilerin ve algoritmalarla yineleyicilerin ilişkisini ortaya koymak gerekir.

Vektör ve çift uçlu kuyruklar; rasgele erişim, iki yönlü, ileri, giriş ve çıkış yineleyicilerini
kabul eder. Yani her çeşit yineleyiciyi kabul eder. Buna karşılık, listeler rasgele erişim
dışındaki tüm yineleyicileri kabul eder.

Bir kab için belirli bir yineleyicinin kullanılabilmesi için, program içinde bazı
tanımlamaların yapılması gerekiyor. Belirli bir kabı gösteren bir yineleyici şu şekilde
tanımlanabilir:

kab_adı<veri_türü>::iterator iterator_adı

Bu şekilde bir tanım yapıldığında, standart şablon


kütüphanesi kabın türüne göre yineleyicinin türünü
belirler. Örneğin, kab bir liste ise, çift yönlü yineleyiciden
söz edilir. Eğer bir vektör ise, yineleyici rasgele erişim
yineleyicisi olarak belirlenir.

Algoritmalar ancak uyumlu oldukları kablara


uygulanabilirler.

! Program örneğini görmek için tıklayınız.

Bir dizinin elemanlarını bir liste içine yerleştirdikten sonra, bu listeyi sıralamak ve
sonuçlarını görüntülemek istiyoruz. Bunu gerçekleştirmek için, liste kabı için yineleyicileri
tanımlayarak kullanacağız.

347
#include <iostream>
#include <list>
#include <algorithm>

using namespace std;


int main()
{

// Başlangıç değerlere sahip bir dizi..


int dizi[] = {56,11,-14,67,88,67,91,90,25,85};
// Liste tanımlanıyor...
list<int> ls;
// Listeye dizi elemanları yerleştiriliyor..
for (int i=0;i<10;i++)
ls.push_back(dizi[i]);
// Yineleyici tanımlanıyor...
list<int>::iterator it;
// Liste elemanları sıralanıyor..
ls.sort();
// Liste elemanlarının görüntülenmesi..
for (it = ls.begin();it != ls.end(); it++)
cout << *it << ' ';

348
cout << endl;

return 0;
}
Bu program çalıştırıldığında aşağıda gösterildiği biçimde bir sonuç elde edilir:

Programın bazı satırlarını yorumlamak istiyoruz: Tamsayı, yani int türü verilerden oluşan
liste için it isimli bir yineleyici şu şekilde tanımlanmıştır:

list<int>::iterator it;

Bu yineleyici kullanılarak aşağıdaki döngü tanımlanmakta ve yineleyicinin işaret ettiği dizi


elemanları görüntülenmektedir.

for (it = ls.begin();it != ls.end(); it++)


cout << *it << ' ';

Listenin başlangıcına işaret eden yineleyici it=ls.begin() ile tanımlanıyor. Listenin sonu
ise it != ls.end() ile denetleniyor. Programda yer alan *it ifadesi yineleyicinin
elemanlarına işaret etmektedir.

Bölüm Özeti
Bu bölümde,

• C++ Standart şablon kütüphanesini,


• Algoritmaları,
• Bazı temel algoritmaları,
• Kabları,
• Dinamik dizileri,
• Listeleri,
• Yığıtları,
• Kuyrukları,
• Yineleyicileri,

öğrendik.

349
350
351
352
353
354
355
356
357
358
359
"Bilgisayar Programlama 2" dersinin
sonuna gelmiş bulunmaktayız. Bu
çalışmamızın tüm öğrencilerimize faydalı
olması dileğiyle...

Sınavlarınızda başarılar...

Dr. Yalçın ÖZKAN

360
361

You might also like