C Programlama Dersi – VIII

Kısa devre değerlendirme, ne yazık ki pek iyi bir çeviri olmadı ve bu yüzdenhiçbir anlam ifade etmeyebilir. İngilizce’de, Short Circuit Evaluation olarakgeçen bu konu, mantıksal ifadelerle ilgilidir.

Hatırlarsanız, daha önce ki derslerimizde iki farklı AND ve OR operatörü görmüştük.Bu yapılardan biri AND için && işaretini kullanıyorken, diğeri sadece tek& simgesini kullanıyordu. Keza, OR ifadesi bir yerde, || şeklindegösteriliyorken, diğer yerde, tek bir | simgesiyle ifade edilmekteydi. Bu işaretler,aynı sonucu üretiyor gibi görünseler de, farklı şekilde çalışırlar.

Çift şekilde yazılan operatörler, ( yani && ve || ) kısa devreoperatörleridir. İngilizce, Short Circuit Operator olarak isimlendirilirler.Tek sembolle yazılan operatörlerden farkı, işlemleri kısaltmalarıdır.

Bir koşul içersinde AND ( && ) operatörü kullandığınızda,koşulun sol tarafı yanlışsa, sağ tarafı kontrol edilmez. Çünkü artık sağ tarafındoğru veya yanlış olmasının önemi yoktur; sonuç her şekilde yanlış olacaktır.

Benzer bir mantık OR ( || ) operatörü içinde geçerlidir. Eğersol taraf doğruysa, sağ tarafın kontrol edilmesine gerek yoktur. Çünkü ORoperatöründe taraflardan birinin doğru olması durumunda, diğerinin önemi kalmazve sonuç doğru döner.

Aşağıdaki örnekleri inceleyelim:

&& Operatörü & Operatörü
#include<stdio.h>
int main( void )
{
int i, j;
i = 0;
j = 5;
if( i == 1 && j++ ) {
printf( “if içersine girdi\n” );
}
else {
printf( “if içersine girmedi\n” );
printf( “i: %d, j: %d\n”, i, j );
}
return 0;
}

#include<stdio.h>
int main( void )
{
int i, j;
i = 0;
j = 5;
if( i == 1 & j++ ) {
printf( “if içersine girdi\n” );
}
else {
printf( “if içersine girmedi\n” );
printf( “i: %d, j: %d\n”, i, j );
}
return 0;
}

if içersine girmedi
i: 0, j: 5

if içersine girmedi
i: 0, j: 6

Gördüğünüz gibi, program çıktıları birbirinden farklıdır. Bunun sebebi, ilk örnekte, i == 1 koşulu yanlış olduğu için, && operatörünün ifadenin sağ tarafınabakmamasıdır. İkinci örnekteyse, & operatörü, koşulun her iki tarafına dabakar. Bu yüzden, j değişkenine ait değer değişir. Benzer bir uygulamayı, OR için|| ve | kullanarak yapabilirsiniz.

ÖNEMLİ NOT: Özetle işlemlerinizi hızlandırmak istiyorsanız; AND kullanacağınızzaman, && operatörüyle çalışın ve yanlış olması muhtemel olan koşulu sol tarafakoyun. Eğer OR operatörü kullanacaksanız, doğru olma ihtimali fazla olan koşulu,ifadenin soluna koyun ve operatör olarak || ile çalışın. Bu şekillde yazılan birprogram, kısa devre operatörleri sayesinde, gereksiz kontrolden kaçınarakişlemlerinizi hızlandıracaktır.

Elbette & veya | operatörlerinin kullanılması gereken durumlarda olabilir.Her n’olursa olsun, koşulun iki tarafınında çalışmasını istiyorsanız, o zaman& ve | operatörlerini kullanmanız gerekmektedir.

Önişlemci Komutları

Bir program yazdığınızı düşünün… Bu programda, PI değerini birçok yerde kullanmanızgerekiyor. Siz de PI değeri olması gereken her yere, 3.14 yazıyorsunuz. Oldukçasıkıcı bir iş. İleride PI’yi, 3.141592653 olarak değiştirmek isterseniz daha dasıkıcı hâle dönüşebilir. Veya canınız istedi, printf( ) fonksiyonuyerine ekrana_yazdir( ) kullanmaya niyetlendiniz… İşte bu gibidurumlarda, Önişlemci Komutlarını ( Preprocessor ) kullanırız. Önişlemcikomutlarının amacı, bir şeyi başka bir şekilde ifade etmektir.

Konuya devam etmeden önce ufak bir uyarı da bulunmakta yarar var. Önişlemci komutlarını, değişkenler veya fonksiyonlarla karıştırmamak gerekiyor. Değişkenlerin vefonksiyonların daha dinamik ve esnek bir yapıları varken, önişlemci komutları statiktir. Programınıza direkt bir kod yazdığınızı düşünün. Bu kod herhangibir şey (sembol, program parçası, sayı, karakter vs…) olabilir. Örneğin, her yerdePI’nin karşılığı olarak 3.14 girmek yerine, PI diye bir sembol tanımlarız ve bunungörüldüğü her yere 3.14’ü koyulmasını isteriz. Önişlemci komutları bu gibi işlerde,biçilmiş kaftandır.

#define Önişlemci Komutu

#define komutu, adından da anlaşılabileceği gibi tanımlama işlemleri için kullanılır.Tanımlama komutunun kullanım mantığı çok basittir. Bütün yapmamız gereken, neyin yerineneyi yazacağımıza karar vermektir. Bunun için #define yazıp bir boşluk bıraktıkansonra, önce kullanacağımız bir isim verilir, ardından da yerine geçeceği değer.

Altta ki program, PI sembolü olan her yere 3.14 koyacak ve işlemleri buna göreyapacaktır:
/* Çember alanını hesaplar */

#include<stdio.h>
#define PI 3.14
int main( void )
{
int yaricap;
float alan;
printf( “Çemberin yarı çapını giriniz> ” );
scanf( “%d”, &yaricap );
alan = PI * yaricap * yaricap;
printf( “Çember alanı: %.2f\n”, alan );
return 0;
}

Gördüğünüz gibi, PI bir değişken olarak tanımlanmamıştır. Ancak #define komutusayesinde, PI’nin aslında 3.14 olduğu derleyici (compiler) tarafından kabul edilmiştir. Sadece#define komutunu kullanarak başka şeylerde yapmak mümkündür. Örneğin, daha öncedediğimizi yapalım ve printf yerine, ekrana_yazdir; scanf yerine de, deger_alisimlerini kullanalım:
/* Yarıçapa göre daire alanı hesaplar */

#include<stdio.h>
#define PI 3.14
#define ekrana_yazdir printf
#define deger_al scanf
int main( void )
{
int yaricap;
float alan;
ekrana_yazdir( “Çemberin yarı çapını giriniz> ” );
deger_al( “%d”, &yaricap );
alan = PI * yaricap * yaricap;
ekrana_yazdir( “Çember alanı: %.2f\n”, alan );
return 0;
}

#define komutunun başka marifetleri de vardır. İlerki konularımızda göreceğimizfonksiyon yapısına benzer şekilde kullanımı mümkündür. Elbette ki, fonksiyonlarçok daha gelişmiştir ve sağladıkları esnekliği, #define tutamaz. Bu yüzden #defineile yapılacakların sınırını çok zorlamamak en iyisi. Ancak yine de bilginiz olması açısından aşağıda ki örneğe göz atabilirsiniz:
/* Istenen sayida, “Merhaba” yazar */

#include<stdio.h>
#define merhaba_yazdir( x ) int i; for ( i = 0; i < (x); i++ ) printf( “Merhaba\n” );
int main( void )
{
int yazdirma_adedi;
printf( “Kaç defa yazdırılsın> ” );
scanf( “%d”, &yazdirma_adedi );
merhaba_yazdir( yazdirma_adedi );
return 0;
}

#undef Önişlemci Komutu

Bazı durumlarda, #define komutuyla tanımladığımız şeyleri, iptal etmek isteriz.Tanımlamayı iptal etmek için, #undef komutu kullanılır. Örneğin #undef PI yazdığınız da,o noktadan itibaren PI tanımsız olacaktır. #define ile oluşturduğunuz sembolleribelirli noktalardan sonra geçerliliğini iptal etmek veya yeniden tanımlamak için #undefkomutunu kullanabilirsiniz.

#ifdef ve #ifndef Önişlemci Komutları

Önişlemci komutlarında bir sembol veya simgenin daha önce tanıtılıp tanıtılmadığını kontrol etmek isteyebiliriz. Tanıtılmışsa, şöyle yapılsın; yok tanıtılmadıysa, böyleolsun gibi farklı durumlarda ne olacağını belirten yapılar gerekebilir. Bu açığı kapatmak için #ifdef (if defined – şayet tanımlandıysa) veya #ifndef(if not defined – şayet tanımlanmadıysa) operatörleri kullanılır.
#include<stdio.h>
#define PI 3.14
int main( void )
{
// Tanımlı PI değeri, tanımsız hâle getiriliyor.
#undef PI

int yaricap;
float alan;
printf( “Çemberin yarı çapını giriniz> ” );
scanf( “%d”, &yaricap );

// PI değerinin tanımlı olup olmadığı kontrol ediliyor.
#ifdef PI
//PI tanımlıysa, daire alanı hesaplanıyor.
alan = PI * yaricap * yaricap;
printf( “Çember alanı: %.2f\n”, alan );
#else
//PI değeri tanımsızsa, HATA mesajı veriliyor.
printf(“HATA: Alan değeri tanımlı değildir.\n”);
#endif

return 0;
}

Yukardaki örneğe bakacak olursak, önce PI değeri tanımlanıyor ve sonrasındatanım kaldırılıyor. Biz de sürprizlerle karşılaşmak istemediğimizden, PI değerinintanım durumunu kontrol ediyoruz. Tek başına çalışan biri için gereksiz bir ayrıntı gibigözükse de, ekip çalışmalarında, bazı şeylerin kontrol edilmesi ve istenmeyendurumlarda, ne yapılacağı belirlenmelidir. Yukarda ki programı şöyle de yazabilirdik:
#include<stdio.h>
int main( void )
{
int yaricap;
float alan;
printf( “Çemberin yarı çapını giriniz> ” );
scanf( “%d”, &yaricap );

// Şu noktaya kadar tanımlı bir PI değeri bulunmuyor.
// #ifndef opertörü bu durumu kontrol ediyor.
// Eğer tanımsızsa, PI’nin tanımlanması sağlanıyor.
#ifndef PI
#define PI 3.14
#endif

alan = PI * yaricap * yaricap;
printf( “Çember alanı: %.2f\n”, alan );

return 0;
}

#if, #else, #endif, #elif Önişlemci Komutları

Bazen bir değerin tanımlanıp, tanımlanmadığını bilmek yetmez. Bazı değerler,bayrak (flag) olarak kullanılır. Yani eğer doğruysa, böyle yapılması lâzım,aksi hâlde böyle olacak gibi… Bazı programlar, önişlemci komutlarındanyararlanır. Değişken yerine, önişlemcileri kullanarak tanımlanan simgeler, buprogramlarda flag görevi görür.

Konumuza dönersek, #if, #else, #endif yapısı daha önce işlemiş olduğumuzif-else yapısıyla hemen hemen aynıdır. if-elif yapısı da if-else if yapısına benzer.Her ikisinin de genel yazım kuralı aşağıda verilmiştir:

#if – #else Yapısı: #if – #elif Yapısı:
#if koşul
komut(lar)
#else
komut(lar)
#endif

#if koşul 1
komut(lar) 1
#elif koşul 2
komut(lar) 2
.
.
.
#elif koşul n-1
komut(lar) n-1
#else
komut(lar) n
#endif

Bir program tasarlayalım. Bu programda, pi sayısınının virgülden sonrakaç basamağının hesaba katılacağına karar veren bir mekanizma olsun. Soruyu, şu ana kadar gördüğümüz, if – else gibi yapılarla rahatça yapabiliriz.Önişlemci komutuyla ise, aşağıdakine benzer bir sistem oluşturulabilir:
/* Daire alanını hesaplar */

#include<stdio.h>
#define HASSASLIK_DERECESI 2
int main( void )
{
int yaricap;
float alan;
printf( “Çemberin yarı çapını giriniz> ” );
scanf( “%d”, &yaricap );

// Hassaslık derecesi, pi sayısının virgülden kaç
// basamak sonrasının hesaba katılacağını belirtir.
// Eğer hassaslık derecesi bunlara uymuyorsa, alan
// değeri -1 yapılır.

#if ( HASSASLIK_DERECESI == 0 )
alan = 3 * yaricap * yaricap;
#elif ( HASSASLIK_DERECESI == 1 )
alan = 3.1 * yaricap * yaricap;
#elif ( HASSASLIK_DERECESI == 2 )
alan = 3.14 * yaricap * yaricap;
#else
alan = -1;
#endif

printf( “Çember alanı: %.2f\n”, alan );
return 0;
}

#include Önişlemci Komutu

#include oldukça tanıdık bir operatördür. Her programımızda, #include önişlemcikomutunu kullanırız. Şayet kullanmasak, printf( ) veya scanf( )gibi fonksiyonları tekrar tekrar yazmamız gerekirdi. #include komutu, programımızabir başlık dosyasının (header file) dâhil edileceğini belirtir. Bu başlıkdosyası, standart giriş çıkış işlemlerini içeren bir kütüphane olabileceği gibi,kendimize ait fonksiyonların bulunduğu bir dosya da olabilir.

Eğer sistem kütüphanelerine ait bir başlık dosyasını programınıza dâhil edeceksek,küçüktür ( < ) ve büyüktür ( > ) işaretlerini kullanırız.Örneğin stdio.h sisteme ait bir kütüphane dosyasıdır ve Linux’ta /usr/include/stdio.hadresinde bulunur. Dolayısıyla stdio.h kütüphanesini programımıza eklerken,#include<stdio.h> şeklinde yazarız. Kendi oluşturduğumuz başlık dosyaları içinse, durum biraz daha farklıdır.

Çalışma ortamımızla aynı klasörde olan bir başlık dosyasını, programımıza eklemekiçin #include “benim.h” şeklinde yazarız. İlerki derslerimizde,kendi başlık dosyalarımızı oluşturmayı göreceğiz. Ancak şimdilik burada keselim…

Önemli Noktalar…

Konuyu noktalarken, söylemek istediğim bazı şeyler bulunuyor. Olabildiğince, önişlemcikomutlarından – #include komutu hariç – uzak durun.Çünkü bu komutlar, esnek bir yapıya sahip değiller ve bu durum, bir noktadan sonra başınızı ağrıtacaktır.Önişlemci komutlarıyla yazılmış kodları takip etmek oldukça zordur ve debug edilemezler.Java gibi gelişmiş dillerde, #define komutu bulunmaz. Modern dillerde, bu yapıdanuzaklaşılmaya başlanmıştır.

Yukarda saydıklarıma rağmen, bazı durumlarda, önişlemci komutlarını kullanmakuygun olabilir. Kaldı ki bu komutların kullanıldığı birçok yer bulunuyor vebiz kullanmasak bile, bilmeye mecbur durumdayız. Sözün özü; bu konuyu esgeçmek uygun değil. Ancak üzerine düşmek oldukça gereksiz.