Bir C programı derlenip çalıştırıldığında, çalışmaya main fonksiyon bloğunun başından başlar ve satır satır deyimleri çalıştırarak devam eder. Bu tip çalıştırmaya programlama dünyasında Ardışık Çalıştırma (Sequential Execution) adı verilmektedir. Fakat her zaman ardışık çalıştırma ile kod yazmak mümkün olur mu? Örneğin aynı bloğun içerisinde, eğer bir koşul gerçekleşirse bir kodu, gerçekleşmezse başka bir kodu çalıştırmak istiyorsunuz. Bunu nasıl yapacaksınız? Her iki kodu da ard arda yazarsanız program her ikisini de çalıştıracaktır. Fakat bu senaryoda biz bunu istemiyoruz değil mi?
Yapısal Programlamada Kontrol Yapıları
1960 yıllarına gittiğimizde o zamanki programcılar da bu sounun farkına vardılar. Ardışık çalıştırma güzeldi, ama birçok konuda yetersiz kalıyordu. Hemen işe koyuldular ve programlama dillerine bu tip yapıları nasıl ekleyebileceklerini tartıştılar. Öncelikle kodda bir noktadan başka bir noktaya atlama olayını Denetimin Aktarılması (Transfer of Control) olarak adlandırdılar.
Daha sonra da goto Deyimi (goto Statement) adı verilen bir yapı ortaya çıkardılar. Bu deyim ile programın çalışmasını anında başka satırlara atlatmak mümkün hale geldi. Bunu da kod içerisinde Etiket (Label) adı verilen çeşitli semboller ile yapmak mümkündü.
Yıllar geçti ve yapısal programlama yaklaşımı konuşulmaya başlandı. Yapısal programlama ile kodlar artık blok yapısına büründü ve okunabilirliği inanılmaz derecede arttı. Fakat burada bir sorun vardı. goto deyimi zamanında pek çok sorunun üstesinden gelmişse de yapısal programlamada tam bir baş belası haline gelmiştir. Çünkü programın herhangi bir yerinden başka bir yerine kolayca atlamak yapısal programlamada istenilen bir şey değildi. Yapısal programlama dilleri bunu o kadar ciddiye aldı ki neredeyse yapısal programlama dillerinin adı goto Elimination (goto Yok Etme) olacaktı.
Yapısal programlama teorisinin babası sayılan Corrado Böhm ve Giuseppe Jacopini isimli iki araştırmacı, 1966 yılında yayımladıkları "Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules" isimli akademik makalede programların 3 adet kontrol yapısı (control structure) ile yazılabileceğini vurgulamışlardır.[1] Bunlar:
- Ardışık Yapı (Sequential Structure)
- Seçmeli Yapı (Selection Structure)
- Yinelemeli Yapı (Iteration Structure)
Ardışık Yapı (Sequential Structure), daha önce bahsettiğimiz ardışık çalıştırma ile aynı anlamı taşıyordu. Bu mantığa göre bir satırdan sonra diğer satır çalıştırılarak programın akışı düzenli bir şekilde devam ediyordu. Bu yapının sözde kod ve akış diyagramı örneği şu şekildedir:
toplam ← 0
toplam ← toplam + 10;
flowchart TD
S(( )) --> A["toplam = 0"]
A --> B["toplam = toplam + 10"]
B --> E(( ))
Akış şemasında dikdörtgen şeklinin belli bir aritmatiksel işlemi belirttiğini daha önce söylemiştik. Buradaki daireler ise bu kod parçasından önce çalışan ve sonra çalışacak olan diğer kodları belirtmek için kullanılmıştır. Burada karmaşık olan hiçbir şey yoktur. Bir kod parçası çalıştıktan sonra bir diğeri çalıştırılır. Zaten şu ana kadar çoğu kodda böyle bir yapı kullandık. Şimdi diğer yapılara geçelim.
Seçmeli Yapı (Selection Structure), kodun çalışma akışını belli şartlara göre dallandırmaya (branching) yarayan yapılara denir. Programda belli kararlar alınması gerektiğinde kullanılır. Programın akışı 2 veya daha fazla yola ayrılabilir. Daha önce kullandığımız ve bu bölümde anlatacağımız if deyimi de bu kategoriye girer. Bu yapının sözde kod ve akış diyagramı örneği şu şekildedir:
IF not <= 40
PRINT "Kaldınız!"
flowchart TD
S(( )) --> A{"not <= 40"}
A -->|Evet| B[/ÇIKTI VER: Kaldınız!/]
B --> E(( ))
A -->|Hayır| E
Eşkenar dörtgenin akış şemasında karar yapısındaki koşulu, paralelkenarın ise girdi/çıktı işlemini belirttiğini daha önce söylemiştik. Burada eğer not değişkeni 40’tan küçük veya ona eşit ise ekrana Kaldınız! yazılacak ve bir sonraki satırdan çalışmaya devam edilecektir. Eğer not değişkeni 40’tan büyük olursa, direk bir sonraki satıra geçiş yapılacaktır. İşte seçmeli yapılar ile denetimin aktarılması (transfer of control) işlemi bu şekilde gerçekleştirilir.
Yinelemeli Yapı (Iteration Structure), bir kodun birden fazla kez çalışmasını sağlayan yapılardır. Bu yapılarda da yine seçmeli yapıda olduğu gibi koşul ifadeleri kullanılır. Fakat bu sefer koşulun gerçekleşmesi sonucu, yapılması gereken işlem birden fazla kez tekrarlanabilir. Bu yapı ile ilgili ayrıntılı bilgileri bir sonraki yazıda zaten göreceğiz. Şimdilik sözde kod ve akış şeması örneğini verip geçelim:
WHILE sayac < 6
sayac ← sayac + 1
flowchart TD
S(( )) --> A{"sayac < 6"}
A -->|Evet| B["sayac = sayac + 1"]
B --> A
A -->|Hayır| E(( ))
C Dilinde Kontrol Yapıları
Kontrol yapıları ile ilgili teorik açıklamaları yaptığımıza göre artık C dilindeki kullanımlarına geçebiliriz. Az önce gösterdiğimiz seçmeli ve yinelemeli yapılar için C dilinde çeşitli Kontrol Deyimleri (Control Statements) bulunmaktadır. Ardışık yapı zaten C dilinde blok içinde satır satır yazdığımız kodları temsil etmektedir. C dilinde kullanılan seçmeli yapılara Seçmeli Deyimler (Selection Statements), yinelemeli yapılara ise Yinelemeli Deyimler (Iterative Statements) adı verilmektedir. C dilinde bulunan ve yazılarım boyunca göreceğimiz seçmeli ve yinelemeli deyimlerin özeti şu şekildedir:
Biz bu bölümde if ve if...else deyimlerini anlatacağız. Öncelikle bu 3 seçmeli deyimin nasıl isimlendirildiklerine bakalım. if deyimi Tek Seçimli Deyim (Single-Selection Statement) olarak adlandırılır. Çünkü bu deyimdeki koşulun doğru olması sonucu ya bir kod parçası çalışır ya da çalışmaz. if...else deyimi ise Çift Seçimli Deyim (Double-Selection Statement) olarak isimlendirilir. Çünkü bu deyimde, eğer deyimdeki koşulun gerçekleşmediği durum ile karşılaşılırsa bunun için de ayrı bir kod bloğu yazılabilir.
Bloklardan herhangi biri çalıştırıldıktan sonra programın akışına devam edilir. Daha sonra göreceğimiz switch deyimi Çok Seçimli Deyim (Multiple-Selection Statement) olarak adlandırılır. Bunun nedeni de bu deyimin birden fazla seçim koşuluna sahip olmasından kaynaklanmaktadır. Aslında bu deyim yerine yine bu yazıda göreceğimiz iç içe if...else deyimleri de kullanılabilir.
if Deyimi
Öncelikle if deyimi ile başlayalım. Bu deyim ile bir kodu sadece belirli bir koşul gerçekleştiğinde çağırabilir, gerçekleşmediğinde ise onu atlayabilirsiniz. if deyiminin genel yapısı aşağıdaki gibidir:
if(<koşul_ifadesi>) {<deyimler>}
Burada "<koşul_ifadesi>" kısmına sonucu 0 veya sıfırdan farklı bir değer üreten herhangi bir ifade gelebilir. Fakat buradaki ifadeler genellikle bir önceki kısımda gördüğümüz karşılaştırma operatörleri ve mantıksal operatörler kullanılarak oluşturulmaktadır. "<deyimler>" kısmına ise, koşul gerçekleştiğinde çalışacak olan kodları yazarız. if deyiminde, gerçekleşecek eylemleri belirten deyimlerin yazıldığı bloğa if bloğu adı verilir. Şimdi bu konu ile ilgili bir sözde kod yazıp bunu C koduna çevirelim:
not ← INPUT
IF not >= 0 AND not <= 40
PRINT "Kaldınız!"
int not;
scanf("%d", ¬);
if((not >= 0) && (not <= 40))
{
puts("KALDINIZ!");
}
Bu küçük örnekte kullanıcıdan bir not bilgisi alınıp not değişkeninde depolanıyor. Daha sonra da if deyimi ile bu değerin 0 ile 40 arasında olup olmadığı kontrol ediliyor. Eğer not 0 ile 40 arasındaysa ekrana KALDINIZ! mesajı yazdırılıyor. Biz burada if bloğu içerisine sadece 1 deyim yazmış olsak da birden fazla deyim de yerleştirmek mümkündür:
if((not >= 0) && (not <= 40))
{
puts("KALDINIZ!");
puts("Daha sonra tekrar deneyin!");
}
Burada if bloğuna yazılan deyimlerin sayısı ile alakalı olan bir özellikten bahsedeceğim. Eğer if bloğu içerisine sadece 1 deyim yazarsanız, bloğun sınırlarını belirten süslü parantezleri koymanıza gerek kalmaz. Örneğin ilk örneğimizi şu şekilde de yazabiliriz:
if((not >= 0) && (not <= 40))
puts("KALDINIZ!");
Daha önce de bahsettiğim gibi burada kullanılan Space, TAB ve Enter gibi boşluk karakterlerinin (whitespace characters) programlamanın doğruluğu açısından pek bir önemi olmasa da okunabilirliği arttırdığı için tercih edilirler. Yani yukarıdaki kodu hiç boşluk bırakmadan şu şekilde de yazabiliriz:
if((not>=0)&&(not<=40))puts("KALDINIZ!");
Fakat bu kodu okumak diğerine göre oldukça zor olur. if bloğu ile ilgili bu konuda dikkat etmeniz gereken en önemli şey, eğer birden fazla deyim yer alıyorsa bu yazım biçimini kullanmanız durumunda çeşitli sorunlar ile karşılaşırsınız. Bu sorunlar her zaman derleme zamanında hataya yol açmaz. Bazen çalışma zamanında mantık hatalarına yol açarak işimizi zorlaştırır. Örneğin:
if((not >= 0) && (not <= 40))
puts("KALDINIZ!");
puts("Daha sonra tekrar deneyin!");
Burada ilk puts satırı if bloğunun içinde sayılırken, ikinci puts satırı if bloğunun dışında sayılır ve if deyiminden sonra her zaman ekrana Daha sonra tekrar deneyin! yazısı basılır. Bu gibi hataları önlemek için her zamanı süslü parantez kullanmanızı öneriyorum.
if…else Deyimi
Şimdi de C’de diğer seçmeli deyimimiz olan if...else deyimine geçelim. if koşulu gerçekleştiğinde bir bloğa girip yapmak istediklerimizi yapabiliyoruz. Peki bu koşul gerçekleşmediği zaman da bir şeyler yapmak istersek ne olur? if deyiminden sonra bazı kodlar yazmak bu işi bir yere kadar çözebilir. Fakat şöyle bir sözde kodu göz önüne alalım:
not ← INPUT
IF not >= 0 AND not <= 40
PRINT "Kaldınız!"
PRINT "Geçtiniz!"
PRINT "Not sorgulama tamamlandı!"
Buradaki problemi çok rahat görebiliyorsunuz değil mi? Burada notumuz 0 ile 40 arasında ise ekrana Kaldınız! yazacak, fakat devamında Geçtiniz! ve Not sorgulama tamamlandı! yazıları da ekrana basılacak. Biz notumuz 0 ile 40 arasındaysa ekrana Kaldınız!, değilse ekrana Geçtiniz! yazmak istiyoruz. En sonunda ise her koşulda Not sorgulama tamamlandı! yazmak istiyoruz.
Bunun için else anahtar kelimesinden yararlanırız. Bu anahtar kelime ile bir if koşulunun gerçekleşmediği durum ele alınır. if...else deyiminin söz dizimi şöyledir:
if(<koşul_ifadesi>)
{<deyimler>}
else
{<deyimler>}
Gördüğünüz gibi tıpkı if bloğunda olduğu gibi else bloğuna da çeşitli deyimler yerleştirmekteyiz. Sözde kodda da bu yapının kullanımı benzerdir. Şimdi öncelikle az önce yapmak istediğimiz not sorgulama sisteminin sözde kod parçasını yazalım:
not ← INPUT
IF not >= 0 AND not <= 40
PRINT "Kaldınız!"
ELSE
PRINT "Geçtiniz!"
PRINT "Not sorgulama tamamlandı!"
int not;
scanf("%d", ¬);
if((not >= 0) && (not <= 40))
{
puts("KALDINIZ!");
}
else
{
puts("GECTINIZ!");
}
puts("Not sorgulama tamamlandi!");
Gördüğünüz gibi else anahtar kelimesi ile yapmak istediğimizi rahatça gerçekleştirebiliyoruz. Tıpkı if bloğunda olduğu gibi else bloğunda da eğer tek deyim kullanıyorsak süslü parantezleri yazmaya gerek yoktur. Ama daha önce söylediğim gibi bunu yapmanızı pek tavsiye etmiyorum. Ayrıca kod yazarken bıraktığınız boşluklara ve yazım biçiminize baştan itibaren dikkat etmenizde fayda var. Ancak bu şekilde kendinizi daha temiz kod yazımına alıştırabilirsiniz.
İç İçe if…else Deyimleri
Anlatacağım son şey iç içe if deyimi kullanmak ve bunu pratik hale getiren else if yazım biçimi olacaktır. C dilinde blok kavramının süslü parantezlerin içini temsil ettiğini söylemiştik. Bloğa yazılan kodlar ardışık, seçmeli veya yinelemeli yapılardan meydana gelebilir. if ve else blokları da sonuçta bir blok özelliği taşıdığından, bu blokların içine başka if ve if...else deyimleri gelebilir. Hatta onların içine de başkaları gelebilir ve bu böyle devam edebilir.
Şimdi yine not sorgulama örneklerimizden devam edelim. Diyelim ki bir kullanıcının notu 70 ile 100 arasındaysa ekrana A, 40 ile 70 arasındaysa ekrana B, 0 ile 40 arasındaysa ise ekrana C yazdıracak olalım. Ayrıca girilen not değeri 0’dan küçük veya 100’den büyük ise ekrana Yanlış not değeri! gibi bir yazı basalım. Bunu yapmak için önce sözde kod ile yapacaklarımızı bir nevi tasarlıyoruz:
not ← INPUT
IF not >= 0 AND not <= 100
IF not < 40
PRINT "C"
ELSE IF not >= 40 AND < 70
PRINT "B"
ELSE
PRINT "A"
ELSE
PRINT "Yanlış not değeri!"
PRINT "Not sorgulama tamamlandı!"
Bu iş biraz karıştı değil mi? Aslında yaptığımız işlemler çok basit: Öncelikle not değerinin 0 ile 100 arasında olup olmadığını kontrol ediyoruz. Eğer 0 ile 100 arasındaysa o zaman da 40’tan küçük olup olmadığını kontrol ediyoruz. Eğer not 40’tan da küçük ise ekrana C yazdırıp geçiyoruz. Eğer not değeri 0 ile 40 arasında değilse o zaman da 40 ile 70 arasında olup olmadığını kontrol ediyoruz. Yine aynı şekilde bu aralıktaysa ekrana B yazdırıp geçerken, bu aralıkta değilse ekrana A yazdırıyoruz. Çünkü bu aralıkta değilse artık olabileceği tek aralığın 70 ile 100 arası olması beklenmektedir. Şimdi de bütün bunları C koduna dökelim:
int not;
scanf("%d", ¬);
if(not >= 0 && not <=100)
{
if(not < 40)
{
puts("C");
}
else
{
if(not >= 40 && not < 70)
{
puts("B");
}
else
{
puts("A");
}
}
}
else
{
puts("Yanlis not degeri!");
}
printf("Not sorgulama tamamlandi!");
Örneğin not değişkenine 90 değerini verdiğimizde, bu değer 0 ile 100 arasında olduğu için ilk if bloğuna girer. 40 değerinden küçük olmadığı için ikinci else bloğuna girer. Son olarak da 40 ile 70 arasında bir değer olmadığı için de son else bloğuna girer ve ekrana A harfini yazdırır. Böylelikle bütün if...else deyimlerini tamamlar ve en sonunda da ekrana Not sorgulama tamamlandi! yazar. Biz bu kodu şu şekilde yazsaydık da olurdu:
int not;
scanf("%d", ¬);
if(not > 100)
{
puts("Yanlis not degeri!");
}
else
{
if(not >= 70)
{
puts("A");
}
else
{
if(not >= 40)
{
puts("B");
}
else
{
if(not >= 0)
{
puts("C");
}
else
{
puts("Yanlis not degeri!");
}
}
}
}
printf("Not sorgulama tamamlandi!");
Sanırım bu şekilde biraz daha düzenli oldu. Diyelim ki not değişkenine 35 değerini verdik. Bu durumda ilk else bloğuna, oradan ikinci else bloğuna, oradan da üçüncü else bloğuna ve son olarak da dördüncü if bloğuna girip ekrana C karakterini basıp en son ise bütün deyimlerin sonunda Not sorgulama tamamlandi! yazısını bastırmış oluruz.
Bu şekilde yazımda aslında şunu farkediyoruz: Biz burada if...else deyimi anlatırken programı iki parçaya dallandırdığından bahsetmiştik. Fakat böyle bir yazımda program aslında bir noktada 5 parçaya ayrılmış oluyor. Fakat bu şekilde yazım birçok karmaşıklığa sebep olabilir. Eğer kodu düzgün bir biçimde yazmazsanız hangi else bloğunun hangi if deyimine ait olduğu karışabilir.
Neyse ki C ve benzeri programlama dillerinde bu problemi çözmek için else ve if anahtar kelimelerini ard arda getirerek else if gibi bir yazım elde edebiliyoruz. Bu yazım şekli ile aslında else bloğuna yazılmış bir başka if bloğunu belirtmekteyiz. Yani yukarıdaki kodu şu şekilde de yazabiliriz:
int not;
scanf("%d", ¬);
if(not > 100)
{
puts("Yanlis not degeri!");
}
else if(not >= 70)
{
puts("A");
}
else if(not >= 40)
{
puts("B");
}
else if(not >= 0)
{
puts("C");
}
else
{
puts("Yanlis not degeri!");
}
printf("Not sorgulama tamamlandi!");
Her iki yazım şekli de C derleyicileri tarafından aynı algılanır. Son yaptığımız yazımın daha temiz ve anlaşılır olduğunu hemen farkedebiliyoruz. Burada program önce ilk if koşulunu kontrol eder, eğer ilk koşul doğrulanmazsa hemen ardındaki else if koşulunu kontrol eder ve bu en sondaki else if koşuluna kadar devam eder. Eğer herhangi bir koşul doğrulandıysa o if veya else if bloğundaki kodu çalıştırır ve if...else deyiminden çıkarak yoluna devam eder. Eğer hiçbir if ve else if koşulu doğru değilse, o zaman da else bloğundaki kodu çalıştırır ve yoluna devam eder. Bu şekilde az önce yaptığımız gibi iç içe kullanımdan kaçınmış ve daha temiz bir kod yazmış oluruz.
Buradaki else if yazımı aslında birkaç farklılık dışında ileride göstereceğimiz ve programı birden fazla koşul ile dallandırmaya yarayan switch deyimine oldukça benzemektedir. Şimdilik bu konuyu burada sonlandırıyorum. Bu konu ile ilgili yaptığımız küçük örneklerden konuyu net bir şekilde anlayacağınızı varsayıyorum. Sonraki yazılarda daha büyük örnekler yazmayı planlamaktayım. Bu ve bir sonraki yazıda gördüğümüz konulara neredeyse bütün C kodlarında rastlayacağınızı aklınızda bulundurmanızda fayda vardır.
Referanslar
- C. Bohm and G. Jacopini, "Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules," Communications of the ACM, Vol. 9, No. 5, May 1966, pp. 336–371.



