A'dan Z'ye C Programlama Dilini Öğrenelim

Bu konuyu okuyanlar

defacerGLD

Müdavim
Katılım
16 Ocak 2020
Mesajlar
3,329
Reaksiyon puanı
3,068
Puanları
113
yazmak bir ömür sürdü okumak baya sürer

C programlama dili

Merhaba arkadaşlar, bu yazımda sizlere C programlama dilinin detaylarından ve inceliklerinden bahsedeceğim. Uzun soluklu bir yazı olacaktır öncelikle onu belirteyim. Girebildiğim kadar girmeye ve pointer mantığını elimden geldiğince basitleştirerek anlatmaya çalışacağım.

Neden C Öğrenmeliyiz?

Programlama dilleri problemin çözümüne doğru giden yolda kullanılan bir araçtan ibarettir. Her programlama dili ile X problemi çözülebilir. Fakat X problemini çözmek için hangi dil uygundur kısmına gelecek olursak bu konu tartışmaya açık bir hale gelir. Belli programlama dilleri belli sorunları çözmek için ortaya çıkagelmiştir. Yazının içinde de göreceksiniz zaten C dilinin ortaya çıkma sebebi UNIX'in geliştirilmek istenmesidir. Yani kısaca her dilin belirli kullanım amaçları vardır diyebiliriz. C programlama dili sembolik makine diline en yakın dil olmasından dolayı çok hızlı çalışmaktadır. Piyasada bulunan en hızlı dildir. Bu cümleden anlayacağınız üzere hızın kritik olduğu durumlarda bu dili kullanmalıyız. Gömülü sistemler, sistem programları, işletim sistemleri, otonom araçlar, akıllı ev sistemleri. Bu uygulama alanlarının dünyasında C/C++ olmazsa olmaz diyebiliriz.

C bilmem ne gibi avantaj sağlayacak ?


İlk olarak C bilmek diğer dillere göre çok daha iyi düzeyde programlama mantığının anlaşılmasını sağlar. C öğrenen kişi çok fazla fonksiyon bulunmadığından dolayı düşündüğü algoritmaları kendi geliştirmek zorundadır. Kısaca C öğrenmek algoritma geliştirme yeteceği katar. Ram ile doğrudan ilişkili olduğumuz bu C dilinde Ram, bilgisayar çalışma mimarisi gibi konuları çok daha iyi anlamamızı sağlar. C dilinde her şey geliştiriciye bırakıldığı için dinamik bellek yönetimi denilen bir kavram oluşmuştur. Bu kavram ile heap-stack alanını daha yakından tanıyıp kavrayabiliriz. Piyasadaki çoğu dil C dilinden esinlenerek ortaya çıkmıştır. C asla eskimez, her daim C/C++ dillerine ihtiyaç olacaktır.
Kısaca özetleyecek olursak;

*Problem çözme becerisi
*Bilgisayarın çalışma mimarisi
*Derleyicinin çalışma mantığı
*Diğer dillere geçişlerde o dilleri daha kolay anlamak
*Gömülü sistemler üzerine çalışabilme
*İşletim sistemlerini daha yakından tanıma(çalışma mantığı)


C Dilinin Tarihçesi




C programlama Dili 1970-1971 yıllarında AT&T Bell laboratuvarında UNIX işletim sisteminin geliştirilmesi süresinde yan ürün olarak tasarlandı. AT&T o zamanlar Multics isimli bir işletim sistemi projesinde çalışıyordu. AT&T bu projeden çekildi ve kendi işletim sistemlerini yazma yoluna saptı. Bu işletim sistemine Multics üzerinden kelime oyunu yapılarak UNIX ismi verildi. O zamanlar işletim sistemleri sembolik makine dili ile yazılıyorlardı. Ken Thompson işleri kolaylaştırmak için B isimli programlama dilini geliştirdi. Sonraki yıllarda Dennis Ritchie bunu geliştirerek C haline getirdi. UNIX işletim sistemi 1973 yılında sil baştan yeniden C ile yazıldı. O zamanlar ilk defa gelişmiş bir programlama dili kullanılarak işletim sistemi yazılmıştır, kısacası bu olay programlama tarihinde devrim niteliği taşımaktadır. 1980 yıllarında IBM ilk kişisel bilgisayarlarını piyasaya sürdü. 1978 yılında Dennis Ritchie ve Brian Kernigan tarafından ‘The C Programming Language’ kitabı yazıldı. C programlama Dili kişisel bilgisayarlarda kullanılan en yaygın dil oldu.




Programlama Dillerinin Seviyelerine Göre Sınıflandırılması



Programlama dillerindeki seviyenin ölçütü programlama dilinin insan algısına yakınlılığını temsil eder. Yüksek seviyeli diller insan algısına en yakın dillerdir ve kolay öğrenilirler. Alçak seviyeli diller ise insan algısına en uzak dillerdir ve öğrenilmeleri oldukça zordur. En alçak seviyeli dil ise makine dilidir (machine language). Bu sistem binary system olarak da geçmektedir. Sadece 1 ve 0’ların olduğu kodlardan bahsediyoruz ve yazmanın ne kadar zor ve zahmetli olduğunu az çok tahmin edebilirsiniz. Bu dilin bir üstünde Assembly dili mevcuttur. Assembly makine dilini göre her ne kadar daha kolay olsa dahi yine de insanlar için algılanması ve kodlanması zor bir dildir. Bunun bir üstünde ise C dili bulunmaktadır. C dili Ingilizce diline daha yakın olmakla beraber, basit matematik işlemi oparetörleri ve basit anahtar kelimeleri ile birlikte çok daha anlaşılır bir dildir (assembly diline göre).


COMPILE (Derlemek)




Compile Ingilizce bir kelimedir. Türkçe anlamı derlemek demektir. C compile edilmesi gereken bir dildir. İşletim sistemi bizim yazdığımız Ingilizce kelimelerden oluşan kodu anlayamaz, bu kodu compile ederek kendinin anlayacağı kod parçacıklarına dönüştürmek zorundadır. Bir C kodunu derlediğimiz taktirde ilk olarak assembler yardımı ile assembly koduna dönüştürülür, ondan sonra ikilik kod dediğimiz makine koduna dönüştürülür, artık işletim sistemi yazdığımız kodun ne olduğunu anlamaktadır.

Günümüzde en çok kullanılan derleyici GCC isimli derleyicidir. GNU C compiler kelimelerinin baş harfinden oluşmaktadır. Microsoft tarafında da çeşitli derleyiciler bulunmaktadır.

IDE KAVRAMI

Normal olarak derleyiciler komut satırından çalıştırılan programlardır. Editör ortamında kod yazılır, terminal üzerinde bu kod derlenir. İşin aslı bu şekildedir. Fakat günümüzde IDE denilen bir kavram ortaya çıkmıştır. Integrated Development Environment kelimelerinin baş harflerinden oluşmaktadır. IDE’lerin çıkma sebebi ise programı editör üzerinde yazıp daha sonra terminal ekranında derlemek çok zahmetli olmaktadır. Biz bilgisayarımıza herhangi bir IDE kurduğumuz taktirde onunla birlikte bir derleyici kurulur. IDE aslında derleyici değildir. Bizim kod yazmamızı ve derlememizi kolaylaştıran bir programdır. Compile & Run gibi butonlar mevcuttur. Derleyici değilse compile butonunun işi ne diyebilirsiniz. Biz o butona bastığımız taktirde terminalde el ile derleme derdinden kurtuluruz, arka planda program bunu bizim için yapmaktadır.



Ön işlem ( preprocess)


C programımız derlenmeden önce ön işlem denilen bir işlemden geçirilmektedir. Bu işlem programın en üstünde # (diyez) işareti ile başlayan satırları yorumlar. Bu satırlar yorumlandıktan sonra ve gerekli işlemler yapıldıktan sonra compile işlemine başlanır.



Compile processing

Compile işlemi tokenizing işlemi ile başlamaktadır. Nedir bu işlem biraz bundan bahsedelim. Derleyici kodu anlamlandırmadan önce kodun içeriğini Token parçalarına ayrılır. Bu işlem yapılırken derleyici isimlere ve cümlelere bakmaz sadece tokenlerine ayırır. Kodun anlamını, neler yaptığını, koddaki mantık hatalarını o evrede anlayamaz. Token en küçük birim anlamına geliyor şeklinde düşünebiliriz. Tokenler nelerdir onları inceleyelim.

Keywords/Reserved Words (Anahtar Sözcük)

Anahtar sözcükler dilin tasarımınca önceden belirlenmiş, baştan özel bir anlam yüklenmiş ve başka bir anlamca kullanılması yasaklanmış olan sözcüklerdir. C dilinde 32 adet anahtar sözcük bulunmaktadır.



Identifiers (İsimlendirme)



C dilindeki varlıklara verilen özgün isimlerdir. Örnek olarak şunları verebiliriz:
*Değişkenlere verilen isimler
*Fonksiyonlara verilen isimler
*Sabitlere verilen isimler
*Etiketlere verilen isimler


Operators (Operatörler)

Operatörler belli bir işlem yaptıran tokenlerdir. Programlama dilinden programlama diline değişiklik gösterse de genel yapısı aynıdır. Matematik işlemleri, mantık işlemleri gibi işlemleri operatörler sayesinde yapabiliyoruz.




Constant/Literal (Sabitler)

Kod içerisinde doğrudan sayısal büyüklük veya karakter dizisi belirten ifadelerdir. Sayısal sabitleri yazmak için 3 ayrı sistem bulunmaktadır.

*Decimal (Onluk)
*Hexadecimal (Onaltılık)
*Octal (Sekizlik)


Decimal sayılar yazılırken olduğu gibi yazılır.
Hexadecimal sayılar yazılırken başlarına ‘0x’ veya ‘0X’ değerlerini alarak yazılırlar. Octal sayılar yazılırken başlarına ‘0’ değerini alarak yazılırlar.
0x35 (10), 035 (16), 35 (8)
Yukarıda gördüğümüz 3 değer de birbirinden farklı sayıları temsil etmektedir.


String Literal (Karakter Sabitleri)

Çift tırnak arasına yazılmış sabitlerdir. ASCII kodlarına göre nitelendirilmektedirler.



Yukarıdaki kodda a,b ve c değişkenlerinin değeri sabit değerdir. Eğer c = a şeklinde bir şey deseydik c değerinin içi sabit değil değişken olacaktı.

Delimeters/Punctuators (Ayıraçlar)

Yukarıda belirttiğimiz grupların dışında kalan, ifadeleri ayırmak için kullanılan tüm atomlara ayıraçlar denir. Örnek verecek olursak ';' ve '{ }' ayıraç atomudur.




Derleyici Kuralları



Tokenizing (atomlarına ayırma) aşamasında yazım kuralı hatası (syntax) bulunmaması gerekmektedir. 2 token arasında herhangi bir satır atlama, boşluk bırakma, boşluk bırakmama gibi bir kural yoktur. Biz çok okunaklı bir kod bile yazsak tokenizing açamasında bu kodlar birleştirilmektedir. Programcı açısından her ne kadar bu bir kural olmasa dahi okunabilirlik açısından okunaklı kod yazmak önemlidir.






Global nameSpace/Local namespace

Yazdığımız C kodu 2 farklı bölümden oluşmaktadır. Global isim alanı ve yerel isim alanı. Fonksiyonlarımızın içinde kalan kısım bizim yerel isim alanımızdır. Fonksiyonların dışında kalan kısım ise global isim alanımızdır. Bu alanların özelliklerine birazdan geleceğiz.






Declaration /Statement

C dilinde kullandığımız her cümle başlıktaki 2 yapıdan birini içermelidir. Declaration bildirim anlamına gelmektedir. Herhangi bir işlem yapılmaz. Sadece bildirim yapılır. Hem global namespace alanında hem de local namespace alanında kullanılabilir. Bir değişkeni veya fonksiyonu tanımlarken kullandığımız cümle demektir. Bildirimde bulunmazsak compiler o değişkenin veya fonksiyonun ne olduğunu tanımayacaktır.

Statement ise deyim anlamına gelmektedir. Compiler deyimleri gördüğü zaman bir işlem yapmasını gerektiğini bilmektedir. Döngü yazarken, karar yapısı koyarken, matematik işlemi yaparken vb. deyimler kullanılmaktadır. Deyimler global namespace üzerinde yazılamaz. Local namespace yani fonksiyon içerisinde yazılmak zorundadır.



Yukarıdaki örnekteki kodu derlediğiniz taktirde hata verecektir. İki değişkeni de global alanda tanımladık, kuralımıza göre global alanda sadece declaration yapabileceğimiz yönündeydi. İlk değişkeni tanımlarken sıkıntı yok, fakat ikinci değişken bir deyim içeriyor. Bir sabit ile bir değişkenin toplanması deyim anlamına geliyor. 5 + 3 diyebilirdik, çünkü 2 değer de Constant değer içeriyor, fakat böyle bir kullanım yapamayız.

#include Direktifi

Bu komut önişlemci komutudur. Bu komut derleme sırasında komutun yerleştirildiği yere kopyalanacağını belirtir, bu satırı silip yerine stdio.h isimli dosyanın içeriğini oraya yerleştirmek ile aynı sonucu doğurur. #include sözcüğü ise dahil etmek anlamına gelir. C dilinde en temel fonksiyonları kullanabilmemiz için bile temel kütüphaneleri dahil etmemiz gerekir, diğer dillerde alışılagelenin aksine.

C Dilinde Karşınıza Çıkabilecek Temel Kütüphaneler

<stdio.h> En temel c kütüphanesidir, standart girdi çıktı fonksiyonlarına ulaşmamız için gereklidir. Stdin, stdout, stderr dosyalarını kullanır.

<stdlib.h> Temel c kütüphanelerinden biridir, temel fonksiyonlara ulaşmamız için bu kütüphaneleri dahil etmemiz gerekmektedir. Bu kütüphane ile gelen fonksiyonları sıralayalım;

Kod:
abort();
abs();
atexit();
atof();
atoi();
atol();
bsearch();
calloc();
div();
exit();
free();
getenv();
labs();
ldiv();
malloc();
mblen();
mbstowcs();
mbtowc();
qsort();
rand();
realloc();
srand();
strtod();
strtol();
strtoul();
system();
wcstombs();
wctomb();
<math.h> Adından da anlaşılacağı üzere matematik işlemleri yapmamıza yarayan fonksiyonları içeren kütüphanedir. Bu kütüphane ile gelen fonksiyonları sıralayalım;

Kod:
acts();
asin();
atan();
atan2();
ceil();
cos();
cosh();
exp();
fabs();
floor();
fmod();
frexp();
ldexp();
log();
log10();
modef();
pow();
sin();
sinh();
sqrt();
tan();
tanh();
<string.h> Karakter dizilerinde belli işlemler yapmamızı sağlayan fonksiyonlardır. Bu kütüphane içerisinde çok fazla üst düzey fonksiyon bulunmaktadır. Gelin bu fonksiyonları da inceleyelim.

Kod:
memccpy();
memchr();
memcmp();
memcpy();
memcpy_s();
memmove();
memmove_S();
memset();
memset_s();
strcat();
strcat_s();
strchr();
strcmp();
strcoll();
strcpy();
strcpy_s();
strcspn();
strdup();
strerror();
strerror_s();
strerrorlen_s();
strlen();
strlen_s();
strncat();
strncat_s();
strncmp();
strncpy_s();
strndup();
strpbrk();
strrchr();
strspn();
strstr();
strtok();
strtok_s();
strxfrm();

#define Direktifi

Define bir ön işlemci emridir, programın içinde kesinlikle değiştirilmesini istemediğimiz verilerimiz için kullanılır. Ön işlemci tarafından işlendiği için program içerisinde değişken gibi değil Constant/literal (sabit) olarak değerlendirilir. Genel kullanım olarak define değerlere isim atama büyük harf ile yapılır, değer ile isim arasına eşittir konulmaz, sadece boşluk bırakılır.

Örnek kullanım ;
Kod:
#define A 15
#define PI 3.14
Not: Define ile sabit tanımlarken büyük harf alışılagelen bir kullanımdır, programcıların dikkat etmesi gerekir.



C Dilinde Değişkenler


Değişkenler adı üzerinde istediğimiz zaman değiştirebildiğimiz bilgisayarın belleği (ram) üzerinde depolanan verilerimizdir.

Neden veri depolamaya ihtiyaç duyarız?

-Programın belli noktalarında kullanıcıdan veri almak isteyebiliriz, programın akışını kullanıcıdan kullanıcıya değiştirebilmek için.

-Belli durumlarda belli işlemlerin yapılmasını ve bu yaptığımız işlemleri programın başka kısmında kullanmak isteyebiliriz. Bunun için geçici bir depolama ünitesine ihtiyaç duyarız.

Ram üzerinde depoladığımız verilerin (değişken) belli bir veri tipi olmak zorundadır. Bu veri tipinin ne olduğunu değişkeni tanımladığımız satırda belirtmek zorundayız. Kısaca bir değişken tanımlamak için şöyle bir kullanım yaparız.

Kod:
veri_tipi degisken_ismi(Identifier) = degisken_degeri ;
Veri tipini belirleriz. Değişkene bir isim veririz ve bu verdiğimiz isim değişkenin ne ile ilgili olduğunu çağrıştırmalıdır, daha sonra programın başka bölümünde çağırırken unutmamak adına. Değişkene isim verirken belli kurallar çerçevesinde veririz bu ismi. O kurallara daha sonra geleceğiz. Ve sonra =(atama operatörü) kullanırız. Matematikteki eşittir işareti ile karıştırmayın. İşaretin buradaki amacı sağdaki değeri al, soldaki değişkene eşitle demektir. Atama operatöründen sonra ise belirlediğimiz değişken tipine göre bir veri veririz.

Ya veriyi kullanıcıdan almak istiyorsak?

Kod:
int x;
scanf("%d",&x);
Veriyi kullanıcıdan almak istediğimiz taktirde sadece veri tipi ve isim belirtmemiz yeterlidir. Bellek üzerinde değişken için bir yer açarız. Henüz bellekte bir değeri yoktur, ama adresi vardır. Kısaca programa x değişkeni için bir yer ayır, ben daha sonra o yeri dolduracağım deriz. scanf fonksiyonu yani kullanıcıdan girdi almamızı sağlayan fonksiyon ile kullanıcıdan veri alırız ve o veriyi x’in adresine kaydet deriz.

Değişken İsim Kuralları

-Değişkenlere verdiğimiz isim sayı ile başlayamaz. Fakat içinde veya sonunda sayı geçebilir.

-Değişkenlere verdiğimiz isim içinde özel karakter barındıramaz. Bu kural için bir adet istisnamız mevcut _ işareti kullanılabilir.

-Değişkenlere verdiğimiz isim 32 karakterden daha uzun olamaz.

-Değişkenlere verdiğimiz isim programın kendi kullandığı özel sözcükleri kullanamaz. (int, short, for, if, **** gibi…)

-C dilinde değişkenler case-sensitive yani büyük harf- küçük harf kurallı dillerdir.

Kod:
int merhaba;
int mErHaBa;
Yukarıda tanımladığımız 2 değişken birbirinden bağımsızdır, ayrıca olan bir değişken ile tıpatıp aynı isimde bir değişken tanımlarsak hata alırız.


C Dilinde Temel Veri Tipleri

Veri Tipi nedir öncelikle ondan bahsedeyim. Veri tipi dediğimiz kavram oluşturduğumuz değişkenin türünü niteler. Aslında temel olarak veri tipi ram üzerinde kaç byte tutulacağını ve o değişkenin nasıl yorumlayacağını bilgisayarın anlaması için oluşturulmuş kavramdır. Programlama dillerinde depolamak istediğimiz her değişken ram üzerinde belli bir adreste depolanır. Verileri tiplerine ayırmamızdaki temel sebep ram üzerinde gereksiz yer harcamamaktır. Küçük sayı kullanacaksak ona göre veri tipini belirleriz ve gereksiz byte harcamış olmayız, işaretsiz tamsayı kullanacaksak ona göre belirleriz ve karmaşıklığa yol açmayız. Kısaca veri tipi bize verinin ne olduğunu söyler. Bellekteki tüm değişkenler (karakter dahi olsa) kodumuzu compile ettikten sonra 0’lar ve 1’lere dönüştürürler.

Char Veri Tipi

Char veri tipi sadece tek 1 karakter içindir. Bellekte bir byte yer tutar ve tanımlanırken ‘ ‘ işaretleri arasına yazılır. Bellekte char için ayrılan kısım bir byte olduğu için char ile 255’den fazla veri tutamayız. Karakter tutuyorduk hani biz bu veri tipiyle dediğinizi duyar gibiyim. Karakter ile 255 sayısını nasıl kıyaslayacağız? Yukarıda yazdığım cümleyi tekrar okuyalım:

Bellekteki tüm değişkenler (karakter dahi olsa) kodumuzu compile ettikten sonra 0’lar ve 1’lere dönüştürürler.

Karakter değişkeninde a harfini tutuyoruz diyelim, a harfini nasıl sayıya dönüştüreceğiz peki? Zamanında programcılar bu soruyu kendilerine sormuşlar ve ortak bir çözüm bulmaları gerektirdiğinden bir standart oluşturmuşlar. Tüm dünyanın kabul göreceği standartlar. Ve buna American Standard Code for Information Interchange(ASCII) adını koymuşlar.
wol_error.gif
Bu resim yeniden boyutlandırıldı, tam halini görmek için tıklayınız.




Yukarıda gördüğümüz üzere klavye üzerinde bulunan her input değeri için bir sayısal değer bulunmaktadır.

Karakter değişkeni tanımlayıp ASCII koduna göre eşdeğerini görelim.

Kod:
char a = 'a';
printf("%d",a); // d placeholder'ı kullanarak integer değerini
printf("%c",a); // c placeholder'ı kullanarak string değerini
Yukarıda gördüğünüz gibi a değişkeninin hem char değerini hem integer değerini yazdırdık.

Integer Veri Tipi

Integer veri tipi tam sayıları depolayan veri tipimizdir. Bellek üzerinde kapladığı alan her ne kadar bilgisayardan bilgisayara farklılık gösterse de genel olarak 4 bytedır. 4 byte değer ise 32 bit yani maksimum olarak 2^32-1 değerini alabilir. Sizin bilgisayarınızda integer veri tipinin bellekte ne kadar yer kapladığını öğrenmek için sizeof() operatörünü kullanabilirsiniz.

Gelin integer veri tipini kullanarak değişken tanımlayalım.

Kod:
int x = 5;
printf("%i",x);
printf("%d",x);
Integer değişkenleri için 2 farklı placeholder (yer tutucu) kullanabiliriz. %d ve %i. Genel olarak %d kullanımı daha yaygındır.

Float ve Double Veri Tipi

Float ve double kayan ondalıklı sayılar, biraz daha türkçeleştirecek olursak virgüllü sayılar için kullanılır. Fakat programlama dilinde ondalık ve tam sayı değerini virgül ile değil nokta ile ayırırız, virgül ile ayırmayı denersek derleyici bunu anlamaz ve syntax hatası olarak karşılar. Madem ikisi de ondalık değerleri temsil ediyor. Neden 2 farklı veri tipi kullanıyoruz sorusunun yanıtına gelirsek cevabı basittir. Depoladıkları alan. Float veri tipi 4 byte kadar alan ayırır, double veri tipi ise 8 byte kadar alan ayırır. 4 byte 32 bit alana denk gelir ve integer kadar veri depolayabilir aslında. Double ise tam olarak iki katı. Bilgisayar üzerinde kayan noktalı sayıların hesaplanması net sonuç vermez, işlemi ne kadar yüksek byte üzerinde yaparsak sonuç doğruluğa o kadar yaklaşır. Ama kesin doğrudur diyemeyiz, bu sebepten ötürü programcılara double veri tipini kullanmaları tavsiye edilir.

Kod:
float x = 5.4;
float y = 5.0;
double z = 4.9;
double t = 4;
Yukarıdaki kullanımların hepsi doğrudur. Son kullanımda t değişkenine direk 4 değeri atanmıştır. Tam sayı değeri, program bunu float olarak kaydeder bellek üzerine. 4.0 yani. Float ve Double veri tipleri için yazdırırken %f placeholder’ı kullanırız. Değer okurken (scanf) float için %f double için %lf kullanırız.


C Dilinde Girdi/Çıktı Fonksiyonları

Biz kodumuzda bir girdi çıktı fonksiyonu kullandığımız veya yazdığımız zaman fonksiyon direk klavyemizden veri okumaz veya direk ekranımıza yazdırmaz. C dilinin ilk olarak UNIX ile ortaya çıktığını söylemiştik, UNIX sistemleri daha önce araştırdıysanız şu cümleyi mutlaka duymuşsunuz demektir;

UNIX Sistemlerde her şey bir dosyadır (donanım dahil).

Yani kısaca C dilinde biz dosyaya yazar, dosyadan okuruz. Kullanıcıdan veri alırken stdin dediğimiz bir dosyadan okuma yapar, ekrana bir şeyler bastırmak istediğimizde ise stdout dosyasına yazarız. Error mesajı da stderr dosyasından basılır.

Çıktı Fonksiyonları

En temel 2 çıktı fonksiyonundan bahsedeceğim sizlere, printf() ve putchar() fonksiyonları.

Printf fonksiyonu içine 2 argüman alır. Birinci argüman içerisine 2 tırnak içerisinde gireceğimiz veriyi veya kullanmak istediğimiz yer tutucuyu yazarız. 2 argümanı virgül ile ayırmak koşuluyla ikinci argümanın yerine de bir ifade (expression) yazarız. a + b, c + 3, 3+2 gibi...

Putchar fonksiyonu ise adından anlaşılacağı gibi tek bir karakteri basmaya yarar. İçine argüman olarak direk değişken verebiliriz veyahut tek tırnak ( ' ' ) şeklinde karakter verebiliriz.


Girdi Fonksiyonları

scanf, getch, getche, getchar, gets.....

Bir sürü girdi fonksiyonu kullanılmaktadır. Bunların hepsinin ufak ufak farkları mevcuttur. Bunlardan bahsedelim.

Scanf fonksiyonu en çok kullandığımız girdi fonksiyonudur. 2 argüman alır, soldaki argümana çift tırnak içerisinde yer tutucumuzu spesifik bir şekilde tanımlayabiliriz. Sağdaki argümana ise atamak istediğimiz değişkenin adresini veririz (&degisken_adi). Bu fonksiyon biz boşluk değerini verene kadarki kısmı okur. Sonrasını almaz (2 değişken okuyorsa boşluktan sonrası diğer değişkene atanır).

Gets fonksiyonu sonradan C standartlarından kaldırılmış ama hala kullanabileceğimiz bir fonksiyondur. Bu fonksiyon ise enter ('\n') girilene kadarki kısmı okur. Yani istediğimiz yerde bitirebiliriz cümlemizi.

Getchar fonksiyonu ise çok dikkatli kullanılması gereken bir fonksiyondur. Neden diye soracak olursak bu fonksiyon karakter karakter okuma yapmaktadır ve enter tuşuna basılmasını bekler. K harfine bastık ve enter tuşuna bastık, hem k karakterini hem '\n' karakterini stdin dosyasına yazmaktadır. Yani putchar ile 2 defa değişkenimizi okursak birincide K harfini bastıracak ikincide ise '\n' karakterini bastıracaktır. Buna çözüm olarak kullanmamız gereken fonksiyon ise fflush() fonksiyonudur. Dosya akışını temizlemeye yarar. Dosya akışını temizleyeceğiz, peki ya hangi dosya akışını?

Cevap belli değil mi? stdin bir dosyadır getchardan aldığı karakteri bu dosyaya yazar. Bizim buranın akışını temizlememiz gerekir. fflush(stdin); diyerek stdin dosyasının akışını temizlemek zorundayız.

Getch fonksiyonu standart kütüphane ile gelmez <conio.h> kütüphanesi ile dahil ederiz. Bu fonksiyon karakteri girdiğimiz gibi işlem yapar enter tuşuna basmamızı beklemez bizden. Karakter alırken bu fonksiyonu kullanmamız daha mantıklı olacaktır bu sebepten dolayı.






Döngüler ve Karar Yapıları

Programa dillerinin temel yapıtaşı olan döngüler ve karar yapılarından bahsedelim. C dilinde 3 farklı döngü yapısı vardır. Hepsi birbirine çevrilebilir, aralarında hiç bir fark bulunmamaktadır. Bu döngülerden bahsedelim;
for döngüsü, while döngüsü ve do while döngüsü

For Döngüsü

For döngülerinin içi 3 kısımdan oluşur. Birinci kısım ilk atama kısmıdır. Eski C standartlarında ilk değişken oluşturma kısmını for döngüsünün üstünde yapmak zorundaydık, yeni C standartlarında bu kalkmış bulunmaktadır. İkinci kısım koşul kısmıdır. Buraya girdiğimiz koşul true döndüğü müddetçe döngümüz çalışacaktır. Döngünün içerisinde 2 noktalı virgül bulunur ve bu 2 noktalı virgül döngüyü 3 parçaya ayırır. Döngünün üçüncü kısmı ise güncelleme kısmıdır. Döngüden çıkmak için bir güncelleme yapmak zorundayız, yoksa sonsuz döngüye gireriz.

Kod:
for (tanım kısmı ; kontrol kısmı ; güncelleme kısmı );
While Döngüsü

While döngüsünün içi bir kısımdan oluşur. Yapı olarak for döngüsünden farklı görünse de tamamen aynıdır. Her for döngüsü while döngüsü ile yapılabilir, her while döngüsü for döngüsü ile yapılabilir. While döngüsü tek kısımdan oluşuyor demiştik, bu kısım ise döngünün karar yapısı, true ise döngü çalışır. False ile döngü çalışmaz. C de true false olayından bahsetmiştik. 0 False değere sahiptir, onun dışındaki bütün değerler true değere sahiptir. Negatif sayılar da dahil olmak üzere.

Kod:
int j = 0; //tanım kısmı
while(j< 10) // kontrol kısmı
{
j++; // güncelleme kısmı
}
Do-While Döngüsü

Do while döngüsü while döngüsüyle hemen hemen aynı bir yapıdadır. Tek farkı önce scope alanındaki işlemler yapılır, sonra kontrolden geçer. While 0 yazsak bile o işlemler en az 1 defa yapılacaktır yani.

Kod:
do{

j++;

}while(j<10);
If-Else Karar Yapısı

If-Else karar yapıları belli koşullarda belli işlemleri yapabildiğimiz yapılardır. Programın genel akışı if-else yapısı ile şekillenir. If içine bir veya birden daha fazla koşul yapılabilir. Döngüden farklı yapan şey bir defa yapılmasıdır işlemlerin.

Kod:
int x =5;
if(x == 5)
{
printf("if koşulu çalıştı");
}
If yapısının ardından derleyici else-if veya else yapısını bekler. Yoksa da ilgilenmez. If doğru ise else-if veya else yapılarının doğruluğu kontrol edilmez.


Yanlış kullanım
Kod:
int x = 5;
if(x <10)
{
printf("if çalıştı);
}
if(x >10)
{
printf("ikinci if çalıştı");
}
Yukarıda yanlış bir kullanım gösterdim, neden yanlış diyeceksiniz. x'in 10'da küçük olması ile x'in 10'dan büyük olması aynı anda mümkün olamaz. Ya küçüktür ya büyüktür. Program 2 defa if yapısına girerek yavaşlayacaktır. Doğru kullanımı ise if-else veya if-else if şeklinde olmalıdır. Çünkü bu yapılarda if doğru ise diğer bağımlı bloklar atlanmaktadır.

Doğru kullanım
Kod:
int x = 5;
if(x <10)
{
printf("if çalıştı);
}
else if(x >10)
{
printf("else if çalıştı");
}
Switch-Case Karar yapısı

Bu karar yapısı çok fazla olasılık varsa kullanılması gereken bir yapıdır. Her switch case yapısı if else if yapısı ile yazılabilir, her if else if yapısı da switch case ile yazılabilir. Fakat fazla olasılık olan durumlarda if else if kullanmak çok zahmetli olmaktadır.

Kod:
switch(değişken)
{
case 1:
xxxxx;
break;
case 2:
yyyyy;
break;
case 3:
zzzzzz;
break;
default:
deger bulunamadı;
}
Switch içine bir değişken alır, diyelim x değişkenini verdik. X değişkenindeki değere bakıyoruz, bu değer de 5 olsun. case 1'e bakar 2'ye bakar, en son da 3'e bakar. Hiçbiri bulunamadıysa default öntanımlı değere girer.



Pointer(işaretçi) Nedir

Pointer ingilizce bir kelimedir. Türkçe anlamı İşaretçi , göstergedir. Kelimenin anlamından anlayacağımız üzere Pointer dediğimiz kavram bir değişkenin adres bilgisini depolar, o değişkeni gösterir. Yani kısaca pointer herhangi bir değişkenin ram üzerinde tutulduğu adres bilgisidir.

Biz herhangi bir programlama dilinde bir değişken oluşturduğumuz zaman oluşturduğumuz değişken ram üzerinde rastgele (random) bir bellek ünitesine yerleşir. Ram üzerindeki adresleme işlemi 16’lık sayı tabanına (hexadecimal) göre yapılır.


Kod:
int x =5; // x adında değişken oluşturup içerisine 5 değerini atıyoruz
Bu x değişkeninin bellek üzerinde rastgele bir yer üzerinde depolandığını biliyoruz. Bu adresin de hexadecimal biçimde gösterildiğini söyledik. Bu adresi öğrenmemiz için bir pointer tanımlamamız gerekiyor. Pointer tanımlamak için * deyimini kullanırız.

Kod:
int *p = &x; // x değişkeninin adresini tutan pointer değişkeni
Yukarıda gördüğümüz kodda & işaretini kullandık. Bu işaretin adı ampersand işaretidir. Bu işaretin iki defa yan yana kullanılması demek ve demektir. Tek başına kullanıldığı taktirde ise …’nın yeri şeklinde kısaca özetleyebiliriz. Ampersand işareti değişkenin yerini, yani değişkenin adresini belirtmektedir.

Pointer tipinde bir değişkeni ekrana yazdırmak istediğimiz taktirde placeholder (yer tutucu) kullanmamız gerekiyor. Pointerlar için kullanacağımız placeholder ise %p işaretidir.

Kod:
int x = 5;

int *p = &x;

printf("%p",p);
Bu yazdığımız kod bize x değişkeninin adresini çıktı (output) verecektir. Kodu tekrar tekrar çalıştırırsak ise farklı sonuçların geldiğini göreceğiz. Yazının giriş kısmında da belirttiğimiz üzere değişkenler ram üzerine rastgele konumlandırılırlar.

Pointer kullanarak tuttuğu adres değişkeninin içindeki değeri görmemiz mümkün müdür? Cevabımız evet. Kullandığımız p pointerı x değişkeninin yerini gösteriyor. Kullandığımız x değişkeni ise bir integer. Yani printf komutunun içine integer değerlerin yer tutucusu olan %d değerini yazarsam ne sonuç alırım? 5 değerini yazdırmasını bekleriz, değil mi?

Kod:
int x = 5;

int *p = &x;

printf("%d",p);
Bu kod parçası bize hata döndürecektir. Hatanın sebebine gelecek olursak printf fonksiyonu ile integer değer bastırmak istiyoruz. Bu yüzden %d ifadesini kullandık. Virgülden sonraki kısmın da integer bir değer olması gerekiyor. Fakat virgülden sonra p yazıyoruz. p integer bir değer değil. p aslında yeni bir değişken tipi. Integer, float, double, char, long, pointer gibi düşünün. Integer değer taşımıyor içerisinde, adres değeri taşıyor. Burada yardımımıza * ifadesi koşuyor. Bu ifade pointerın gösterdiği adresin içindeki değer anlamına gelmektedir. Pointerın gösterdiği adres dediğimiz şey aslında x değişkeninin adresidir. Yani x değişkenin adresinin içi demek istiyoruz. Yani x değeri, yani 5….

Doğru kod :
Kod:
int x = 5;

int *p = &x;

printf("%d", *p);
Mantığını daha yakından kavramak için birkaç örnek yapalım.

Kod:
int a = 10;

int *ptr = &a;

*ptr = 15;
Kod:
int b = 20;

int c = 30;

int *ptr2 = &b;

ptr2 = &c;
Yukarıda yazdığım iki kod arasındaki farkı anlayabiliniz mi ?

Birinci kod parçasında a adında değişken oluşturduk ptr pointerı a değişkeninin adresini tutuyor ve ptr pointerının tuttuğu adresin içini (*) 15 değeriyle değiştiriyoruz. Bu kavram çok önemli pointerın tuttuğu adresin içi (yani doğrudan a değişkeni değişiyor)

İkinci kod parçasına geldiğimizde ise iki farklı değişken oluşturuyoruz b ve c adında. ptr2 isimli pointerımız b değişkeninin adresini tutuyor. Sonradan ptr2 pointerını (içi değil, işaret kullanmadık) yani pointerın gösterdiği değişkeni değiştiriyoruz. Yani ptr2 pointerımız artık b değişkeninin adresini değil de c değişkeninin adresini tutuyor.

Pointer konusunu anlamak 2 işaretin anlamından ibaret aslında. Ampersand ve yıldız işareti bu konu için çok önemlidir.

Programlama dillerinde her değişkenin bir yer tuttuğunu belirtmiştik yazının başında. Tanımladığmız pointerlar da birer değişken değil midir? Yani kısacası pointerlar da hafızada bir yer tutar. Pointerın hafızada tuttuğu yeri görelim.

Kod:
int x = 5;
int *p = &x;
int *p2 = &p;
Bu kullanımı olan bir gösterim değildir. Kimse pointerın tutulduğu adresi bilmek istemez (extrem durumlar haricinde) Burada sadece mantığını anlatmak için böyle bir gösterim yaptım. Bu kod parçasında p2 pointerı da ram üzerinde random bir alana konumlanmıştır. Bu böyle sonsuz döngüye kadar gidebilir yani.

Pointer tanımlamadan da bir değişkenin adresini görmemiz mümkündür, bu kullanım integer tanımlamadan integer yazdırmaya benzer aslında.

Kod:
printf("%d",15); // integer değişken tanımlamadan 15 yazdırdık.

int x = 5;
printf("%p", &x); // Pointer tanımlamadan x in adresini gösterdik.
Gördüğümüz gibi pointer değişken oluşturmamıza gerek kalmadan x değişkeninin adresini görebiliyoruz.

Kod:
int a = 50;
printf("%d", *&a);
Şeklinde bir kullanım yaparsak ne olur? Ampersand işareti değişkenin adresi demekti ve yıldız işareti de değişkenin içi demekti.

Yani burada biz a değişkeninin adresinin içini yazdır demek istiyoruz. Burada sadece mantığını anlatmaya çalışıyorum bu kullanım ilerde görebileceğiniz bir kullanım değil. *&a yazmak ile direkt a yazmak aynı kapıya çıkıyor aslında. a yazdığımda a değişkenini bastırır. *&a yazdırdığımızda ise dolaylı yoldan (gereksiz ) a değişkenini yazdırmış oluruz.

Aşağıdaki çıktıları kendiniz bulup compile edip karşılaştırınız :
Kod:
int a = 10;
int b = 20;
int c = 30;

int *p = &a;
*p = 15;
p = &c;
*p = b;

printf("%d", a);
printf("%d", b);
printf("%d", c);
C Dilinde Stringler(Char List)

C dilinde stringler kendi uzunluklarının bir fazlası kadar yer tutarlar bellek üzerinde. Sebebi ise her stringin bir bitiş karakteri bulunmasıdır. String bittikten sonra son karakter olarak \0 karakteri bulunur. Bu sayede bilgisayar stringlerin ne zaman bittiğini anlamış oluyor. C dilinde string tanımlamak için önceden kaç haneli olacağını belirlemek zorundayız. Ona göre bellekte bize yer ayrılır ve bellekte ayırdığımız yeri programın başka yerinde değiştiremeyiz.

Kod:
char isim[10] = "Emir";
Şeklinde bir tanımlama yaptırdığımız taktirde bunun uzunluğunu değiştiremeyiz ve uzunluğunu aştığımız taktirde programı compile ederken hata alırız. Bunun sebebi bize ayrılan alanın dışına çıkmamızdır.

C dilinde string temel veri tipleri arasında değildir. Aslında stringler char veri tipinden oluşan listelerdir. String kütüphanesini dahil ederek stringleri kullanabiliriz istersek fakat orada da her ne kadar string yazsak da aslında char dizilerinin maskelenmiş halini kullanıyoruz.

Pointer Kullanarak String Tanımlama

Pointerların belli değişkenlerin adresini tuttuğunu biliyoruz. Stringlerin de karakter dizileri olduğunu söylemiştik. Yani her dizinin başlangıç adresi bulunmaktadır ram üzerinde ve char dizileri olduğu için 1 byte 1 byte şeklinde artmaktadır. Pointer kullanarak kaç haneli string gireceğimiz belirtmeden string tanımlayabiliyoruz. Pointer kullanarak bir dizi tanımlamak istediğimiz taktirde aşağıdaki kullanımı yaparız.

Pointer kullanarak string tanımlayalım.

Kod:
char *isim = "Emir";
Yukarıda isim adında bir gösterici tanımladık. Bellek üzerinde rastgele bir yer tuttu bize(biz bu yere 0x123 diyelim) Belleğimizin 0x123. adresinde ‘E’ karakteri mevcut. 0x124. adres üzerinde ‘m’ karakteri , 0x125 üzerinde ‘i karakteri, 0x125 üzerinde ise ‘r’ karakteri mevcuttur. Stringler kendi uzunluğunun 1 fazlası kadar byte kullanır demiştik. Yani 0x126 üzerinde de ‘\0’ karakteri mevcut. Şimdi bunun nasıl olduğunu görelim.

Kod:
char *isim = "Emir";
if (*(isim+4)=='\0')
{
printf("!!!!");
}
Yukarıdaki kodda pointer kullanarak bellekte yer ayırdık. char veri tipi 1 byte bildiğimiz üzere, isim+4 dediğimizde isimden 4 bytelık alan ileri gitmiş oluruz yani Emir stringindeki r den sonraki alana gitmiş oluruz. Yani stringin bitiş belirteci. Gerçekte var mı yok mu bundan emin değiliz ve if conditionun içinde printf ile bir kanıt göstermesini istiyoruz. Bu kodu derleyicinize yazıp derlediğiniz zaman !!!! çıktısını alacaksınız.

Pointer ile Integer Listesi tanımlayalım.

Stringler bir liste demiştik, o halde listeleri de pointer yardımı ile tanımlayabiliriz. Pointer ile liste tanımlamak için int *dizi şeklinde bir kullanım yapıyoruz.

Kod:
int *dizi = {1,2,3};
int dizi2[3];
dizi2[0] = 1;
Yukarıdaki kodda iki kullanımı birden gösterdim. Şimdi integer listesi(array) oluşturarak char pointer ile integer listesinin elemanlarına müdahale etmeyi deneyelim.

Kod:
int dizi[4];
dizi[4] = 10;
char *p = dizi;
*(p+12) = 150;
Yukarıdaki kodda dizi tanımlıyoruz. Dizinin 5. elemanını pointer yardımı ile değiştiriyoruz. Evet güzel ama bu nasıl oluyor diyebilirsiniz. Integer veri tipi 4 byte bildiğimiz üzere, char değişken tipi de 1 byte. Integer dizi oluşturduğumuzda ram üzerinde 4 byte 4 byte şeklinde 6 tane kutu oluşturur. Yani toplamda 24 bytelık bir alan oluşur belleğimizde. Sonuncu elemana char pointerı ile erişmek için 4’er 4’er atlamamız gerekiyor çünkü char 1 byte. Integer bir değeri Char bir pointerla göstermeye çalışıyoruz. Yani 6 kutu değil de 24 kutu gibi düşünelim. 20. kutuya geldiğimizde Integer listeye göre son kutunun başına gelmiş oluyoruz yani dizi[5] elemanına denk geliyor ve pointer yardımıyla değiştiriyoruz. İnanmazsanız kodu çalıştırın :)
wol_error.gif
Bu resim yeniden boyutlandırıldı, tam halini görmek için tıklayınız.




Fotoğrafta görüldüğü üzere adres 4-4 şeklinde artıyor.

Char pointer tanımlarken dikkat etmemiz gereken nokta ise char pointerları 1 byte kadar veri depolayabilirler. 1 byte dediğimiz şey 8 bitin(binary digit) bir araya gelmesiyle oluşur. Yani toplamda 2^8 ihtimal mevcuttur. Bir byte’ın maksimum alacağı değer 255 olmalıdır(0 ı da dahil ediyoruz toplamda 256 değer) Biz dizi[5] elemanı pointer yardımıyla 255 den büyük bir değere eşitlemek istediğimiz taktirde mod 256’ya göre sonuç verecektir. Hep beraber görelim.

Kod:
int dizi[4];
dizi[3] = 10;
printf("%d\n", dizi[3]);
char *p = dizi;
*(p + 12) = 256;
printf("%i\n", dizi[3]);
short *p2 = dizi;
*(p2+6) = 25;
printf("%i", dizi[3]);
Dizinin 3. elemanını bastırmak istediğimiz taktirde 0 çıktısını veriyor. mod 256’ya göre 256 0’a eşittir çünkü :)

Ayrıca yukarıdaki kodda short kullanılmış, short 2 byte olduğu için + 12 değil de + 6 değerini kullandık. Short atlaya atlaya 6 adım git gibi düşünebiliriz. short * 6 yani 2*6 = 12 Aynı kapıya çıkıyor gördüğümüz gibi sonuç.



Struct (Yapı) Kavramı

Struct dediğimiz kavram veri yapılarıdır. C dilinde temel olarak int, float, double, char veri tipleri olduğunu söylemiştik. Bunlar tabiki bizlere yetmeyecektir. Kullanıcıdan tarih almak istediğimizde daha gelişmiş veri yapısı oluşturmamız gerekecektir. Diğer programlama dillerindeki Sınıf yapısına benzetebilirsiniz bu yapıları.

Kod:
struct cyber{
int a;
char b;
float c;
}
Yukarıda cyber adında bir yapı tanımladım. Bu yapının 3 tane elemanı var farklı veri tiplerinden. Bu yapıdan bir değişken oluşturalım.

Kod:
struct cyber a;
a.a = 5;
a.b = 'a';
a.c = 5.12;
Bu oluşturduğum yapıdan bir değişken oluşturup tüm elemanlarına eleman atadım.

Daha açıklayıcı olması açısından bir de tarih yapısı oluşturalım

Kod:
struct tarih{
int ay;
int gun;
int yil;
}
Tarih yapısını oluşturduk, atamalarımızı yapalım.

Kod:
struct tarih doğumGunum;
doğumGunum.ay = 10;
doğumGunum.gun = 10;
doğumGunum.yil = 1999;
Gördüğünüz gibi tarih biçiminde bir veri istersek bu veriyi böyle karışıklık olmadan tutabiliyoruz bellek üzerinde.

Typedef ile de tanımlama yapabiliriz, ki bu daha çok tercih edilir. Çünkü syntax olarak daha basittir.

Kod:
typedef unsigned long long ULL;

ULL a;
Gördüğünüz gibi Unsigned long long tipini typedef ile kendim tekrardan tanımladım. Kullanım kolaylığı sağladı, bunu structlar için de yapabiliyoruz.

Kod:
typedef struct a{
char a;
}
Kod:
a degisken_adi;


C Dilinde Fonksiyonlar

Fonksiyonlar o program üzerinde bir işlemi birden fazla yapacağımız taktirde, aynı algoritmayı tekrar tekrar yazmamız zor olacağı için ortaya çıkmış kavramlardır. Fonksiyon yapısı içine değer veya değerler alabilir, bu değerlere göre belirli algoritmaya göre işlem yaparak geriye bir değer döndürebilirler. Fonksiyon bildirimi yaparken hangi türden değer döndüreceğini en başta yazmamız gerekmektedir. Değer döndürmeyecekse(içerde çıktı yazacaksa veya swap yapacaksa) **** tipinde bir fonksiyon tanımlarız. **** kelimesinin türkçesi boş demektir zaten. Gelin bir fonksiyon tanımlayalım.

Kod:
**** myFunc()
{
printf("Ben bir fonksiyonum!!");
}
Burada bir fonksiyon tanımlaması yaptık. Fonksiyon tanımlaması fonksiyon çağrısının üstünde yapılırsa fonksiyon prototipini tanımlamamız gerekir.

Neden böyle bir şey yapmalıyız?
Çünkü derleyici kodları yukarıdan aşağıya doğru okuyacaktır kodları. Sırayla gidecektir. myFunc() satırına geldiğinde daha önce o tanımlamayı görmediği için "ben seni tanımıyorum" diyecektir. Bu yüzden prototip tanımlarız. Genelde önişlemci direktiflerinin hemen altında tanımlanır prototip. Örneğine bakalım.

Kod:
#include <stdio.h>
**** myFunc();
Derleyici bu prototipi görünce satır atlayacak ve fonksiyonu tanımladığımız satıra dönecektir. Tanımı yapıp tekrar olduğu yere dönecektir.

Fonksiyona parametre vermek ?

Fonksiyon belirli algoritmalarda işlem yapabilmek için bizden belli sayıları isteyebilir. Mesela toplama fonksiyonu yazıyoruz diyelim. a+b yi toplayacak fakat a ve b değerlerimiz neler, bu değerler spesifik olmak zorundadır dinamik bir yapı oluşturabilmek için. 5 ile 3 ü toplamak istiyoruz. 5 değerimiz a yerine 3 değerimiz b yerine geçecektir.

Kod:
int topla(int a, int b)
{
return a+b;
}
Şimdi yukarıda yazdığım kodda 2 farklı sayıyı topluyoruz. Gelelim bu kavramı açıklamaya, burada bir return sözcüğü görüyoruz.

Ne işe yarıyor ve neden kullanıyoruz ?

Biz fonksiyonu çağırdığımız zaman ram üzerinde geçici değişkenler oluşturuyoruz. (int a, int b) dediğimiz kısımda a ve b değişkenleri fonksiyonun çalışma zamanında oluşuyor. Fonksiyon bitince değişkenler siliniyor. Program o değişkeni tanımıyor (silinmemesi için değişkenin başına static diyerek static değişken oluşturabiliriz. Bu ayrı bir konu ) Fonksiyondan sonraki satırda printf ile a yı yazdırmaya kalkarsak derleyici ben a yı tanımıyorum diyerek compile time error verecektir. Bunun için return kavramı kullanılır, geriye bir şey döndür demektir.

Yani kısaca sen benim oluşturduğum değişkenleri siliyorsun kardeşim, benim elimde ne kaldı 5 ile 3 ü topladım bu değeri nasıl kullanacağım sorununa çözüm olarak return değerimiz fonksiyonun çağrıldığı adrese o değerini döner. Örnek üzerinde anlatalım. 5 ile 3 ü topladık a ve b değişkenleri silindi artık yoklar. topla(3, 5) şeklinde çağırdığımız fonksiyonu bir değişkene atayabiliriz veya direk printf yardımı ile yazdırabiliriz.

Kod:
int main()
{
int c;
c = topla(a, b);

}

int topla(int a, int b)
{
return a+b;
}
Yukarıdaki kodda c değişkenimin içindeki değer fonksiyondan geri dönen değer olacaktır. İşte bu sebepten dolayı return kullanırız.


Call By Value (Değer ile Çağırma)

Call by value yani parametre olarak değer aldığımız fonksiyonlarda değişken oluşturulur, fonksiyon biter ve değişken silinir. Bu durumda x değerim 4 olsun y değerim 7 olsun. Ben bu değişkenleri birbiri ile değiştirmek istersem ( x=7 y=4 ) call by value yöntemi ile çağırırsam hatalı olur.

Neden hatalı olur ?

Çünkü fonksiyona gönderdiğim parametreler içerde kendi ayrı değişkenleri oluşturur, kendi içerinde değiştirir ve bu değişkenleri bellekten siler. Geriye ne kaldı elde. Hiçbir şey....

Örnek Fonksiyon

Kod:
int main()
{
int x = 4, y=7;
swap(x,y);

}
int swap(int x, int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
}
Yukarıda yazdığım kod değerleri değiştirmeyecektir. Çünkü fonksiyon parametresinde yazdığım (int x, int y) kısmı sadece bir gösterimden ibarettir. Aynı değişken isimlerini yazsak bile ayrı bir değişken oluşturup sonra hafızadan salacaktır bu değişkenleri. Bu yüzden call by reference dediğimiz yönteme başvurmalıyız bu tip problemlerimiz için.

Call By Reference (Referans ile Çağırma)

Referans ile çağırma diyince kelime olarak pek bir anlam ifade etmiyor olabilir. Pointer kullanarak çağırma dersek aklınızda daha net canlacaktır. Biz fonksiyona parametre olarak adres verirsek fonksiyon kendi içerisinde adres oluşturamayacağı için o adrese gider. O adres üzerinde işlem yapar. O adres ise bizim x ve y değerlerimiz, kısaca swap işlemini başarı ile yapmış olur.

Kod:
int main()
{
int x = 4, y=7;
swap(&x, &y);

}
int swap(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
Adresle işlem yaptığımız için ampersand işaretini (&) koymayı unutmamamız gerekmektedir. Dizilerde işlem yaparken call by reference kullanmaktayız. Çünkü diziler bir adres belirtir pointer oldukları için int dizi[10] diye tanımlayalım. printf ile dizi yazdırmayı denersek bu bize dizinin ilk indeksinin adresini dönmektedir. Pointerlarda yapılabilen bütün işlemler dizilerde de yapılabilir.



Recursive (Özyinelemeli) Fonksiyon

Bu tip fonksiyonlar kendi kendini çağıran fonksiyonlardır. Bu tip fonksiyonları kullanmak çok risklidir, eğer fonksiyon döngüsünden çıkmayı başaramazsak stack alanı dolar ve stackoverflow hatası(segmentation fault:11) alabiliriz. Dikkatli kullanılması gerekmektedir.

Kod:
#include <stdio.h>
int asalMi(int sayi, int i);
int main(int argc, char const *argv[])
{
int n, kontrol;
printf("sayi giriniz\n");
scanf("%d", &n);
for (int i = 2; i < n; ++i)
{
kontrol = asalMi(i, i/2);
if (kontrol == 1)
{
printf("%d\n", i);
}
}
return 0;
}

int asalMi(int sayi, int i)
{
if (i == 1)
{
return 1;
}
else if (sayi % i == 0)
{
return 0;
}
else
{
return asalMi(sayi, i-1);
}
}
Yukarıda yazdığım kodda asal sayı bulmamızı sağlayan rekürsif fonksiyon örneğini yaptım. Dikkat ederseniz fonksiyonu yeniden çağırma işlemim bir koşul bloğunun altında. Böyle bir kullanım yapmak fonksiyonun sonsuza dek(stack alanı dolana dek) kendini çağırmasını engelleyecektir.

Bu konu çok karıştırılan örnek olduğu için bir örnek daha paylaşmak istiyorum.

Kod:
#include <stdio.h>
#include <stdlib.h>
int fibonacci(int n);
int main(int argc, char const *argv[])
{
system("clear");
int x;
printf("kaç basamaklı fibonacci olsun : ");
scanf("%d", &x);
printf("\nFibonacci dizisi :\n\n");
for (int i = 0; i < x; ++i)
{
printf("%4d", fibonacci(i));
}
printf("\n\n\n");


return 0;
}

int fibonacci(int n)
{
if (n==0)
{
return 0;
}
else if(n==1)
{
return 1;
}
else
{
return (fibonacci(n-2) + fibonacci(n-1));
}
}
Bu kod da kullanıcının girdiği basamakta Fibonacci dizisi basmaya yarar, fark ettiyseniz yine fonksiyonu yeniden çağırma noktamız bir koşulun altında.










Umarım İşinize Yarar





 
Son düzenleme:

SiberOptik

Asistan
Cezalı
Katılım
4 Mayıs 2020
Mesajlar
365
Reaksiyon puanı
359
Puanları
63
Kalite ve emek kokusu aliyorum gayet güzel bu yaşta yapilara özyinilemeli fonksiyonlara deginmen memory icersini anlatmak çok güzel. Bu bilgilerini üniversite ile taçlandir.Programala işinde başarılar.
 

Nixc0r

Forumgezer
Emektar
Katılım
24 Şubat 2020
Mesajlar
5,977
Çözümler
12
Reaksiyon puanı
15,589
Puanları
113
Yaş
17
şuan ihtiyaç duyacağımı sanmam ama yararlı bir konu bu mesajı buraya bırakıyımda kolay buluyum konuyu
 

GhosT57

Müdavim
Katılım
27 Temmuz 2020
Mesajlar
4,009
Reaksiyon puanı
4,914
Puanları
113
Yaş
23
C den sonra D olur mu ki?
 

Apple Lover

Müdavim
Katılım
20 Nisan 2021
Mesajlar
1,030
Reaksiyon puanı
708
Puanları
113
Yaş
24
zaten kendime web sitesi oluşturmak istiyordum yararlı oldu
 

_emre_emre_

Müdavim
Katılım
6 Nisan 2020
Mesajlar
2,442
Çözümler
1
Reaksiyon puanı
2,265
Puanları
113
Yaş
124
Tavsiye : Resimleri Foruma yada İmgur'a upload et; resimler gidebilir.
 

MG4446

Müdavim
Katılım
18 Nisan 2020
Mesajlar
1,966
Çözümler
1
Reaksiyon puanı
1,311
Puanları
113
Yaş
20
bende fotolar gözükmüyor çözümü var mı?
 
Üst