Professional Documents
Culture Documents
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.
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.
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.
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.
5
1. Kapsülleme (Encapsulation)
2. Kalıtım (Inheritance)
3. Çok biçimlilik (Polymorphism)
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.
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.
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.
7
Otomobil kullanmak için otomobilin her parçası hakkında bilgiye gereksinim yoktur.
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.
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.
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.
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.
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.
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:
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.
Ç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.
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,
öğ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.
Bir C++ programı bir veya daha fazla fonksiyondan oluşmakta ve her bir fonksiyon bir
veya daha fazla sayıda deyim içermektedir.
Ş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,
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;
#include <iostream>
using namespace std;
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,
öğrenmiş olacaksınız.
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.
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.
#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.
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
...
...
...
...
25
Bu önişlemci emri, bir kaynak dosyasının bu programa dahil edilmesini sağlar.
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;
}
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;
}
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.
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.
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.
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;
int main()
{
int i;
for (i=0;i<=50;i++)
cout << i << "\n";
return 0;
}
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
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>
int main()
int durum;
durum=1;
...
return 0;
Şekil1'de durum isimli yerel bir değişken tanımlanmakta ve ona bir değer atanmaktadır.
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>
durum=1;
main()
...
#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:
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()
...
35
5-) register Değişkenler:
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:
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 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.
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;
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
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;
int main()
{
int sayi1=120;
int sayi2=300;
int toplam;
toplam = sayi1 + sayi2;
cout << "Sonuc:" <<toplam<<"\n";
return 0;
}
a=a+1
b=b-1
a++
39
b--
#include <iostream>
using namespace std;
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;
}
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.
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:
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.
43
#include <iostream>
using namespace std;
44
Bölüm Özeti
Bu bölümde,
öğ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,
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.
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.
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:
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ı
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;
}
Kosul dogru
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 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ı
Ö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;
}
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
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.
Ö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;
int main()
{
for (i=1;i<=10;i++)
cout << i << "\n";
return 0;
}
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;
62
int main()
{
int i,baslangic,son;
int toplam=0;
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:
Örnek3:
Verilen bir sayısal değerin faktöriyelini bulmak istiyoruz. n bir tamsayı olmak izere, n'in
faktöriyeli,
n!=1x2x3x...n
63
#include <iostream>
using namespace std;
int main()
{
int i,sayi;
int faktoriyel=1;
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.
65
Şekil 2: while ve do while döngülerinin çalışma şekilleri.
Ö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;
int i;
int n=5;
int main()
{
while(i<n)
cout << i++ << "\n";
return 0;
}
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;
}
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.
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.
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ı.
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:
int i;
void main()
{
for (i=1;i<100;i++)
{
cout << i << "\n";
if (i==5)
break;
}
}
Ö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";
}
}
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.
72
// switch deyimi için örnek..
#include <iostream>
using namespace std;
void main()
{
int kod;
cout << "Il trafik kodu :";
cin >> kod;
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
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.
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}
! 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;
void main()
{
cout << a[0] << "\n";
cout << a[1] << "\n";
cout << a[2] << "\n";
cout << a[3] << "\n";
cout << a[4] << "\n";
}
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;
void main()
{
for (i=0;i<=4;i++)
cout << a[i] << "\n";
}
Ö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;
}
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.
88
! gets() fonksiyonunun işleyişini görmek için "Klavyeden
Değer Gir" düğmesine 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";
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:
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ı.
******************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ı.
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.
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:
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:
Örnek
96
Bu program çalıştırıldığında, katar isimli dizinin uzunluğu, yani kaç karakter içerdiği
bulunur.
char a[]={'B','u','r','a','k'} ;
Bunun yerine,
char a[]="Burak";
Ö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:
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.
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.
int *a;
99
Göstergelerin bildirimi için yukarıdaki genel tanım kullanılır.
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:
Ö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:
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.
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.
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();
int sayi(void);
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,
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.
hos geldiniz..
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.
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:
deyimler
Ö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:
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:
Ş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.
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.
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:
p Yapı değişkeni.
111
yapı_değişkeni.alan_adı
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.
Ö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:
112
Şekil 1: testYapi adında bir yapı tanımı.
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.
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.
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
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;
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.
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.
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,
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.
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ı
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.
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.
Ö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.
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.
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.
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.
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.
Ö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.
Ö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.
! 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.
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
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.
Örnek
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.
! Fonksiyonlardan nesne döndürmek ile ilgili program örneğini görmek için tıklayınız.
Örnek
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();
…
};
Ö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.
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İ
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.
Ö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.
Ö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:
! Ç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:
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.
Ö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.
class testSinif
{
int a;
..
};
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:
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.
Ö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
Değer ile çağırma yönteminde çağrılan fonksiyona sadece değişkenlerin değeri gönderilir,
değişkenler gönderilmez.
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.
Ö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.
167
Örnek2:
Başvuruları açıklamak üzere aşağıdaki programı düzenliyoruz. Bu programda,
onceSonra fonksiyonuna değişkenlerin başvuruları geçirilmektedir.
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.
Ö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)
/*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.
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.
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 :
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,
Ö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;
172
6.5.1.1. Diziler İçin Yer Ayırma
Nesne çok sayıda elemandan ibaret ise, yani bir dizi söz konusu ise,
Ö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.
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.
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.
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
Bölüm Hedefi
Bu bölümü bitirdiğinizde,
öğrenmiş olacaksınız.
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.
• 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 ?
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.
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";
}
! Tarih bilgisini ekrana yazan bir fonksiyonun aşırı yüklenmesiyle ilgili program örneğini
görmek için tıklayınız.
185
#include <iostream>
using namespace std;
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:
! 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 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 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);
}
! 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);
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,
Sayi 1=5
testSinif s2(10);
ile bu yeni argümana bağlı olarak sonuç görüntülenir. Son olarak yeniden,
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,
öğrendik.
195
196
197
198
İŞLEÇLERE AŞIRI YÜKLEME
Bölüm Hedefi
Bu bölümü bitirdiğinizde,
öğrenmiş olacaksınız.
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:
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:
. :: ?
"+" 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.
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);
};
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;
}
203
İşleç Anlamı İşleç Anlamı
== Eşit && Ve
< Küçük
Ö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,
öğrendik.
209
210
211
212
KALITIM
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.
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.
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.
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:
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.
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;
};
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:
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;
}
};
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..
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.
Bir sınıf bir diğer sınıftan private kalıtımla oluşturulurken şu şekilde bir tanım yapılır:
! 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;
}
};
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..
return 0;
}
! 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..
225
private ile kalıtımda, türetilmiş sınıfın, temel sınıfın public üyelerine erişimi engellenir.
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..
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;
230
! Kurucu ve yok edici fonksiyonlarının işleyiş sırasını görmek için simgelere tıklayınız.
231
{
..
};
! İ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;
}
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.
Ö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.
Ö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:
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.
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.
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)
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.
Üç 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";
return 0;
}
Bu program çalıştıırldığında şöyle bir sonuç elde edilir:
cout.width(20);
252
! Fonksiyonları kullaranak biçimlendirme yapan program örneklerini görmek için
tıklayınız.
#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:
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.
254
Argüman alan bir kısım ios manipülatörleri ise aşağıdaki tabloda yer almaktadır:
Üç 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;
}
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:
Eğer parametresiz manipülatör kullanılacak ise ise aşağıda belirtildiği biçimde tanımlanır:
Üç 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:
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.
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.
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.
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;
}
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");
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.
! 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:
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.
277
! get() fonksiyonuyla ilgili program örneğini görmek için tıklayınız.
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");
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;
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:
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:
Eğer okuma işlemi gereçekleşecek ise read() fonksiyonu için aşağıdaki tanım yapılır:
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.
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;
int main()
{
// Yazdırılacak dosya tanımlanıyor..
ofstream dosya ("test.txt", ios::out | ios::binary);
283
// Disk dosyasına yazdırma işlemi..
dosya.write(katar, strlen(katar));
dosya.write((char *) &ogrenci_sayi, sizeof(int));
Ö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);
// Okuma işlemi..
dosya.read(katar, MAX);
dosya.read((char *) &ogrenci_sayi, sizeof(int));
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:
Ö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)
286
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
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;
}
287
return 0;
}
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);
return 0;
}
Bu program çalıştırıldığında aşağıdaki sonuç görüntülenir:
! 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;
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();
return 0;
}
! 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;
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);
// 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.
TANIM AYGIT
293
Lpt1 veya prn Birinci paralel port
Bu tabloda görüldüğü gibi, prn veya lpt1 üzerine yönlendirme yapılarak, verilerin
yazıcıdan aynen çıkması sağlanabilir.
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;
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:
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,
öğrenmiş olacaksınız.
• 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.
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.
Bu algoritma, başlangıç ve bitiş noktalarından oluşan bir aralık içinde belirli bir değeri
bulmaktadır.
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;
}
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.
sort(başlangıç, bitiş)
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ş)
#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;
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;
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;
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);
return 0;
}
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.
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.
#include <vector>
Bir vektörü kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:
Örnek:
Tamsayı değerler içeren tVector isimli bir dinamik vektör şu şekilde tanımlanır:
vector<int> tVector
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);
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ö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:
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;
return 0;
}
328
Bu program çalıştırıldığında şöyle bir sonuç elde edilir:
vektör adı.pop_back(veri);
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);
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:
! 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.
#include <list>
Bir listeyi kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:
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:
liste adı.sort();
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.
#include <stack>
Bir yığıtı kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:
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.
#include <queue>
Bir kuyruğu kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:
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.
#include <deque>
Çift uçlu kuyrukları kullanabilmek için, en basit gösterimiyle şu şekilde bir tanım yapılır:
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);
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
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;
346
Aşağıdaki tabloda Ğiriş-Çıkış yönünden yineleyiciler verilmektedir:
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ı
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>
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;
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,
öğ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...
360
361