Günlük yaşamımıza baktığımızda tıpkı daha önce bahsettiğimiz algoritmalara benzer olan çeşitli rutin işlerimizin olduğunu görürüz. Algoritmalarda olduğu gibi bu işlerimiz de belli alt adımlardan ve bu adımların sırasından meydana gelmektedir. Örneğin sabah kalktınız ve omlet yapmak istyorsunuz. Bunun için öncelikle buzdolabını açıp yumurtaları almanız gerekmektedir. Daha sonra da ocağı açıp üstüne tavayı koymalı ve tavaya yağ koyup biraz bekledikten sonra yumurtaları üzerine kırıp pişirmelisiniz.
Burada açmak, koymak ve kırmak gibi çeşitli eylemler yaparız. Bunları birer algoritma adımı olarak düşünürseniz, sırayla bu adımlara yaptığınız eylemleri yerleştirerek algoritmayı oluşturabilirsiniz. Peki hayatta her şey her zaman bu kadar yolunda mı gitmektedir? Elbette hayır…
Şimdi yine verdiğimiz omlet pişirme örneğini baz alalım. Ya dolabı açtığınızda hiç yumurtanız kalmadığını farkederseniz ne olur? Bunu geçtim, ocağa düzgün bir şekilde gazın gittiğinden (eğer tüp kullanıyorsanız gazın kalıp kalmadığından) veya tavaya koyacak yağınızın kalıp kalmadığından nasıl emin olursunuz? Buradaki gibi durumlar düz eylemler değildirler. Hayatta bu gibi karşımıza çıkan eylemlere aslında kontrol etme adı verilmektedir. Bir şeyin varlığını veya durumunu kontrol ettiğinizde gerçekte hiçbir eylem yapmamış, sadece bir şeylerden emin olmuş olursunuz. Emin olup doğruladıktan sonra da eylemi gerçekleştirirsiniz.
Karşılaştırma Operatörleri
C dilinde deyimlerden (statements) daha önce bahsetmiştik. Deyimler aslında belli bir işi (görevi) gerçekleştiren en küçük birimlerdir. Şimdiye dek deyimleri hep bu şekilde ifade ettik. Fakat C’de ve pek çok programlama dilinde kullanılan bazı deyimler, yukarıda bahsettiğim karar verme görevini de yerine getirmektedirler. Örneğin bir insanın yaşının 30’dan büyük olup olmadığını, sıcaklık değerinin 15 dereceden küçük olup olmadığını veya bir tarihteki yıl değerinin 2001’e eşit olup olmadığını bu deyimler ile kontrol edebilirsiniz.
Bu tür deyimleri ayrıntılı olarak bir sonraki ve ondan sonraki bölümlerde zaten göreceğiz. Ben size en basit olan ve en sık kullanılan if deyimini (if statement) gösterecek ve onun üzerinden örneklendirme yapacağım. C ve C++’daki if deyimin yapısı şu şekildedir:
if(<koşul_ifadesi>) {<deyimler>}
Gördüğünüz gibi bu deyim iki kısımdan meydana geliyor "<koşul_ifadesi>" kısmına sonucu 0 veya sıfırdan farklı bir değer üreten herhangi bir ifade gelebilir. Eğer oraya yazılan ifadenin değeri 0 ise if bloğunun içinde yer alan "<deyimler>" kısmına girilmeden devam edilir. Eğer 0’dan faklı bir değer ise if bloğunun içinde yer alan deyimler kısmı çalıştırılıp daha sonra bloğun dışındaki kısımlardan devam edilir. "<deyimler>" kelimesinden anlayacağınız gibi, if bloğu içerisinde birden fazla deyim de yer alabilmektedir. Bizim burada odaklanacağımız kısım "<koşul_ifadesi>" kısmıdır.
Matematikteki büyüktür, küçüktür veya küçük eşittir gibi işlemleri zaten biliyorsunuzdur. Bu işlemler aslında iki niceliğin birbirleriyle karşılaştırılmasını sağlarlar. Benzer mantık C benzeri dillerde de mevcuttur. Şimdi matematikteki bazı işlemlerle birebir karşılık gelen C operatörlerine bakalım:
| Matematiksel Karşılığı | C Karşılığı | C’deki Kullanımı | Anlamı |
|---|---|---|---|
| < | < | x < y | “x” değeri “y” değerinden küçüktür. |
| > | > | x > y | “x” değeri “y” değerinden büyüktür. |
| ≤ | <= | x <= y | “x” değeri “y” değerinden küçük veya ona eşittir. |
| ≥ | >= | x >= y | “x” değeri “y” değerinden büyük veya ona eşittir. |
| = | == | x == y | “x” değeri “y” değerine eşittir. |
| ≠ | != | x != y | “x” değeri “y” değerine eşit değildir. |
Buradaki <, >, <= ve >= operatörlerine C ve benzeri dillerde İlişkisel Operatörler (Relational Operators) adı verilir. == ve != operatörlerine ise Eşitlik Operatörleri (Equality Operators) adı verilir. Bu operatörlerin tamamı ise Karşılaştırma Operatörleri (Comparison Operators) olarak adlandırılır. Yukarıda gördüğünüz x ve y yerine sabit bir değer, bir değişken veya daha karmaşık bir ifade gelebilmektedir.
Şimdi hızlıca if deyiminin ve bu operatörlerin kullanımını görmek için bir örnek yapalım. Örneğimizde kullanıcıdan iki sayı alıp bunları iki farklı değişkende depolayacağız. Daha sonra if deyimleri ile bu iki sayı arasındaki ilişkiyi anlamaya çalışacak ve ekrana bununla ilgili bir bilgiler yazacağız.
#include <stdio.h>
int main()
{
printf("2 adet tamsayi girin: ");
int number1, number2; // Sayıları tutacak olan değişkenleri tanımlıyoruz
scanf("%d %d", &number1, &number2); // Sayıları arasında bir boşluk olacak şekilde kullanıcıdan alıyoruz
if(number1 < number2) {
printf("%d sayisi %d sayisindan kucuktur!\n", number1, number2);
}
if(number1 > number2) {
printf("%d sayisi %d sayisindan buyuktur!\n", number1, number2);
}
if(number1 <= number2) {
printf("%d sayisi %d sayisindan kucuk veya ona esittir!\n", number1, number2);
}
if(number1 >= number2) {
printf("%d sayisi %d sayisindan buyuk veya ona esittir!\n", number1, number2);
}
if(number1 == number2) {
printf("%d sayisi %d sayisina esittir!\n", number1, number2);
}
if(number1 != number2) {
printf("%d sayisi %d sayisina esit degildir!\n", number1, number2);
}
return 0;
}
6 sayisi 8 sayisindan kucuktur!
6 sayisi 8 sayisindan kucuk veya ona esittir!
6 sayisi 8 sayisina esit degildir!
if deyimlerinde koşul ve deyim parantezleri arasında istediğiniz gibi boşluk bırakıp alt satıra geçebilirsiniz. Fakat kodun okunabilirliği açısından bir satıra if anahtar kelimesi ile koşulu yerleştirip alt satırlara bir TAB ileride olacak şekilde deyimleri yerleştirmek daha iyi olacaktır (tıpkı yukarıda yaptığım gibi). Böylelikle hangi deyimin hangi koşula ait olduğunu aramakta zorluk çekmeyeceksiniz.
Bir noktaya daha değinmemde fayda var; <=, >=, != veya == gibi iki karakterden meydana gelen operatörlerin arasına boşluk koyulmamalıdır. Eğer "<=" operatörünü "< =" şeklinde kullanırsanız derleyici bunu düzgün yorumlayamaz ve hata alırsınız.
Şimdi kodumuzu yorumlamaya geçebiliriz. Öncelikle kullanıcıdan 2 tam sayı değeri alıp bunları sırasıyla number1 ve number2 isimli değişkenlerde depoluyoruz. Örneğin number1 değişkenine "6", number2 değişkenine de "8" sayısını atadığımızı varsayalım. İlk if deyimine geldiğimizde, number1 sayısının number2 sayısından küçük olup olmadığı kontrol edilmektedir. İlk sayı ikincisinden küçük olduğu için bu bloğun içerisinde yer alan printf satırı çalıştırılır ve ekrana bununla ilgili bilgi basılır.
Daha sonra ikinci if koşulu kontrol edilir. Fakat ilk sayı ikincisinden büyük olmadığı için, içerisindeki printf satırına girilmeden üçüncü if satırına atlanır. Çalışma mantığı bu şekilde devam eder. Gördüğünüz gibi karşılaştırma operatörleri genellikle bu gibi durumlar için uygundur. Eğer koşul ifadesi doğru ise 1, yanlış ise 0 değerini geri döndürürler. Eski C standartlarında bool veri türü olmadığı için, bu veri türünün alternatifi olarak int veri türü kullanılır. Koşul ifadelerinden çıkan sonuçları şu şekilde de inceleyebilirsiniz:
int a = 5;
int b = 6;
int dogruIfade = (a < b);
int yanlisIfade = (a > b);
printf("Dogru ifade sonucu: %d \nYanlis ifade sonucu: %d", dogruIfade, yanlisIfade);
Yanlis ifade sonucu: 0
Mantıksal Operatörler
Şimdi de şöyle bir örnek düşünelim: Diyelim ki yapacağımız iş birden fazla koşulun sonucuna bağlı olsun. Örneğin çay demlemek için elimizde çay ve suyun bulunması gerekmektedir. Eğer bu malzemelerden biri bile eksikse biz çay demleyemeyiz. Şimdi de bir pasta yaptığımızı düşünelim. Biz bu pastayı çilekli ve muzlu yapmak istiyoruz. Fakat elimizdeki malzemelerin stoğuna göre bu pastayı sadece çilekli veya sadece muzlu da yapabiliriz. Bu durumda elimizde çilek veya muzun bulunup bulunmadığına bakmamız gerekir. Bu gibi durumlar aslında daha önce yazısını yazdığım Önermeli Mantık konusuyla doğrudan ilişkilidir.
Örneklerden de anlayacağınız gibi günlük hayatta bunun gibi belli iki veya daha fazla koşulun sağlanması veya koşullardan en az birinin sağlanması gereken durumlar olabilir. Bazen de bir koşul sağlanmadığı zaman bir eylemi gerçekleştirmemiz gereken durumlar vardır. Örneğin sinemaya gittiğimizde eğer elimizde bir bilet yoksa, gidip gişeden biletimizi alırız.
Verdiğim örneklerde VE, VEYA ve DEĞİL gibi önermeli mantık konusuyla ilgili işlemlerin analojilerini görebilirsiniz. İşte C programlama dilinde de bu üç işleme karşılık gelen ve Mantıksal Operatörler (Logical Operators) olarak adlandırılan operatörler bulunmaktadır. Bunların matematikteki ve C’deki karşılıklarını ve kullanımlarını şu şekilde görebilirsiniz:
| Matematiksel Karşılığı | C Karşılığı | C’deki Kullanımı | Anlamı |
|---|---|---|---|
| p ˄ q | && | (ifade1) && (ifade2) | “ifade1” ve “ifade2” ifadelerinin her ikisi de doğruysa… |
| p ˅ q | || | (ifade1) || (ifade2) | “ifade1” veya “ifade2” ifadelerinden en az biri doğruysa… |
| p’ | ! | !(ifade) | “ifade1” ifadesi doğru değilse… |
Yine || ve && gibi iki karakterden meydana gelen operatörlerin arasına boşluk koymamanız gerektiğini hatırlatalım. Mantıksal operatörleri daha iyi anlamak için hemen bir örnek yapalım. Bu örnekte kullanıcıdan 0 ile 100 arasında bir not değeri alacak ve bu not değeri eğer gerçekten bu aralıktaysa (0 ve 100 de dahil) ekrana basacak, değilse ekrana hiçbir şey basmayacağız:
#include <stdio.h>
int main()
{
printf("0 ile 100 arasinda bir not girin: ");
int grade; // Not değerini tutacak olan değişkeni bildiriyoruz.
scanf("%d", &grade); // Değişkene kullanıcıdan gelen sayıyı atıyoruz.
// Eğer girilen değer 0 ile 100 arasındaysa ekrana basıyoruz:
if(grade >= 0 && grade <= 100) {
printf("%d", grade);
}
return 0;
}
55
Örnekteki && operatörü ile iki koşulun da sağlanması durumunu beklemekteyiz. Eğer operatörün sağ ve sol tarafındaki ifadeler doğru ise (bu durumda iki ifadeden de 0 dışı bir değer geri dönmesini bekleriz), sonuç 1 gibi sıfırdan farklı bir tam sayı çıkacaktır. Eğer ifadelerden biri bile yanlışsa, bütün ifadeden 0 sonucu çıkacak ve if bloğuna girilmeden devam edilecektir. Aynı durum || operatörü için de geçerlidir. Fakat bu operatörde sağ ve sol taraflardan en az birinin doğru olması yeterlidir.
Operatörlerde Terim Sayısı ve DEĞİL Operatörü
Şimdiye kadar bahsettiğimiz operatörler genellikle iki terim ile iş yapan operatörlerdi. Yani bu operatörlerin sağ ve sol tarafları işleme katılmaktaydı. Bu tip operatörlere İki Terimli Operatörler (Binary Operators) adı verilmektedir. Fakat C’deki bütün operatörler iki terimli değildir. Bazen tek bir tarafını işleme alır ve sonuç üretirler.
Örneğin çıkarma işlemi için değil de bir sayıyı veya terimi negatif hale getirmek için kullnaılan – operatörünü düşünelim. Bu operatör sağ tarafında bulunan ifadeyi negatif hale getirir ve görevini tamamlar. Tıpkı bu operatör gibi tek bir tarafındaki ifade ile işlemi gerçekleştiren operatörlere Tek Terimli Operatörler (Unary Operators) adı verilmektedir.
Tıpkı – operatörü gibi, bir ifadeyi DEĞİL işleminden geçirerek sonuç üreten ! operatörü de bir tek terimli operatördür. Bu operatör sağ tarafındaki ifadeyi mantıksal olarak DEĞİL işlemine sokar. Eğer ifade sıfırdan farklı bir değerdeyse sonuç 0 olur. İfade 0 değerindeyse ise sonuç 1 veya derleyiciye göre sıfırdan farklı bir değerde olur. Bu operatörün kullanımı şu şekildedir:
int a = 9, b = 9;
if(!(a == b))
{
// Blok içi kodlar
}
Yukarıdaki kod parçasında if bloğunun içine girilmez. Çünkü a ve b değişkenlerinin değerleri birbirine eşit olup == operatöründen sıfır dışı bir değer gelse bile, ! operatörü bunu 0’a döndürecek ve if koşulu sağlanmayacaktır.
Burada parantez kullanımına dikkat edin. Eğer parantez kullanmasaydık "!a == b" gibi bir ifade ortaya çıkacaktı. Bu ifadede önce a değişkeninin değeri 0 dışı bir değer olduğundan ! operatörünün sonucu 0 çıkacaktı. b değişkeninin değeri de 0 olmadığından bütün ifadenin değeri 0 çıkacaktı. Burada sonuç yine değişmese de yapılmak istenen işlem bu olmadığından ileride sıkıntılara yol açabilir. Örneğin a ve b değişkenlerinin değerleri 0 olsaydı parantezli halinin sonucu yine 0 çıkacaktı. Fakat parantez kullanmasaydık bu sefer sonuç 0 dışı bir değer çıkacak ve if bloğuna girilecekti.
C99 Standartlarında bool Türünü Ele Almak
Eski C standartlarında C++’taki bool türü gibi bir tür yer almadığından anlatımı "0" ve "0 dışı" gibi ifadeler kullanarak yaptım. Aslında C99 standartlarıyla beraber dile _Bool türü eklenmiştir ve bu tip mantıksal ve karşılaştırmaya dayalı operatörler artık bu türde geri dönüş sağlamaktadırlar. Ancak C23 standartları ile bu sorun tamamen ortadan kaldırılmış ve doğal bool türü dile eklenmiştir (önceki yazılarda bahsetmiştim).
_Bool türünün eklenmesi "0" ve "0 dışı" olayında pek bir değişikliğe yol açmamıştır. Bu tür sadece 0 ve 1 değerlerini tutabilmektedir. Eğer ki bu türü C++’daki bool anahtar kelimesini kulllanır gibi kullanırsak ve yine C++’taki true ve false anahtar kelimelerini sırasıyla 1 ve 0 değerlerine karşılık gelecek şekilde kullanırsak, kodunuza stdbool.h başlık dosyasını eklemeniz grekmektedir. Dediğim gibi bu sadece C99 ve üstü standartlar için geçerlidir. Hemen bir örnek gösterelim:
#include <stdio.h>
#include <stdbool.h> // "bool", "true" ve "false" kelimelerini kullanmak için
int main()
{
bool dogruluk1 = true; // Başlık dosyası ekleyerek kullanma (C23'te gerekli değil)
_Bool dogrukuk2 = 0; // Başlık dosyası eklemeyerek kullanma
printf("%d %d", dogruluk1, dogrukuk2);
return 0;
}
Çıktıya bakarak aslında arkaplanda pek bir şeyin değişmediğini görebilirsiniz. Fakat bu şekilde kullanım, değişkeni hangi amaçla kullandığınızı belirtmek açısından oldukça önemlidir. Bu nedenle eğer C99 ve üstünü destekleyen bir derleyici kullanıyorsanız bu şekilde bool ile çalışmanız daha iyi olur. Ancak eğer C23 ve üstünü destekleyen bir derleyici kullanıyorsanız doğal bool anahtar kelimesini kullanmanızı kesinlikle tavsiye ediyorum.
Bu bölümde birçok konuda kullanacağımız karşılaştırma operatörlerini ve mantıksal opertörleri gördük. Ayrıca C99 standardı ile gelen _Bool anahtar kelimesine ve stdbool.h başlık dosyasında yer alan çeşitli kullanışlı önişlemci bildirimlerine değindik. Bundan sonraki bölümlerde çeşitli algoritmaları dilde impelement edebilmemizi sağlayacak olan kontrol yapılarını anlatacağız.


