C

30 Temmuz 2006

C Programlama Dersi - IV

Bu yazıda öğrenecekleriniz:


İçiçe geçmiş (Nested) İfadeler

Daha önceki yazımızda, koşullu ifadeleri görmüştük. Hatırlatmak için üzerinden geçersek, if ile bir ifadeyi kontrol ediyor ve doğruysa, buna göre işlemler yapıyorduk. Bir de if - else yapısı vardı. if - else yapısında da, koşulu gene kontrol ediyor, doğruysa if bloğunun altında kalanları yapıyorduk; yanlışsa, else bloğunda olan kodlar işleme alınıyordu. Son derece basit bir mantık üzerine kurulmuş bu yapıyla, yapılamayacak kontrol yoktur. Ancak öyle durumlar vardır ki, if - else yapısı yeterli verimliliği sunamaz.

Diyelim ki, birden fazla kontrol yapmanız gereken bir durum oluştu. Hatta örnek vererek konuyu daha da somutlaştıralım. İstenilen bir programda, klavyeden size yaş bilgisi veriliyor. Siz de bu bilgiye göre, şayet yaş 18'den küçükse çocuk; 18-30 yaş arasında genç; 30-50 yaş arasında ortayaş diye bir mesaj bastırıyorsunuz. Basit bir program.

Şimdi bunu sadece if yapısıyla kuruyor olsaydık, her seferinde yaşın uygun aralıklara düşüp düşmediğini kontrol eder ve ona göre sonucu ekrana bastırırdık. Ama bu son derece verimsiz bir yöntem olurdu. Çünkü zaten yaş bilgisinin genç olduğuna dair bir karar vermişsek, sonrasında tutup bunun yaşlı olup olmadığını kontrol etmenin bir esprisi olmayacaktır. Verilebilecek en kötü cevabı aşağıda bulabilirsiniz:

/*
Sorulan soruya verilebilecek en 
kötü cevap.
*/
#include<stdio.h>
int main( void )
{
	int girilen_yas;
	printf("Lütfen yaşınızı giriniz> ");
	scanf("%d",&girilen_yas);
	if( girilen_yas < 18 )
		printf("Daha çocuk yaştasınız, hayatın başındasınız.\n");
	if( girilen_yas >= 18 && girilen_yas <= 30 )
		printf("Gençliğin, güzelliği bambaşka!\n");
	if( girilen_yas > 30 && girilen_yas <= 50 )
		printf("Hepsini boşverin, olgunluk ortayaşta başlar!\n");
	return 0;
}

Yukarda ki kodu if - else kullanarak daha efektif hale getirebiliriz:

/*
if - else yapısıyla daha
efektif bir yapı
*/
#include<stdio.h>
int main( void )
{
	int girilen_yas;
	printf("Lütfen yaşınızı giriniz> ");
	scanf("%d",&girilen_yas);
	if( girilen_yas < 18 )
		printf("Daha çocuk yaştasınız, hayatın başındasınız.\n");
	else {
		if( girilen_yas >= 18 && girilen_yas <= 30 )
			printf("Gençliğin, güzelliği bambaşka!\n");
		else {
			if( girilen_yas > 30 && girilen_yas <= 50 )
				printf("Hepsini boşverin, olgunluk ortayaşta başlar!\n");
		}
	}
	return 0;
}

Yukardaki program daha efektif bir yapı sunmuş olmasına rağmen, eğer kontrol ettiğimiz aralıkların sayısı çok fazla olsaydı, tam bir başbelası olacaktı! Çünkü if - else içinde, bir başka if - else bloğu ve onun içinde bir başkası... bu böyle sürüp gidecekti. Kısacası performans olarak çok bir şey değişmese de, kodu yazan ve/veya okuyacak olan için tam bir eziyete dönüşecekti. İşte bu nedenlerle daha efektif yapılara ihtiyaç duyuyoruz.

if - else if Merdiveni

if - else if merdiveni yukarda verdiğimiz örnekler için biçilmiş kaftandır. if - else if merdiveni, doğru bir şey bulduğu zaman kontrolu orada keser ve diğer koşulları kontrol etmeden blok sonlandırılır.

Aşağıda if - else if yapısını ve akış diyagramını bulabilirsiniz:

if - else if Yapısı if - else if Akış Diyagramı
if( koşul 1 ) {
	komut(lar) 1
}
else if( koşul 2 ) {
	komut(lar) 2
}
	.
	.
	.
else if( koşul n ) {
	komut(lar) n
}
else {
	komut(lar) n
}

if - else if ile söylenebilecek son bir şey sonunda ki else'tir. else koymak zorunlu değildir. Ancak hiçbir koşula uymayan bir durumla karşılaştığınızda, else devreye girer. Örneğin yukarda anlatıp, kodunu vermiş olduğumuz programda, belirtilen yaş aralıklarında değer girilmezse, hiçbir şey ekrana bastırılmayacaktır. Çünkü programa tanınmayan yaş aralığında ne yapılacağı öğretilmemiştir. Şimdi bu durumu da içerecek şekilde, programamımızı if - else if yapısıyla tekrar yazalım:

#include<stdio.h>
int main( void )
{
	int girilen_yas;
	printf("Lütfen yaşınızı giriniz> ");
	scanf("%d",&girilen_yas);

	if( girilen_yas < 18 )
		printf("Daha çocuk yaştasınız, hayatın başındasınız.\n");
	else if( girilen_yas >= 18 && girilen_yas <= 30 )
			printf("Gençliğin, güzelliği bambaşka!\n");
	else if( girilen_yas > 30 && girilen_yas <= 50 )
			printf("Hepsini boşverin, olgunluk ortayaşta başlar!\n");
	else
		printf("HATA: Girilen yaş tanımlı değildir!\n");
		
	return 0;
}

swicth - case ifadesi

switch - case, if - else if yapısına oldukça benzer bir ifadedir. Ancak aralarında iki fark vardır. Birincisi, switch - case yapısında, aralık değeri girmezsiniz. Direkt olarak ifadelerin bir şeylere eşit olup olmadığına bakarsınız. İkinci farksa, switch - case yapılarında, illa ki uygun koşulun sağlanmasıyla yapının kesilmek zorunda olmayışıdır. 'break' komutu kullanmadığınız takdirde, diğer şartların içindeki işlemleri de yapma imkanınız olabilir. switch case en tepeden başlayarak şartları tek tek kontrol eder. Uygun şart yakalanırsa, bundan sonra ki ifadeleri kontrol etmeden doğru kabul eder. Ve şayet siz break koymamışsanız, eşitlik uygun olsun olmasın, alt tarafta kalan case'lere ait komutlarda çalıştırılacaktır. if - else if ise daha önce söylemiş olduğumuz gibi böyle değildir. Uygun koşul sağlandığında, yapı dışarsına çıkılır.

switch case yapısında ki durumu, aşağıdaki tabloda görebilirsiniz:

switch case Yapısı switch case Akış Diyagramı
switch( degisken ) {
	case sabit1:
		komut(lar)
		[break]
	case sabit2:
		komut(lar)
		[break]
	.
	.
	.
	case sabitN:
		komut(lar)
		[break]
	default:
		komut(lar);
}		

Sanırım gözünüze biraz farklı gözüktü. Yapı olarak şimdiye kadar görmüş olduğunuz if else gibi gözükmese de, bir örnekten sonra arasında pek bir fark olmadığını göreceksiniz. Her komut sonunda koyduğum break komutu, zorunlu değildir ve o nedenle köşeli parantezle belirtilmiştir. break koyduğuz takdirde, uygun koşul sağlandıktan sonra, daha fazla kontrol yapılmayacak ve aynen if - else if yapısında olduğu gibi program orada kesilecektir. Ama break koymazsanız, altında kalan bütün işlemler -bir daha ki break'e kadar- yapılacaktır.

Kodun sonunda gördüğünüz default komutu, if - else if yapısında ki sonuncu else gibidir. Uygun hiçbir şart bulunamazsa, default komutu çalışır.

Öğrendiğimiz bilginin pekişmesi için biraz pratik yapalım. Bir not değerlendirme sistemi olsun. 100 - 90 arası A, 89 - 80 arası B, 79 - 70 arası C, 69 - 60 arası D, 59 ve altıysa F olsun. Eğer 100'den büyük veya negatif bir sayı girilirse, o zaman program hatalı bir giriş yapıldığını konusunda bizleri uyarsın. Bunu şimdiye kadar öğrendiğiniz bilgilerle, if - else if yapısını kullanarak rahatlıkla yanıtlayabilirsiniz. Ama şu an konumuz switch case olduğundan, cevabını öyle verelim:

#include<stdio.h>
int main( void )
{
	int not;
	printf("Lütfen notu giriniz> ");
	scanf("%d",&not);
	switch( not / 10 ) {
		case 10: 
		case 9: printf("NOT: A\n"); break;
		case 8: printf("NOT: B\n"); break;
		case 7: printf("NOT: C\n"); break;
		case 6: printf("NOT: D\n"); break;
		case 5:
		case 4:
		case 3:
		case 2: 
		case 1:
		case 0: printf("NOT: F\n"); break;
		default:
			printf("HATA: Bilinmeyen bir değer girdiniz!\n");
	}
	
	return 0;
}

Algoritmaya bakalım: Önce sayıyı alıyor ve 10'a bölüyoruz. Yani girilen not, 57 ise 5.7 sonucunu elde ediyoruz. Ancak iki tam sayının sonucu bir virgüllü sayı veremez, tıpkı işleme giren değişkenler gibi tam sayı olarak döner. Dolayısıyla bilgisayarın elde edeceği sonuç, 5.7 değil, sadece 5'tir. switch case yapısında koşullar yukardan başlayarak kontrol ediliyor. case 5'e gelindiğinde eşitlik sağlanıyor. Ama break konmadığı için, switch case'ten çıkılmıyor. Ve altında kalan işlemlerde yapılıyor. Altında herhangi bir işlem veya break olmadığından case 0'a kadar bu böyle sürüyor. Ve case 0'da ekrana bir çıktı alıp switch case yapısı break ile sonlandırılmaktadır.

switch case, if - else if yapısının sunduğu esnekliğe sahip değildir. Daha çok menü olarak sunulacak işlerde kullanılır. Örneğin Unix'in ünlü listeleme komutu ls içersinde, verilen parametrelerin kontrolü switch case kullanılarak sağlanmıştır. Open Solaris, FreeBSD veya Linux kodlarını incelerseniz bunun gibi yüzlerce örnek bulabilirsiniz.

Arttırma (Increment) ve azaltma (decrement) işlemleri

Daha önceki derslerimizde, aritmetik işlemlerden bahsetmiştik. Bunların dışında yapabileceğimiz başka şeylerde bulunmaktadır. Bunlardan biri de, arttırma ve azaltma işlemleridir.

Eğer i adında bir değişkenin değerini 1 arttırmak isterseniz, i = i + 1 olarak yazarsınız. Veya 1 azaltmak isterseniz, benzer şekilde i = i - 1 de yazabilirsiniz. Arttırma ve azaltma işlemleri bu olayı daha basit bir forma sokmaktadır. i = i + 1 yazmak yerine i++ veya i = i - 1 yazmak yerine i-- yazabilirsiniz.

Arttırma ve azaltma işlemleri temelde iki çeşittir. Birinci yöntemde yukarda yazdığımız gibi, arttırma/azaltma sonradan yapılır. İkinci yöntemdeyse arttırma/azaltma ilk başta yapılır. Aşağıdaki örneklere bakalım.

/*
Bu programda, arttırma ve azaltma 
işlemleri önce yapılacaktır. 
*/
#include<stdio.h>
int main( void )
{
	int i = 10, j = 60;
	printf("i = %d ve j = %d\n", ++i, --j);
	return 0;
}

Yukardaki programı yazar ve çalıştırısanız elde edeceğiniz çıktı şu şekilde görünecektir:

i = 11 ve j = 59

Çünkü arttırma ve azaltma işlemleri ekrana bastırmadan önce yapılmış ve i ile j'nin değerleri değiştirilmiştir. Şimdi programı değiştirip şöyle yazalım:

/*
Bu programda, arttırma ve azaltma 
işlemleri sonra yapılacaktır. 
*/
#include<stdio.h>
int main( void )
{
	int i = 10, j = 60;
	printf("i = %d ve j = %d\n", i++, j--);
	return 0;
}

Bu sefer program çıktısı şöyle olacaktır:

i = 10 ve j = 60

Farkettiğiniz üzere hiçbir değişiklik yapılmamış gibi duruyor. Aslında değişiklik yapıldı ve program sonlanmadan önce i 10 olurken, j'de 59 oldu. Ama arttırma ve azaltma işlemleri printf komutu çalıştırıldıktan sonra yapıldığı için, biz bir değişiklik göremedik.

Kısacası önce arttırma (pre-increment) veya önce azaltma (pre-decrement) kullandığınızda, ilgili komut satırında çalışacak ilk şey bu komutlar olur. Ancak sonra arttırma (post increment) veya sonra azaltma kullanırsanız, o zaman bu işlemlerin etkileri ilgili komut satırından sonra geçerli olacaktır. Aşağıdaki özel tabloya bakabilirsiniz:

Form Tip İsim Açıklama
i++ postfix post-increment İşlem sonrası arttırma
++i prefix pre-increment İşlem öncesi arttırma
i-- postfix post-decrement İşlem sonrası azaltma
--i prefix pre-decrement İşlem öncesi azaltma

Gelişmiş atama (Advanced Assignment) yöntemleri

C'de yazım kolaylığı amacıyla sunulmuş bir başka konu da, gelişmiş aşama yöntemleridir. Biraz daha uzun yazacağınız kodu, kısaltmanıza yaramaktadır.

degisken_1 = degisken_1 (operator) degisken_2 şeklinde yazacağınız ifadeleri, daha kısa yazabilmeniz için, degisken_1 (operator) = degisken_2 şeklinde ifade edebilirsiniz. Gelişmiş atamalarda sunulan genel formlar şu şekildedir:

+= , -= , *= , /= , %=

Sanırım aşağıdaki örneklere bakarsanız, konuyu çok daha net anlayacaksınız:

1-) j = j * ( 3 + x )  ==> j *= ( 3 + x )
2-) a = a / ( 5 - z ) ==> a /= ( 5 - z )
3-) x = x - 5 ==> x -= 5

Conditional Operator ( ? )

Türkçe karşılık bulamadığım bir başka C kavramı da, Conditional Operator. Aslında mot a mot çeviri yaparsam, koşullu operatör anlamına geliyor. Ama şu ana kadar gördüğümüz birçok yapıyı da bu şekilde tanımlamak mümkünken, koşullu operatör ifadesini kullanmayı pek tercih etmiyorum. Neyse lafı uzatmayalım...

Conditional Operator, if-else ile tamamen aynı yapıdadır. Hiçbir farkı yoktur. Tek farkı koda bakıldığında anlaşılmasının biraz daha zor oluşudur. Bir de if - else gibi yazıyla ifade edilmez. Onun yerine soru işareti (?) ve iki nokta üst üste ( : ) kullanarak yazarız. Aşağıdaki tabloda if else yapısıyla karşılaştırılmalı olarak, Conditional Operator verilmiştir:

if-else Yapısı Conditional Operator ( ? ) Yapısı
if( koşul ) {
	if_komut(lar)
}
else {
	else_komut(lar)
}
koşul?if_komut(lar):else_komutlar
Conditional Operator ( ? ) Akış Diyagramı

Şimdi de aynı programı, hem if-else, hem de conditional operator kullanarak yazalım:

/*
Girilen tam sayının 
10'dan büyük olup 
olmadığını gösteren
program
*/
#include<stdio.h>
int main( void )
{
	int sayi;
	printf("Lütfen bir sayı giriniz> ");
	scanf("%d",&sayi);
	if( sayi > 10 ) 
		printf("Sayı 10'dan büyüktür\n");
	else
		printf("Sayı 10'dan küçüktür veya 10'a eşittir\n");
	return 0;
}

Şimdi de aynı programı conditional operator kullanarak yazalım:

/*
Girilen tam sayının 
10'dan büyük olup 
olmadığını söyleyen 
program
*/
#include<stdio.h>
int main( void )
{
	int sayi;
	printf("Lütfen bir sayı giriniz> ");
	scanf("%d",&sayi);
	( sayi > 10 ) ? printf("Sayı 10'dan büyüktür\n"):
			printf("Sayı 10'dan küçüktür veya 10'a eşittir\n");
	return 0;
}

Program gördüğünüz gibi biraz daha kısaldı.

Conditional Operator'ler pek kullanmayı sevmediğim bir yapıdır. Çünkü kodun kısa olmasından çok, anlaşılabilir olması önemli. Ve conditional operator kullanmak ne yazık ki, kodu daha karmaşık hale getiriyor. UNIX filozofisinde bir şeyi akıllıca yapacağınıza, temiz/açık yapın diye bir yaklaşım mevcut. Belki bu yüzden veya belki de tembellik, conditional operator'lere alışamadım gitti... :)

Şimdi örnek sorularımıza gelelim...

Örnek Sorular

Soru 1: Aşağıdaki kodu yorumlayınız:

s = ( x < 0 ) ? -1 : x * x

Cevap için tıklayınız...

Soru 2: İki tam sayı alacak ve verilecek operatöre göre (+, -, *, /, %) işlem yapacak bir program yazınız.

Cevap için tıklayınız...

Soru 3: Verilecek üç sayıdan en büyüğünü ekrana yazdıracak bir program yazınız.

Cevap için tıklayınız...

Çağatay ÇEBİ


<< Geri İleri >>