13279 lines
		
	
	
		
			428 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			13279 lines
		
	
	
		
			428 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
C Programlama Dili
 | 
						||
 | 
						||
Kurs Notları
 | 
						||
 | 
						||
Kaan ASLAN
 | 
						||
 | 
						||
C ve Sistem Programcıları Derneği
 | 
						||
 | 
						||
Güncelleme Tarihi: 12/07/2003
 | 
						||
 | 
						||
Bu kurs notları Kaan ASLAN tarafından yazılmıştır. Kaynak belirtilmek koşuluyla her türlü alıntı yapılabilir.
 | 
						||
 | 
						||
---------------------------------------------------------------------------------------------------------------------------------------------------
 | 
						||
 | 
						||
C Programlama Dilinin Tarihsel Gelişimi
 | 
						||
 | 
						||
C Programalama Dili 1970-1971 yıllarında AT&T Bell Lab.'ta UNIX İşletimn Sisteminin geliştirilmesi sürecinde
 | 
						||
bir yan ürün olarak tasarlandı. AT&T o zamanlar Multics isimli bir işletim sistemi projesinde çalışıyordu. AT&T
 | 
						||
bu projeden çakildi ve kendi işletim sistemlerini yazma yoluna saptı. Bu işletim sistemine Mulctics'ten kelime
 | 
						||
oyunu yapılarak UNIX ismi verildi. UNIX DEC firmaısnının PDP-7 makinalarında ilk kez yazılmıştır.
 | 
						||
 | 
						||
O zamanlar işletimsistemleri sembolik Makine dilinde yazılıyordu. Ken Thompson (grup lideri) işleri
 | 
						||
kolaylaştırmak için B isimli bir programlama dilciği tasarladı. Sonra Dennis Ritchie bunu geliştirrek C haline
 | 
						||
getirdi. C Programlama dili UNIX proje ekibindeki Dennis Ritchie tarafından geliştirilmiştir.
 | 
						||
UNIX İşletim Sistemi 1973 yılında sil baştan yeniden C ile yeniden yazılmıştır. O zamana kadar bir işletimsistemi
 | 
						||
yüksek seviyeli bir dilde yazılmış değildi. Bunedenle UNIX'in C'de yazılması bir devrim niteliğindir. UNIX
 | 
						||
sayesinde C Programalam Dili 1970'lerde tanınmaya başladı. 1980-81 yıllarında IBM ilk kişisel bilgisayarı çıkarttı.
 | 
						||
 | 
						||
C Programlama Dili kişisel bilgisayarlarda kullanılan en yaygın dil oldu.
 | 
						||
 | 
						||
1978 yılında Dennis Ritchie ve Brian Kernigan tarafından “The C Programming Language” kitabı yazıldı. Bu kitap
 | 
						||
üm zamanların en önemli bilgisayar kitaplarından biridir. (Hatta HelloWorld programı ilk kez burada yazılmıştır.)
 | 
						||
C Programlama dili ilk kez 1989 yılında ANSI tarafından standardize edildi. ISO 1990 yılında ANSı stanadrşlarını
 | 
						||
alarak (bölüm numaralandırmasını değiştirerek) ISO standardı olarak onayladı. Bu standardın resmi ismi ISO/IEC
 | 
						||
9899: 1990'dı. Bu standartlar kısaca C90 ismiyle bilinmektedir. C 1999 yılında bazı özelliker eklenerek yeniden
 | 
						||
standardize edildi. Bu standartlara da ISO/IEC 9899: 1999 denilmektedir ve kısaca C99 olarak bilinir. C99
 | 
						||
derleyici yazan firmalar ve kurumlar tarafından çok fazla destek görmedi. C'ye nihayet 2011 yılında bazı özellikler
 | 
						||
eklenmiştir. Bu son standartlar ISO/IEC 9899:2011 kod adıyla yayınlanmıştır. Buna da kısaca C11 denilmektedir.
 | 
						||
 | 
						||
 | 
						||
 | 
						||
Bu kursta klasik C olan C90 ele alınmaktadır. C99 ve C11'deki yeni özellikler "Sistem Programlama Ve C İleri C
 | 
						||
Uygulamaları" kursunda ele alınacaktır. C denildiğinde default olarak akla C90 gelmelidir.
 | 
						||
Anahtar Notlar: Bu kurs C Programlama Dilini her yönüyle anlatan bir kurstur. Bu kurstan sonra aşağıdaki kursalara katıllınabilir:
 | 
						||
- Sistem Programlama ve İleri C Uygulamaları (I) (kesinlikle tavsiye edilir)
 | 
						||
- C++ (kesinlikle tavsiye edilir)
 | 
						||
- Qt ile C++'ta Programlama (pencereli Windows ve Linux programları yazmak için)
 | 
						||
- UNIX/Linux Sistem Programalama
 | 
						||
- Windows Sistem Programalama
 | 
						||
- Sistem Programlama ve İleri C Uygulamaları (II)
 | 
						||
- PIC mikrodenetleyicileri ile programlama (biraz elektonik bilgisi tabanı gerekir).
 | 
						||
 | 
						||
1
 | 
						||
 | 
						||
- PIC mikrodenetleyicileri ile programlama (biraz elektonik bilgisi tabanı gerekir).
 | 
						||
 | 
						||
C Programlama Dili şu an itibari ile Tiobe Index'e göre dünyanın en fazla kullanılan programlama dilidir.
 | 
						||
Programalama Dillerinin Sınıflandırılması
 | 
						||
Programlama dilleri teorisyenler tarafından genellikle üç biçimde sınıflandırılmaktadır:
 | 
						||
1) Seviyelerine Göre Sınıflandırma
 | 
						||
2) Kullanım Alanlarına Göre Sınıflandırma
 | 
						||
3) Programlama Modeline Göre Sınıflandırma
 | 
						||
Seviyelerine Göre Sınıflandırma
 | 
						||
Seviye (level) bir programlama dilinin insan algısına yakınlığının bir ölçüsüdür. Yüksek seviyeli diller insana
 | 
						||
yakın yani kolay dillerdir. Alçak seviyeli diller makinaya yakın fakat zor öğrenilen dillerdir. Olabilecek en aşağı
 | 
						||
seviyeli dil saf makina dilidir (machine language). Bu dil yalnızca 1'lerden ve 0'lardan oluşur. Görsel diller,
 | 
						||
veritabanı dilleri vs. çok yüksek seviyeli dilledir. Java, C#, Pascal, Basic, PHP vs. diller yüksek seviyeli dillerdir. C
 | 
						||
Programlama Dili orta seviyeli bir dildir. Yani makinaya diğer dillerden daha yakındır.
 | 
						||
Kullanım Alanlarına Göre Sınıflandırma
 | 
						||
Programlama dilleri bazı konularda bazı iddialara sahiptir. Bzı dillerde web işlemleri yapmak daha kolayken, bazı
 | 
						||
dillerde veritabanı işlemleri yapmak daha kolaydır. İşte bir dilin hangi tür uygulamalarda kullanılabileceğine
 | 
						||
yönelik sınıflandırnadır bu.
 | 
						||
Bilimsel ve Mühendislik Diller: Bu tür diller bilimsel ve mühendislik problemlerin çözülmesi için birincil olarak
 | 
						||
tercih edilen dillerdir. Örneğin C, C++, Fortran, Pascal, Java, C#, Matlab vs.
 | 
						||
Veritabanı Dilleri: Bunlar veritabanlarının oluşturulması ve idaresinde tercih edilen dillerdir. SQL, Foxpro, Dbase
 | 
						||
vs. gibi...
 | 
						||
Web Dilleri: İnteraktif web sayfalarını oluşturabilmek için tercih edilen dillerdir. Örneğin Java, C#, PHP, Python
 | 
						||
gibi...
 | 
						||
Yapay Zeka Dilleri: İnsan düşüncesini eşitli boyutlarda taklit eden programlara yapay zeka programları denir. Bu
 | 
						||
programların yazılması için tercih edilen dillere yapay zeka dilleri denir. Örneğin C, C++, Java, C#, Lisp, Prolog vs.
 | 
						||
gibi
 | 
						||
Görsel ve Animasyonm Dilleri: Animasyon programları için kullanılan yüksek seviyeli script dilleridir. Action
 | 
						||
Script gibi...
 | 
						||
Sistem Programlama Dilleri: Sistem Programlama yapmak için kullanılan dilledir. Tipik olarak C, C++ ve
 | 
						||
sembolikmakina dilleri sistem programlama dilleridir.
 | 
						||
Anahtar Notlar: Sistem programları bilgisayar donanımı ile arayüz oluşturan, uygulma programlarını çeşitli
 | 
						||
bakımlardan hizmet veren, aşağı seviyeli taban programlardır. Örneğin: İşletim sistemleri, editörler, derleyiciler ve
 | 
						||
bağlayıcılar, haberleşme programları vs.
 | 
						||
Programlama Modeline Göre Sınıflandırma
 | 
						||
Programlama modeli o dilde programı hangi teknikle yazdığımıza ilişkindir. Bazı dillerin bazı tekniklerin
 | 
						||
kullanılmasını açıkça desteklemektedir. Temel olarak (fakat daha fazla olabilir) programlama dillerini
 | 
						||
2
 | 
						||
 | 
						||
prograramlama modellerine (programming paradigm) 4 biimde sınıflandırabiliriz:
 | 
						||
1) Prosedürel Programlama Dilleri: Bu diller prosedürel programlama modelini destekleyen dillrdir. Örneğin C
 | 
						||
prosedürel bir programlama dilidir. Prosedürel teknikte program altprogramların (prosedürlerin) birbirlerini
 | 
						||
çağırmasıyla yazılır. Bunun dışında klasik Pascal, Basic, Fortran vs. gibi diller prosedürel dillerdir.
 | 
						||
2) Nesne Yönelimli Programlama Dilleri: Bu diller nesne yönelimli programlama modelini (object oriented
 | 
						||
programming) uygulayabilecek yetenkte dillerdir. Bu modelde program sınıflar oluşturarak yazılır. C++, C'nin
 | 
						||
nesne yönelimli versiyonudur. Pek prosedürel dilin daha sonra nesne yönelimli versiyonları oluşturulmuştur.
 | 
						||
Örneğin Object Pascal, VB.NET. C#, Java temelde nesne yönelimli dillerdir.
 | 
						||
3) Fonksiyonel Programlama Dilleri: Bu tür diller fonksiyonel programlama modelini (functional programming
 | 
						||
paradigm) açıkça destekleyen dillerdir. Örneğin Haskell, F#, R, Scheme, Erlang. Fonksiyonel teknikte programlar
 | 
						||
sanki formül yazarmış gibi yazılırlar.
 | 
						||
4) Çok Modelli Programlama Dilleri: Bu dillerde birden fazla programlama modeli kullanılabilmektedir.
 | 
						||
Örneğin C++'ta hem prosedürel hem de nesne yönelimli teknik kullanılabilir. C++ çok modelli (multi paradigm)
 | 
						||
bir programlama dilidir. Yeni özelliklerle C# ve Java'da artık çok modelli olmuştur.
 | 
						||
Temel Kavramlar
 | 
						||
İşletim Sistemi (Operating System): İşletim sistemi makinanın donanımını yöneten temel bir sistem yazılımıdır.
 | 
						||
Bir kaynak yöneticisi gibi çalışır. Yönettiği tipik donanımsal kaynaklar şunlardır: İşlemci, RAM, Disk, Yazıcı,
 | 
						||
Network kartı ve haberleşme sistemi. Eğer işletim sistemi olmasaydı bilgisayar hiç kolay kullanılamazdı. İşletim
 | 
						||
sistemi temel pek çok hizmeti bizim için sağlamaktadır. Kullanıcı ile makine donanımı arasında köprü kurmaktadır.
 | 
						||
İşletim sistemi insan vücudu gibi çeşitli alt sistemlere sahiptir. Bu alt sistemler karşılıklı etkileşim halindedir.
 | 
						||
İşletim sistemlerini kabaca çekirdek (kernel) ve kabuk (shell) olmak üzere iki katmana ayırabiliriz. Çekirdek
 | 
						||
makinanın donanımını yöneten motor kısımdır. Kabuk kullanıcı ile ilişki kurankısımdır. (Örneğin Windows'un
 | 
						||
masaüstü kabuk durumundadır). Tipik olarak (fakat her zaman değil) biz işletim sisteminin üzerinde çalışırız.
 | 
						||
En çok kullanılan Masaüstü işletim sistemleri Windows, Linux (UNIX türevi sistemler), Mac OS X sistemleridir.
 | 
						||
En çok kullanılan mobil işleim sistemleri de Android, IOS, Windows Mobile sistemleridir.
 | 
						||
Çevirici Programlar, Derleyiciler ve Yorumlayıcılar:
 | 
						||
Bir programlama dilinde yazılmış olan programı eşdeğer olarak başka bir dile dönüştüren programlara çevirici
 | 
						||
programlar (translators) denilmektedir. Burada asıl programın diline kaynak dil (source language), dönüştürülecek
 | 
						||
dile hedef dil (target language) denilmektedir. Bir çevirici programda hedef dil alçak seviyeli bir dilse (sembolik
 | 
						||
Makine dili, ara kod ya da saf makine dili) böyle çevirici programlara derleyici (compiler) denir.
 | 
						||
Yorumlayıcılar (interpreters) kaynak kodu doğrudan okuyup o anda çalıştırırlar. Bir hedef kod üretmezler.
 | 
						||
Dolayısıyla bunlar çevirici program durumunda değillerdir. Bazı diller için yalnızca derleyiciler kullanılır (Örneğin
 | 
						||
C gibi). Bazıları için yalnızca yorumlayıcılar kullanılır (örneğin PHP, Perl gibi), Bazı dillerin derleyicileri de
 | 
						||
yorumlayızıları da vardır (örneğin Basic gibi). Genel olarak yorumlayıcı yazmak derleyici yazmaktan daha
 | 
						||
kolaydır. Derleyiciler kodun daha hızlı çalışmasına yol açarlar. Yorumlayıcılarda kaynak kod gizlenemez.
 | 
						||
Açık Kaynak Kod ve Özgür Yazılım
 | 
						||
1980'li yılların ortalarında Richard Stallman, FSF (Free Software Foundation) isimli bir dernek kurdu ve özgür
 | 
						||
yazılım (free software) kavramını ortaya attı. Özgür yazılım kabaca, yazılan programların kaynak kodlarını
 | 
						||
sahiplenmemek, başkalarının da onu devam ettirmesini sağlamak anlamına gelir. Birisi özgür yazılım lisansına
 | 
						||
sahip bir ürünü değiştirdiği ya da özelleştirdiği zaman onu o da açmak zorundadır. Fakat alıcı varsa bunlar parayla
 | 
						||
da satılabilir. Açık kaynak kod (open source) aşağı yukarı (fakat tamamen değil) özgür yazılımla aynı anlama
 | 
						||
3
 | 
						||
 | 
						||
gelmektedir.
 | 
						||
Her yazılımın bir lisansı vardır. Fakat çeşitli yazılım akımları için çeşitli lisanslar kalıp olarak oluşturulmuştur.
 | 
						||
Örneğin Özgür yazılımın ana lisansı GPL(Gnu Public Licence)'dir. Fakat GPL'ye benzer pek çok lisans vardır
 | 
						||
(BSD, Apachie, MIT, ...).
 | 
						||
Richard Stallman FSF'yi kurduğunda GNU isimli bir proje başlattı. Bu projenin amacı bedava, açık kaynak kodlu
 | 
						||
bir işletim sistemi ve geliştirme araçları yazmaktı. Gerçekten de gcc derleyicisi, ld bağlayıcısı ve diğer pek çok
 | 
						||
utility program bu proje kapsamında yazıldı.
 | 
						||
Linux işletim sistemi GNU malzemeleri kullanılşarak Linus Torwalds'ın önderliğinde geniş bir ekip tarafından
 | 
						||
geliştirilmştir ve sürdürülmektedir. Linux aslında bir çekirdek geliştirme projesidir. Bugün Linux diye dağıtılan
 | 
						||
sistemlerde binlerce açık kaynak kodlu yazılım bulunmaktadır. Yani Linux adeta GNU projesinin işletim sistemi
 | 
						||
gibi olmuştur (maalesef GNU projesinin öngörülen işletim sistemi bir türlü son haline getirilememiştir.) Bu
 | 
						||
nedenle bazı kesimler bu işletim sisteminin isminin Linux değil GNU/Linux olması gerektiğini söylemektedir.
 | 
						||
Çok kullanılan C Derleyicileri
 | 
						||
Pek çok derleyicisi olsa da bugün için en önemli iki derleyici Microsoft'un C derleyicileri ve gcc derleyicileridir.
 | 
						||
Ekiden Borland derleyicileri de çok kullanılıyordu. Intel firmasının da ayrı bir C derleyicisi vardır. UNIX/Linux
 | 
						||
sistemlerinde gcc derleyicileri çok yoğun kullanılırken, Windows sistemlerinde Microsoft C derleyicileri yoğun
 | 
						||
kullanılmaktadır. Gcc'nin Windows portuna MinGW denilmektedir.
 | 
						||
IDE Kavramı
 | 
						||
Normal olarak derleyiciler komut satırından çalıştırılan programlardır. Yani biz programı editör denilen bir
 | 
						||
ortamda yazarız. Bunu save ederiz. Sonra da komut satırından derleriz. Bu tarz çalışma biraz zahmetli olduğu için
 | 
						||
IDE (Integrated Development environment) denilen özel yazılımlardan faydalanılır. IDE'lerin editörleri vardır,
 | 
						||
menüleri vardır ve birtakım araçları vardır. IDE derleyici değildir. Derleyicinin dışındaki yazılım geliştirmeyi
 | 
						||
kolaylaştıran araçlar toplamıdır.
 | 
						||
Bugün pek çok IDE seçenek olarak bulunmaktdır. Microsoft'un ünlü IDE'sinin ismi “Visual Studio”dur.
 | 
						||
Kursumuzda ağırlıklı bu IDE kullanılacaktır. Eclipse, Netbeans, MonoDevelop gibi open source IDE'ler de vardır.
 | 
						||
Apple'ın da X-Code denilen kendi bir IDE'si vardır.
 | 
						||
IDE derleyici değildir. IDE'ye “derle” denildiği zaman IDE gerçek derleyiciyi çağırır. Biz bilgisayarımıza bir IDE
 | 
						||
yüklediğimizde yalnızca IDE değil, onun kullanacağı derleyici de yüklenecektir. Tabi IDE olmadan yalnızca
 | 
						||
drleyiciyi de yükleyebiliriz.
 | 
						||
Visual Studio paralı bir IDE'dir. Ancak Microsoft “Express Edition” ismiyle bu IDE'nin bedava bir versiyonunu da
 | 
						||
oluşturmuştur. Bu kurs için ve genel olarak C programlama için Express Edition yeterlidir.
 | 
						||
Visual Studio Windows sistemlerinde kullanılabilen bir IDE'dir. Oysa Eclipse ve Netbeans IDE'leri corss
 | 
						||
platform'dur. Linux sistemlerinde IDE olarak QtCreator, Eclipse, Netbeans ya da MonoDevelop tercih edilebilir.
 | 
						||
Şu anda Visual Studio IDE'sisn son versiyonu “Visual Studio 2013”tür.
 | 
						||
Sayı Sistemleri
 | 
						||
Biz günlük hayatımızda 10'luk sistemi kullanmaktayız. 10'luk sistemde sayıları ifade etmek için 10 sembol
 | 
						||
kullanılır:
 | 
						||
0 1 2 3 4 5 6 7 8 9 (toplam 10 tane)
 | 
						||
4
 | 
						||
 | 
						||
Aslında 10'luk sistemdeki sayıların her bir basamağı 10'un kuvvetlerini temsil eder. Örneğin:
 | 
						||
123 = 3 * 100 + 2 * 101 + 1 * 102
 | 
						||
10'luk sistemde her bir basamağ “digit” denilmektedir.
 | 
						||
Makina ikilik sistemi kullanır. Çünkü sayıların elektriksel olarak ifade edilmeleri ikilik sistemde çok kolaydır ve
 | 
						||
standartlaşma böyle oluşmuştur. Elektroniğin bu alanına dijital elektronik denilmektedir.
 | 
						||
İkilik sistemde toplam 2 sembol vardır:
 | 
						||
0 ve 1 (toplam 2 tane)
 | 
						||
İkilik sistemdeki bir sayı ikinin kuvvetleriyle çarpılarak elde edilir. Örneğin:
 | 
						||
1010 = 0 * 20 + 1 * 21 + 0 * 22 + 1 * 23
 | 
						||
İkilik sistemdeki her bir basmağa bit (binary digit'ten kısaltma) denilmektedir. Örneğin:
 | 
						||
10100010 sayısı 8 bitlik bir sayıdır.
 | 
						||
Bilgisayarımızın belleği de yalnızca bitleri tutar. Yani bellkekteki herşey bit'lrden oluşmaktadır. Bit en düşük
 | 
						||
bellek birimidir.
 | 
						||
8 bite byte denilmektedir. Kilo 102luk sistemde 1000 katı anlamına gelir (örneğin 1 Kilometre 1000 metredir.)
 | 
						||
Fakat bilgisayar bilimlerinde kilo olarak 1000 anlamlı gözükmemektedir. Çünkü 1000 sayısı 10'un kuvvetidir. İşte
 | 
						||
bilgisayar bilimlerinde kilo 1024 katı anlamına gelir (1024, 2'nin 10'uncu kuvvetidir ve 1000'e de benzemektedir.).
 | 
						||
Mega da kilonun 1024 katıdır. Bu durumda 1 MB = 1024 * 1024 byte'tır. Giga da mega'nın 1024 katıdır.
 | 
						||
Bilgisayarın belleğindeki her şey 2'lik sistemdeki sayılardan oluşur: Komutlar, yazılar, sayılar hep aslında ikilik
 | 
						||
sistemde bulunurlar.
 | 
						||
Tamsayıların 2'lik Sistemde İfade Edilmesi
 | 
						||
Tamsayılar için iki sistem söz konusudur: İşaretsiz sistem ve işaretli sistem. İşaretsiz (unsigned) sistemde tamsayı
 | 
						||
ya pozitif ya da sıfır olabilir. Fakat negatif yorumlanamaz. İşaretli (signed) sistemde tamsayı negatif de olabilir.
 | 
						||
Örneğin aşağıdaki 8 bit sayı işaretsiz tamsayı olarak yorumlanırsa kaçtır?
 | 
						||
1000 1010 = 138
 | 
						||
8 bitle işaretsiz tamsayı sisteminde ifade edilecek en küçük sayı 0, en büyük sayı 255'tir.
 | 
						||
0000 0000
 | 
						||
.........
 | 
						||
1111 1111
 | 
						||
 | 
						||
--> 0
 | 
						||
--> 255
 | 
						||
 | 
						||
2 byte (16 bit) ile işaretsiz sistemde ifade edilebilecek en küçük sayı 0, en büyük sayı 65535'tir.
 | 
						||
0000 0000 0000 0000 --> 0
 | 
						||
.... .... .... ....
 | 
						||
1111 1111 1111 1111 --> 65535
 | 
						||
5
 | 
						||
 | 
						||
İşaretli tamsayıları ifade etmek için tarih boyunca 3 sistem denenmiştir. Bugün ikiye tümleme sistemi denilen
 | 
						||
sistem en iyi sistem olarak kullanılmaktadır. Bu sistemin özellikleri şöyledir:
 | 
						||
- Sayının en solundaki bit işaret bitidir. Bu bit 1 ise sayı negatif, 0 ise pozitiftir.
 | 
						||
- Negatif ve pozitif sayılar birbirlerinin ikiye tümleyenidirler.
 | 
						||
- Bu sistemde bir tane sıfır vardır.
 | 
						||
Bir sayının ikiye tüöleyene sayının 1'e tümleyenine 1 eklenerek elde edilir (Syının 1'e tümleyeni 1'lerin 0, 0'ların 1
 | 
						||
yapılmasıyla elde edilir.) Örneğin:
 | 
						||
Sayı: 1011 1010
 | 
						||
1'e tümleyeni: 0100 0101
 | 
						||
2'ye tümleyeni: 0100 0101 + 1 = 0100 0110
 | 
						||
Bu işlemin klay bir yolu vardır: Sayının sağında sola doğru ilk 1 görene kadar (ilk 1 dahil olmak üzere) aynısı
 | 
						||
yazılarak ilerlenir. Sonra 0'lar 1, 1'ler 0 yapılarak devam edilir. Örneğin:
 | 
						||
Sayı: 1010 1100
 | 
						||
2'ye tümleyeni: 0101 0100
 | 
						||
Örneğin:
 | 
						||
Sayı: 1111 1111
 | 
						||
2'ye tümleyeni: 0000 0001
 | 
						||
Sayının 2'ye tümleyeninin 2'ye tümleyeni sayının kendisine eşittir. Örneğin:
 | 
						||
Sayı: 1111 1111
 | 
						||
2'ye tümleyni: 0000 0001
 | 
						||
2'ye tümleynin 2'ye tümleyeni: 1111 1111
 | 
						||
Bu sistemde negatif ve pozitif sayılar birbirlerinin 2'ye tümleyenidirler. Örneğin:
 | 
						||
0000 1010 --> +10
 | 
						||
1111 0110 --> -10
 | 
						||
Bu sistemde negatif bir sayı yazmak istersek önce onun pozitiflisini yazıp sonra 2'ye tümleyenini alarak yazabiliriz.
 | 
						||
(Tabi doğrudan yazabiliyorsak ne mutlu bize.) Örneğin, bu sistemde -1 yazmak isteyelim:
 | 
						||
0000 0001 --> +1
 | 
						||
1111 1111 --> -1
 | 
						||
Bu sistemde birisi bize “bu sayı kaç” diye sorduğunda şöyle yanıtı bulabiliriz: Önce sayının işaret bitine bakıp
 | 
						||
onun pozitif mi yoksa negatif mi olduğunu belirleriz. Sayı pozitif ise doğrudan hesaplarız. Negatif ise, sayının
 | 
						||
ikiye tümleyeni alırız. Ondan faydalanarak sayıyı belirleriz. Örneğin, aşağıdaki sayı işaretli tamsayı sisteminde
 | 
						||
kaçtır?
 | 
						||
1111 0101
 | 
						||
Sayı negatif, 2'ye tümleyenini alaım:
 | 
						||
0000 1011
 | 
						||
6
 | 
						||
 | 
						||
bu +11 olduğuna göre demek ki o sayı -11'dir.
 | 
						||
Bu sistemde 1 tane sıfır vardır. Bu sistemde 2 tuhaf sayı vardır ki bunların 2'ye tümleyenleri yoktur (yani
 | 
						||
kendisiyle aynıdır). Bunlar tüm bitleri 0 olan sayı ve ilk biti 1, diğer bitleri 0 olan sayıdır:
 | 
						||
0000 0000 (2'ye tümleyeni yok)
 | 
						||
1000 0000 (2'ye tümleyeni yok)
 | 
						||
İşte bu ilk sayı 0'dır, diğeri ise -128'dir. Yani 8 bit içerisinde işaretli tamsayı sisteminde yazılabilecek en büyük
 | 
						||
pozitif ve en küçük negatif sayılar şöyledir:
 | 
						||
0111 1111 --> +127
 | 
						||
1000 0000 --> -128
 | 
						||
Peki 2 byte (16 bit) içerisinde yazılabilecek en büyük ve en küçük işaretli sayılar nelerdir?
 | 
						||
0111 1111 1111 1111 --> +32767
 | 
						||
1000 0000 0000 0000 --> -32768
 | 
						||
Peki 4 byte (32 bit) içerisinde yazılabilecek en büyük ve en küçük işaretli sayılar nelerdir?
 | 
						||
0111 1111 1111 1111 1111 1111 1111 1111 --> +2147483647
 | 
						||
1000 0000 0000 0000 0000 0000 0000 0000 --> -2147483648
 | 
						||
Soru: Aşağıdaki 1 byte'lık sayı işaretli ve işaretsiz tamsayı sistemlerinde kaçtır?
 | 
						||
1111 1111
 | 
						||
Cevap: İşaretsiz sistemde +255, işaretli sistemde -1'dir.
 | 
						||
Peki bu sistemde örneğin 1 byte içerisindeki en büyük pozitif sayıya 1 toplarsak ne olur? Bir kere zaten sınırı aşmış
 | 
						||
oluruz. Peki aşınca ne olur?
 | 
						||
0111 1111 --> +127
 | 
						||
0000 0001
 | 
						||
--------------1000 0000 --> -128
 | 
						||
Peki 2 toplasaydık ne olurdu? Yanıt: 1000 0001 = -127
 | 
						||
Demek ki bu sistemde pozitif sınırı yanlışlıkla aşarsa kendimi negatif yüksek sayılarda buluruz. Buna
 | 
						||
programlamada üstten taşma (overflow) denilmektedir. Taşma alttan da (underflow) olabilir. Örneğin -128'den 1
 | 
						||
çıkartırsak +127 elde ederiz.
 | 
						||
Gerçek Sayıların (Noktalı Sayıların) 2'lik Sistemde İfade Edilmesi
 | 
						||
Noktalı sayıları 2'lik sistemde ifade edebilmek için tarih boyunca iki tür format grubu kullanılmıştır. Sabit noktalı
 | 
						||
(fixed point) formatlarda noktanın yeri sabittir. Onun solunda sayının tam kısmı, sağında noktadan sonraki kısım
 | 
						||
utulur. Örneğin 12.567 bu tür formatlarda şöyle tutulmaktadır:
 | 
						||
Tabi sayının tam ve noktalı kısımları burada 10'luk sistemde değil, 2'lik sistemde tutulmaktadır.
 | 
						||
Sabit noktalı formatlar pek verimli değildir. Çünkü dinamik değildir. Yani sayının tam kısmı büyük fakat noktalı
 | 
						||
kısmı küçükse ya da tersi durumda sayı ifade edilemez. Halbuki yeteri kadar yer vardır. Bunun için kayan noktalı
 | 
						||
7
 | 
						||
 | 
						||
(floating point) formatlar geliştirilmiştir. Bugün bilgisayarlarımızda bu formatlar kullanılmaktadır. Kayan noktalı
 | 
						||
formatlarda sayı sanki noktası yokmuş gibi yazılır. Noktanın yeri ayrıca format içerisinde belirtilir. Örneğin:
 | 
						||
 | 
						||
Sayının noktası kaldırılmış olan haline mantis (manissa) denir. Noktanın yerini belirten kısmına ise genellikle üstel
 | 
						||
kısım (exponential part) dnilmektedir. Çeşitli kaynak noktalı formatlar olsa da bugün hemen tüm sistemlerde
 | 
						||
IEEE'nin 754 numaralı formatı kullanılmaktadır.
 | 
						||
Tabi 2'lik sistemde sayının noktadan sonraki kısmı 2'nin negatif kuvvetleriyle çarpılarak oluşturulmaktadır. Bu da
 | 
						||
yuvarlama hatası (rounding error) bir hatanın oluşmasına zemin hazırlar. Yuvarlama hatası noktalı bir sayının tam
 | 
						||
olarak ifade edilemeyip ona yakın bir sayının ifade edilmesiyle oluşan bir hatadır. Yuvarlama hatası elimine
 | 
						||
edilemez. Olsa olsa onun etkisi aazaltılabilir. Bunun için de sayının daha geniş olarak ifade edilmesi yoluna gidilir.
 | 
						||
Örneğin C, C# ve Java'da float türü 4 byte'lık gerçek sayı türünü, double türü 8 byte'lık gerçek sayı türünü temsil
 | 
						||
eder. Ve C programcıları bu nedenle en çok double türünü kullanırlar.
 | 
						||
Anahtar Notlar: İlk elektronik bilgisayarlar 40'lı yıllarda yapıldı. Bunlar da vakum tüpler kullanılıyordu. Sonra transistör icad edildi.
 | 
						||
50'li yıllarda bilgisayarlar transistörlerle yapılmaya başlandı. 70'li yıllarda entegre devre (integrated circuit) teknolojisi geliştirilince artık
 | 
						||
bilgisayarların işlem birimi entegre devre olarak yapılmaya başlandı. Bunlara mikroişlemci dediler. Kişisel bilgisayarlar bunlardan sonra
 | 
						||
gelişti.
 | 
						||
 | 
						||
Yuvarlama hataları her noktalı sayıda değil bazı sayılarda ortaya çıkar. Sayı ilk kez depolanırken de oluşabilir.
 | 
						||
İşlem sonucunda da oluşabilir. Bu nedenle programlama dillerinde gerçek sayıların “eşit mi”, “eşit değil mi”
 | 
						||
biçiminde karşılaştırılması iyi bir teknik değildir.
 | 
						||
Yazıların 2'lik Sistemde İfade Edilmesi
 | 
						||
Yazılar karakterlerden oluşur. Yazının her karakteri bir byte kodlanabilir. Böylece aslıunda yazı 2'lik sistemde
 | 
						||
sayılarla ifade edilmiş olur. İşte hangi karakterin hangi sayıyla ifade edileceğini belirlemek için ASCII (American
 | 
						||
Standard Code Information Interchange) tablosu denilen bir tablo yaygın bir biçimde kullanılmaktadır. ASCII
 | 
						||
tablosu orijinal olarak 7 bitti. Sonra bu 8'bite çıkartıldı. ASCII tablosunun uzunluğu 256 satırdır. Bu 256 satır ile
 | 
						||
tüm karakterler ifade edilemez. (Japonlar ne yapsın?). ASCII tablosunun ilk 128 karakteri standarttır. Diğer 128
 | 
						||
karakteri ülkelere göre değişmektedir. Böylece ASCII tablosunun çeşitli varyasyonları oluşmuştur. Bunlara code
 | 
						||
page denilmektedir. Ancak son 15 yıldır artık karakterlerin 2 byte ile kodlanması yaygınlık kazanmaya başlamıştır.
 | 
						||
UNICODE denilen tablo 2 byte'lık ulaslarası kabul görmüş en önemli tablodur. Ve bugün artık en yoğun kullanılan
 | 
						||
tablo haline gelmiştir.
 | 
						||
16'lık Sayı Sistemi (Hexadecimal system)
 | 
						||
16'lık sayı sisteminde toplam 16 sembol vardır:
 | 
						||
0123456789ABCDEF
 | 
						||
16'lık sistem bilgisayar bilimlerinde 2'lik sistemin basit ve yoğun bir gösterimini sağlamak için kullanılır. 16'lık
 | 
						||
sistemdeki her hex digit 4 bit ile ifade edilebilir:
 | 
						||
8
 | 
						||
 | 
						||
0
 | 
						||
1
 | 
						||
2
 | 
						||
3
 | 
						||
4
 | 
						||
5
 | 
						||
6
 | 
						||
7
 | 
						||
8
 | 
						||
9
 | 
						||
A
 | 
						||
B
 | 
						||
C
 | 
						||
D
 | 
						||
E
 | 
						||
F
 | 
						||
 | 
						||
0000
 | 
						||
0001
 | 
						||
0010
 | 
						||
0011
 | 
						||
0100
 | 
						||
0101
 | 
						||
0110
 | 
						||
0111
 | 
						||
1000
 | 
						||
1001
 | 
						||
1010
 | 
						||
1011
 | 
						||
1100
 | 
						||
1101
 | 
						||
1110
 | 
						||
1111
 | 
						||
 | 
						||
16'lık sistemdeki bir sayıyı 2'lik sisteme dönüştyürmek için her hex digit için 4 bit yazılır. Örneğin:
 | 
						||
C4A8 = 1100 0100 1010 1000
 | 
						||
Tam ters işlem de yapılabilir. Örneğin:
 | 
						||
1001 0101 1100 0010 = 95C2
 | 
						||
8'lik Sayı Sistemi (Octal System)
 | 
						||
Sekizlik sisemde her bir octal digit 3 bitle açılır. Örneğin:
 | 
						||
0
 | 
						||
1
 | 
						||
2
 | 
						||
3
 | 
						||
4
 | 
						||
5
 | 
						||
6
 | 
						||
7
 | 
						||
 | 
						||
000
 | 
						||
001
 | 
						||
010
 | 
						||
011
 | 
						||
100
 | 
						||
101
 | 
						||
110
 | 
						||
111
 | 
						||
 | 
						||
Örneğin 8'lik sistemdeki 756 sayısı 111 101 110 biçiminde 2'lik sisteme dönüştürülebilir.
 | 
						||
Bilgisayarın Basit Mimarisi
 | 
						||
Bir bilgisayar sisteminde üç önemli birim vardır: CPU, RAM ve Disk. İşlemleri yapan elektronik devrelerin
 | 
						||
bulunduğu bölüme CPU denir. Bugün CPU'lar entegre devre biçiminde üretilmektedir ve onlara mikroişlemci de
 | 
						||
denilmektedir. CPU ile elektriksel olarak bağlantılı olan ve CPU2nun çalışması sırasında sürekli kullanılan
 | 
						||
belleklere Ana Bellek (Main Memory) ya da Birincil Bellek (Primary Memory) denilmektedir. Bunlar RAM
 | 
						||
teknolojisiyle üretildiklerinden bunlara RAM de denir. Bir programın çalışması için ana belleğe yüklenmesi gerekir.
 | 
						||
Bilgisayarın güç kaynağı kesildiğinde ana bellekte bilgiler kaybolur. Bu durumda güç kaynağı kesilince bilgileri
 | 
						||
tutan birime ihtiyaç duyulmuştur. İşte onlara İkincil Bellekler (Secondary Storage Devices)" denilmektedir. Bugün
 | 
						||
kullandığımız diskler, flash EPROM'lar, EEPROM bellekler ves. hep ikincil belleklerdir.
 | 
						||
 | 
						||
9
 | 
						||
 | 
						||
Bir program diskte bulunur. Onu çalıştırmak istediğimizde işletim sistemi onu diskten alır, birincil belleğe yükler
 | 
						||
ve orada çalıştırır. Örneğin :
 | 
						||
a = b + c;
 | 
						||
gibi bir ifadede a, b ve c RAM'dedir. CPU önce a ve b'yi RAM'den çeker. Toplama işlemini yapar, sonucu yeniden
 | 
						||
RAM'deki c'ye yazar.
 | 
						||
Dil Nedir?
 | 
						||
Dil olgusunun tek bir cümleyle tanımını yapmak kolay değildir. Fakat “iletişimde kullanılan semboller kümesi”
 | 
						||
biçiminde tanımlanabilir. Diller kabaca doğal diller ve yapay diller (ya da kurgusal diller) olmak üzere ikiye ayrılır.
 | 
						||
Doğal dillerde sentaks kesin ve açık olarak ifade edilemez. İstisnalar vardır ve bunlar kurala dönüştürülememktedir.
 | 
						||
Yapay dilelr ise insanlar tarafından tasarlanmış kurgusal dillerdir. Programlama dilleri yapay dillerdir.
 | 
						||
Bir dilin bütün kurallarına gramer denir. Gramerin iki önemli alt alanı vardır: Sentaks ve semantik. Doğal dillerin
 | 
						||
fonetik gibi, morfoloji gibi başka kurallar kümesi olsa da sentaks ve semantik bütün dillerde olan en önemli
 | 
						||
olgudur. Bir olguya dil diyebilmek için kesinlikle sentaks ve semantik kuralların bulunuyor olması gerekir.
 | 
						||
Bir dil aslında yalın elemanlardan oluşur. (örneğin doğal dillerdeki sözcükler). Buelemanlar gelişigüzel dizilemez.
 | 
						||
İşte sentaks dildeki öğelerin doğru yazılmasına ve doğru dizilmesine ilişkin kurallardır. Örneğin aşağıda bir sentaks
 | 
						||
hatası yapılmıştır:
 | 
						||
i am school going to
 | 
						||
Aşağıda yine bir sentaks hatası yapılmıştır:
 | 
						||
if a == 2)(
 | 
						||
Doğru yazılmış öğelerin ne anlam ifade ettiğine ilişkin kurallara semantik denilmektedir. Yani örneğin “I am going
 | 
						||
to school” sentaks olark düzgün bir sünledir. Fakat ne denilmek istenmektedir. Bu semantiktir. Benzer biçimde:
 | 
						||
if (a == 10)
 | 
						||
printf(“evet\n”);
 | 
						||
Sentaks olarak geçerlidir. Semantik olarak “a 10'a eşitse ekrana evet yazdır” anlamına gelir.
 | 
						||
Bilgisayar Dilleri (Computer Languages) ve Programalam Dilleri (Programming Languages)
 | 
						||
Bilgisayar bilimlerinde kullanılan, insanlar tarafından tasarlanmış ve grameri matematiksel olarak ifade edilebilen
 | 
						||
dillere bilgisayar dilleri denir. Bilgisayar bilimlerinde kullanılan ve sentaks ve semantik yapıya sahip her olgu bir
 | 
						||
bilgisayar dilidir. (html, xml vs.). Bir bilgisayar dili özellikle akış öğesi de içerisiyorsa bir programlama dilidir. Her
 | 
						||
programlama dili bilgisayar dilidir. Fakat tersi her zaman doğru değildir. (C, C++, Java, C# vs)”
 | 
						||
10
 | 
						||
 | 
						||
C dili bir programlama dilidir.
 | 
						||
Bir C Programını Oluşturmak
 | 
						||
Bir C programının çalıştırılabilir program haline getirilmesi için genel olarak aşağıdaki adımlar uygulanır:
 | 
						||
1. Kaynak dosyanın oluşturulması:
 | 
						||
Kaynak dosya metin düzenleyici bir programla yazılır. C dilinin kurallarına göre yazılan dosyanın uzantısı
 | 
						||
geleneksel olarak “.c” dir.
 | 
						||
2. Kaynak dosyanın derleyici program (compiler) tarafından derlenmesi:
 | 
						||
Derleyici program kullanılarak .c uzantılı dosya derlenir. Derleme işlemi yazılan programın C dilinin kurallarına
 | 
						||
uygunluğunun belirlenmesi işlemidir. Derleme işlemi her zaman başarılı olmayabilir. Derleme işlemi başarılıysa
 | 
						||
derleyici program ismine amaç dosya (object file) denilen bir dosya üretir. Amaç dosyalar işletim sitemine göre
 | 
						||
değişiklik gösterebilir. Örneğin Windows sistemleri için amaç dosya uzantısı “.obj” dur. Unix/Linux sitemleri için
 | 
						||
amaç dosyanın uzantısı genel olarak “.o” dur.
 | 
						||
Derleyiciler komut satırından kolayca çalıştırılabilen programlardır. Başka programlar tarafından çağrılabilmeleri
 | 
						||
için basit bir kullanıma sahiptirler.
 | 
						||
3. Amaç dosya(lar) bağlayıcı (linker) program tarafından birleştirilerek çalıştırılabilir (executable) dosya
 | 
						||
oluşturulur.
 | 
						||
Anahtar Notlar: Windows sistemlerinde çalıştırılabilir dosyalar “.exe” uzantılıdır. Unix/Linux sistemlerinde
 | 
						||
uzantınıın önemi yoktur. Özel bir biti set edilmiş dosyalar Unix/Linux sistemleri için çalıştırılabilir dosya olarak
 | 
						||
algılanır.
 | 
						||
Windows sistemlerinde derleme ve bağlama işlemi
 | 
						||
Windows sistemlerinde genel olarak kullanılan Microsoft firmasınının cl isimli derleyicisidir. Bu program bağlama
 | 
						||
işlemini de yapabilmektedir.
 | 
						||
Anahtar Notlar: Windows sistemlerinde komut yorumlayıcı program üzerinde “cd” komutu ile dizin geçişleri
 | 
						||
yapılabilir. (cd c:\Users\csd\Desktop\Test)
 | 
						||
cl derleyici programının hem derleme hem de bağlama işlemini yapabildiği basit bir kullanımı şu şekildedir:
 | 
						||
cl hello.c
 | 
						||
Üretilen hello.exe programı aşağıdaki gibi çalıştırılabilir.
 | 
						||
hello
 | 
						||
Unix/Linux Sistemlerinde Derleme ve Bağlama İşlemi
 | 
						||
Unix/linux sistemlerinde derleyici program olarak çoğunlukla “gcc” kullanılmaktadır. Unix/Linux sistemleri için
 | 
						||
bu program parasız olarak kurulabilmektedir.
 | 
						||
Anahtar Notlar: Bir çok Linux dağıtımı bulunmaktadır. Bunlardan lisanssız olarak kullanılabilen çeşitli
 | 
						||
dağıtımlar da mevcuttur. (Ubuntu, Mint, Debian, Open Suse vs). Bu sistemler sanal makine programlarına da
 | 
						||
kurulabilir. (Virtualbox vs.)
 | 
						||
11
 | 
						||
 | 
						||
gcc programıyla derlemeve bağlama işlemi basit olarak aşağıdaki biçimde yapılabilir.
 | 
						||
gcc -o hello hello.c
 | 
						||
Burada “hello” isimli çalıştırılabilir bir dosya üretilecektir. Bu sistemlerde programın çalıştırılması terminal
 | 
						||
penceresinde aşağıdaki gibi yapılabilir.
 | 
						||
./hello
 | 
						||
Visual Studio IDE'sinde Derleme ve Çalıştırma İşlemleri
 | 
						||
Visual Studio IDE'si ile bir programı derleyip çalıştırmak için sırasıyla şunların yapılması gerekir:
 | 
						||
1) Öncelikle bir proje oluşturmak gerekir. Projeler tek başlarına değil “Solution” denilen kapların içerisinde
 | 
						||
bulunur. Dolayısıyla bir proje yaratırken aynı zamanda bir solution da yaratılır. Proje yaratmak için
 | 
						||
File/New/Project seçilir. Karşımıza “New Project” dialog penceresi çıkar. Burada template olarak “Visual C++”
 | 
						||
“Win32 Console Application” seçilir. Sonra Proje dizininin yaratılacağı dizin ve Proje ismi belirlenir. “Create
 | 
						||
Directory for Solution” seçenk kutusu default çarpılanmış durumda olabilir. Bunun çarpısı kaldırılabilir. Bu
 | 
						||
durumda solution bilgileri ile proje bilgileri aynı klasörde tutulacaktır. Bu dialog penceresi kapatılınca karşımıza
 | 
						||
yeni bir dialog penceresi gelir. Burada “Empty Project” seçilmelidir. SDL seçenk kutusu da unchecked yapılabilir.
 | 
						||
2) Solution yaratıldıktan sonra bununla ilgili işlem yapabilmek için “Solution Expolorer” denilen bir pencere
 | 
						||
kullanılır. Solution Explorer View menüsü yoluyla,, araç çubuğı simgesiyle ya da Ctrl + Alt + L kısayol tuşuyla
 | 
						||
açılabilir. Solution Explorer “yuvalanabilir (dockable)” bir penceredir. Solution Explorer bir “treeview” kontrolü
 | 
						||
içermektedir.
 | 
						||
3) Artık projeye bir kaynak dosyanın eklenmesi zamanı gelmiştir. Bunun için “Project/Add New Item” menüsü ya
 | 
						||
da Solution Explorer'da projenin üzerinde bağlam menüsünden “Add/New Item” seçilir. Karşımıza “Add New
 | 
						||
Item” dialog penceresi gelir. Burada kaynak dosyaya isim vererek onu yaratırız. Dosya uzantısının .c olması
 | 
						||
gerekmektedir. Artık programı dosyaya yazabiliriz.
 | 
						||
4) Programı derlemek için “Build/Compile” seçilir. Link işlemi için “Build/Build Solution” ya da “Build/Build
 | 
						||
XXX” seçilir (Buradaki XXX projenin ismidir). Programı çalıştırmak için “Debug/Start Without Debugging” ya da
 | 
						||
Ctrl+F5 tuşlarına basılır. Sonraki bir aşama seçilirse zaten öncekiler de yapılır. Böylece programı derleyip, bağlaıp
 | 
						||
çalıştırabilmek için tek yapılacak şey Ctrl+F5 tuşlarına basmaktır.
 | 
						||
5) Projeyi tekrar açabilmek için “File/Open/Project-Solution” seçilir. Projenin dizinine gelinir ve .sln dosyası
 | 
						||
seçilir.
 | 
						||
6) IDE'den çıkmadan projeyi kapatmak için “File/Close Solution” seçilir.
 | 
						||
Atom (Token) Kavramı
 | 
						||
Bir programlama dilindeki anlamlı en küçük birime atom (token) denir. Program aslında atomların yan yana
 | 
						||
gelmesiyle oluşturulur. (Atomlar doğal dillerdeki sözcüklere benzetilebilirler). Örneğin “merhaba dünya”
 | 
						||
programını atomlarına ayıralım:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
printf(“Merhaba Dunya\n”);
 | 
						||
12
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Atomlar:
 | 
						||
# include
 | 
						||
<
 | 
						||
) {
 | 
						||
printf
 | 
						||
return
 | 
						||
0
 | 
						||
 | 
						||
(
 | 
						||
;
 | 
						||
 | 
						||
stdio.h
 | 
						||
>
 | 
						||
int
 | 
						||
“Merhaba Dunya”
 | 
						||
)
 | 
						||
}
 | 
						||
 | 
						||
main
 | 
						||
;
 | 
						||
 | 
						||
(
 | 
						||
 | 
						||
void
 | 
						||
 | 
						||
Atomlar daha fazla parçaya ayrılamazlar. Atomları altı gruba ayırabiliriz:
 | 
						||
1) Anahtar Sözcükler (Keywords/Reserved Words): Dil için özel anlamı olan, değişken olarak kullanılması
 | 
						||
yasaklanmış sözcüklerdir. Örneğin if, for, int, return gibi...
 | 
						||
2) Değişkenler (Identifiers/Variables): İsmini bizim istediğimiz gibi verebildiğimiz atomlardır. Örneğin: x, y, count,
 | 
						||
printf gibi...
 | 
						||
3) Sabitler (Literals/Constants): Bir değer ya b,ir değişkenin içerisinde bulunur ya da doğrudan yazılır. İşte
 | 
						||
doğrudan yazılan sayılara sabit denir. Örneğin:
 | 
						||
a = b + 10;
 | 
						||
ifadesinde a ve b birer değişken atom, 10 ise sabit atomdur.
 | 
						||
4) Operatörler (Operators): Bir işleme yol açan, işlem sonucunda bir değer üretilmesini sağlayan atomlara operatör
 | 
						||
denir. Örneğin:
 | 
						||
a = b + 10;
 | 
						||
Burada + ve = bierer operatördür.
 | 
						||
5) String'ler (Strings): Programlama dillerinde iki tırnak içerisine yazılmış yazılar iki tırnaklarıyla tek bir atom
 | 
						||
belirtirler. Bunlara string denir. Örneğin: “Merhaba Dunya\n” gibi...
 | 
						||
6) Ayıraçlar (Delimeters/Punctuators): Yukarıdakşi grupların dışında kalan, ifadeleri ayırmak için kullanılan tüm
 | 
						||
atomlar ayıraç atomdur. Örneğin ; gibi, } gibi...
 | 
						||
Sentaks Gösterimleri
 | 
						||
Programlama dillerinin sentakslarını betimlemek için en çok kullanılan yöntem BNF notasyonu ve türevleridir.
 | 
						||
Ancak biz kurusumuzda açısal parantez, köşeli parantez tekniğini kullanacağız. Açısal parantezler içerisinde
 | 
						||
yazılanlar mutlaka bulunması zorunlu öğeleri, köşeli parantez içerisindekiler “yazılmasa da olur (optional)” öğeleri
 | 
						||
belirtir. Örneğin:
 | 
						||
[geri dönüş değerinin türü] <fonksiyon ismi>([parametre bildirimi])
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
Açısal ve köşeli parantezlerin dışındaki tüm atomlar aynı biçimde bulundurulmak zorundadır.
 | 
						||
Merhaba Dunya Programının Açıklaması
 | 
						||
13
 | 
						||
 | 
						||
C programlarının başında genellikle aşağıdaki satır bulunur:
 | 
						||
#include <stdio.h>
 | 
						||
Bu bir önişlemci (preprocessor) komutudur. Bu komut açısal parantez içerisindeki dosyanın içeriğininn derleme
 | 
						||
sırasında komutun yerleştirildiği yere kopyalanacağını beliritr. (Yani bu satırı silip onun yerine stdio.h isimli
 | 
						||
dosyanın içeriğini oraya yerleştirmek aynı etkiye yol açar). Neden bu dosyanın içeriğinin bulunmasına gereksinim
 | 
						||
duyulduğu ileride açıklanacaktır.
 | 
						||
C programları kabaca fonksiyonlardan (functions) oluşur. Altprogramlara C'de fonksiyon denilmektedir. Bir
 | 
						||
fonksiyonun tanımlanması (definition) o fonksiyonun bizim tarafımızdan yazılması anlamına gelir. Çağrılması (call)
 | 
						||
onun çalıştırılması anlamına gelir. “Merhaba Dunya” programında main fonksiyonu tanımlanmıştır. Fonksiyon
 | 
						||
tanımlamanın genel biçimi şöyledir:
 | 
						||
[geri dönüş değerinin türü] <fonksiyon ismi> ([parametre bildirimi])
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
Anahtar Notlar: Genel biçimlerdeki /* ... */ gösterimi “burada birşeyler var, fakat biz şimdilik onlarla ilgilenmiyoruz” anlamına
 | 
						||
gelmektedir.
 | 
						||
 | 
						||
İki küme parantezi arasındaki bölgeye blok denilmektedir. C'de her fonksiyonun bir ana bloğu vardır. C
 | 
						||
programları main isminde bir fonksiyondan çalışmaya başlar. main bitince program da biter. Örnek programımızda
 | 
						||
main fonksiyonunda printf isimli bir fonksiyon çağrılmıştır. Printf standart bir C fonksiyonudur. Standart C
 | 
						||
fonksiyonu demek, tüm C derleyicilerinde bulunmak zorunda olan, derleyicileri yazanlar tarafından zaten yazılmış
 | 
						||
olarak bulunan fonksiyonlar demektir. Bir fonksiyon çağırmanın genel biçimi şöyledir:
 | 
						||
<fonksiyon ismi>([argüman listesi]);
 | 
						||
printf fonksiyonu iki tırnak içerisindeki yazıları imlecin (cursor) bulundauğu yerden itibaren ekrana yazar. İmleç
 | 
						||
program çalıştığında sol üst köşededir. printf imleci yazının sonunda bırakır. “\n” “imleci aşağıdaki satırın başına
 | 
						||
geçir” anlamına gelir.
 | 
						||
return deyimi fonksiyonu sonlandırır. Eğer return yoksa fonksiyon kapanış küme parantezine geldiğinde sonlanır.
 | 
						||
Bir C programında istenildiği kadar çok fonksiyon tanımlanabilir. Bunların sırasının bir önemi yoktur. Main
 | 
						||
fonksiyonunun da özel bir yerde bulunması gerekmez. Fakat program her zaman main fonksiyonundan çalışmaya
 | 
						||
başlar.
 | 
						||
C'de iç içe fonksiyon tanımlanamaz. Her fonksiyon diğerinin dışında tanımlanmak zorundadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
foo()
 | 
						||
{
 | 
						||
printf("I am foo\n");
 | 
						||
}
 | 
						||
main()
 | 
						||
{
 | 
						||
foo();
 | 
						||
}
 | 
						||
 | 
						||
14
 | 
						||
 | 
						||
Anahtar Notlar: Örneklerimizde fonksiyon isimleri uydurulurken foo, bar, tar gibi, func gibi isimler
 | 
						||
kullanılacaktır. Bu isimlerin hiçbir özel anlamı yoktur.
 | 
						||
İfade (Expression) Kavramı
 | 
						||
Değişkenlerin, sabitlerin ve operatörlerin herbir birleşimine (kombinasyonuna) ifade denir. Örneğin:
 | 
						||
x
 | 
						||
30
 | 
						||
x + 30
 | 
						||
x + 30 – y
 | 
						||
foo()
 | 
						||
birer ifadedir. Görüldüğü gibi tekbaşına bir değişken ve sabit ifade belirtir, fakat tek başına bir operatör ifade
 | 
						||
belirtmez.
 | 
						||
Nesne (Object) Kavramı
 | 
						||
Bellekte yer kaplayan ve erişilebilir olan bölgeler nesne denir. Örneğin programlama dillerindeki değişkenler tipik
 | 
						||
birer nesnedir. Bir ifade ya bir nesne belirtir ya da nesne belirtmez. Örneğin:
 | 
						||
100 bir ifadedir fakat nesne belirtmez. Örneğin:
 | 
						||
x bir ifadedir ve nesne belirtir. Örneğin:
 | 
						||
x + 10 bir ifadedir, nesne belirtmez.
 | 
						||
Sol Taraf Değeri (Left Value) ve Sağ Taraf Değeri (Right Value)
 | 
						||
Nesne belirten ifadelere sol taraf değeri (lvalue), belirtmeyene ifadeleri sağ taraf değeri (rvalue) denilmektedir.
 | 
						||
Örneğin:
 | 
						||
10 --> sağ taraf değeri
 | 
						||
x --> sol taraf değeri
 | 
						||
x + 10 --> sağ taraf değeri
 | 
						||
Sol taraf değeri denmesinin nedeni atama operatörünün solunda bulunabilmesindendir. Sağ taraf değeri denmesinin
 | 
						||
sebebi atama operatörünün solunda bulunamamasındandır (tipik olarak sağında bulunduğu için).
 | 
						||
C'nin Veri Türleri
 | 
						||
Tür (type) bir nesnenin bellekte kaç byte yer kaplayacağını ve onun içerisindeki 0'ların ve 1'lerin nasıl
 | 
						||
yorumlanacağını anlatan temel bir kavramdır. Her nesnin C'de bir türü vardır. Ve bu tür programın çalışması
 | 
						||
sırasında değişmez. (Aslında yalnızca nesnelerin değil genel olarak her ifadenin bir türü vardır. Bundan ileride
 | 
						||
bahsedilecektir.)
 | 
						||
C'nin temel türleri aşağıdaki tabloda gösteriöektedir:
 | 
						||
Tür
 | 
						||
Belirten Uzunluk (byte)
 | 
						||
Sınır Değerler (Windows)
 | 
						||
Anahtar Sözcükler Windows (UNIX/Linux)
 | 
						||
[signed] int
 | 
						||
 | 
						||
4 (4)
 | 
						||
 | 
						||
[-2147483648, +2147483647]
 | 
						||
15
 | 
						||
 | 
						||
unsigned [int]
 | 
						||
 | 
						||
4 (4)
 | 
						||
 | 
						||
[0, +4294967295]
 | 
						||
 | 
						||
[signed] short [int]
 | 
						||
 | 
						||
2 (2)
 | 
						||
 | 
						||
[-32768, +32767]
 | 
						||
 | 
						||
unsigned short [int] 2 (2)
 | 
						||
 | 
						||
[0, +65535]
 | 
						||
 | 
						||
[signed] long [int]
 | 
						||
 | 
						||
4 (8)
 | 
						||
 | 
						||
[-2147483648, +2147483647]
 | 
						||
 | 
						||
unsigned long [int]
 | 
						||
 | 
						||
4 (8)
 | 
						||
 | 
						||
[0, +4294967295]
 | 
						||
 | 
						||
char
 | 
						||
 | 
						||
1 (1)
 | 
						||
 | 
						||
[-128, +127] [0, +255]
 | 
						||
 | 
						||
signed char
 | 
						||
 | 
						||
1 (1)
 | 
						||
 | 
						||
[-128, +127]
 | 
						||
 | 
						||
unsigned char
 | 
						||
 | 
						||
1 (1)
 | 
						||
 | 
						||
[0, +255]
 | 
						||
 | 
						||
float
 | 
						||
 | 
						||
4 (4)
 | 
						||
 | 
						||
[±3.6*10-38, ±3.6*10+38]
 | 
						||
 | 
						||
double
 | 
						||
 | 
						||
8 (8)
 | 
						||
 | 
						||
[±1.8*10-308, ±1.8*10+308]
 | 
						||
 | 
						||
long double
 | 
						||
 | 
						||
8 (8)
 | 
						||
 | 
						||
[±1.8*10-308, ±1.8*10+308]
 | 
						||
 | 
						||
[signed] long long
 | 
						||
 | 
						||
8 (8)
 | 
						||
 | 
						||
[-9223372036854775808, +9223372036854775807]
 | 
						||
 | 
						||
unsigned long long
 | 
						||
(C99 ve C11
 | 
						||
ve C++11)
 | 
						||
 | 
						||
8 (8)
 | 
						||
 | 
						||
[0, +18446744073709551615]
 | 
						||
 | 
						||
_Bool
 | 
						||
(C99, C11)
 | 
						||
 | 
						||
1 (1)
 | 
						||
 | 
						||
true, false
 | 
						||
 | 
						||
- int türü işaretli bir tamsayı türüdür. int türünün uzunluğu sistemden sisteme değişebilir. Standartlarda en az 2 byte
 | 
						||
olması zorunlu tutulmuştur. Ancak derleyicileri yazanların isteğine bırakılmıştır.
 | 
						||
- C'de her tamsayı türünün işaretli ve işaretsiz versiyonları vardır. int türünün işaretsiz biçimi unsigned int türüdür.
 | 
						||
Yalnızca unsigned demekle unsigned int demek aynı anlamdadır.
 | 
						||
- Standartlara göre short türü ya int türü kadardır ya da int türünden küçüktür. Örneğin DOS'ta int türü de short türü
 | 
						||
de 2 byte uzunluktadır. Oysa Windows'ta ve UNIX/Linux sistemlerinde int türü 4 byte, short türü 2 byte
 | 
						||
uzunluktadır.
 | 
						||
- short türünün işaretsiz biçimi unsigned short türüdür.
 | 
						||
- long türü standartlara göre ya int türü kadar olmak zorundadır ya da ondan büyük olmak zorundadır. 32 ve 64 bit
 | 
						||
Windows sistemlerinde long türü int türü aynı uzunluktadır (4 byte). 32 bit UNIX/Linux sistemlerinde long türü 4
 | 
						||
byte, 64 bit UNIX/Linux sistemlerinde 8 byte'tır.
 | 
						||
- long türünün işaretsiz biçimi unsigned long türüdür.
 | 
						||
- char türü standartlara göre her sistemde 1 byte olmak zorundadır. (Bu türün ismi belki byte olsaydı daha iyiydi).
 | 
						||
char ismi her ne kadar karakteri çağrıştırıyorsa da bunun karkterle bir ilgisi yoktur. char türü C'de 1 byte uzunlukta
 | 
						||
bir tamsayı türüdür. C'de yalnızca char denildiğinde bunun sisgned char mı, yoksa unsignd char mı olacağı
 | 
						||
derleyicileri yazanların isteğime bırakılmıştır. Örneğin Windows ve UNIX/Linux sistemlerindeki derleyicilerde
 | 
						||
char denildiğinde signed char anlaşılmaktadır (fakat bu durum ayarlardan da değiştirilebilmektdir).
 | 
						||
- C'de gerçek sayı türlerinin işaretli ve işaretsiz biçimleri yoktur. Onlar zaten her zaman default işaretlidir.
 | 
						||
- float türü en az 4 byte olması öngörülen bir gerçek sayı (noktalı sayı) türüdür. float türünün yuvarlama hatalarına
 | 
						||
direnci zayıftır. Bu nedenle float yerine programcılar daha çok double türünü tercih ederler.
 | 
						||
16
 | 
						||
 | 
						||
- double türü standartlara göre en az float kadar olmak zorundadır. Yani float türüyle aynı duyarlıkta olabilir ya da
 | 
						||
ondan daha geniş olabilir.
 | 
						||
- long double türü en az double kadar olmak zorundadır. double ile aynı uzunlukta olabilir ya da ondan daha uzun
 | 
						||
olabilir.
 | 
						||
- Birden fazla sözcükten oluşan türler için bu sözcüklerin yerleri değiştirilebilir (örneğin signed long int yerine int
 | 
						||
signed long denilebilir.)
 | 
						||
- C'de en fazla kullanılan tamsayı türü int, en fazla kullanılan gerçek sayı türü double türüdür. Programcının
 | 
						||
öncelikle bunları tercih etmelidir. Özel durum varsa diğerlerini düşünmelidir.
 | 
						||
- C99'da long long isimli bir tamsayı türü daha eklendi. Bu türün long türünden daha uzun olması öngörülmüştür.
 | 
						||
Standartlara göre bu tür ya long türüyle aynı uzunluktadır ya da ondan daha uzundur.
 | 
						||
- C'ye C99'la birlikte nihayet bir bool türü de eklenmiştir. Fakat klasik C'de böyle bir tür yoktur.
 | 
						||
Derleyicilerin Hata Mesajları
 | 
						||
Derleyicilerin hata mesajları üçe ayrılmaktadır:
 | 
						||
1. Uyarılar (Warnings): Uyarılar gerçek hatalar değildir. Program içerisindeki program yapmış olabileceği olası
 | 
						||
mantık hatalarına dikkati çekmek için verilirler. Uyarılar derleme işleminin başarısızlığına yol açmazlar. Ancak
 | 
						||
programcıların uyarılara çok dikkat etmesi gerekir. Çünkü pek çok uyarıda derleyici haklı bir yere dikkat
 | 
						||
çekmektedir.
 | 
						||
2. Gerçek Hatalar (Errors): Bunlar dilin sentaks ve semantik kurallarına uyulmaması yüzünden verilirler.
 | 
						||
Bunların mutlaka düzeltilmesi gerekir. Bir programda bir tane bile “error” olsa program başarılı olarak derlenemez.
 | 
						||
3. Ölümcül Hatalar (Fatal Errors): Dereleme işleminin bile devam etmesini engelleyen ciddi hatalardır. Normal
 | 
						||
olarak bir programda ne kadar hata olursa olsun tüm kod gözden geçirilir. Tüm hatalar en sonında listelenir. Fakat
 | 
						||
bir ölümcül hata oluştuğunda artık derleme işlemi sonlandırılır. Ölümcül hatalar genellikle sistemdeki ciddi
 | 
						||
sorunlar yüzünden ortaya çıkmaktadır (örneğin diskte yeterli alan olmayabilir, ya da sistemde yeterli RAM
 | 
						||
bulunmuyor olabilir.)
 | 
						||
Verilen hata mesajlarının metinleri derleyiciden derleyiciye değişebilir. Ayrıca bir hata durumunda bir derleyici
 | 
						||
buna birmesaj verirken diğeri daha fazla mesaj verebilir.
 | 
						||
C Programlarının Geçerliliği
 | 
						||
Standartlar bir C programının geçerliliği ve derlenip derlenmeyeceği konusunda şunları söylemektedir:
 | 
						||
1. Standartlarda belirtilen sentaks ve semantik kurallara uygun programlar kesinlikle derlenmek zorundadır.
 | 
						||
2. Standartlara uygun bir program başarılı olarak derlendikten sonra birtakım mesajlar (diagnostics) da verilebilir.
 | 
						||
(Tabi bu durumda bu mesajlar uyarı mesajları olacaktır)
 | 
						||
3. Sentaks ve semantik kurllara uyulmadığı her durumda derleyici bu durumu belirten bir mesaj (diagnostic)
 | 
						||
vermek zorundadır. Bu mesaj error ya da warning olabilir.
 | 
						||
4. Geçersiz bir program yine de başarılı olarak derlenebilir. Ya da tersten şöyle düşünebiliriz: “Bir programın
 | 
						||
başarılı olarak derlenmesi onun geçerli olduğu anlamına gelmez”.
 | 
						||
Özellikle 4. Madde C programcıları tarafından maalesef bilinmemektedir. Bazı programcılar sırf derleyici
 | 
						||
programılarını derledi diye programlarının geçerli olduğunu sanmaktadırler. Böyle bir program başka C
 | 
						||
derleyicileri tarafından derlenmeyebilir.
 | 
						||
17
 | 
						||
 | 
						||
Taşınabilirlik (Portability) Nedir?
 | 
						||
Taşınabilirlik bir programlama dilinde yazılmış olan programın başka bir sisteme götürüldüğünde orada derlenerek
 | 
						||
sorunsuz çalışabilmesine denilmektedir. Taşınabilirlik bu anlamda bir standadizasyonun bulunmasını gerektirir.
 | 
						||
Çünkü derleyicileri yazanlar hep aynı kuralları kabul etmişlerse taşınabilirlik oluşabilir. C Programlama dili
 | 
						||
oldukça taşınabilir bir dildir. Burada söz konusu edilen taşınabilirlik kaynak kodun taşınabilirliğidir. Yoksa biz
 | 
						||
derlenip exe yapılmış bir programı başka bir sisteme götürüp orada çalıştıramayız. Ancak bunun mümkün olduğu
 | 
						||
ortamlar da vardır (.NET ve Java gibi). Bu ortamlarda derlenmiş olan kod yeniden derlenmeden başka sistemlere
 | 
						||
götürüldüğünde çalıştırılabilmektedir.
 | 
						||
Bildirim ve Tanımlama Kavramları (Declaration & Definitions)
 | 
						||
C, C++, C# ve Java gibi katı tür kontrolünün uygulandığı dillerde (strongly typed languages) bir değişken
 | 
						||
kullanılmadan önce derleyiciye tanıtılmak zorundadır. Kullanılmadan önce değişkenlerin derleyiciye tanıtılması
 | 
						||
işlemine bildirim (declaration) denilmektedir. Bir bildirim yapıldığında eğer derleyici bildirilen değişken için
 | 
						||
bellekte bir yer ayırıyorsa o bildirim aynı zamanda bir tanımlama (definition) işlemidir. Yani tanımlama
 | 
						||
derleyicinin yer ayırdığı bildirim işlemleridir. Bildirim daha geneldir. Her tanımlama bir bildirimdir fakat her
 | 
						||
bildirim bir tanımlama değildir. Kurusumuzda aksi belirtilmediği sürece bildirimler aynı zamanda tanımlama
 | 
						||
işlemi olarak kubul edilmelidir.
 | 
						||
Bildirim işleminin genel biçimi şöyledir:
 | 
						||
<tür> <değişken listesi>;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int a;
 | 
						||
long b, c, d;
 | 
						||
double x, y;
 | 
						||
 | 
						||
Değişken listyesi bir'den fazla değişkenden oluşuyorsa onları ayırmak için ',' atomu kullanılır. Atomlar arasında
 | 
						||
istenildiği kadar boşluk karakteri bırakılabildiğine göre aşağıdaki bildirim d geçerlidir.
 | 
						||
long b
 | 
						||
,
 | 
						||
c,
 | 
						||
 | 
						||
d;
 | 
						||
 | 
						||
C'de (C90) bildirimler 3 yerde yapılabilir:
 | 
						||
1) Blokların başlarında. Yani blok açıldığında henüz hiçbir fonksiyon çağrısı yapılmadan bildirimler yapılmalıdır.
 | 
						||
Blok başlarında bildirilen değişkenlere yerel değişkenler (local variables) de denilmektedir.
 | 
						||
2) Tüm blokların dışında. C'de tüm blokların dışında bildirilen değişkenlere global değişkenler (global variables)
 | 
						||
de denilmektedir.
 | 
						||
3) Fonksiyonların parametre parantezleri içerisinde. Böyle bildirilmiş olan değişkenlere “parametre değişkenleri
 | 
						||
(parameters)” denilmektedir.
 | 
						||
Değişken isimlendirmede şu kurallar söz konusudur:
 | 
						||
- C büyük harf-küçük harf duyarlılığı olan (case sensetive) bir programlama dilidir. Yani büyük harflerle küçük
 | 
						||
harfler tamamen farklı karakterlermiş gibi ele alınırlar.
 | 
						||
18
 | 
						||
 | 
						||
- Değişkenler boşluk içermez. Değişken isimleri sayısal karakterlerle başlatılamaz. Ancak alfabetik karakterlerle ya
 | 
						||
da _ ile başlatılıp sayısal devam ettirilebilirler.
 | 
						||
- Değişken isimlendirmede yalnızca İngilizce büyük harfler, küçük harfler, sayısal karakterler ve alt tire
 | 
						||
karakterleri kullanılabilir. (Yani değişkenlere Türkçe isimler veremeyiz).
 | 
						||
- C standartlarına göre değişken uzunlukları için derleyiciler minimum 32 karakteri dikkate almak zorundadır. Bu
 | 
						||
limit en az 32 olmak koşuluyla derleyiciden derleyiciye değişebilir. Fakat daha uzun değişken isimleri derleyici
 | 
						||
tarafından geçerli olarak değerlendirilir. ncak en az ilk 32 karakter ayırıcı olarak dikkate alınacaktır.
 | 
						||
- Anahtar sözcükler de değişken ismi olarak kullanılamazlar
 | 
						||
Değişkenlere İlkdeğer Verilmesi (Initialization)
 | 
						||
Bildirim sırasında bildirim işleminin bir parçası olarak değişkenlere değer vermeye ilkdeğer verme (initialization)
 | 
						||
denilmektedir. Örneğin:
 | 
						||
int a, b = 10, c;
 | 
						||
 | 
						||
burada a ve c'ye ilkdeğer verilmemiştir, fakat b'ye verilmiştir. İlkdeğer vermekle ilk kez değer vermek çoğu zaman
 | 
						||
aynı etkiye yol açsa da gramatik olarak aynı şey değildir. Örneğin:
 | 
						||
int a = 0;
 | 
						||
int b;
 | 
						||
b = 10;
 | 
						||
 | 
						||
/* ilkdeğer verme işlemi */
 | 
						||
/* ilkdeğer verme işlemi değil, ilk kez değer verme işlemi */
 | 
						||
 | 
						||
İçerisine heniz değer atanmamış yerel değişkenlrin içerisinde rastgele değerler vardır. C terminolojisinde buna çöp
 | 
						||
değer (garbage value) denilmektedir. Halbuki ilkdeğer verilmemiş global değişkenlerin içerisinde kesinlikle sıfır
 | 
						||
değeri bulunur.
 | 
						||
Nesnelerin İçerisindeki Değerlerin printf Fonksiyonuyla Yazdırılması
 | 
						||
printf fonksiyonunda iki tırnak içerisindeki karakterler ekrana yazdırılır. Fakat iki tırnağın içerisinde % karakteri
 | 
						||
varsa, printf bu % karakterini ve yanındaki bir ya da birkaç karakteri format karakteri olarak kabul eder. printf %
 | 
						||
karakterini ve onu izleyen formak karaktrlerini ekrana yazdırmaz. Bunlar yer tutucudur. Bunların yerine iki
 | 
						||
tırnaktan sonraki ifadelrin değerleri yazdırılır. İki tırnak içerisindeki her format karakteri sırasıyla iki tırnaktan
 | 
						||
sonraki argümanlarla eşleştirilir. Formak karakterleri yerine bunların değerleri yazdırılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
int a = 10, b = 20;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
printf("a = %d, b = %d\n", b, a);
 | 
						||
printf("%d%d\n", a, b);
 | 
						||
 | 
						||
/* a = 10, b = 20 */
 | 
						||
/* a = 20, b = 10 */
 | 
						||
/* 1020 */
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
% karakterinin yanındaki format karakterleri nelerdir? Format karakterleri yazdırılacak ifadenin türüne bağlıdır.
 | 
						||
Ayrıca format karakterleri yazdırma işleminin nasıl yapılacağını da (örneğin kaçlık sistemde) belirler. Temel
 | 
						||
format karakterleri ve anlamları şöyledir:
 | 
						||
 | 
						||
19
 | 
						||
 | 
						||
Format Karakterleri Anlamı
 | 
						||
%d
 | 
						||
 | 
						||
int, short ve char türlerini 10'luk sistemde yazdırır
 | 
						||
 | 
						||
%ld
 | 
						||
 | 
						||
long int türünü 10'luk sistemde yazdırır
 | 
						||
 | 
						||
%x, %X
 | 
						||
 | 
						||
int, short ve char türlerini hex sistemde yazdırır
 | 
						||
 | 
						||
%lx, %lX
 | 
						||
 | 
						||
long int ve unsigned long int türlerini hex sistemde yazdırır
 | 
						||
 | 
						||
%u
 | 
						||
 | 
						||
unsigned int, unsigned short ve unsigned char türlerini 10'luk sistemde yazdırır
 | 
						||
 | 
						||
%lu
 | 
						||
 | 
						||
unsigned long int türünü 10'luk sistemde yazdırır
 | 
						||
 | 
						||
%f
 | 
						||
 | 
						||
float ve double türlerini 10'luk sistemde yazdırır
 | 
						||
 | 
						||
%c
 | 
						||
 | 
						||
char, short ve int türlerini karakter görüntüsaü olarak yazdırır
 | 
						||
 | 
						||
%o
 | 
						||
 | 
						||
char, short ve int türlerini octal sistemde yazdırır
 | 
						||
 | 
						||
%lo
 | 
						||
 | 
						||
long ve unsigned long türlerini octal sistemde yazdırır
 | 
						||
 | 
						||
printf %f formatında default olarak noktadan sonra 6 basamak yazdırır. Yazdırılacak değer noktadan sonra 6
 | 
						||
basamaktan fazlaysa yuvarlama yapılır. eğer printf ile noktadan sonra istediğimiz kadar basamak yazdırmak
 | 
						||
istiyorsak %.nf formatı kullanılmalıdır (burada n yerine bir sayı olmalıdır. Örneğin %.10f gibi).
 | 
						||
scanf Fonksiyonuyla Klavyeden Okuma Yapılması
 | 
						||
scanf fonksiyonun kullanımı printf fonksiyonuna çok benzemektedir. Yine scanf fonksiyonunun bir format kısmı
 | 
						||
vardır. Bu format kısmını içerisine okunan değerin yerleştirileceği nesne adresleri izler. scanf'te iki tırnak içerisinde
 | 
						||
yalnızca formak karakterleri bulunmalıdır. Bu fonksiyon iki tırnak içerisindekileri ekrana yazdırmaz. scanf'te iki
 | 
						||
tırnak içerisindeki format karakterlerinin dışındaki karakterler tamamen başka anlama gelirler. scanf
 | 
						||
fonksiyonunda değerin yerleştirileceği nesnelerin başında & operatörü bulunmalıdır. (Bu & operatörü bir adres
 | 
						||
operatörüdür. Nesnenin adresini elde etmekte kullanılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
printf("sayi giriniz:");
 | 
						||
scanf("%d", &a);
 | 
						||
printf("Girilen deger: %d\n", a);
 | 
						||
}
 | 
						||
 | 
						||
scanf kullanırken şunlara dikkat etmek gerekir:
 | 
						||
- İki tırnak içerisinde yalnızca format karakteri bulunmalıdır. Orada boşuk karakterleri ya da \n karakteri
 | 
						||
bulunmamalıdır.
 | 
						||
- Değişkenlerin önündeki & operatörü unutulmamalıdır.
 | 
						||
Scanf fonksiyonuyla bir'den fazla değer girilirken girişler arasında istenildiği kadar boşluk karakterleri bırakılabilir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
 | 
						||
20
 | 
						||
 | 
						||
int b;
 | 
						||
printf("Iki sayi giriniz:");
 | 
						||
scanf("%d%d", &a, &b);
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
}
 | 
						||
 | 
						||
printf fonksiyonundaki format karakterleri scanf fonksiyonunda klavyeden girişi belirlemektedir. Yani örneğin biz
 | 
						||
pir int değeri printf ile %x kullanarak yazdırırsak bu değer 16'lık sistemde ekrana yazdırılır. Fakat bir int değeri
 | 
						||
scanf ile %x ile okumak istersek klavyeden yaptığımız girişin 16'lık sistemde olduğu kabul edilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
int a, b;
 | 
						||
printf("Bir sayi giriniz : ");
 | 
						||
scanf("%x", &a);
 | 
						||
printf("a = %d\n", a);
 | 
						||
}
 | 
						||
 | 
						||
printf fonksiyonunda hem float hem de double türleri %f ile yazdırılır. Ancak scanf fonksiyonunda float %f ile,
 | 
						||
double %lf ile okunur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
float f;
 | 
						||
double d;
 | 
						||
printf("float bir deger giriniz:");
 | 
						||
scanf("%f", &f);
 | 
						||
printf("double bir deger giriniz:");
 | 
						||
scanf("%lf", &d);
 | 
						||
printf("f = %f, d = %f\n", f, d);
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması
 | 
						||
İsmi a, b, c olan double türden 3 değişken tanımlayınız. Sonra a ve b için klavyeden scaf fonksiyonuyla okuma
 | 
						||
yapınız. Bu ikisinin toplamını c'ye atayınız ve c'yi yazdırınız.
 | 
						||
Çözümü
 | 
						||
#include <stdio.h>
 | 
						||
main()
 | 
						||
{
 | 
						||
double a, b, c;
 | 
						||
printf("a degerini giriniz :");
 | 
						||
scanf("%lf", &a);
 | 
						||
printf("b degerini giriniz :");
 | 
						||
scanf("%lf", &b);
 | 
						||
c = a + b;
 | 
						||
 | 
						||
21
 | 
						||
 | 
						||
printf("c = %f\n", c);
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonların Geri Dönüş Değerleri (return value)
 | 
						||
Bir fonksiyon çağrıldığında akış fonksiyona gider. Fonksiyonun içerisindeki kodlar çalışır. Fonksiyonun çalışması
 | 
						||
bitince akış kalınan yerden devam eder. İşte fonksiyonun çalışması bittiğinde onu çağıran fonksiyona ilettiği değer
 | 
						||
geri dönüş değeri denilmektedir. Fonksiyonun geri dönüş değerinin de bir türü vardır. Bu tür tanımlama sırasında
 | 
						||
fonksiyonun isminin soluna yazılır. Örneğin:
 | 
						||
double foo()
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
long bar()
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
Fonksiyonun geri dönüş değeri yerine birşey yazılmazsa sanki int yazılmış gibi işlem görür. Yani örneğin:
 | 
						||
foo()
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
ile
 | 
						||
int foo()
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
tamamen aynı anlamdadır. C99 ve C11'de geri dönüş değerinin yazılmaması seçeneği kaldırılmıştır. C'nin bu yeni
 | 
						||
versiyonlarında fonksiyonların geri dönüş değerlerinin türleri yazılmak zorundadır.
 | 
						||
Örneğin:
 | 
						||
result = foo() * 2;
 | 
						||
Burada önce foo fonksiyonu çağrılır, geri dönüş değeri elde edilir, ikiyle çarpılır ve result değişkenine atanır.
 | 
						||
Fonksiyonun geri dönüş değeri return deyimiyle oluşturulur. return deyiminin genel biçimi şöyledir:
 | 
						||
return [ifade];
 | 
						||
Programın akışı return anahtar sözcüğünü gördüğünde önce ifadenin değeri hesaplanır, sonra fonksiyon bu değerle
 | 
						||
sonlandırılır. Yani return deyiminin iki işlevi vardır:
 | 
						||
1) Fonksiyonu sonlandırır
 | 
						||
2) Geri dönüş değerini oluşturur
 | 
						||
22
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int foo()
 | 
						||
{
 | 
						||
printf("Foo\n");
 | 
						||
return 100;
 | 
						||
printf("test\n");
 | 
						||
 | 
						||
/* unreachable code! */
 | 
						||
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
int result;
 | 
						||
result = foo() * 2;
 | 
						||
printf("%d\n", result);
 | 
						||
}
 | 
						||
Anahtar Notlar: Linux Sanal makinaya nasıl kurulur? Öncelikle sanal makina programını bilgisayarımıza yüklememiz gerekir. Bunun
 | 
						||
için iki önemli seçenek vardır. Birincisi VMWare firmasının VMWare programı, ikincisi open source VirtualBox programı. VMWare
 | 
						||
normalde paralı bir programdır. Fakat bunun VMWare Player isminde bedava bir sürümü de vardır. Sanal makinaya Linux kurulur
 | 
						||
(örneğin Mint sürümü). Kuruum doğrudan ISO dosyasıyla yapılabilir. Kurulum sırasında bizden bir user name ve password istenecktir
 | 
						||
(Örneğin derste yaptığımız kurulumda user name = csd ve password = csd1993 verdirk.)
 | 
						||
 | 
						||
C'de bir fonksiyonun geri dönüş değeri olduğu halde return ile belli bir değere geri dönülmezse geri dönüş değeri
 | 
						||
olarak çöp bir değer elde edilir. (Halbuki C# ve Java gibi bazı dillerde fonksiyonların geri dönüş değeri varsa
 | 
						||
return kullanmak zorunludur.)
 | 
						||
C'de main fonksiyonun geri dönüş değeri int olmak zorundadır. Yani örneğin void, long vs. olamaz. Ayrıca main
 | 
						||
fonksiyonunda hiç return kullanmazsak sanki 0 ile geri dönmüş gibi işlem görür. Yani main fonksiyonunda return
 | 
						||
kullanmamakla ana bloğun sonunda 0 ile geri dönmek aynı anlamdadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf("I am main\n");
 | 
						||
return 0;
 | 
						||
 | 
						||
/* bu olmasaydı da aynı anlam oluşacaktı */
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonun geri dönüş değeri yerine void anahtar sözcüğü yazılırsa bu durum fonksiyonun geri dönüş değerinin
 | 
						||
olmadığı anlamına gelir. Böyle fonksiyonlara void fonksiyonlar denir. void fonksiyonlarda return kullanılabilir
 | 
						||
fakat yanına ifade yazılamaz. void fonksiyonlarda return kullanılmamışsa fonksiyon ana blok sonlanınca sonlanır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo()
 | 
						||
{
 | 
						||
printf("foo\n");
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
 | 
						||
23
 | 
						||
 | 
						||
printf("I am main\n");
 | 
						||
foo();
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonun geri dönüş değeri return işlemi sırasında önce geçici bir değişkene aktaraılır, oradan alınarak
 | 
						||
kullanılır. Örneğin:
 | 
						||
return 100;
 | 
						||
----> temp = 100;
 | 
						||
...
 | 
						||
result = foo() * 2---> result = temp * 2;
 | 
						||
Aslında return işlemi geçici değişkene yapılan atama işlemidir. Fonksiyonun geri dönüş değerinin türü de geçici
 | 
						||
değişkenin türüdür. Geçici değişken derleyici tarafından return işlemi sırasında yaratılır, kullanım bittiğinde yok
 | 
						||
edilir. return işlemi de bir çeşit atama işlemidir.
 | 
						||
Değişkenlerin Faaliyet Alanları (Scope)
 | 
						||
Bir değişkenin derleyici tarafından tanınabildiği program aralığına faaliyet alanı (scope) denilmektedir.
 | 
						||
Değişkenler belirli faliyet alanlarına sahiptir. C'de 3 çeşit faaliyet alanı vardır:
 | 
						||
1) Blok faaliyet alanı (block scope)
 | 
						||
2) Fonksiyon faaliyet alanı (function scope)
 | 
						||
3) Dosya faaliyet alanı (file scope)
 | 
						||
Bir fonksiyonun bir ana bloğu olmak zorundadır. O ana bloğun içerisinde istenildiği kadar çok iç içe ya da ayrık
 | 
						||
blok açılabilir. Örneğin:
 | 
						||
void foo()
 | 
						||
{
 | 
						||
{
 | 
						||
...
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
}
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
}
 | 
						||
C'de (C++, C# ve Java'da da öyle) iç içe fonksiyon bildirilemez. Örneğin:
 | 
						||
void foo()
 | 
						||
{
 | 
						||
...
 | 
						||
void bar()
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
...
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
24
 | 
						||
 | 
						||
}
 | 
						||
Blok faaliyet alanı yalnızca bir blokta ve o bloğun kapsadığı bloklarda tanınma alanıdır. Fonksiyon faaliyet faaliyet
 | 
						||
alanı tek bir fonksiyonun her yerinde tanınma aralığıdır. Dosya faaliyet alanı bir dosyanın her yerinde tanınma
 | 
						||
aralığıdır.
 | 
						||
Yerel Değişkenlerin Faaliyet Alanları
 | 
						||
Yerel değişkenler blok faaliyet alanı kuralına uyarlar. Yerel değişken hangi blokta bildirilmişs yalnızca o blokta ve
 | 
						||
bloğun kapsadığı bloklarda kullanılabilir. Örneğin:
 | 
						||
void foo()
 | 
						||
{
 | 
						||
int a;
 | 
						||
{
 | 
						||
int b;
 | 
						||
/* a ve b burada kullanılabilir */
 | 
						||
}
 | 
						||
/* a burada kullanılabilir, b kullanılamaz */
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
{
 | 
						||
int b;
 | 
						||
b = 20;
 | 
						||
a = 10;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
}
 | 
						||
printf("a = %d\n", a);
 | 
						||
printf("b = %d\n", b);
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* error! */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C'de aynı faaliyet alanına sahip birden fazla aynı isimli değişken tanımlanamaz. Farklı faaliyet alanlarına sahip
 | 
						||
aynı isimli değişkenler tanımlanabilir. Bu durumda örneğin, iç içe yerel bloklarda aynı isimli değişkenler
 | 
						||
tanımlanabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo()
 | 
						||
{
 | 
						||
int a;
 | 
						||
{
 | 
						||
int a;
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
25
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C'de bir blokta bir'den fazla değişken faaliyet gösteriyorsa o blokta o değişken ismi kullanıldığında dar faaliyet
 | 
						||
alanına sahip olan değişkene erişilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = 10;
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = 20;
 | 
						||
printf("%d\n", a);/* 20 */
 | 
						||
}
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 10 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Global Değişkenlerin Faaliyet Alanı
 | 
						||
Bildirimleri fonksiyonların dışında yapılan değişkenlere global değişkenler denir. Global değişkenler dosya faaliyet
 | 
						||
alanına (file scope) sahiptir. Yani tüm fonksiyonlarda tanınırlar.
 | 
						||
#include <stdio.h>
 | 
						||
int a;
 | 
						||
void foo()
 | 
						||
{
 | 
						||
a = 10;
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
a = 20;
 | 
						||
printf("%d\n", a);
 | 
						||
foo();
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 20 */
 | 
						||
/* 10 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir global değişkenle aynı isimli yerel değişkenler tanımlanabilir. Çünkü bunlar farklı faaliyet alanlarına sahiptir.
 | 
						||
Tabi ilgili blokta bu değişken ismi kullanıldığında dar faaliyet alanaına sahip olana (yani yerel olana) erişilir.
 | 
						||
26
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int a;
 | 
						||
void foo()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = 10;
 | 
						||
 | 
						||
/* yerel olan a */
 | 
						||
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
a = 20;
 | 
						||
/* global olan a */
 | 
						||
printf("%d\n", a);
 | 
						||
/* 20 */
 | 
						||
foo();
 | 
						||
printf("%d\n", a);
 | 
						||
/* 20 */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C'de derleme işleminin bir yönü vardır. Bu yön yukarıdan aşağıya doğrudur. Derleyicinin önce değişkenin
 | 
						||
bildirimini görmesi gerekir. Bu nedenle bir global değişkeni aşağıda bildirip daha yukarıda kullanamayız. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo()
 | 
						||
{
 | 
						||
a = 10;
 | 
						||
}
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
int a;
 | 
						||
int main()
 | 
						||
{
 | 
						||
a = 10;
 | 
						||
/* geçerli */
 | 
						||
printf("%d\n", a);
 | 
						||
/* geçerli */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu durumda global değişkenler için en iyi bildirim yeri programın tepesidir.
 | 
						||
Fonksiyonların Parametre Değişkenleri
 | 
						||
Fonksiyonlar onları çağıran fonksiyonlardan değerler alabilirler. Bunlara fonksiyonların parametre değişkenleri
 | 
						||
(parameters) denilmektedir. Parametre değişkenlerini bildirmenin C'de iki yolu vardır: Eski biçim (old style) ve
 | 
						||
yeni biçim (modern style). Biz kursumuzda hiç eski biçimi kullanmayacağız. Bu eski biçim çok eskiden
 | 
						||
kullanılıyordu. Yeni biçim ortaya çıkınca programcılar bunu kullanmaz oldular. C standartları oluşturulduğunda
 | 
						||
her iki biçimi de destekledi. Eski biçim C++'ta C99'da ve C11'de desteklenmemektedir.
 | 
						||
Eski biçimde parametre bildirimi yapılırken önce parametre parantezlerinin içerisine parametre değişkenlerinin
 | 
						||
isimleri aralarına virgül atomu getirilerek listelenir. Daha sonra henüz ana blok açılmadan bunların bildirimleri
 | 
						||
yapılır. Örneğin:
 | 
						||
void foo(a, b, c)
 | 
						||
int a;
 | 
						||
 | 
						||
27
 | 
						||
 | 
						||
long b, c;
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Yeni biçimde parametre değişkenleri parametre parantezinin içerisinde tür belirtilerek bildirilir. Örneğin:
 | 
						||
void foo(int a, long b, long c)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Yeni biçimde parametre değişkenleri aynı türden olsa bile her defasında tür belirten atomları yeniden belirtmek
 | 
						||
gerekir. Örneğin:
 | 
						||
void foo(int a, b)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
bildirimin şöyle yapılması gerekirdi:
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Parametreli bir fonksiyon çağrılırken parametre sayısı kadar argüman kullanılır. Örneğin:
 | 
						||
foo(10, 20);
 | 
						||
Argümanlar ',' operatörü ile birbirlerinden ayrılmaktadır. Argüman olarak herhangi birer ifade (expression)
 | 
						||
kullanılabilir. Örneğin:
 | 
						||
foo(a + b - 10, c + d + 20);
 | 
						||
Anahtar Notlar: Bir fonksiyon çağrılırken parametre parantezinin içerisine yazılan ifadelere argüman (argument), fonksiyonun parametre
 | 
						||
değişkenlerine kısaca parametre (parameter) denilmektedir.
 | 
						||
 | 
						||
Parametreli bir fonksiyon çağrıldığında önce argümanların değerleri hesaplanır. Sonra argümanlardan parametre
 | 
						||
değişkenlerine karşılıklı bir atama yapılır. Sonra programın akışı fonkisyona geçer. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
int x = 10, y = 20;
 | 
						||
foo(x, y);
 | 
						||
foo(x + 10, y + 20);
 | 
						||
foo(100, 200);
 | 
						||
return 0;
 | 
						||
 | 
						||
28
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Fonskiyonların parametre değişkenleri faaliyet alanı bakımından sanki ana bloğun başınd tanımlanmış değişkenler
 | 
						||
gibi işlem görür. Yani fonksiyonların parameyre değişkenleri o fonksiyonda tanınabilmektedir. Aynı faaliyet
 | 
						||
alanına sahip aynı isimli değişken tanımlanamayacağına göre aşağıdaki bildirim de geçerli değildir:
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
long a;
 | 
						||
/* geçerli değil! */
 | 
						||
...
 | 
						||
}
 | 
						||
Fakat:
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
...
 | 
						||
{
 | 
						||
long a;
 | 
						||
// geçerli */
 | 
						||
...
 | 
						||
}
 | 
						||
...
 | 
						||
}
 | 
						||
Bir fonksiyon parametreleriyle aldığı değere işlemler uygulayıp sonucu geri dönüş değeri olarak verebilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int add(int a, int b)
 | 
						||
{
 | 
						||
return a + b;
 | 
						||
}
 | 
						||
int mul(int a, int b)
 | 
						||
{
 | 
						||
return a * b;
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
int result;
 | 
						||
result = add(10, 20);
 | 
						||
printf("%d\n", result);
 | 
						||
result = mul(10, 20);
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bazı Matematiksel Standart C Fonksiyonları
 | 
						||
C'de temel bazı matemetik işlemleri yapan çeşitli standart C fonksiyonları vardır. Bu fonksiyonları kullanırken
 | 
						||
<math.h> dosyası include edilmelidir.
 | 
						||
29
 | 
						||
 | 
						||
- sqrt fonksiyonu double bir parametreye sahiptir, parametresi ile aldığı değerin karekökünü geri dönüş değeri
 | 
						||
olarak verir.
 | 
						||
double sqrt(double val);
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double val;
 | 
						||
double result;
 | 
						||
printf("Lutfen bir sayi giriniz:");
 | 
						||
scanf("%lf", &val);
 | 
						||
result = sqrt(val);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
pow fonksiyonu üs almak için kullanılır. Parametrik yapısı şöyledir:
 | 
						||
double pow(double base, double e);
 | 
						||
Bu fonksiyon birinci parametresiyle belirtilen değerin ikinci parametresiyle belirtilen kuvvetini alır ve onu geri
 | 
						||
dönüş değeri olarak verir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = pow(3, 4);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
log fonksiyonu e tabanına göre log10 fonksiyonu 10 tabanına göre logaritma alır:
 | 
						||
double log(double val);
 | 
						||
double log10(double val);
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
 | 
						||
30
 | 
						||
 | 
						||
result = log10(1000);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sin, cos, asin, acos, tan, atan fonksiyonları trigonometrik işlemleri yapar. Bu fonksiyonlardaki açılar radyan
 | 
						||
cinsinden girilmelidir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = sin(3.141592653589793238462643 / 6);
 | 
						||
printf("%f\n", result);
 | 
						||
result = cos(3.141592653589793238462643 / 3);
 | 
						||
printf("%f\n", result);
 | 
						||
result = sin(3.141592653589793238462643 / 4) / cos(3.141592653589793238462643 / 4);
 | 
						||
printf("%f\n", result);
 | 
						||
result = tan(3.141592653589793238462643 / 2);
 | 
						||
printf("%f\n", result);
 | 
						||
result = tan(3.141592653589793238462643 / 4);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
exp fonksiyonu e üzeri işlemi yapmaktadır.
 | 
						||
Sabitler (Literals)
 | 
						||
Program içerisinde doğrudan yazılan sayılara sabit denir. C'de yalnızca nesnelerin değil, aynı zamanda sabitlerin de
 | 
						||
türü vardır. Sabitin türü onun niceliğiyle ve sonuna getirilen eklerle ilgilidir. Kurallar şöyledir:
 | 
						||
1) Sayı nokta içermiyorsa ve sayının sonunda hiçbir ek yoksa sabit int, long ve unsigned long türlerinin hangisinin
 | 
						||
sınırları içerisinde ilk kez kalıyorsa o türdendir. Örneğin:
 | 
						||
0 ---> int
 | 
						||
100 ---> int
 | 
						||
long türünün 8 byte int türünün 4 byte olduğunu varsayalım:
 | 
						||
123 ---> int
 | 
						||
3000000000 ---> long
 | 
						||
Eğer sayı 16'lık ya da 8'lik sistemde belirtilmişse sabit int, unsigned int, long ve unsigned long türlerinin hangisinin
 | 
						||
sınırları içerisinde ilk kez kalıyorsa sabit o türdendir.
 | 
						||
2) Sayı nokta içermiyorsa ve sayının sonuna küçük harf ya da büyük harf L getirilmişse sabit long ve unsigned
 | 
						||
long türlerinin hangisinin sınırları içerisinde ilk kez giriyorsa o türdendir. Örneğin:
 | 
						||
31
 | 
						||
 | 
						||
100L ---> long
 | 
						||
0L ---> long
 | 
						||
3) Sayı nokta içermiyorsa ve sayının sonunda küçük harf ya da büyük harf U varsa sabit unsigned int ve unsigned
 | 
						||
long türlerinin hangisinin sınırları içerisinde ilk kez kalıyorsa o türdendir. Örneğin:
 | 
						||
123U ---> unsigned int
 | 
						||
1000u ---> unsigned int
 | 
						||
4) Sayı nokta içermiyorsa ve sayının sonunda küçük harf ya da büyük harf UL ya da LU varsa (UL, Ul, uL, LU, Lu,
 | 
						||
ul, lu) sabit unsigned long türündendir.
 | 
						||
100ul ---> unsigned long
 | 
						||
1LU ---> unsigned long
 | 
						||
5) Sayı nokta içeriyorsa ve sayının sonında hiçbir ek yoksa sabit double türdendir. Örneğin:
 | 
						||
123.65 ---> double
 | 
						||
0.5 ---> double
 | 
						||
Pek çok programlama dilinde olduğu gibi C'de de noktanın solunda ya da sağında birşey yoksa sıfır olduğu
 | 
						||
varsayılır. Örneğin:
 | 
						||
.10 ---> double
 | 
						||
10 ---> int
 | 
						||
10. ---> double
 | 
						||
6) Sayı nokta içeriyorsa ve sayının sonunda küçük harf ya da büyük harf F varsa sabit float türdendir. Örneğin:
 | 
						||
12.34F ---> float
 | 
						||
10F ---> geçersiz
 | 
						||
10.f ---> float
 | 
						||
7) Sayı nokta içeriyorsa ve sonunda küçük harf ya da büyük harf L varsa sabit long double türdendir. Örneğin:
 | 
						||
100.23L ---> long double
 | 
						||
10.l ---> long double
 | 
						||
.10L ---> long double
 | 
						||
10L ---> long
 | 
						||
8) Tek tırnak içerisine bri karakter yerleştirilirse bu bir sayı belirtir. O karakterin karakter tablosundaki sıra
 | 
						||
numarasını belirtir. (Örneğin tipik olarak ASCII.) Örneğin 'a' bir sabit belirtir. Yani aslında 'a' bir sayıdır. ASCII
 | 
						||
tablosu kullanılıyorsa 97 anlamındadır. Örneğin:
 | 
						||
x = 'A' + 1;
 | 
						||
Burada x'e 66 atanmaktadır. 66 da 'B' nin ASCII kodudur.
 | 
						||
Anahtar Notlar: ASCII tablosunda önce büyük hrfler, sonra küçük harfler gelir. Büyük harflerle küçük harfler arasında 6 farklı karakter
 | 
						||
vardır. Bu özellikle 'a' - 'A' farkının 32 etmesi için düşünülmüştür.
 | 
						||
Anahtar Notlar: UNICODE tablonun ilk 128 elemanı standart ASCII tablosusun aynısıdır. ikinci 128'lik elemanı ASCII Latin-1 code
 | 
						||
page'i ile aynıdır.
 | 
						||
 | 
						||
32
 | 
						||
 | 
						||
C'de tek tırnak içerisine int türünün byte uzunluğu kadar karakter yazılabilir (Örneğin şu andaki yaygın sistemlerde
 | 
						||
int türü 4 byte uzunluğundadır. Bu nedenle tek tırnak içerisine 4 karakter yazılabilir.) Tek tırnak içerisine bir'den
 | 
						||
fazla karakter yazılırsa bunun hangi sayı anlamına geleceği derleyicileri yazanların isteğine bırakılmıştır. Yani C'de
 | 
						||
örneğin 'ab' ifadesi geçerlidir. Fakat bunun hangi sayıyı belirttiği derleyiciden derleyiciye değişebilir. Genellikle
 | 
						||
tek tırnak içerisinde tek bir karakter yerleştiririz.
 | 
						||
C'de tek tırnak içerisine yerleştirilen sabitler int türdendir (halbuki C++'ta char türdendir.)
 | 
						||
Karakter tablolarındaki tüm karakterler görüntülenebilir değildir. Bu tür görüntülenemeyen karakterleri (nonprintable characters) ekrana basmak istediğimzde ekranda birşey göremeyiz. Bazı olaylar gerçekleşir. Tek tırnak
 | 
						||
içerisinde önce bir ters bölü ve sonra özel bazı karakterler bazı görüntülenemeyen özel karakterleri belirtir.
 | 
						||
Bunların listesi şöyledir:
 | 
						||
Karakter
 | 
						||
 | 
						||
Anlamı
 | 
						||
 | 
						||
'\a'
 | 
						||
 | 
						||
Alert (ekrana gönderildiğinde beep sesi çıkar)
 | 
						||
 | 
						||
'\b'
 | 
						||
 | 
						||
Back Space (ekrana gönderildiğinde sanki geri tuşuna basılmış gibi imleç bir
 | 
						||
geriye gider)
 | 
						||
 | 
						||
'\f'
 | 
						||
 | 
						||
Form Feed (bu karakter yazıcıya gönderildiğinde bir sayfa atar)
 | 
						||
 | 
						||
'\n'
 | 
						||
 | 
						||
New Line (ekrana gönderildiğinde imleç aşağı satırın başına geçer)
 | 
						||
 | 
						||
'\r'
 | 
						||
 | 
						||
Carriage Return (ekrana göndeildiğinde imleç aynı satırın başına geçer)
 | 
						||
 | 
						||
'\t'
 | 
						||
 | 
						||
Tab (ekrana gönderildiğinde imleç bir tab atar)
 | 
						||
 | 
						||
'\v'
 | 
						||
 | 
						||
Vertical Tab (ekrana gönderildiğinde düşey zıplama yapar)
 | 
						||
 | 
						||
Bu ters bölü karakterleri iki tırnak içerisinde tek bir karakter belirtirler. Örneğin:
 | 
						||
printf("ali\tveli\nselami\tayse\n");
 | 
						||
 | 
						||
Ters bölü karakterinin kendisi '\\' ile belirtilir. '\' ifadesi geçersizdir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf("c:\temp\a.dat\n");
 | 
						||
printf("c:\\temp\\a.dat\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Tek tırnak karakterinin karakter sabiti ''' biçiminde yazılamaz. '\'' biçiminde belirtilir. Fakat çift tırnak içerisinde tek
 | 
						||
tırnak soruna yol açmaz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
char ch = '\'';
 | 
						||
printf("%c\n", ch);
 | 
						||
printf("Turkiye'nin baskenti Ankara'dir\n");
 | 
						||
/* geçerli */
 | 
						||
printf("Turkiye\'nin baskenti Ankara\'dir\n"); /* geçerli */
 | 
						||
 | 
						||
33
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İki tırnak karakterine ilişkin karakter sabiti '\”' ile belirtilir. Fakat tek tırnak içerisinde iki tırnak soruna yol açmaz.
 | 
						||
Ancak iki tırnak içerisinde iki tırnak soruna yol açar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
char ch1 = '\"';
 | 
						||
char ch2 = '"';
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
printf("%c, %c\n", ch1, ch2);
 | 
						||
printf("\"Ankara\"\n"); /* geçerli */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Karakter sabitleri tek tırnak içerisinde önce bir ters bölü sonra oktal digit'ler belirtilerek de yazılabilir. Örneğin '\7'
 | 
						||
karakteri '\a' karakteri ile ASCII tablosunda aynı değer sahiptir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
char ch = '\11';
 | 
						||
printf("Ankara%cIzmir\n", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Karaktre sabitleri tek tırnak içerisinde önce bir ters bölü sonra küçük harf bir x sonra da hex digit'ler belirtilerek de
 | 
						||
yazılabilir. Örneğin '\x1B' gibi. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf("Ankara\x9Izmir\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İki tırnak içerisinde oktal ve hex sistemde karakterleri yazarken dikkat etmek gerekir. Çünkü derleyici anlamlı en
 | 
						||
uzun karakter kümesinden ters bölü karakterlerini oluşturur. Yani örneğin “6Ankara\x91Adana” stringinde ters
 | 
						||
bölü karakteri '\x9' biçiminde değil '\x91A' biçiminde ele alınır. Fakat “6Ankara\x9\x31\x41\x64\x61na\n"
 | 
						||
istediğimizi yapabilir.
 | 
						||
9) C'de short, unsigned short, char, signed char ve unsigned char türünden karakter sabitleri yoktur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf("6Ankara\x9\x31\x41\x64\x61na\n");
 | 
						||
 | 
						||
34
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Anahtar Notlar: C'de tek tırnak içerisinde yazılan sabitlere karakter sabitleri denir fakat bunlar int türdendir. Yani örneğin C'de 'a' bir
 | 
						||
karakter sabitidir fakat int türdendir. Oysa C++'ta tek tırnak içerisindeki karakterler (eğer tek tırnak içerisinde tek karakter varsa) char
 | 
						||
türdendir.
 | 
						||
 | 
						||
Tamsayıların 10'luk, 8'lik ve 16'lık Sistemde Belirtilmesi
 | 
						||
C'de bir sayı yazdığımızda onu default olarak 10'luk sistemde yazdığımız anlaşılır. eğer sayıyı 0x ya da 0X ile
 | 
						||
başlatarak yazarsak sayının 16'lık sistemde yazılmış olduğukabul edilir. Örneğin:
 | 
						||
int a, b;
 | 
						||
a = 100;
 | 
						||
b = 0x64;
 | 
						||
Eğer sayıyı başına 0 getirerek yazarsak 8'lik sistem anlaşılır. Örneğin:
 | 
						||
a = 0144;
 | 
						||
Sabitin kaçlık sistemde yazıldığının sabit türüyle bir ilgisi yoktur. Örneğin 0x64 sabiti yine int türden 0x64L sabiti
 | 
						||
long türdendir. Ayrıca C90'da float, double ve long double türleri 16'lık ve 8'lik sistemde belirtilemezler. Ancak
 | 
						||
C99'da 16'lık sistemde belirtilebilirler.
 | 
						||
Gerçek Sayıların Üstel Biçimde Belirtilmesi
 | 
						||
float, double ve long double türden sabitler üstel biçimde yazılabilirler. Üstel biçimin genel biçimi şöyledir:
 | 
						||
<noktalı ya da noktasız sayı><e ya da E>[±]<üs>;
 | 
						||
Örneğin 1e20, 1.2e-50, -2.3e+15 gibi...
 | 
						||
Sayıyı üstel biçimde belirtmişsek sayı sonuna ek almamışsa double türdendir. Örneği 1E20 sabiti double türdendir.
 | 
						||
Ancak 1E20F float, 1E20L long double türdendir.
 | 
						||
Üsetel biçim özellikle çok büyük ve çok küçük sayıları ifad etmek için kullanılır.
 | 
						||
C90'da ve C99'da ikilik sistemde sayılar belirtilememektedir.
 | 
						||
Operatörler
 | 
						||
Bir işleme yol açan ve işlem sonucunda bir değer üretimesini sağlayan atomlara operatör denir. Operatörlerin
 | 
						||
teknik olarak tanımlamak için onları sınıflandıracağız. Operatörler üç biçimde sınıflandırılabilir:
 | 
						||
1) İşlevlerine Göre Sınıflandırma
 | 
						||
2) Operand Sayılarına Göre
 | 
						||
3) Operatörün Konumuna Göre
 | 
						||
1) İşlevlerine Göre Sınıflandırma: Bu sınıflandırma operatörün hangi tür işlem yaptığına yönelik bir
 | 
						||
sınıflandırmadır. Tipik olarak operatör şu sınıflardan birine ilişkin olabilir:
 | 
						||
- Aritmetik Operatörler (Arithmetic Operators): Bunlar +, -, * ve / gibi dört işleme yönelik operatörlerdir.
 | 
						||
35
 | 
						||
 | 
						||
- Karşılaştırma Operatörleri (Comparision / Relational Operators): Bunlar karşılaştırma için kullanılan
 | 
						||
operatörledir. Örneğin >, <, >=, <= gibi.
 | 
						||
- Mantıksal Operatörler (Logical Operators): Bunlar AND, OR, NOT gibi mantıksal işlem yapan operatörlerdir.
 | 
						||
- Bit Operatörleri (Bitwise Operators): Bunlar sayıların karşılıklı bitleri üzerinde işlem yapan operatörlerdir.
 | 
						||
- Adres Operatörleri (Pointer Operators): Bunlar adres işlemi yapan operatörlerdir.
 | 
						||
Özel Amaçlı Operatörler (Special Purpose Operators): Bunlar çeşitli konulara ilişkin özel işlem yapan
 | 
						||
operatörlerdir.
 | 
						||
2) Operand Sayılarına Göre Sınıflandırma: Operatörün işleme soktuğu ifadelere operand denilmektedir.
 | 
						||
Örneğin a + b ifadesinde + operatördür, a ve b bunun operandlarıdır. Operatörler tek operand, iki operand ve üç
 | 
						||
operand alabilir. Tek operand alan operatörlere İngilizce “unary”, iki operand alan operatörlere “binary” ve üç
 | 
						||
operand alan operatörler “ternary” operatör denilmektedir. Örneğin ! tek operandlı (unary bir operatördür ve !a
 | 
						||
biçiminde kullanılır. / iki operandlı bir operatördür.
 | 
						||
3) Operatörün Konumuna Göre Sınıflandırma: Operatör operandlarının önünde, sonunda ya da arasında
 | 
						||
bulunabilir. Opeandlarının önüne getirilerek kullanılan operatörlere “önek (prefix) operatörler, arasına getirilerek
 | 
						||
kullanılan operatörlere “araek (infix) operatörler” ve operandlarının sonuna getirilerek kullanılan operatörlere
 | 
						||
“sonek (postfix)” operatörler denilmektedir.
 | 
						||
Bir operatörü teknik olarak tanımlayabilmek için öncelikle yukarıdaki üç sınıflandırma biçiminde de nereye
 | 
						||
düştüğünün belirtilmesi gerekir. Örneğin “! operatörü tek operandlı önek (unary prefix) mantıksal operatördür” gibi.
 | 
						||
Ya da “/ operatörü iki operandlı araek (binry indfix) aritmetik operatördür” gibi...
 | 
						||
Operatörler Arasındaki Öncelik İlişkisi
 | 
						||
Bir ifadedeki operatörler belli bir sırada seri olarak yapılır. Örneğin:
 | 
						||
a = b + c * d;
 | 
						||
İ1: c * d
 | 
						||
İ2: b + İ1
 | 
						||
İ3: a = İ2
 | 
						||
Derleyiciler ifadedeki işlemi yapacak makine komutlarını seri olarak üretirler. Run time sırasında işlemci onları
 | 
						||
sırasıyla çalıştırır.
 | 
						||
Operatörler arasındaki öncelik ilişkisi “Operatörlerin Öncelik Tablosu” denilen bir tabloyla betimlenmektedir.
 | 
						||
Operatörlerin Öncelik Tablosunun basit bir biçimi aşağıdaki gibidir:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
* /
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Tablo satırlardan oluşur. Üst satırdaki operatörler alt satırdaki operatörlerden daha yüksek önceliklidir. Aynı
 | 
						||
satırdaki operatörler eşit önceliklidir. Aynı satırdaki operatörler “Soldan-Sağa” ya da “Sağdan-Sola” eşit öncelikli
 | 
						||
36
 | 
						||
 | 
						||
olabilirler. “Soldan-Sağa” öncelik demek, ifade içerisinde “o satırda solda olan önce yapılır” demektir. “SağdanSola” ise “ifade içerisinde o satırda sağda olan önce yapılır” demektir.
 | 
						||
Tablonun en yüksek öncelik grubunda fonksiyon çağırma operatörü ve öncelik parantezi vardır. Örneğin:
 | 
						||
result = foo() + bar() * 2;
 | 
						||
İ1: foo()
 | 
						||
İ2: bar()
 | 
						||
İ3: İ2 * 2
 | 
						||
İ4: İ1 + İ3
 | 
						||
İ5: result = İ4
 | 
						||
Parantezler öncelik kazandırmak için kullanılabilirler. Örneğin:
 | 
						||
a = (b + c) * d;
 | 
						||
İ1: b + c
 | 
						||
İ2: İ1 * d
 | 
						||
İ3 = a = İ2
 | 
						||
*, /, + ve - Operatörleri
 | 
						||
Bu operatörler iki operandlı araek (binary infix) aritmetik operatörlerdir. Temel dört işlemi yaparlar.
 | 
						||
% Operatörü
 | 
						||
İki operandlı araek aritmetik operatördür. Bu operatör soldaki oprandın sağdaki operanda bölümünden elde edilen
 | 
						||
kalan değerini verir. Öncelik tablosunda * ve / ile aynı gruptadır.
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int result;
 | 
						||
result = 10 % 4 - 1;
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
% operatörünün her iki operandının da tamsayı türlerine ilişkin olması zorunludur. Örneğin 15.2 % 2 ifadesi geçerli
 | 
						||
değildir. Negatif sayının prozitif sayıya bölümünden elde edilen kalan negatiftir. (Örneğin -10 % 4 ifadesi -2
 | 
						||
sonucunu verir).
 | 
						||
37
 | 
						||
 | 
						||
İşaret - ve İşaret + Operatörleri
 | 
						||
+ ve - sembolleri hem toplama ve çıkarma operatörleri için hem de işret - ve işaret + işlemleri için kullanılmaktadır.
 | 
						||
Aynı sembollere sahip olsalar da bu operatörlerin birbirleriyle hiçbir ilgisi yoktur. Örneğin:
 | 
						||
a = -3 - 5;
 | 
						||
Burada soldaki - işaret eksi operatörü, sağdaki ise çıkartma operatörüdür. İşaret + ve işaret - operatörleri tek
 | 
						||
operandlı önek aritmetik operatörlerdir. Öncelik tablosunun ikinci düzeyinde Sağdan-Sola grupta bulunurlar.
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Örneğin:
 | 
						||
a = -3 - 5;
 | 
						||
İ1:-3
 | 
						||
İ2: İ1 - 5
 | 
						||
İ3: a = İ2
 | 
						||
Örneğin:
 | 
						||
a = - - b + 2;
 | 
						||
İ1: -b
 | 
						||
İ2: -İ1
 | 
						||
İ3: İ2 + 2
 | 
						||
İ4: a = İ3
 | 
						||
İşaret - operatörü operandının negatif değerini üretir. İşaret + operatörü ise operandıyla aynı değeri üretir. (Yani
 | 
						||
aslında birşey yapmaz.). Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int result;
 | 
						||
result = - - - - - - - - - - - - - - - -3 - 5;
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Tanımsız, Belirsiz ve Derleyiciye Bağlı Davranışlar
 | 
						||
C standartlarında bazı kodların tanımsız davranışa yol açacağı (undefined behavior) belirtilmiştir. Tanımsız
 | 
						||
davranışa yol açan kodlamalar derleme aşamasında bir soruna yol açmazlar. Ancak programın çalışma zamanı
 | 
						||
38
 | 
						||
 | 
						||
sırasında programın çökmesine, yanlış çalışmasına yol açabilirler. Bazen tanımsız davranışa yol açan kodlar hiçbir
 | 
						||
soruna yol amayabilirler. Programcının böylesi farkında olması ve bunlardan uzak durması gerekir.
 | 
						||
Yine standartlarda bazı durumlar için belirsiz davranışın (unspecified behavior) oluşacağı söylenmiştir. Belirsiz
 | 
						||
davranışa yol açan kodlarda birkaç seçenek vardır. Derleyici yazanlar bu seçenklerden birini tercih etmiş
 | 
						||
durumdadırlar. Ancak hangi seçeneği tercih ettiklerini belirtmek zorunda değillerdir. Derleyici hangi seçeneği
 | 
						||
seçmiş olursa olsun bu kodun çökmesine yol açmaz. Ancak belirsiz davranışa yol açan kodlar taşınabilirliği
 | 
						||
bozarlar. Yani bu programlar başka sistemlerde derlendiğinde farklı sonuçlar elde edilebilir. En iyisi belirsiz
 | 
						||
davranış sonucunda farklı sonuçların oluşacağı tarzda kodlardan kaçınmaktadır.
 | 
						||
Standartlarda bazı durumlardaki belirlemeler derleyiciyi yazanların isteğine bırakılmıştır (implementation defined
 | 
						||
behavior). Fakat derleyici yazanlar bunu referans kitaplarında açıkça belirtmek zorundadır.
 | 
						||
Örneğin tahsis edi,lmemiş bir alan erişilmesi tanımsız davranışa yol açan bir işlemdir. C'de fonksiyon
 | 
						||
çağrıldığındanda parametre aktarımının soldan sağa mı ya da sağdan sola mı yapılacağı belirsiz davranışa sahiptir.
 | 
						||
Büyük tamsayı türünden işaretli küçük tamsayı türüne dönüştürme yapıldığında bilgi kaybının nasıl olacağı
 | 
						||
derleyiciyi yazanların isteğine bırakılmıştır.
 | 
						||
++ ve -- Operatörleri
 | 
						||
Bu operatörler tek operandlı önek ve sonek kullanılabilen operatörlerdir. Yani ++a ya da a++ biçiminde
 | 
						||
kullanılabilirler. ++ operatörüne artırma (increment), -- operatörüne eksiltme (decrement) operatörü denilmektedir.
 | 
						||
++ operatörü “operand içerisindeki değeri 1 artır”, -- operatörü “operand içerisindeki değeri 1 esksilt” anlamına
 | 
						||
gelir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = 3;
 | 
						||
++a;
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 4 */
 | 
						||
 | 
						||
a = 3;
 | 
						||
--a;
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 2 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
++ ve -- operatörleri öncelik tablosunun ikinci düzeyinde sağdan sola grupta bulunurlar.
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ --
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Bu operatörlerin önek ve sonek kullanımları arasında farklılık vardır. Her iki kullanımda da artırma ya da eksiltme
 | 
						||
tablodaki öncelikle yapılır. Önek kullanımda sonraki işleme nesnenin artırılmış ya da eksiltilmiş değeri sokulurken,
 | 
						||
sonek kullanımda artırılmamış ya da eksiltilmemiş değeri sokulur. Örneğin:
 | 
						||
39
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a, b;
 | 
						||
a = 3;
 | 
						||
b = ++a * 2;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
 | 
						||
/* a = 4, b = 8 */
 | 
						||
 | 
						||
a = 3;
 | 
						||
b = a++ * 2;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
 | 
						||
/* a = 4, b = 6 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a, b;
 | 
						||
a = 3;
 | 
						||
b = ++a;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
 | 
						||
/* a = 4, b = 4 */
 | 
						||
 | 
						||
a = 3;
 | 
						||
b = a++;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
 | 
						||
/* a = 4, b = 3 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a, b, c;
 | 
						||
a = 3;
 | 
						||
b = 2;
 | 
						||
c = ++a * b--;
 | 
						||
printf("a = %d, b = %d, c = %d\n", a, b, c); /* a = 4, b = 1, c = 8 */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şüphesiz bu operatörler tek başlarına başka bir operatör olmaksızın kullanılıyorsa, bunların önek ve sonek
 | 
						||
biçimleri arasında bir fark oluşmaz. Yani örneğin:
 | 
						||
++a;
 | 
						||
ile
 | 
						||
40
 | 
						||
 | 
						||
a++;
 | 
						||
arasında bir fark oluşmaz.
 | 
						||
C'de bir nesne bir ifadede ++ ya da -- operatörüyle kullanılmışsa artık aynı ifadede bir daha o nesnenin
 | 
						||
görülmemesi gerekir. Aksi halde tanımsız davranış oluşur. Örneğin aşağıdaki gibi ifadeleri kullanmamalıyız.
 | 
						||
Burada ne olacağı belli değildir:
 | 
						||
b = ++a + a;
 | 
						||
b = ++a + ++a;
 | 
						||
b = ++b;
 | 
						||
b = a + a++;
 | 
						||
++ ve -- operatörlerinin operandlarının nesne belirtmesi gerekir. Örneğin:
 | 
						||
++3;
 | 
						||
 | 
						||
ifadesi geçerli değildir. Ya da örneğin:
 | 
						||
b = ++(a - 2);
 | 
						||
 | 
						||
iafdesi de geçersizdir. Çünkü a - 2 bir nesne belirtmemektedir.
 | 
						||
Bu operatörlerden elde edilen ürün nesne belirtmez. Örneğin:
 | 
						||
b = ++a--;
 | 
						||
 | 
						||
gibi bir ifade geçersizdir. Burada a--'den elde edilen ürün nesne belirtmez.
 | 
						||
Karşılaştırma Operatörleri
 | 
						||
C'de 6 karşılaştırma operatörü vardır:
 | 
						||
<, >, <=, >=
 | 
						||
==, !=
 | 
						||
Bu operatörlerin hepsi iki operandlı araek (binary infix) operatörlerdir. Öncelik tablosunda karşılaştırma
 | 
						||
operatörleri aritmetik operatörlerden daha düşük önceliklidir:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ --
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Bu operatörler önerme doğruysa 1 değerini, yanlışsa 0 değerini üretirler. Bu operatörlerin ürettiği bu değerler int
 | 
						||
türdendir ve istenirse başka işlemlere sokulabilirler. Örneğin:
 | 
						||
41
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a = 3, b;
 | 
						||
b = (a > 2) + 1;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* 2 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Karşılaştırma operatörleri kendi aralarında iki öncelik grubunda bulunmaktadır. Örneğin:
 | 
						||
result = a == b > c;
 | 
						||
İ1: b > c
 | 
						||
İ2: a == İ1
 | 
						||
İ3: result = İ2
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a = 3, b;
 | 
						||
b = a++ == 3;
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a = 20, b;
 | 
						||
b = 0 < a < 10;
 | 
						||
/* dikkat bu ifade a'nın 0 ile 10 arasında olduğuna bakamaz */
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Mantıksal Operatörler
 | 
						||
C'de üç mantıksal operatör vardır:
 | 
						||
! (NOT)
 | 
						||
&& (AND)
 | 
						||
|| (OR)
 | 
						||
! operatörü öncelik tablosunun ikinci düzeyinde Sağdan-Sola grupta bulunur. && ve || operatörleri karşılaştırma
 | 
						||
operatörlerinden daha düşük önceliklidir:
 | 
						||
42
 | 
						||
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- !
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
=
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
Anahtar Notlar: C'nin tüm tek operandlı (unary) operatörleri öncelik tablosunun ikinci düzeyinde Sağdan-Sola grupta bulunmaktadır.
 | 
						||
 | 
						||
! operatörü tek operandlı önek, && ve || operatörleri iki operandlı araek operatörlerdir.
 | 
						||
AND, OR ve NOT işlemleri operand olarak Doğru, Yanlış Değerlerini alır. Bu mantıksal işlemler aşağıdaki gibi
 | 
						||
etki gösterirler:
 | 
						||
A
 | 
						||
 | 
						||
B
 | 
						||
 | 
						||
A AND B A OR B
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
A
 | 
						||
 | 
						||
NOT A
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Yanlış
 | 
						||
 | 
						||
Doğru
 | 
						||
 | 
						||
C'de bool türü yoktur. Peki Doğru ve Yanlış'lar nasıl ifade edilmektedir?
 | 
						||
İşte !, && ve || operatörleri önce operandlarını Doğru ya da Yanlış olarak yorumlarlar. Sıfır dışı değerler (tamsayı
 | 
						||
da gerçek sayı türünden) C'de Doğru olarak, sıfır değeri Yanlış olarak ele alınır. Bu operatörler yine Doğru sonuç
 | 
						||
için 1 değerini, yanlış sonuç için 0 değerini üretirler. Örneğin:
 | 
						||
-2.3 && 10.2
 | 
						||
0 || 10
 | 
						||
10.2 && 0
 | 
						||
0.2 && 0.2
 | 
						||
 | 
						||
=> 1
 | 
						||
=> 1
 | 
						||
=> 0
 | 
						||
=> 1
 | 
						||
 | 
						||
Tabi genellikle mantıksal operatörler doğrudan sayısal değerlerle değil, karşılaştırma operatörlerinin çıktıklarıyla
 | 
						||
işleme sokulurlar. Örneğin:
 | 
						||
result = a > 10 && a < 20;
 | 
						||
Burada iki koşul da doğruysa && operatörü 1 değerini üretecektir. Aksi halde 0 değeri üretilecektir. Örneğin:
 | 
						||
43
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
int result;
 | 
						||
printf("a:");
 | 
						||
scanf("%d", &a);
 | 
						||
result = a > 10 && a < 20;
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Anahtar Notlar: Microsoft'un C derleyicileri belirli bir versiyondan sonra bazı standart C fonksiyonları için “Unsafe” uyarısı vermektedir.
 | 
						||
Üstelik de proje yaratılırkenm SDL Check disable edilmemişse bu uyarı error'e dönüştürülmektedir. Bu “Unsafe” uyarıları ne anlama gelir?
 | 
						||
Kısaca bu uyarılarda “bu fonksiyonlar esasen standart C fonksiyonlarıdır fakat tasarımlarında küçük birtakım kusurlar vardır”. Evet her ne
 | 
						||
kadar Microsoft bunda haklıysa da yine de bu fonksiyon çağırılarını Uyarı olarak değerlendirmesi hoş değildir. Eğer programcı bu uyarı
 | 
						||
mesajlarından kurtulmak istiyorsa iki yol deniyebilir. Birincisi Proje ayarlarından C/C++/Preprocessor kısmına gelip “Preprocesssor
 | 
						||
Definitions” listesine _CRT_SECURE_NO_WARNINGS makrosunu eklemektir. İkincisi de Proje ayarlarından C/C++/General tabına
 | 
						||
gelip Uyarı “düzeylerini (Warning Level)” 2'ye çekmektir.
 | 
						||
Anahtar Notlar: Eğer proje yaratılırken yanlışlıkla SDL Check çarpısı kaldırılmamışsa bu işlem sonradan da yapılabilir. Bunun için proje
 | 
						||
ayarlarına gelinir. C/C++/General tabından “SDL Checks” disable edilir.
 | 
						||
Anahtar Notlar: Linux ortamında C ile program geliştirmek için hiç IDE kullanılmayabilir. Program iyi bir Editör'de (Örneğin Kate)
 | 
						||
yazılıp, komut satırında aşağıdaki gibi derlenebilir:
 | 
						||
gcc -o sample sample.c
 | 
						||
Şöyle çalıştırılabilir:
 | 
						||
./sample
 | 
						||
IDE olarak Linux sistemlerinde şu seçenler dikkate değerdir:
 | 
						||
- MonoDevelop (Mono-core ve MonoDevlop paketleri kurulmalı)
 | 
						||
- QtCreator
 | 
						||
- Eclipse (C/C++ için)
 | 
						||
- Netbeans (C/C++ için)
 | 
						||
MonoDevelop çalışma biçimi olarak Visual Studio'ya en fazla benzeyen IDE'dir ve kullanımı çok basittir.
 | 
						||
Anahtar Notlar: UNIX/Linux sistemlerinde komut satırı çalışması hala en yaygın çalışma biçimidir. Çünkü bu sistemler ağırlıklı olarak
 | 
						||
server amaçlı kullanılmaktadır (Server kullanım oranı %60-70 civarı, Client kullanım oranı %1-%2 civarındadır.) Linux komutlarının belli
 | 
						||
bir düzeyde öğrenilmesi tavsiye edilir. Bunun için pek çok kitap vardır.
 | 
						||
 | 
						||
Kısa devre özelliği “eğer işlemler hiç bu özellik olmadan yapılsaydı” ile aynı sonucu oluşturur. Yani kısa devre
 | 
						||
özelliği aslında öncelik tablosunda belirtilen sırada işlemlerin yapılması durumunda elde edilecek olan değerin
 | 
						||
daha hızlı elde edilmesini sağlar.
 | 
						||
&& ve || operatörleri aynı ifade içerisinde bulunuyorsa her zaman sol taraftaki operatörün sol tarafı önce yapılır.
 | 
						||
Duruma göre diğer taraflar yapılmaktadır. Örneğin:
 | 
						||
result = foo() || bar() && tar();
 | 
						||
 | 
						||
Burada önce foo yapılır. foo sıfır dışı ise başka hiçbir şey yapılmaz ve sonuç 1 olarak elde edilir. Eğer foo sıfır ise
 | 
						||
44
 | 
						||
 | 
						||
bu durumda bar yapılır. bar da sıfır ise tar yapılmaz. Örneğin:
 | 
						||
result = foo() && bar() || tar();
 | 
						||
 | 
						||
Burada önce foo yapılır. foo sıfır ise bar yapılmaz fakat tar yapılır. foo sıfır dışı ise bar yapılır. bar da sıfır dışı ise
 | 
						||
tar yapılmaz.
 | 
						||
Atama Operatörü
 | 
						||
Atama operatörü iki operandlı araek özel amaçlı bir operatördür. Bu operatör sağdaki ifadenin değerini soldaki
 | 
						||
nesneye yerleştirir. Atama operatörü de bir değer üretmektedir. Eğer atama operatörü ifadenin son operatörü
 | 
						||
değilse ondan elde edilen değeri diğer operatörlere sokabiliriz. Atama operatörü sol taraftaki nesneye atanmış olan
 | 
						||
değeri üretir. Atama operatörünün öncelik tablousunda Sağdan-Sola grupta bulunduğuna dikkat ediniz. Örneğin:
 | 
						||
a = b = 10;
 | 
						||
İ1: b = 10 => 10
 | 
						||
İ2: a = İ1 => 10
 | 
						||
Örneğin:
 | 
						||
a = (b = 10) + 20;
 | 
						||
İ1: b = 10
 | 
						||
İ2: İ1 + 20
 | 
						||
İ3: a = İ2
 | 
						||
Atama operatörünün sol tarafındaki operand nesne belirtmek zorudadır (yani LValue olmak zorundadır). Örneğin:
 | 
						||
a + b = c;
 | 
						||
10 = 20;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Aşağıdaki gibi bir ifade de geçerlidir:
 | 
						||
foo(a = 10);
 | 
						||
Burada önce a = 10 ifadesinin değeri hesaplanır, sonra bu değer foo fonksiyonunun parametre değişkenine atanır.
 | 
						||
İşlemli Atama Operatörleri (Compound Assignment Operators)
 | 
						||
C'de bir grup +=, -=, *=, /=, %=, .... gibi işlemli atama operatörü vardır. a <op>= b ifadesi ile a = a <op> b ifadesi
 | 
						||
tamamen eşdeğerdir. Örneğin:
 | 
						||
a += 2;
 | 
						||
a *= 3;
 | 
						||
 | 
						||
/* a = a + 2; */
 | 
						||
/* a = a * 3; */
 | 
						||
 | 
						||
İşlemli atama operataörlerinin hepsi öncelik tablosunda atama operatörüyle Sağdan-Sola aynı öncelik grubunda
 | 
						||
bulunmaktadır:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- !
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
45
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
= += -= *= /= %=, ... Sağdan-Sola
 | 
						||
Örneğin:
 | 
						||
a *= 2 + 3;
 | 
						||
İ1: 2 + 3;
 | 
						||
İ2: a *= İ1;
 | 
						||
 | 
						||
Virgül Operatörü
 | 
						||
Virgül atomu aynı zamanda iki operandlı araek bir operatör belirtir. Virgül operatörü iki farklı ifadeyi tek ifade gibi
 | 
						||
ifade etmek için kullanılır. Örneğin:
 | 
						||
a = 10, b = 20 tek bir ifadedir. Çünkü virgül de bir operatördür. Virgül operatörü özel bir operatördür. Klasik
 | 
						||
öncelik tablosu kuralına uymaz. Ancak öncelik tablosunda tablonun sonuna yerleştirilmiştir:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- !
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
= += -= *= /= %=, ... Sağdan-Sola
 | 
						||
,
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
Virgül operatörünün sağında ne olursa olsun önce onun sol tarafındaki ifade tamamen yapılıp bitirilir. Sonra
 | 
						||
sağındaki ifade tamamen yapılı bitirilir. Virgül operatörü de bir değer üretir. Virgül operatörünün ürettiği değer sağ
 | 
						||
tafındaki ifadenin değeridir. Virgülün solundaki ifadenin değer üretmekte bir etkisi yoktır. Örneğin:
 | 
						||
a = (b = 10, 20 + 30);
 | 
						||
 | 
						||
Burada parantezler olmasaydı virgül operatörünün sol tarafındaki operand a = b = 10 ifadesi olacaktı. Parantezler
 | 
						||
virgül operatörünü diğer operatörlerden ayrıştırmaktadır. Burada önce parantez içi yapılır. Parantez içerisinde
 | 
						||
virgül operatörü vardır. Onun da önce sol tarafı sonra sağ tarafı yapılır. Virgül operatörünün hepsinden elde edilen
 | 
						||
değer sağ tarafındaki ifadenin değeridir. Yani yukarıdaki örnekte a'ya 50 atanacaktır.
 | 
						||
Bir'den fazla virgül operatörü de kombine edilebilir. Örneğin:
 | 
						||
x = (foo(), bar(), tar());
 | 
						||
 | 
						||
46
 | 
						||
 | 
						||
İ1: foo(), bar()
 | 
						||
İ2: İ1, tar()
 | 
						||
İ3: x = İ2
 | 
						||
 | 
						||
Her virgülü de virgül operatörü sanmamak gerekir. Örneğin:
 | 
						||
int a, b, c;
 | 
						||
Buradaki virgüller ayıraç görevindedir. Örneğin:
 | 
						||
foo(a, b);
 | 
						||
Buradaki virgül de argüman ayıracı görevindedir. Ancak ekstra bir parantez virgülü operatör durumuna sokar.
 | 
						||
Örneğin:
 | 
						||
foo((a, b), c);
 | 
						||
Burada fonksiyonun iki parametre değişkeni vardır. Birinci parametre değişkenine b, ikincisine c kopyalanır.
 | 
						||
Noktalı Virgülün İşlevi
 | 
						||
Noktalı virgül ifadeleri birbirlerinden ayırarak onların bağımsız bir biçimde ele alınmasını sağlar. Örneğin:
 | 
						||
a = 10 + 20;
 | 
						||
b = 30 + 40;
 | 
						||
Burada a = 10 + 20 ile b = 30 + 40'ın bir ilişkisi yontur. Bunlar iki ayrı ifade belirtirler. İfadeleri ayırmak için
 | 
						||
kullanılan bu tür atomlara sonlandırıcı (terminator) denilmektedir. Eğer noktalı virgül unutulursa derleyici önceki
 | 
						||
ifadeyle sonraki ifadeyi tek bir bağımsız ifade olarak ele alır o da sentaks bakımından soruna yol açar. Örneğin:
 | 
						||
a = 10 + 20
 | 
						||
b = 30 + 40;
 | 
						||
 | 
						||
/* noktalı virgül unutulmuş */
 | 
						||
 | 
						||
Burada derleyiciye göre tek bir ifade vardır o da anlamsızdır.
 | 
						||
Bazı dillerde sonlandırıcı olarak : bazılarında da \n kullanılabilmektedir. Örneğin BASIC'te aslında \n karakteri
 | 
						||
(ENTER tuşuna basılınca elde edilen karakter) sonlandırıcı görevindedir. Tabibudurumda her satıra tek bir ifade
 | 
						||
yazılmak zorundadır.
 | 
						||
Deyimler (Statements)
 | 
						||
Bir programdaki çalıştırma birimlerine deyim denir. Imperative dillerin teorisinde program deyimlerin çalışmasıyla
 | 
						||
çalışır. Deyim deyimi içerebilir. Deyimler 5 gruba ayrılmaktadır:
 | 
						||
1) Basit Deyimler (Simple Statements): Bir ifadenin sonuna ; getirilirse artık o atom grubu bir deyim olur. Bu tür
 | 
						||
deyimlere basit deyimler denir. Yani basit deyimler “ifade;” biçimindeki deyimlerdir. Örneğin:
 | 
						||
x = 10;
 | 
						||
foo();
 | 
						||
birer basit deyimdir. Görüldüğü gibi ifade kavramı noktalı birgülü içermemektedir. İfadenin sonuna noktalı virgül
 | 
						||
konulunca o deyim olur.
 | 
						||
47
 | 
						||
 | 
						||
2) Bileşik Deyimler (Compund Statements): C'de bir bloğun içerisine sıfır tane ya da daha fazla deyim
 | 
						||
yerleştirilirse o bloğun tamamı da bir deyim olur. Bunlara bileşik deyim denilmektedir. Bileşik deyimi diğer
 | 
						||
deyimleri içeren deyimler gibi (yani büyük kutu gibi) düşünebiliriz. Örneğin:
 | 
						||
ifade1;
 | 
						||
{
 | 
						||
ifade2;
 | 
						||
{
 | 
						||
ifade3;
 | 
						||
ifade4;
 | 
						||
}
 | 
						||
ifade5;
 | 
						||
}
 | 
						||
ifade6;
 | 
						||
Burada dışarıdan bakıldığında 3 deyim vardır: İkisi basit biri bileşik deyim. Bileşik deyimin içerisinde 3 tane
 | 
						||
deyim bulunmaktadır.
 | 
						||
3) Kontrol Deyimleri (Control Statements): if gibi, for gibi, while gibi akış üzerinde etkili olan özel deyimlere
 | 
						||
kontrol deyimleri denilmektedir. Zaten ilerleyen bölümde bu kontrol deyimleri tek tek ele alınıp incelenecektir.
 | 
						||
4) Bildirim Deyimleri (Declaration Statements): Bildirim yapmakta kullanılan sentaks yapıları da aslında deyim
 | 
						||
belirmektedir. Bunlara bildirim deyimleri denir. Örneğin:
 | 
						||
int a, b;
 | 
						||
int x;
 | 
						||
a = 10;
 | 
						||
b = 20;
 | 
						||
Burada iki bildirim deyimi, iki de basit deyim vardır.
 | 
						||
5) Boş Dyimler (Null Statements): Solunda ifade olmayan noktalı virgüllere boş deyim denilmektedir. Örneğin:
 | 
						||
x = 10;;
 | 
						||
Buradaki ilk noktalı virgül ifadeyi deyim yapan sonlandırıcıdır. Fakat ikinci noktalı virgülün solunda bir ifade
 | 
						||
yoktur. Boş deyimler “hiçbirşey;” gibi düşünülebilir. Boş deyim bir etkiye yol açmasa da yine de bir deyim olarak
 | 
						||
değerlendirilir. yukarıdki örnekte tolam iki deyim vardır.
 | 
						||
Her deyim çalıştığında birşeyler olur:
 | 
						||
1) Bir basit deyimin çalıştırılması demek o deyimi oluşturan ifadenin yapılması demektir.
 | 
						||
2) Bir bileşik deyimin çalıştırılması demek onu oluşturan deyimlerin tek tek çalıştırılması demektir. Örneğin:
 | 
						||
{
 | 
						||
ifade1;
 | 
						||
{
 | 
						||
ifade2;
 | 
						||
ifade3;
 | 
						||
48
 | 
						||
 | 
						||
}
 | 
						||
ifade4;
 | 
						||
}
 | 
						||
Burada bu bileşik deyim çalıştırıldığında önce ifade1; basit deyimi çalıştırılır. Sonra içerideki bileşik deyim
 | 
						||
çalıştırılır. Bu da ifade2; ve ifade3; basit deyimlerinin çalıştırışması anlamına gelir. Sonra da ifade4; basit deyimi
 | 
						||
çalıştırılır. Özetle bir bileşik deyim çalıştırıldığında onu oluşturan deyimler yukarıdan aşağıya doğru çalıştırılır.
 | 
						||
Aslında her fonksiyon ana bloğuyla bir bileşik deyim belirtir. Bir fonksiyon çağrıldığında o fonksiyonun ana
 | 
						||
bloğuna ilişkin bileşik deyim çalıştırılır. O halde her şey main fonksiyonun çağrılmasıyla başlar. Bir C programının
 | 
						||
çalıştırılması demek main fonksiyonunun çağrılması demektir. main fonksiyonu programı yükleyen işletim sistemi
 | 
						||
tarafından çağrılır.
 | 
						||
3) Bir kontrol deyimi çalıştırıldığında nelerin olacağı izleyen bölümde ele alınmaktadır.
 | 
						||
4) Bir bildirim deyimi çalıştırıldığında bildirilen değişkenler için yer ayrılır.
 | 
						||
5) Boş deyim karşısında derleyiciler hiçbir şey yapmaz.
 | 
						||
Kontrol Deyimleri
 | 
						||
Programın akışı üzerinde etkili olan deyimler kontrol deyimleri denir. Burada C'deki kontrol deyimleri başlıklar
 | 
						||
haliden ele alınacaktır.
 | 
						||
if Deyimi
 | 
						||
if deyiminin genel biçimi şöyledir:
 | 
						||
if (<ifade>)
 | 
						||
<deyim>
 | 
						||
[
 | 
						||
else
 | 
						||
<deyim>
 | 
						||
]
 | 
						||
if anahtar sözcüğünden sonra parantezler içerisinde bir ifadenin bulunması zorunludur. if deyimi doğruysa ve
 | 
						||
yanlışsa kısımlarından oluşur. Doruysa ve yanlışsa kısımlarında tek bir deyim bulunmak zorundadır. Eğer
 | 
						||
programcı bu kısımlarda birden fazla deyim bulundurmak istiyorsa onu bileşik deyim olarak ifade etmelidir (yani
 | 
						||
bloklamalıdır). if deyiminin yanlışsa kısmı bulunmak zorunda değildir. if deyiminin tamamı tek bir deyimdir.
 | 
						||
if deyimi şöyle çalışır: Önce if parantezi içerisindeki ifadenin sayısal değeri hesaplanır. Bu değer sıfır dışı ise if
 | 
						||
deyiminin doğruysa kısmı, sıfır ise yanlışsa kısmı çalıştırlır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &a);
 | 
						||
if (a > 0)
 | 
						||
printf("pozitif\n");
 | 
						||
else
 | 
						||
printf("negatif ya da sifir\n");
 | 
						||
 | 
						||
49
 | 
						||
 | 
						||
printf("son\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
double a, b, c;
 | 
						||
double delta;
 | 
						||
printf("a:");
 | 
						||
scanf("%lf", &a);
 | 
						||
printf("b:");
 | 
						||
scanf("%lf", &b);
 | 
						||
printf("c:");
 | 
						||
scanf("%lf", &c);
 | 
						||
delta = b * b - 4 * a * c;
 | 
						||
if (delta < 0)
 | 
						||
printf("Kok yok!\n");
 | 
						||
else {
 | 
						||
double x1, x2;
 | 
						||
x1 = (-b + sqrt(delta)) / (2 * a);
 | 
						||
x2 = (-b - sqrt(delta)) / (2 * a);
 | 
						||
printf("x1 = %f, x2 = %f\n", x1, x2);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İf deyiminin doğruysa kısmı bulunmak zorundadır. Ancak yanlışsa kısmı bulunmak zorunda değildir. Örneğin:
 | 
						||
if (ifade1)
 | 
						||
ifade2; ifade3;
 | 
						||
Burada if deyiminin doğruysa kısmında yalnızca ifade; deyimi vardır. ifade3; deyimi if dışındadır. Örneğin:
 | 
						||
if (ifade1) {
 | 
						||
ifade2;
 | 
						||
ifade3;
 | 
						||
}
 | 
						||
ifade4;
 | 
						||
Burada ifade4; deyimi if dışındadır. Derleyici if deyiminin doğruysa kısmı bittiğinde else anahtar sözcüğünün olup
 | 
						||
olmadığına bakar. eğer else anahtar sözcüğü yoksa if deyimi bitmiştir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
 | 
						||
50
 | 
						||
 | 
						||
{
 | 
						||
int a;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &a);
 | 
						||
if (a > 0)
 | 
						||
printf("pozitif\n");
 | 
						||
printf("son\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
if deyiminin yanlışlıkla boş deyim ile kapatılması sık rastalanan bir hatadır. Örneğğin:
 | 
						||
if (a > 0);
 | 
						||
/* dikkat yanlışlıkla yerleştirilmiş boş deyim */
 | 
						||
printf("pozitif\n");
 | 
						||
 | 
						||
Boş deyim için bir şey yapılmıyor olsa da boş deyim yine bir deyimdir.
 | 
						||
Yalnızca yanlışsa kısmı olan bir if olamaz. Fakat bunu aşağıdaki gibi yapay bir biçimde oluşturabiliriz:
 | 
						||
if (ifade1)
 | 
						||
;
 | 
						||
else
 | 
						||
ifade2;
 | 
						||
Tabi bunun yerine soruyu ters çevirmek daha iyi bir tekniktir:
 | 
						||
if (!ifade1)
 | 
						||
ifade2;
 | 
						||
Aşağıdaki if cümlesinde derleyici hatayı nasıl tespit edecektir?
 | 
						||
if (ifade1)
 | 
						||
ifade2;
 | 
						||
ifade3;
 | 
						||
else
 | 
						||
ifade4;
 | 
						||
Bu bize if deyiminin doğruysa kısmında birden fazla deyim olduğu halde bloklama yapılmamış hatası izlenimini
 | 
						||
vermektedir. Oysa derleyiciye göre ifade’den sonra if deyimi bitmiştir. Dolayısıyla if olmadan kullanılan bir else
 | 
						||
söz konusudur. Örneğin Microsoft derleyicileri bu durumda şu error mesajını vermektedir: “illegal else without
 | 
						||
matching if”.
 | 
						||
İç içe if Deyimleri (Nested if Statements)
 | 
						||
Bir if deyiminin doğruysa ya da yanlışsa kısmında başka bir ifa deyimi bulunabilir. Örneğin:
 | 
						||
 | 
						||
51
 | 
						||
 | 
						||
Bu akış diagramının if cümlesi şöyle yazılabilir:
 | 
						||
if (ifade1)
 | 
						||
if (ifade2) {
 | 
						||
ifade3;
 | 
						||
ifade4;
 | 
						||
}
 | 
						||
else
 | 
						||
ifade5;
 | 
						||
else
 | 
						||
ifade6;
 | 
						||
 | 
						||
İki if için tek bir else varsa else hangi if’e ilişkindir. Örneğin:
 | 
						||
if (ifade1) if (ifade2) ifade3; else ifade4;
 | 
						||
 | 
						||
Görünüşe bakılırsa burada iki durum da sanki mümkünmüş gibidir. else birinci if’in else kısmı da olabilir (yani
 | 
						||
ikinci if else’siz olabilir), ikinci if’in else’i de olabilir (yani birinsi if else’sizdir). Bu duruma “dangling else”
 | 
						||
denilmektedir. Burada else içteki if’e ilişkindir. Deneyimli programcılar bile aşağdaki gibi hatalı kod
 | 
						||
yazabilemktedir:
 | 
						||
if (ifade1)
 | 
						||
if (ifade2)
 | 
						||
ifede3;
 | 
						||
else
 | 
						||
ifade4;
 | 
						||
 | 
						||
Burada else’in gerçekten birinci if’e ilişkin olması isteniyorsa bilinçli bloklama yapılmalıdır:
 | 
						||
if (ifade1) {
 | 
						||
if (ifade2)
 | 
						||
ifede3;
 | 
						||
}
 | 
						||
else
 | 
						||
ifade4;
 | 
						||
 | 
						||
Ayrık Koşullar
 | 
						||
Bir grup koşuldan herhangi birisi doğruyken diğerlerinin doğru olma olasılığı yoksa bu koşullara ayrık koşullar
 | 
						||
52
 | 
						||
 | 
						||
denilmektedir. Örneğin:
 | 
						||
a > 0
 | 
						||
a < 0
 | 
						||
 | 
						||
koşulları ayrıktır. Örneğin:
 | 
						||
a == 0
 | 
						||
a > 0
 | 
						||
a < 0
 | 
						||
 | 
						||
koşulları ayrıktır. Örneğin:
 | 
						||
a == 1
 | 
						||
a == 2
 | 
						||
a == 3
 | 
						||
 | 
						||
koşulları ayrıktır. Fakat:
 | 
						||
a > 0
 | 
						||
a > 10
 | 
						||
 | 
						||
koşulları ayrık değildir.
 | 
						||
Ayrık koşulların ayrık if’lerle ifade edilmesi kötü bir tekniktir. Örneğin:
 | 
						||
if (a > 0)
 | 
						||
printf("pozitif\n");
 | 
						||
if (a < 0)
 | 
						||
printf("negatif\n");
 | 
						||
 | 
						||
Burada a > 0 ise a < 0 olma olasılığı yoktur. Fakat gereksiz bir biçimde yine bu koşul yapılacaktır. Ayrık koşulların
 | 
						||
else if’lerle ifade edilmesi gerekir. Örneğin:
 | 
						||
if (a > 0)
 | 
						||
printf("pozitif\n");
 | 
						||
else
 | 
						||
if (a < 0)
 | 
						||
printf("negatif\n");
 | 
						||
 | 
						||
Burada artık a > 0 ise gereksiz bir biçimde a < 0 işlemi yapılmayacaktır.
 | 
						||
Bazen else-if durumu bir merdiven haline gelebilir. Örneğin:
 | 
						||
if (a == 1)
 | 
						||
printf("bir\n");
 | 
						||
else if (a == 2)
 | 
						||
printf("iki\n");
 | 
						||
else if (a == 3)
 | 
						||
printf("uc\n");
 | 
						||
else if (a == 4)
 | 
						||
printf("dort\n");
 | 
						||
else
 | 
						||
printf("hic biri\n");
 | 
						||
 | 
						||
else if merdivenlerinde olasılığı yüksek olanları yukarıya yerleştirmek daha iyi bir tekniktir.
 | 
						||
53
 | 
						||
 | 
						||
Klavyeden Karakter Okumak
 | 
						||
Klavyeden scanf fonksiyonuyla %c formatıyla karakter okuyabiliriz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
printf("bir karakter giriniz:");
 | 
						||
scanf("%c", &ch);
 | 
						||
printf("girilen karakter: %c\n", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fakat karakter okumak için getchar isimli standart bir C fonksiyonu da vardır:
 | 
						||
int getchar(void);
 | 
						||
 | 
						||
Fonksiyon bir tuşa basılıp ENTER tuşuna basılana kadar bekler. Basılan tuşun karakter tablosundaki sıra
 | 
						||
numarasına geri döner. Geri dönüş değeri aslında bir byte’lık karakterin kod numarasıdır. Tipik olarak char türden
 | 
						||
bir nesneye atanabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
ch = getchar();
 | 
						||
printf("girilen karakter: %c\n", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
stdin dosyasından okuma yapan fonksiyonlar tamponlu (buffered) bir çalışmaya sahiptir. Bu çalışma -bazı detayları
 | 
						||
olmakla birlikte- kabaca şöyledir: getchar fonksiyonuyla biz bişeyler girdiğimizde girdiğimiz karakterler ve ilave
 | 
						||
olarak \n karakteri önce bir tampona aktarılır. Sonra oaradan sırasıyla alınır. (En son ‘\n’ alınacaktır.) Eğer
 | 
						||
tamponda karakter varsa getchar klavyden birşey istemez. Tampondan alır. Ancak tamponda hiçbir karakter
 | 
						||
kalmamaışsa getchar klavyeden değer ister. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
ch = getchar();
 | 
						||
printf("girilen karakter: %c\n", ch);
 | 
						||
ch = getchar();
 | 
						||
printf("girilen karakter: %c\n", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
54
 | 
						||
 | 
						||
Burada birinci getchar’da biz k karaktereine basıp ENTER tuşuna basmış olalım. Tamponda k\n karakterleri
 | 
						||
bulunacaktır. Birinci getchar k’yı alacak ikincisi hiç beklemeden ‘\n’ karakterini alacaktır.
 | 
						||
Sınıf Çalışması: Klavyeden getchar fonksiyonuyla bir karakter okuyunuz. Karakter ‘a’ ise ali, ‘b’ ise burhan, ‘c’
 | 
						||
ise cemal ‘d’ ise demir, ‘e’ ise ercan ve bunların dışında bir karakterse hiçbiri yazısını bastırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
ch = getchar();
 | 
						||
if (ch == 'a')
 | 
						||
printf("ali\n");
 | 
						||
else if (ch == 'b')
 | 
						||
printf("burhan\n");
 | 
						||
else if (ch == 'c')
 | 
						||
printf("cemal\n");
 | 
						||
else if (ch == 'd')
 | 
						||
printf("demir\n");
 | 
						||
else if (ch == 'e')
 | 
						||
printf("ercan\n");
 | 
						||
else
 | 
						||
printf("hicbiri\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Anahtar Notlar: Standartlar asgariyi belirlemek için oluşturulmuştur. Standartlara uygun bir C derleyicisinin ekstra özellikleri olabilir.
 | 
						||
Bunlara eklenti (extension) denilmektedir. Eklentiler sentaksa ilişkin olabileceği gibi kütüphaneye ilişkin de olabilir. Eklenti özellikleri
 | 
						||
kullanırken dikkatli olmalıyız. Çünkü bunlar her derleyicide bulunmak zorunda olmayan özelliklerdir. Bu durumda kodumuzun
 | 
						||
taşınabilirliği (portability) zarar görür. Ancak bazı eklentiler çok yaygın bir biçimde desteklenmektedir. Hatta bu eklentileri bazı
 | 
						||
programcılar standart özellik dahi sanabilmektedir.
 | 
						||
 | 
						||
Klavyeden karakter okuyan diğer bir fonksiyon da getch fonksiyonudur. getch standart bir C fonksiyonu değildir.
 | 
						||
Pek çok C derleyicisind bulunan bir eklenti fonksiyondur. Microsoft C derleyicilerinde ve gcc derleyicilerinde bu
 | 
						||
fonksiyon bulunmaktadır. Fonksiyonun parametrik yapısı şöyledir:
 | 
						||
#include <conio.h>
 | 
						||
int getch(void);
 | 
						||
 | 
						||
Bu fonksiyon ENTER tuşuna gereksinim duymaz ve tuşu basar basmaz alır. Basılan tuş görüntülenmez (biz
 | 
						||
istersek görüntüleriz). Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
printf("bir karakter giriniz:");
 | 
						||
ch = getch();
 | 
						||
 | 
						||
55
 | 
						||
 | 
						||
printf("\ngirilen karakter: %c\n", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bazen programcılar bu fonksiyonu bir tuşa basılana kadar akışı bekletmek için de kullanmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
printf("Press any key to continue...\n");
 | 
						||
getch();
 | 
						||
printf("ok\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Diğer bir karakter okuyan eklenti fonksiyon getche fonksiyonudur. Bu fonksiyonun getch’tan farkı basıldığı
 | 
						||
zaman basılan tuşun da görüntülenmesidir.
 | 
						||
putchar Fonksiyonu
 | 
						||
putchar standart bir C fonksiyonudur. Bu fonksiyon parametresiyle aldığı sayıya karşılık gelen karakter
 | 
						||
tablosundaki karakter görüntüsünü ekrana yazdırır. Örneğin:
 | 
						||
char ch = ‘a’;
 | 
						||
putchar(ch);
 | 
						||
 | 
						||
Burada ekrana a karakteri çıkar. Yani:
 | 
						||
putchar(ch);
 | 
						||
 | 
						||
ile:
 | 
						||
printf(“%c”, ch);
 | 
						||
 | 
						||
aynı etkiyi oluşturur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while ((ch = getch()) != 'q')
 | 
						||
putchar(ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
56
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while ((ch = getchar()) != '\n')
 | 
						||
putchar(ch);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Döngü Deyimleri
 | 
						||
Bir program parçasının yinelemeli olarak çalıştırılmasını sağlayan kontrol deyimlerine döngü (loop) denir. C’de 2
 | 
						||
döngü deyimi vardır: while döngüleri ve for döngüleri. while döngüleri de kendi aralarında “kontrolün başta
 | 
						||
yapıldığı while döngüleri” ve “kontrolün sonda yapıldığı while döngüleri (do-while döngüleri)” olmak üzere ikiye
 | 
						||
ayrılır.
 | 
						||
 | 
						||
Kontrolün başta yapıldığı while döngüleri
 | 
						||
Bu biçimdeki while döngüleri pek çok programlama dilinde benzer biçimde bulunmaktadır. Genel biçimi şöyledir:
 | 
						||
while (<ifade>)
 | 
						||
<deyim>
 | 
						||
while anahtar sözcüğünden sonra parantezler içerisinde bir ifade bulunmak zorundadır. Döngü içerisinde tek bir
 | 
						||
deyim vardır. O yinelenecek olan deyimdir.
 | 
						||
while döngüsü şöyle çalışır: while parantezi içerisindeki ifadenin sayısal değeri hesaplanır. Bu değer sıfır dışı bir
 | 
						||
değerse doğru kabul edilir ve döngü deyimi çalıştırılıp başa dönülür. Bu ifade sıfır ise while deyimi sonlanır. while
 | 
						||
döngüleri paranztez içerisindeki ifade sıfır dışı olduğu sürece yinelenen döngülerdir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 0;
 | 
						||
while (i < 10) {
 | 
						||
printf("%d\n", i);
 | 
						||
++i;
 | 
						||
}
 | 
						||
 | 
						||
57
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 10;
 | 
						||
while (i) {
 | 
						||
printf("%d\n", i);
 | 
						||
--i;
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir değeri atayıp, atanan değeri karşılaştırmak için parantezler kullanılmalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while ((ch = getch()) != 'q')
 | 
						||
printf("%c", ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
while parantezi içerisindeki ifadede virgül operatörü kullanılabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while (ch = getch(), ch != 'q')
 | 
						||
putchar(ch);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada önce ch = getch() ifadesi yapılır, sonra ch != ‘q’ ifadesi yapılır. Test işlemine ch != ‘q’ ifadesi sokulur.
 | 
						||
Anahtar Notlar: ENTER tuşuna basıldığında normal olarak “Carriage Return (CR)” karakterinin sayısal değeri elde edilir. CR
 | 
						||
karakterini ekrana yazdırdığımızda imleç bulunduğu satırın başına geçer. Fakat getchar fonksiyonunda karakter girdikten sonra basılan
 | 
						||
ENTER tuşu için getchar tampona CR değil “LF (Line Feed)” karakterini yerleştirmektedir. LF karakterini ekrana yazdırmak
 | 
						||
istediğimizde imleç aşağı satırın başına geçer. getch ve getche fonksiyonlarında ENTER tuşuna bastığımızda CR karakterini elde edriz.
 | 
						||
ENTER tuşu niyet olarak aşağı satırın başına geçmek amacıyla klavyeye yerleştirilmiş olsa da onun asıl kodu CR’dir. ENTER’a
 | 
						||
bastığımızda CR yerine LF karakterinin elde edilmesi girdi alan fonksiyonların yaptığı bir şeydir. CR karakteri (13 nolu ASCII karakter)
 | 
						||
C’de ‘\r’ ile, LF karakteri de (10 numaralı ASCII karakteri) C’de ‘\n’ ile temsil edilir.
 | 
						||
 | 
						||
58
 | 
						||
 | 
						||
while döngülerinin de yanlışlıkla boş deyim kapatılması durumuyla karşılaşılmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while (ch = getch(), ch != 'q');
 | 
						||
putchar(ch);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada artık döngünün içerisinde boş deyim vardır.
 | 
						||
while parantezinin içerisinde ++ ve -- operatörlerinin kullanılması kafa karıştırabilmektedir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 0;
 | 
						||
while (++i < 10)
 | 
						||
printf("%d ", i); /* 1'den 9'a kadar */
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
while parantezinde sonek bir ++ ya da -- varsa önce artırım ya da eksiltim uygulanır fakat sonraki operatöre
 | 
						||
nesnenin artırılmamış ya da eksiltilmemiş değeri sokulur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 0;
 | 
						||
while (i++ < 10)
 | 
						||
printf("%d ", i); /* 1'den 10'a kadar */
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 10;
 | 
						||
while (--i)
 | 
						||
 | 
						||
59
 | 
						||
 | 
						||
printf("%d ", i); /* 9'dan 1'e kadar */
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i = 10;
 | 
						||
while (i--)
 | 
						||
printf("%d ", i); /* 9'dan 0'a kadar */
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada while parantezi içerisindeki ifadeyi while (i-- != 0) biçiminde değerlendirmek gerekir. Yani burada önce i
 | 
						||
bir eksiltilir fakat teste i’nin eksiltilmemiş değeri sokulur. Başka bir deyişle:
 | 
						||
while (i--) {
 | 
						||
//...
 | 
						||
}
 | 
						||
 | 
						||
ile,
 | 
						||
while (i-- != 0) {
 | 
						||
//...
 | 
						||
}
 | 
						||
 | 
						||
eşdeğer etkiye sahiptir.
 | 
						||
while ile sonsuz döngü aşağıdaki gibi oluşturulabilir:
 | 
						||
while (1) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Kontrolün Sonda Yapıldığı while Döngüleri (do-while Döngüsü)
 | 
						||
Kontrolün sonda yapıldığı while döngüsünün genel b içimi şöyledir:
 | 
						||
do
 | 
						||
<deyim>
 | 
						||
while (ifade);
 | 
						||
 | 
						||
while parantezinin sonundaki noktalı virgül boş deyim belirtmez. Bu noktalı virgül sentaksın bir parçasıdır ve
 | 
						||
orada bulunmak zorundadır. Kontrolün sonda yapıldığı while döngüleri kontrolün başta yapıldığı while
 | 
						||
döngülerinin ters yüz edilmiş bir biçimidir. Ancak başına bir de do anahtar sözcüğü getirilmiştir. do-while
 | 
						||
döngülerinde döngü deyimi en az bir kez yapılmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
60
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
i = 0;
 | 
						||
do {
 | 
						||
printf("%d\n", i);
 | 
						||
++i;
 | 
						||
} while (i < 10);
 | 
						||
 | 
						||
/* 0...9 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Kontrolün başta yapıldığı while döngülerine kontrolün sonda yapıldığı while döngülerine göre çok fazla
 | 
						||
gereksinim duyulur. Fakat bazen kontrolün sonda yapıldığı while döngülerini kullanmak daha anlamlı
 | 
						||
olabilmektedir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
ch = ' ';
 | 
						||
while (ch != 'e' && ch != 'h') {
 | 
						||
printf("(e)vet/(h)ayir?");
 | 
						||
ch = getch();
 | 
						||
printf("%c\n", ch);
 | 
						||
}
 | 
						||
if (ch == 'e')
 | 
						||
printf("evet secildi\n");
 | 
						||
else
 | 
						||
printf("hayir secildi\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada kontrolün sonda yapıldığı while döngüsü daha uygundur:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
do
 | 
						||
{
 | 
						||
printf("(e)vet/(h)ayir?");
 | 
						||
ch = getch();
 | 
						||
printf("%c\n", ch);
 | 
						||
} while (ch != 'e' && ch != 'h');
 | 
						||
if (ch == 'e')
 | 
						||
printf("evet secildi\n");
 | 
						||
else
 | 
						||
printf("hayir secildi\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
61
 | 
						||
 | 
						||
Karakter Test Fonksiyonları
 | 
						||
C’de başı “is” ile başlayan isxxx biçiminde isimlendirilmiş bir grup standart fonksiyon vardır. Bunlara karakter test
 | 
						||
fonksiyonları denilmektedir. Bu fonksiyonların hepsi parametre olarak bir karakter alır ve int türden bir geri dönüş
 | 
						||
değeri verir. Bu fonksiyonlar parametreleriyle aldıkları karakterleri test ederler. Test doğruysa sıfır dışı bir değere
 | 
						||
yanlışsa sıfır değerine geri dönerler. Bu fonksiyonların listesi şöyledir:
 | 
						||
isupper (büyük harf mi?)
 | 
						||
islower (küçük harf mi?)
 | 
						||
isdigit (‘0’ ile ‘9’ arasındaki karakterlerden biri mi?)
 | 
						||
isxdigit (hex karakterlerden biri mi?)
 | 
						||
isalpha (alfebetik karakterlerden biri mi?)
 | 
						||
isalnum (alfabetik ya da nümerik (alfanümerik) karakterlerden biri mi?)
 | 
						||
isspace (boşluk karakterlerinden biri mi? (Space, tab, new line, carriage return, vertical tab))
 | 
						||
isascii (ASCII tablosunun ilk yarısındaki karakterlerden biri mi?)
 | 
						||
iscntrl (İlk 32 kontrol karakterinden biri mi?)
 | 
						||
ispunct (noktalı virgül, nokta vs. gibi karakterlerden biri mi)
 | 
						||
Bu fonksiyonları kullanırken <ctype.h> dosyası include edilmelidir.
 | 
						||
Anahtar Notlar: Önce atama yapıp sonra atanan değerin karşılaştırılmasını istiyorsak parantez kullanmalıyız. Örneğin:
 | 
						||
if ((ch = getch()) != ‘q’) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
#include <ctype.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
while ((ch = getch()) != 'q') {
 | 
						||
printf("%c\n", ch);
 | 
						||
if (isupper(ch))
 | 
						||
printf("buyuk harf\n");
 | 
						||
else if (islower(ch))
 | 
						||
printf("kucuk harf\n");
 | 
						||
else if (isdigit(ch))
 | 
						||
printf("digit karakter\n");
 | 
						||
else if (isspace(ch))
 | 
						||
printf("bosluk karakterlerinden biri\n");
 | 
						||
else if (ispunct(ch))
 | 
						||
printf("noktalama karakterlerinden biri\n");
 | 
						||
else
 | 
						||
printf("diger bir karakter\n");
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Anahtar Notlar: UNIX/Linux sistemlerinde getch ve getche fonksiyonları yalnızca curses kütüphanesinde bulunmaktadır. Bu nedenle o
 | 
						||
kütüphanenin kurulması gerekir. Benzer biçimde gcc’nin Windows portunda da bu fonksiyonlar bulunmamaktadır.
 | 
						||
 | 
						||
Karakter test fonksiyonlarını biz de yazabiliriz. Örneğin isupper şöyle yazılabilir:
 | 
						||
62
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int myisupper(char ch)
 | 
						||
{
 | 
						||
if (ch >= 'A' && ch <= 'Z')
 | 
						||
return 1;
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int myislower(char ch)
 | 
						||
{
 | 
						||
if (ch >= 'a' && ch <= 'z')
 | 
						||
return 1;
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int myisalpha(char ch)
 | 
						||
{
 | 
						||
if (myisupper(ch) || myislower(ch))
 | 
						||
return 1;
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
printf("Karakter: ");
 | 
						||
ch = getchar();
 | 
						||
if (myisalpha(ch))
 | 
						||
printf("alfabetik\n");
 | 
						||
else
 | 
						||
printf("alfabetik degil\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Karakter test fonksiyonları ancak karakter 0-127 arasındaysa doğru çalışmaktadır. Bu fonksiyonlar ASCII
 | 
						||
karkterlerinin ilk 7 bitini dikkate almaktadır.
 | 
						||
Büyük Harf Küçük Harf Dönüştürmesi Yapan Standart C Fonksiyonları
 | 
						||
toupper fonksiyonu eğer parametresi ile belirtilen karakter küçük bir karakterse onun büyük karşılığıyla geri döner
 | 
						||
fakat küçük harf karakter değilse aynısı i,le geri döner. tolower fonksiyonu da benzer biçimde parametresi ile aldığı
 | 
						||
karakter büyük harf ise onun küçük harf karşılığıyla değilse onun aynısıyle geri dönmektedir. Bu fonksiyonları da
 | 
						||
kullanmadan önce <ctype.h> dosyasının include edilmesi gerekir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <ctype.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
printf("Karakter giriniz:");
 | 
						||
ch = getchar();
 | 
						||
ch = toupper(ch);
 | 
						||
printf("%c\n", ch);
 | 
						||
 | 
						||
63
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Anahtar Notlar: Karakter test fonksiyonları ve toupper ile tolower fonksiyonları C90 ekleriyle lokal spesifik özelliğe sahip olmuştur.
 | 
						||
Yani uygun lokal set edilmişse bunlar o dile göre de çalışabilir. Default lokal İngilizce olduğu için bunların da default davranışı standart
 | 
						||
İngilizce karakterler dikkate alınarak yapılır.
 | 
						||
 | 
						||
toupper ya da tolower kullanımına tipik bir örnek de şöyle olabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <ctype.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
do
 | 
						||
{
 | 
						||
printf("(e)vet/(h)ayir?");
 | 
						||
ch = tolower(getch());
 | 
						||
printf("%c\n", ch);
 | 
						||
} while (ch != 'e' && ch != 'h');
 | 
						||
if (ch == 'e')
 | 
						||
printf("evet secildi\n");
 | 
						||
else
 | 
						||
printf("hayir secildi\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for Döngüleri
 | 
						||
for döngüleri aslında while döngülerinin daha genel bir biçimidir. for döngülerinin genel biçimi şöyledir:
 | 
						||
for ([ifade1];[ifade2];[ifade3])
 | 
						||
<deyim>
 | 
						||
 | 
						||
for anahtar sözcüğünden sonra parantezler içerisinde iki noktalı virgül bulunmak zorundadır. Bu for döngüsünü üç
 | 
						||
kısma ayırır. Bu üç kısımda da ifade tanımına uyan herhangi birer ifade kullanılabilir.
 | 
						||
for döngüsü şöyle çalışır:
 | 
						||
 | 
						||
64
 | 
						||
 | 
						||
for döngüsünün birinci kısmındaki ifade döngüye girişte bir kez yapılır. Bir daha da yapılmaz. Döngü ikinci
 | 
						||
kısımdaki ifade sıfır dışı olduğu sürece yinelenir. İkinci kısımdaki ifade sıfır dışı ise önce döngü deyimi yapılır.
 | 
						||
Sonra üçüncü kısımdaki ifade yapılır ve yeniden kontrole girer.
 | 
						||
for döngülerinin en çok kullanılan kalıbı şöyledir:
 | 
						||
for (ilkdeğer; koşul; artırım)
 | 
						||
<deyim>
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d\n", i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
 | 
						||
65
 | 
						||
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; i += 2)
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
double x;
 | 
						||
for (x = 0; x < 6.28; x += 0.1)
 | 
						||
printf("Sin(%.3f)=%.3f\n", x, sin(x));
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 10; i >= 0; --i)
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şüphesiz üçüncü kısımda yapılan artırım ya da eksiltimlerin önek ya da sonek olması arasında bir fark yoktur.
 | 
						||
1’den 100’e kadar sayıların toplamı şöyle bulunabilir:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, total;
 | 
						||
total = 0;
 | 
						||
for (i = 1; i <= 100; ++i)
 | 
						||
total += i;
 | 
						||
printf("%d\n", total);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir döngünün döngü deyimi başka bir döngü olabilir. Bu duruma iç içe döngüler (nested loops) denilmektedir.
 | 
						||
Örneğin:
 | 
						||
66
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
for (k = 0; k < 3; ++k)
 | 
						||
printf("(%d, %d)\n", i, k);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngülerinin de yanlışlıkla boş deyim ile kapatılması durumuyla karşılaşılmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; ++i);
 | 
						||
printf("%d\n", i);
 | 
						||
 | 
						||
/* Dikkat boş deyim */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngüsünün üç kısmına da ifade tanımına uyan her şey yerleştirilebilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
i = 0;
 | 
						||
for (printf("birinci kisim\n"); i < 3; printf("ucuncu kisim\n")) {
 | 
						||
printf("dongu deyimi\n");
 | 
						||
++i;
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngüsünün birinci kısmındaki ifade yazılmayabilir. Buradaki ifadeyi yukarı alsak da değişen birşey olmaz.
 | 
						||
Örneğin:
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
ile
 | 
						||
i = 0;
 | 
						||
for (; i < 10; ++i) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
tamamen işlevsel olarak eşdeğerdir. Örneğin:
 | 
						||
67
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
i = 0;
 | 
						||
for (; i < 10; ++i)
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngüsünün üçüncü kısmı da yazılmayabilir. Aslında üçüncü kısmındaki ifade döngü değiminin sonuna
 | 
						||
yerleştirilirse değişen bir şey olmaz. Örneğin:
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
ile
 | 
						||
i = 0;
 | 
						||
for (; i < 10; ) {
 | 
						||
...
 | 
						||
++i;
 | 
						||
}
 | 
						||
 | 
						||
işlevsel olarak eşdeğerdir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
i = 0;
 | 
						||
for (; i < 10;) {
 | 
						||
printf("%d ", i);
 | 
						||
++i;
 | 
						||
}
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Birinci ve üçüncü kısmı olmayan for döngüleri tamamen while döngüleriyle eşdeğerdir. Yani:
 | 
						||
for (;ifade;) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
ile
 | 
						||
while (ifade) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
tamamen eşdeğerdir.
 | 
						||
68
 | 
						||
 | 
						||
Şüphesiz for döngüsü olmasaydı while döngüsünden for elde edebilirdik. Yani:
 | 
						||
ifade1;
 | 
						||
while (ifade2) {
 | 
						||
<deyim>
 | 
						||
ifade3;
 | 
						||
}
 | 
						||
 | 
						||
aslında aşağıdaki for döngüsüyle eşdeğerdir:
 | 
						||
for (ifade1; ifade2; ifade3)
 | 
						||
<deyim>
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
i = 0;
 | 
						||
while (i < 10) {
 | 
						||
printf("%d\n", i);
 | 
						||
++i;
 | 
						||
}
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d\n", i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngüsünün ikinci kısmı da boş bırakılabilir. Bu durumda döngü koşulunun her zaman sağlandığı kabul edilir.
 | 
						||
Yani döngünün ikinci kısmına birşey yazmamakla buraya sıfır dışı bir değer yazmak aynı anlamdadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0;; ++i)
 | 
						||
/* sonsuz döngü (infinite loop) */
 | 
						||
printf("%d\n", i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
for döngüsünün üç kısmında da virgül operatörü zenginlik katmak için kullanılabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, total;
 | 
						||
for (i = 1, total = 0; i <= 100; total += i, ++i)
 | 
						||
;
 | 
						||
printf("%d\n", total);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
69
 | 
						||
 | 
						||
Burada döngünün birinci kısmını i = 1, total = 0 ifadesi oluşturmaktadır. Üçüncü kısmını ise total += i, ++i ifadesi
 | 
						||
oluşturmaktadır.
 | 
						||
Anahtar Notlar: Bir problemi kesin çözüme götüren adımlar topluluğuna algoritma (algorithm) denilmektedir. Algoritmaları hız
 | 
						||
bakımından ya da kaynak kullanımı bakımından karşılaştırabiliriz. Baskın ölçüt hızdır ve default olarak hız akla gelmektedir. Bazen bir
 | 
						||
problemin algoritması probleminin yapısı gereği çok uzun bilgisayar zamanı alabilmektedir. Bu durumda makul iyi bir çözümle
 | 
						||
yetinilebilir. Problemi kesin çözüme götürmeyen ancak iyi bir çözüm vaat eden adımlar topluluğuna sezgisel yöntem (heuristic)
 | 
						||
denilmektedir.
 | 
						||
 | 
						||
Önce C++'a sonra da C99 sokulmuş olan for döngülerinde önemli bir özellik vardır. C++'ta ve C99'da for
 | 
						||
döngülerinin birinci kısmıunda bildirim yapılabilir. Bu prtaiklik sağlamaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
for (int i = 0; i < 10; ++i)
 | 
						||
printf("%d\n", i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Birden fazla değişkenin de aynı türden olmak koşuluyla bu biçimde bildirimi yapılabilir. Örneğin:
 | 
						||
for (int i = 0, k = 100; i + k >= 50; ++i, k -= 2) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Bu biçimde bildirilen değişkenler ancak for döngüsünün içerisinde kullanılabilir. C++ ve C99'un standartlarına
 | 
						||
göre,
 | 
						||
for (bildirim; ifade2; ifade3)
 | 
						||
<deyim>
 | 
						||
 | 
						||
ile,
 | 
						||
{
 | 
						||
bildirim;
 | 
						||
for (;ifade2; ifade3)
 | 
						||
<deyim>
 | 
						||
}
 | 
						||
 | 
						||
eşdeğerdir.
 | 
						||
Biz kursumuzda C90 gördüğümüz için bu özelliği kullanmayacağız. Ancak bu özellik pek çok C90 derleyicisinde
 | 
						||
ekstra bir eklenti olarak bulunmaktadır.
 | 
						||
break Deyimi
 | 
						||
break deyiminin genel biçimi şöyledir:
 | 
						||
break;
 | 
						||
 | 
						||
break deyimi yalnızca döngülerin ya da switch deyiminin içerisinde kullanılılır. Programın akışı break deyimini
 | 
						||
gördüğünde döngü deyiminin kendisi sonlandırılır, akış sonraki deyimle devam eder. (Yani break adeta döngü
 | 
						||
dışına goto yapmak gibi etki gösterir.). Örneğin:
 | 
						||
70
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int val;
 | 
						||
for (;;) {
 | 
						||
printf("Sayi giriniz:");
 | 
						||
scanf("%d", &val);
 | 
						||
if (!val)
 | 
						||
break;
 | 
						||
printf("%d\n", val * val);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Asal sayı testi aşağıdaki gibi yapılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
int isprime(int val)
 | 
						||
{
 | 
						||
int i, result;
 | 
						||
result = 1;
 | 
						||
for (i = 2; i < val; ++i)
 | 
						||
if (val % i == 0) {
 | 
						||
result = 0;
 | 
						||
break;
 | 
						||
}
 | 
						||
return result;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 2; i < 1000; ++i)
 | 
						||
if (isprime(i))
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yukarıdaki asallık testi aşağıdaki gibi iyileştirilebilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
int isprime(int val)
 | 
						||
{
 | 
						||
int i, result;
 | 
						||
double n;
 | 
						||
if (val % 2 == 0)
 | 
						||
return val == 2;
 | 
						||
n = sqrt(val);
 | 
						||
result = 1;
 | 
						||
for (i = 3; i <= n; i += 2)
 | 
						||
if (val % i == 0) {
 | 
						||
 | 
						||
71
 | 
						||
 | 
						||
result = 0;
 | 
						||
break;
 | 
						||
}
 | 
						||
return result;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 2; i < 1000; ++i)
 | 
						||
if (isprime(i))
 | 
						||
printf("%d ", i);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İç içe döngülerde break yalnızca kendi döngüsünden çıkar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
for (k = 0; k < 10; ++k) {
 | 
						||
printf("(%d, %d)\n", i, k);
 | 
						||
if (getch() == 'q')
 | 
						||
break;
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yukarıdaki örnekte 'q' tuşuna basıldığında her iki döngüden de çıkılması isteniyorsa dış döngü için de ayrıca break
 | 
						||
kullanılmalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
char ch;
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
for (k = 0; k < 10; ++k) {
 | 
						||
printf("(%d, %d)\n", i, k);
 | 
						||
if ((ch = getch()) == 'q')
 | 
						||
break;
 | 
						||
}
 | 
						||
if (ch == 'q')
 | 
						||
break;
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Klavyeden int türden bir n değeri alınız ve aşağıdaki deseni çıkartınız:
 | 
						||
72
 | 
						||
 | 
						||
*
 | 
						||
**
 | 
						||
***
 | 
						||
****
 | 
						||
*****
 | 
						||
....
 | 
						||
******....***** (n tane)
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, k, n;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &n);
 | 
						||
for (i = 1; i <= n; ++i) {
 | 
						||
for (k = 1; k <= i; ++k)
 | 
						||
putchar('*');
 | 
						||
putchar('\n');
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Klavyeden int türden bir sayı okuyunuz ve onun asal çarpanlarını yan yana yazdırınız. Örneğin:
 | 
						||
Sayi giriniz: 28
 | 
						||
2 2 7
 | 
						||
 | 
						||
İpucu: önce sürekli ikiye bölünerek ilerlenir. ikiye bölünmeyince bu sefer üçe bölünerek ilerlenir, bölünmediği
 | 
						||
zaman dörde bölünerek ilerlenir ve böyle devam ettirilir.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int n, i;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &n);
 | 
						||
i = 2;
 | 
						||
while (n != 1) {
 | 
						||
if (n % i == 0) {
 | 
						||
printf("%d ", i);
 | 
						||
n /= i;
 | 
						||
}
 | 
						||
else
 | 
						||
++i;
 | 
						||
}
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
73
 | 
						||
 | 
						||
continue Deyimi
 | 
						||
continue deyimi break deyimine göre daha seyrek kullanılır. Genel biçimi şöyledir:
 | 
						||
continue;
 | 
						||
 | 
						||
Programın akışı continue anahtar sözcüğünü gördüğünde döngünün içindeki deyim sonlandırılır. Böylece yeni bir
 | 
						||
yenilemeye geçilmiş olur. break döngü deyiminin kendisini sonlandırırken, continue döngü içerisindeki yinelenen
 | 
						||
deyimi sonlandırır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
if (i % 2 == 0)
 | 
						||
continue;
 | 
						||
printf("%d\n", i);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada ekrana yalnızca tek sayılar basılacaktır. continue deyimi yalnızca döngüler içerisinde kullanılabilir.
 | 
						||
Sabit İfadeleri (Constant Expressions)
 | 
						||
Yalnızca sabit ve operatörlerden oluşan ifadelere sabit ifadeleri (constant expressions) denilmektedir. Örneğin:
 | 
						||
10
 | 
						||
10 + 20
 | 
						||
10 + 20 - 30 / 2
 | 
						||
 | 
						||
birer sabit ifadesidir. Aşağıdaki ifadeler sabit ifadesi değildir:
 | 
						||
x
 | 
						||
x + 10
 | 
						||
10 - 20 - foo()
 | 
						||
 | 
						||
Sabit ifadelerinin net sayısal değerleri derleme aşamasında belirlenebilir. C'de kimi durumlarda sabit ifadeleri
 | 
						||
zorunludur. Örneğin global değişkenlere verilen ilkdeğerlerin sabit ifadesi olması gerekir. Örneğin:
 | 
						||
int x = 10;
 | 
						||
int y = x + 10;
 | 
						||
void foo()
 | 
						||
{
 | 
						||
int a = 10;
 | 
						||
int b = a + 10;
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
/* geçerli, b yerel */
 | 
						||
 | 
						||
switch Deyimi
 | 
						||
switch deyimi bir ifadenin çeşitli sayısal değerleri için çeşitli farklı işlemlerin yapılması amacıyla kullanılır. Genel
 | 
						||
74
 | 
						||
 | 
						||
biçimi şöyledir:
 | 
						||
switch (<ifade>) {
 | 
						||
case <s.i>:
 | 
						||
....
 | 
						||
case <s.i>:
 | 
						||
....
 | 
						||
case <s.i>:
 | 
						||
....
 | 
						||
[default:
 | 
						||
....
 | 
						||
]
 | 
						||
}
 | 
						||
 | 
						||
switch anahtar sözcüğünden sonra derleyici parantezler içerisinde bir ifade bekler. switch deyimi case
 | 
						||
bölümlerinden oluşur. case anahtar sözcüğünü bir sabit ifadesi ve sonra da ':' atomu izlemek zorundadır. switch
 | 
						||
deyiminin default bölümü olabilir. switch deyiminin tamamı tek bir deyimdir. switch deyimi şöyle çalışır: Önce
 | 
						||
switch parantezinin içerisindeki değer hesaplanır. Sonra bu değere eşit olan bir case bölümü var mı diye bakılır.
 | 
						||
Eğer böyle bir case bölümü varsa akış o case bölümüne aktarılır. eğer böyle case bölümü yoksa fakat switch
 | 
						||
deyiminin default bölümü varsa akış default bölüme aktarılır. default bölüm de yoksa akış switch deyimine girdiği
 | 
						||
gibi çıkar. Akış bir case bölümüne aktarıldığında artık diğer case etiketlerinin bir önemi kalmaz. Akış aşağıya
 | 
						||
doğru switch deyiminin sonuna kadar akar. (Buna "fall through" denilmektedir.). break deyimi döngülerin yanı sıra
 | 
						||
switch deyimini de kırmak için kullanılmaktadır. Genellikle her case bölümü break ile sonlandırılır. Böylece
 | 
						||
yalnızca o case bölümü yapılmış olur.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &a);
 | 
						||
switch (a) {
 | 
						||
case 1:
 | 
						||
printf("bir\n");
 | 
						||
break;
 | 
						||
case 2:
 | 
						||
printf("iki\n");
 | 
						||
break;
 | 
						||
case 3:
 | 
						||
printf("uc\n");
 | 
						||
break;
 | 
						||
case 4:
 | 
						||
printf("dort\n");
 | 
						||
break;
 | 
						||
default:
 | 
						||
printf("hicbiri\n");
 | 
						||
break;
 | 
						||
}
 | 
						||
printf("son\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
case bölümlerinin sıralı olması ve default sonda olması zorunlu değildir. Bazen farklı case değerleri için aynı
 | 
						||
75
 | 
						||
 | 
						||
işlemlerin yapılması istenebilir. Bunun tek yolu aşağıdaki gibidir. Daha pratik yolu yoktur:
 | 
						||
case 1:
 | 
						||
case 2:
 | 
						||
ifade;
 | 
						||
break;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
char ch;
 | 
						||
for (;;) {
 | 
						||
printf("CSD>");
 | 
						||
ch = getch();
 | 
						||
printf("%c\n", ch);
 | 
						||
if (ch == 'q')
 | 
						||
break;
 | 
						||
switch (ch) {
 | 
						||
case 'r':
 | 
						||
case 'd':
 | 
						||
printf("delete command\n");
 | 
						||
break;
 | 
						||
case 'c':
 | 
						||
printf("copy command\n");
 | 
						||
break;
 | 
						||
case 'm':
 | 
						||
printf("move command\n");
 | 
						||
break;
 | 
						||
default:
 | 
						||
printf("bad command!\n");
 | 
						||
break;
 | 
						||
}
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aynı değere ilişkin birden fazla case bölümü bulunamaz. Örneğin:
 | 
						||
case 1 + 1:
 | 
						||
...
 | 
						||
break;
 | 
						||
case 2:
 | 
						||
...
 | 
						||
break;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Zaten case ifadelerinin sabit ifadesi olma zorunluluğu buradan gelmektedir. Eğer case ifadeleri sabit ifadesi
 | 
						||
olmasaydı bu durum derleme aşamasında denetlenemezdi.
 | 
						||
case bölümlerine istenildiği kadar çok deyim yerleştirilebilir. İç içe switch deyimleri söz konusu olabilir.
 | 
						||
case bölümlerinin hemen switch bloğunun içerisinde bulundurulması zorunlu değildir. Yani case bölümleri daha
 | 
						||
diplerde de bulunabilir.
 | 
						||
switch (a) {
 | 
						||
case 1:
 | 
						||
 | 
						||
76
 | 
						||
 | 
						||
/*.... */
 | 
						||
if (ifade) {
 | 
						||
case 2:
 | 
						||
/* .... */
 | 
						||
}
 | 
						||
break;
 | 
						||
}
 | 
						||
 | 
						||
switch parantezi içerisindeki ifade tamsayı türlerine ilişkin olmak zorundadır. case ifadeleri de gerçek sayı
 | 
						||
türünden sabit ifadesi olamaz. Tamsayı türlerine ilişkin olmak zorundadır.
 | 
						||
Sınıf Çalışması: Klavyeden gün, ay ve yıl için ü.ç değer isteyiniz. Ay bilgisi yazı ile olacak biçimde tarihi gün-ayyıl biçiminde yazdırınız. Örneğin:
 | 
						||
Gün:10
 | 
						||
Ay: 12
 | 
						||
Yil: 1994
 | 
						||
10-Aralik-1994
 | 
						||
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int day, month, year;
 | 
						||
printf("Gun:");
 | 
						||
scanf("%d", &day);
 | 
						||
printf("Ay:");
 | 
						||
scanf("%d", &month);
 | 
						||
printf("Yil:");
 | 
						||
scanf("%d", &year);
 | 
						||
printf("%02d-", day);
 | 
						||
switch (month) {
 | 
						||
case 1:
 | 
						||
printf("Ocak");
 | 
						||
break;
 | 
						||
case 2:
 | 
						||
printf("Subat");
 | 
						||
break;
 | 
						||
case 3:
 | 
						||
printf("Mart");
 | 
						||
break;
 | 
						||
case 4:
 | 
						||
printf("Nisan");
 | 
						||
break;
 | 
						||
case 5:
 | 
						||
printf("Mayis");
 | 
						||
break;
 | 
						||
case 6:
 | 
						||
printf("Haziran");
 | 
						||
break;
 | 
						||
case 7:
 | 
						||
printf("Temmuz");
 | 
						||
break;
 | 
						||
case 8:
 | 
						||
printf("Agustos");
 | 
						||
break;
 | 
						||
 | 
						||
77
 | 
						||
 | 
						||
case 9:
 | 
						||
printf("Eylul");
 | 
						||
break;
 | 
						||
case 10:
 | 
						||
printf("Ekim");
 | 
						||
break;
 | 
						||
case 11:
 | 
						||
printf("Kasim");
 | 
						||
break;
 | 
						||
case 12:
 | 
						||
printf("Aralik");
 | 
						||
break;
 | 
						||
}
 | 
						||
printf("-%02d\n", year);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sayıların printf Fonksiyonuyla Formatlanması
 | 
						||
printf fonksiyonunda format karakterinden hemen önce bir sayı getirilirse, o sayı kadar alan ayrılır ve o alana sayı
 | 
						||
sağa dayılı olarak yazdırılır. Eğer o alana sayının sola dayalı olarak yazdırılması isteniyorsa sayının başına karakteri getirilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double f = 12.3;
 | 
						||
int a = 123;
 | 
						||
printf("%-10d%f\n", a, f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Eğer format karakterinden önceki sayı, yazdırılacak sayının basamk sayısından küçükse bu durumda o sayının bir
 | 
						||
önemi kalmaz. Sayının hepsi yazdırılır.
 | 
						||
Birtakım sayıların bıçakla kesilmiş gibi hizalanması için bu özellik kullanılmaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 1; i <= 100; ++i)
 | 
						||
printf("%-10d%d\n", i, i *i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
eğer format karakterinden önce + kullanılıyorsa (örneğin "%+10d" gibi, "%+d" gibi) sayının işareti her zaman
 | 
						||
yazdırlır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i;
 | 
						||
 | 
						||
78
 | 
						||
 | 
						||
i = -123;
 | 
						||
printf("%+d\n", i);
 | 
						||
i = 123;
 | 
						||
printf("%+d\n", i);
 | 
						||
 | 
						||
/* -123 */
 | 
						||
/* +123 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Format karakterinden öncekki sayının başına 0 getirilirse (örneğin "%010d" gibi) geri kalan boş alan sıfırla
 | 
						||
doldurulur. Örneğin:
 | 
						||
day = 7;
 | 
						||
month = 6;
 | 
						||
year = 2009;
 | 
						||
printf("%02d/%02d/%04d", day, month, year); /* 07/06/2009 */
 | 
						||
 | 
						||
float ve double türleri default olarak noktadan sonra 6 basamak yazdırılır:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double d;
 | 
						||
d = 3.6;
 | 
						||
printf("%f\n", d);
 | 
						||
d = 3;
 | 
						||
printf("%f\n", d);
 | 
						||
 | 
						||
/* 3.600000 */
 | 
						||
/* 3.000000 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
printf fonksiyonunda "%n.kf " "toplam n tane alan ayır, noktadan sonra k basamak yazdır" anlamına gelir. Eğer
 | 
						||
sayının tam kısmı için yeterli alan belirtilmemişse sayının hepsi yazdırılır. Şüphesiz noktadan sonraki k basamak
 | 
						||
için yuvarlama yapılmaktadır. Eğer sayının tam kısmı ile ilgilenilmiyorsa "%.kf" biçiminde format belirtilebilir.
 | 
						||
Örneğin:
 | 
						||
printf("%.10f\n", f);
 | 
						||
 | 
						||
/* sayının tam kısmının hepsini yazdır, fakat noktadan sonra 10 basamak yazdır */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double d;
 | 
						||
d = 12345.6789;
 | 
						||
printf("%10.2f\n", d); /*
 | 
						||
12345.68 */
 | 
						||
printf("%4.2f\n", d); /*12345.68 */
 | 
						||
d = 12.78;
 | 
						||
printf("%.0f\n", d);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
goto Deyimi
 | 
						||
79
 | 
						||
 | 
						||
goto deyimi programın akışını belli bir noktaya koşulsuz olarak aktarmak amacıyla kullanılır. Genel biçimi şöyledi:
 | 
						||
goto <etiket>;
 | 
						||
....
 | 
						||
<etiket>:
 | 
						||
 | 
						||
programın akışı goto anahtar sözcüğünü gördüğünde akış koşulsuz olarak etiket ile belirtilen noktaya aktarılır.
 | 
						||
Etiket (label) isimlendirme kuralına uygun herhangi bir isim olabilir. Bazı programcılar okunabilirlik için goto
 | 
						||
etiketlerini büyük harflerle harflendirirler.
 | 
						||
Eskiden ilk yüksek seviyeli diller makina dillerinin etkisi altındaydı. Bu dillerde neredeyse goto kullanmak
 | 
						||
zorunluydu. Sonraları goto'lar kodun takip edilebilirlğini bozması nedeniyle dillerden dışlanmaya başladılar. Pek
 | 
						||
çok deyim goto'suz bloklu bir tasarıma sahip oldu. Bugün goto deyiminin kullanımı birkaç durum dışında tavsiye
 | 
						||
edilmemektedir. Fakat tipik olarak goto kullanılmasının anlamlığı olduğu birkaç durum vardır.
 | 
						||
Aşağıdaki örnekte goto ile bir döngü oluşturulmuştur. Kesinlikte goto'lar döngü oluşturmak amacıyla
 | 
						||
kullanılmamalıdır. Bu nedenle aşağıdaki örnek goto kullanımının anlamlı olduğu bir durum için değil, onun
 | 
						||
çalışma mekanizmasını açıklamak için verilmiştir:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i = 0;
 | 
						||
REPEAT:
 | 
						||
printf("%d\n", i);
 | 
						||
++i;
 | 
						||
if (i < 10)
 | 
						||
goto REPEAT;
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
goto etiketi yalnızca goto işlemi sırasında etkili olur. Onun dışında bu etiketin bir işlevi yoktur. goto etiketinin
 | 
						||
bulundurulması ona goto yapılmasını zorunla hale getirmez. (Ancak kendisine goto yapılmamış etiketler için
 | 
						||
derleyiciler uyarı verebilmektedir.)
 | 
						||
goto etiketlerini bir deyim izlemek zorundaıdr. Örneğin:
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
....
 | 
						||
XX:
 | 
						||
/* geçersiz! */
 | 
						||
}
 | 
						||
 | 
						||
Fakat:
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
....
 | 
						||
XX:
 | 
						||
/* geçerli */
 | 
						||
;
 | 
						||
}
 | 
						||
 | 
						||
goto etiketleri fonksiyon faaliyet alanına sahiptir. Yani aynı fonksiyon içerisinde aynı isimli tek bir goto etiketi
 | 
						||
bulunabilir.
 | 
						||
80
 | 
						||
 | 
						||
goto deyimi ile başka bir fonksiyona atlama yapılamaz. Aynı fonksiyon içerisinde başka bir bölgeye atlama
 | 
						||
yapılabilir.
 | 
						||
goto ile iç bir bloğa atlama yapılırken dikkat edilmelidir. Çünkü o bloktaki değişkenler için ilkdeğerleme işlemleri
 | 
						||
yapılmamış olabilir. Örneğin:
 | 
						||
if (ifade)
 | 
						||
goto XX;
 | 
						||
{
 | 
						||
int i = 10;
 | 
						||
XX:
 | 
						||
....
 | 
						||
}
 | 
						||
 | 
						||
Burada atlanan noktada i çöp değere sahip olabilir. Başka bir dyeişle C'de iç bloğa goto yapıldığında o iç bloğun
 | 
						||
başında bildirilmiş değişkenler çöp değerlerdedir. Örneğin aşağıdaki programı çalıştırdığımızda ekrana çöp değer
 | 
						||
basılacaktır:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
goto TEST;
 | 
						||
{
 | 
						||
int i = 10;
 | 
						||
TEST:
 | 
						||
printf("%d\n", i);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şüphesiz aynı etikete fonksiyonun farklı yerlerinden birden fazla kez goto yapılabilir.
 | 
						||
goto deyiminin aşağıdaki üç durumda kullanılması tavsiye edilmektedir:
 | 
						||
1) İç içe döngülerden ya da döngü içerisindeki switch deyiminden tek hamlede çıkmak için. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
for (k = 0; k < 10; ++k) {
 | 
						||
printf("(%d, %d)\n", i, k);
 | 
						||
if (getch() == 'q')
 | 
						||
goto EXIT;
 | 
						||
}
 | 
						||
}
 | 
						||
EXIT:
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
81
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#include <conio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
char ch;
 | 
						||
for (;;) {
 | 
						||
printf("CSD>");
 | 
						||
ch = getch();
 | 
						||
printf("%c\n", ch);
 | 
						||
switch (ch) {
 | 
						||
case 'r':
 | 
						||
case 'd':
 | 
						||
printf("delete command\n");
 | 
						||
break;
 | 
						||
case 'c':
 | 
						||
printf("copy command\n");
 | 
						||
break;
 | 
						||
case 'm':
 | 
						||
printf("move command\n");
 | 
						||
break;
 | 
						||
case 'q':
 | 
						||
goto EXIT;
 | 
						||
default:
 | 
						||
printf("bad command!\n");
 | 
						||
break;
 | 
						||
}
 | 
						||
}
 | 
						||
EXIT:
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
2) goto ters sırada kaynak boşaltımı amacıyla kullanılabilir:
 | 
						||
3) Bazı özel durumlarda goto kullanılmazsa algortima çok çetrefil hale gelebilir. Yani goto bazı durumlarda kodu
 | 
						||
kısaltmakta ve okunabilirliği artırmaktadır.
 | 
						||
Rastgele (Rassal) Sayı Üretimi
 | 
						||
Bilgisayarda rassal sayılar aritmetik yöntemlerle üretilirler. Bu biçimde üretilmiş rassal sayılara sahte rassal sayılar
 | 
						||
(pseudo random numbers) denilmektedir. Sahte rassal sayı üretmek için pek çok yöntem bulunuyor olsa da
 | 
						||
bunların hemen hepsi bir başlangıç sayısı alınıp, onun üzerinde belirle işlem yapıp yeni sayı elde edilmesi ve aynı
 | 
						||
işlemlerin bu yeni sayı üzerinde devam ettirilmesi biçiminde elde edilmektedir. Örneğin bir sayıdan başlayıp onun
 | 
						||
karesini alıp ortadaki n basamağını alırsa bu n basamak rassaldır. Örneğin ilk sayı 123 olsun
 | 
						||
123
 | 
						||
512
 | 
						||
621
 | 
						||
856
 | 
						||
....
 | 
						||
Bu biçimde elde edilen sayılar rassaldır. Tabi burada ilk değer aynı ise her defasında aynı dizilim bulunur.
 | 
						||
Programın ger çalışmasında farklı bir dizilimin elde edilmesi için bu ilkdeğerin her çalışmada farklı alınması
 | 
						||
gerekir.
 | 
						||
C'de rassal sayı üretimi için iki standart fonksiyon vardır: srand ve rand. Fonksiyonların parametrik yapıları
 | 
						||
şöyledir:
 | 
						||
82
 | 
						||
 | 
						||
#include <stdlib.h>
 | 
						||
int rand(void);
 | 
						||
void srand(unsigned int seed);
 | 
						||
 | 
						||
rand fonksiyonu her çağrıldığında 0 ile RAND_MAX değeri arasında rastgele bir sayıyla geri dönmektedir.
 | 
						||
RAND_MAX değerinin kaç olacağı derleyicileri yazanların isteğine bırakılmıştır. Bu değer her derleyicide farklı
 | 
						||
olabilir (pek çok derleyicide 32767 ya da 2147483647'dir.) Elde böyle bir fonksiyon varsa herhangi bir aralıkta
 | 
						||
rassal sayı elde edilebilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i, val;
 | 
						||
for (i = 0; i < 20; ++i) {
 | 
						||
val = rand() % 10;
 | 
						||
printf("%d ", val);
 | 
						||
}
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu program her çalıştıtıldığında aynı dizilimi bize verir. Aynı dizilim bize verilmesi sayıların rassal olmadığı
 | 
						||
anlamına gelmez. Tabi genellikle program her çalıştığında farklı bir dizilimin verilmesi istenir.
 | 
						||
srand fonksiyonu rassal sayı üreticisinin kullandığı ilkdeğeri değiştirmek amacıyla kullanılır. Bu değere
 | 
						||
terminolojide tohum değer (seed value) denilmektedir. Programın her çalışmasında farklı bir dizilimin elde
 | 
						||
edilmesi için programın her çalışmasında srand'ın farklı bir değerle çağrılması gerekir. İşte bu bilgisayarın
 | 
						||
içerisindeki saatle sağlanır. İleride ele alınacak olan time fonksiyonu bize bilgisayarın saatine bakarak bize
 | 
						||
01/01/1970'ten fonksiyonun çağrıldığı zamana kadar kaç saniye geçtiğini verir. Bu fonksiyon için <time.h>
 | 
						||
dosyasının include edilmesi gerekir. Fonksiyon sıfır parametresiyle çağrılmalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i, val;
 | 
						||
srand(time(0));
 | 
						||
for (i = 0; i < 10; ++i) {
 | 
						||
val = rand() % 10;
 | 
						||
printf("%d ", val);
 | 
						||
}
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
srand(time(0)) çağrısı programın başında yalnızca bir kez yapılmaktadır. Bazen programcılar yanlışlıkla bu çağrıyı
 | 
						||
da döngüsünün içerisine yerleştirirler. Bu durum çoğu kez aynı hep aynı değerin elde edilmesine yol açar. srand
 | 
						||
fonksiyonu hiç çağrılmazsa tohum değer hep aynı sayıdan başlar.
 | 
						||
Olasılığın görelilik tanımı (büyük sayılar yasası): Yazı tura işlemindeki limit
 | 
						||
83
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double head, tail, headRatio, tailRatio;
 | 
						||
unsigned long i;
 | 
						||
unsigned long n;
 | 
						||
srand(time(0));
 | 
						||
head = tail = 0;
 | 
						||
n = 1000000000;
 | 
						||
for (i = 0; i < n; ++i)
 | 
						||
if (rand() % 2 == 0)
 | 
						||
++head;
 | 
						||
else
 | 
						||
++tail;
 | 
						||
headRatio = head / n;
 | 
						||
tailRatio = tail / n;
 | 
						||
printf("Head Ratio = %f, Tail Ratio = %f\n", headRatio, tailRatio);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Zarda 6 gelme olasılığı nedir?
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double six, sixRatio;
 | 
						||
unsigned long i;
 | 
						||
unsigned long n;
 | 
						||
srand(time(0));
 | 
						||
six = 0;
 | 
						||
n = 1000000000;
 | 
						||
for (i = 0; i < n; ++i)
 | 
						||
if (rand() % 6 + 1 == 6)
 | 
						||
++six;
 | 
						||
sixRatio = six / n;
 | 
						||
printf("Head Ratio = %f\n", sixRatio);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Bir döngü içerisinde getch fonksiyonuyla karakter bekleyiniz. Her tuşa baısldığında "Ali", "Veli",
 | 
						||
"Selami", "Ayşe", "Fatma isimlerinden birini rastgele yazdırınız. 'q' tuşuna basıldığında program sonlansın.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
#include <conio.h>
 | 
						||
 | 
						||
84
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
srand(time(0));
 | 
						||
while (getch() != 'q') {
 | 
						||
switch (rand() % 5) {
 | 
						||
case 0:
 | 
						||
printf("Ali\n");
 | 
						||
break;
 | 
						||
case 1:
 | 
						||
printf("Veli\n");
 | 
						||
break;
 | 
						||
case 2:
 | 
						||
printf("Selami\n");
 | 
						||
break;
 | 
						||
case 3:
 | 
						||
printf("Ayse\n");
 | 
						||
break;
 | 
						||
case 4:
 | 
						||
printf("Fatma\n");
 | 
						||
break;
 | 
						||
}
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Her tuşa basıldığında 16 büyük harf karakterden oluşan bir dizilimi ekrana bastırınız (Yalnızca
 | 
						||
İngilizce karakterler kullanılacaktır.). 'q' tuşuna basıldığında program sonlansın.
 | 
						||
GDSUSLYERVFXWTER
 | 
						||
KREYDSBSKAIEIRIT
 | 
						||
...
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
#include <conio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i;
 | 
						||
srand(time(0));
 | 
						||
while (getch() != 'q') {
 | 
						||
for (i = 0; i < 16; ++i)
 | 
						||
putchar('A' + rand() % 26);
 | 
						||
putchar('\n');
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Farklı Türlerin Birbirlerine Atanması
 | 
						||
C'de tüm aritmektik türler birbirlerine atanabilir. Ancak atama işleminde bilgi kaybı söz konusu olabilir. Eğer bilgi
 | 
						||
kaybı söz konusu oluyorsa, bilginin nerisinin kaybedildiği stadartlarda belirtilmiştir.
 | 
						||
85
 | 
						||
 | 
						||
Bir atamada kaynak ve hedef tür vardır. Örneğin:
 | 
						||
a = b;
 | 
						||
Burada kaynak tür b'nin türüdür, hedef tür a'nın türüdür. Standartlarda farklı türlerin birbirlerine atanması bir tür
 | 
						||
dönüştürme faaliyeti gibi ele alınmıştır. Standartlara göre atama işleminde kaynak türle hedef tür birbirlerinden
 | 
						||
farklıysa önce kaynak tür hedef türe dönüştürülür, sonra atama yapılır. Yani farklı türlerin birbirlerine atanması
 | 
						||
aslında farklı türlerin birbirlerine dönüştürülmesiyle aynı anlamdadır.
 | 
						||
Aşağıda farklı türlerin birbirlerine atanması sırasında ne olacağı tek tek açıklanmıştır (else-if gibi değerlendiriniz)
 | 
						||
1) Eğer kaynak türün içerisindeki değer hedef türün sınırları içerisinde kalıyorsa bilgi kaybı söz konsu olmaz.
 | 
						||
Örneğin:
 | 
						||
long a = 10;
 | 
						||
short b;
 | 
						||
b = a;
 | 
						||
2) Eğer kaynak tür bir tamsayı türünden (signed char, unsigned char, signed short, unsigned short, signed int,
 | 
						||
unsigned int, signed long, unsigned long) hedef tür de işaretsiz bir tamsayı türündense sayının yüksek anlamlı
 | 
						||
byte'ları atılır, düşük anlamlı byte'ları atanır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
long a = 12345678;
 | 
						||
unsigned short b;
 | 
						||
b = a;
 | 
						||
printf("%u\n", b);
 | 
						||
 | 
						||
/* 0xBC614E */
 | 
						||
 | 
						||
/* 24910 = 0x614E */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
3) Eğer kaynak tür bir tamsayı türünden (signed char, unsigned char, signed short, unsigned short, signed int,
 | 
						||
unsigned int, signed long, unsigned long) hedef tür de işaretli bir tamsayı türündense bilgi kaybının nasıl olacağını
 | 
						||
(yani nerenin atılacağı) derleyiciyi yazanların isteğine bırakılmıştır (implementation dependent). Ancak
 | 
						||
derleyicilerin hemen hepsi bu durumda yine sayının yüksek anlamlı byte değerlerini atar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
long a = 7654321;
 | 
						||
short b;
 | 
						||
b = a;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* 0x74CBB1 */
 | 
						||
 | 
						||
/* 0xCBB1 = -13391 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
4) Eğer kaynak ile hedef tür aynı tamsayı türünün işaretli ve işaretsiz biçimlerinden oluşuyorsa sayının bit kalıbı
 | 
						||
değişmez, yalnızca işaret bitinin anlamı değişir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
86
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a = -1;
 | 
						||
unsigned int b;
 | 
						||
b = a;
 | 
						||
printf("%u\n", b); /* 4294967295 */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
5) Eğer kaynak tür küçük işaretli bir tamsayı türü hedef tür de büyük işaretsiz bir tamsayı türü ise bu durumda
 | 
						||
dönüştürme iki aşamada yürütülür. Önce küçük işaretli tür, büyük türün işaretli biçimine dönüştürülür, sonra büyük
 | 
						||
türün işaretli biçiminden büyük türün işaretsiz biçimine dönüştürme yapılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
signed char a = -1;
 | 
						||
unsigned int b;
 | 
						||
b = a;
 | 
						||
printf("%u\n", b); /* 4294967295 */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
6) Eğer kaynak tür bir gerçek sayı türündense (float, double, long double) hedef tür bir tamsayı türündense sayının
 | 
						||
noktadan sonraki kısmı atılır, tam kısmı elde edilir. Eğer sayının noktadan sonraki kısmı atıldıktan sonra elde
 | 
						||
edilen tam kısım hala hedef türün sınırları içerisinde kalmıyorsa tanımsız davranış (undefined behavior) oluşru.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double a;
 | 
						||
int b;
 | 
						||
a = 3.99;
 | 
						||
b = a;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* 3 */
 | 
						||
 | 
						||
a = -3.99;
 | 
						||
b = a;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* -3 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
7) Eğer kaynak tür bir tamsayı türü hedef tür gerçek sayı türüyse ve sayı tam olarak tutulamıyorsa, kaynak tür ile
 | 
						||
belirtilen değere en yakın büyük ya da en yakın küçük değer elde edilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
long a;
 | 
						||
float b;
 | 
						||
 | 
						||
87
 | 
						||
 | 
						||
a = 123456789;
 | 
						||
b = a;
 | 
						||
printf("%.0f\n", b);
 | 
						||
 | 
						||
/* 123456792 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
8) Eğer kaynak tür ve hedef tür de gerçek sayı türündense bu durumda bilgi kaybının niteliğine bakılır. eğer
 | 
						||
basamaksal bir kayıp (magnitute kaybı) varsa tanımsız davtranış oluşur. Basamksal değil de mantis kaybı varsa
 | 
						||
yine kaynak tür ile belirtilen değere en yakın büyük ya da en yakın küçük değer elde edilir.
 | 
						||
İşlem Öncesi Otomatik Tür Dönüştürmeleri
 | 
						||
Yalnızca değişkenlerin ve sabitlerin değil, her ifadenin de bir türü vardır. Bir operatörün opendaları farklı
 | 
						||
türlerdense önce derleyici onları aynı türe dönüştürür, sonra işlemi yapar. İşlem sonucunda elde edilen türü bu
 | 
						||
dönüştürülen ortak tür türündendir. İşlem öncesi otomatik tür dönüştürmesinin özet kuralı "küçük tür büyük türe
 | 
						||
dönüştürülür, sonuç büyük türünden çıkar" biçimindedir. Örneğin:
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
 | 
						||
...
 | 
						||
a + b işleminde a long türüne dönüştürülür, ondan sonra toplama yapılır. a + b ifadesinin türü long olur.
 | 
						||
İşlem öncesi otomatik tür dönüştürmeleri geçici nesne yoluyla yapılmaktadır. Yani dönüştürülmek istenen değer
 | 
						||
önce dönüştürülecek türden geçici bir nesneye atanır, işleme o sokulur, sonra o nesne yok edilir:
 | 
						||
long temp = a;
 | 
						||
temp + b
 | 
						||
temp yok ediliyor
 | 
						||
İşlem öncesi otomatik tür dönüştürmelerinin bazı ayrıntıları vardır:
 | 
						||
1) Eğer bölme işleminde iki operand da tamsayı türündense sonuç tamsayı türünden çıkar. Bu durumda bölme
 | 
						||
yapılır, sayının noktadan sonraki kısmı atılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double a;
 | 
						||
a = 10 / 4;
 | 
						||
printf("%f\n", a);
 | 
						||
 | 
						||
/* 2.0 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fakat örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double a;
 | 
						||
a = 10 / 4.;
 | 
						||
 | 
						||
88
 | 
						||
 | 
						||
printf("%f\n", a);
 | 
						||
 | 
						||
/* 2.5 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
2. Eğer operandlardan her ikisi de int türünden küçükse (örneğin char-char, char-short, short-short gibi) Bu
 | 
						||
durumda önce her iki operand da bağımsız olarak int türüne dönüştürülür, sonuç int türünden çıkar. Bu kurala "int
 | 
						||
türüne yükseltme kuralı (integral promotion)" denilmektedir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
short b = 30000;
 | 
						||
short c = 30000;
 | 
						||
a = b + c; /* 60000, sonuç int türden */
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu kuralın şöyle bir ayrıntısı da vardır: Eğer ilgili sistemde short ile int aynı uzunluktaysa ve operandlardan biri
 | 
						||
unsigned short türündense dönüştürme int türüne doğru değil unsigned int türüne doğru yapılır.
 | 
						||
3) Operandlardan biri tamsayı türünden, diğeri gerçek sayı türündense dönüştürme her zaman gerçek sayı türüne
 | 
						||
doğru yapılır. Örneğin long ile float işleme sokulursa sonuç float türden çıkar.
 | 
						||
4) Operandlar aynı tamsayı türünün işaretli ve işaretsiz biçimine ilişkinse dönüştürme her zaman işaretsiz türe
 | 
						||
doğru yapılır (örneğin int ile unsigned int işleme sokulsa sonuç unsigned int türünden çıkar.)
 | 
						||
İşlem öncesi tür dönüştürmeleri aşağıdaki gibi de açıklanabilir:
 | 
						||
1. İşleme giren operandlardan en az biri gerçek sayı türlerindense (if-else if şeklinde düşünülmelidir): Eğer
 | 
						||
operandlardan biri long double türündense diğer operand long double türüne dönüştürülür. Eğer operandlardan bir
 | 
						||
tanesi double türündense diğer operand double türüne dönüştürülür. Eğer operandlardan bir tanesi float türündense
 | 
						||
diğer operand float türüne dönüştürülür. Bu tanımlamadan şu kural çıkartılabilir. İşleme giren operandlardan bir
 | 
						||
tanesi gerçek sayı türünden, diğeri tamsayı türünden ise tamsayı türünden operand o gerçek sayı türüne
 | 
						||
dönüştürülerek işlem yapılmaktadır.
 | 
						||
2. İşleme giren operandlar tam sayı türlerindense: Operandlardan en az bir tanesi short int, unsigned short int, char,
 | 
						||
signed char, unsigned char türlerinden biri ise öncelikle değerler int türüne dönüştürülür. Buna tamsayıya
 | 
						||
yükseltme (integer/integral promotion) denilmektedir. Daha sonra algoritma şu şekildedir.
 | 
						||
Eğer operandlardan bir tanesi unsigned long türündense diğer operanda unsigned long türüne dönüştürülür.
 | 
						||
Eğer operandlardan bir tanesi signed long türündense diğeri long türüne dönüştürülür.
 | 
						||
Eğer operandlardan bir tanesi unsigned int türündense diğeri unsigned int türüne dönüştürülür.
 | 
						||
Tür Dönüştürme Operatörü
 | 
						||
Bazen bir değişkeni işleme sokarken onun sanki başka tür olarak işleme girmesini isteyebiliriz. Örneğin:
 | 
						||
89
 | 
						||
 | 
						||
int a = 10, b = 4;
 | 
						||
double c;
 | 
						||
c = a / b;
 | 
						||
 | 
						||
Burada kırpıolma olacak ve c'ye 2 değeri atanacaktır. Şüphesiz biz a ya da b'den en az birini double olarak bildirip
 | 
						||
sorunu çözebiliriz. Ancak a ve b'nin int olarak kalması da gerekiyor olabilir. İşte bunun için tür dönüştürme
 | 
						||
operatörü kullanılmaktadır. Tür dönüştürme operatörünün genel biçimi şöyledir:
 | 
						||
(<tür>) operand
 | 
						||
 | 
						||
Buadaki parantez öncelik parantezi değildir, opğeratör görevindedir. Tür dönüştürme operatörü tek operandlı önek
 | 
						||
bir operatördür. Öncelik tablosununun ikinci düzeyinde sağdan sola grupta bulunur:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! (tür)
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
= += -= *= /= %=, ... Sağdan-Sola
 | 
						||
,
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
Örneğin:
 | 
						||
a = (long) b * c;
 | 
						||
İ1: (long)b
 | 
						||
İ2: İ1 * c
 | 
						||
İ3: a = İ2
 | 
						||
 | 
						||
Örneğin:
 | 
						||
a = (long)(a * b);
 | 
						||
İ1: a * b
 | 
						||
İ2: (long)İ1
 | 
						||
İ3: a = İ2;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a = 10, b = 4;
 | 
						||
double c;
 | 
						||
c = (double)a / b;
 | 
						||
printf("%f\n", c);
 | 
						||
 | 
						||
90
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
a = (double)(long) b * c;
 | 
						||
İ1: (long)b
 | 
						||
İ2:(double)İ1
 | 
						||
İ3:İ2 * c
 | 
						||
İ4: a = İ3
 | 
						||
 | 
						||
Tür dönüştürmesi yine geçeici nesne yoluyla yapılmaktadır. Yani derleyici önce dönüştürülmek istenen tür
 | 
						||
türünden geçici bir nesne yaratır. Dönüştürülmek istenen ifadeyi oraya atar. Sonra işleme onu sokar. En sonunda da
 | 
						||
geçici nesneyi yok eder.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int n;
 | 
						||
int i, val, total;
 | 
						||
double avg;
 | 
						||
printf("Kac sayi gireceksiniz:");
 | 
						||
scanf("%d", &n);
 | 
						||
total = 0;
 | 
						||
for (i = 1; i <= n; ++i) {
 | 
						||
printf("%d. Sayiyi giriniz:", i);
 | 
						||
scanf("%d", &val);
 | 
						||
total += val;
 | 
						||
}
 | 
						||
avg = (double)total / n;
 | 
						||
/* dikkat! */
 | 
						||
printf("Ortalama = %f\n", avg);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Nesnelerin Ömürleri
 | 
						||
Bir programda yaratılan nesnelerin hepsi program sonuna kadar bellekte kalmaz. Bazı nesneler programın
 | 
						||
çalışmasının belli bir aşamasında yaratılır, bir süre faaliyet gösterir, sonra yok edilir. Ömür (duration) bir nesnenin
 | 
						||
bellekte tutulduğu zaman aralığına denilmektedir. Bir nesne potansiyel en uzun ömür programın çalışma zamanı
 | 
						||
kadar (run time) olabilir. Bir nesnenin ömrü statik ya da dinamik olabilir. Statik ömür demek ömürün çalışma
 | 
						||
zamanına eşit olması demektir. Statik ömürlü nesneler program çalışmak üzere belleğe yüklendiğinde yaratılır,
 | 
						||
program sonlanana kadar bellekte kalır. Dinamik ömürlü nesnelerde ömür programın çalışma zamanından küçüktür.
 | 
						||
 | 
						||
91
 | 
						||
 | 
						||
Global Nesnelerin Ömürleri
 | 
						||
Global nesneler statik ömürlüdür. Yani bunlar programın çalışma zamanı boyunca bellekte yer kaplarlar. Global
 | 
						||
nesneler belleğin Data ve BSS denilen bölümlerinde tutulmaktadır.
 | 
						||
Yerel Değişkenlerin Ömürleri
 | 
						||
Yerel nesneler dinamik ömürlüdür. Programın akışı nesnenin bildirildiği bloğa girdiğinde o blokta bildirilmiş bütün
 | 
						||
yerel nesneler yaratılır, akış o bloktan çıktığında o blokta bildirilmiş bütün yerel nesneler otomatik yok edilir. Yerel
 | 
						||
değişkenler belleği "stack" denilen bir bölümünde yaratılmaktadır. Stack'te yaratım ve yokedim çok hızlı yapılır.
 | 
						||
Parametre Değişkenlerinin Ömürleri
 | 
						||
Parametre değişkenleri de dinamik ömürlüdür. Fonksiyon çağrıldığında yaratılırlar, fonksiyon çalıştığı sürece
 | 
						||
bellekte kalırlar. Fonksiyonun çalışması bittiğinde yok edilirler. Parametre değişkenleri de stack denilen bölümde
 | 
						||
yaratılmaktedır.
 | 
						||
Önişlemci Kavramı (Preprocessor)
 | 
						||
Aslında bir C derleyicisi iki modülden oluşamaktadır: "Önişlemci Modülü" ve "Derleme Modülü". Önişlemci
 | 
						||
modülü kaynak kodu alır, onun üzerinde bazı işlemler uygular. Sonra onu derleme modülüne verir. Asıl derleme
 | 
						||
işlemi derleme modülü tarafından gerçekleştirilmektedir.
 | 
						||
 | 
						||
Pek çok programlama dilinde böyle bir önişlemci modülü yoktur.
 | 
						||
C'de '#' karakteri ile başlayan satırlar önişlemciye ilişkindir. Yani önişlemci yalnızca başı '#' ile başlayan satırlarla
 | 
						||
ilgilenmektedir. '#' atomunu önişlemci komutu denilen bir anahtar sözcük izler. '#' ile bu anahtar sözcük arasında
 | 
						||
boşluk karakterleriş bırakılabilir. Fakat önişlemci komutları tekbir satırda bulunmak zorundadır. Önişlemci komutu
 | 
						||
önişlemciye ne yapması gerektiğini anlatır. 20'ye yakın önişlemci komutu vardır. Ancak en çok kullanılanı
 | 
						||
#include ve #define komutlarıdır. Kursumuzun bu bölümünde bu iki komut ele alınacaktır. Diğerleri kursumuzun
 | 
						||
sonlarına doğru ele alınacaktır.
 | 
						||
#include Komutu
 | 
						||
#include komutun açısal parantezler içerisinde ya da iki tırnak içerisinde bir dosya ismi izlemek zorundadır.
 | 
						||
Örneğin:
 | 
						||
92
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#include "a.c"
 | 
						||
#include komutu kaynak kodun tepesinde bulunmak zorunda değildir. Herhangi bir yerd ebulunabilir. Fakat bir
 | 
						||
satırı sadece kendisi kaplamak zorundadır. Önişlemci #include komutunu gördüğünde dosyayı diskten okur ve
 | 
						||
onun içindekilerini komutun yazılı olduğu yere yapıştırır. Tabi bunu geçici bir dosya açarak yapmaktadır. Derleme
 | 
						||
modülüne de bu geçici dosyayı verir. Derleme modülü kodu aldığında artık orada #include görmez. Onun içeriğini
 | 
						||
görür. #include komutu ile include ettiğimiz dosyanın içerisinde C'ce anlamlı şeyler olmalıdır. Örneğin:
 | 
						||
/* test.c */
 | 
						||
int a;
 | 
						||
/* sample.c */
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
#include "test.c"
 | 
						||
a = 10;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Eğer dosya açısal parantezler içerisinde include edilirse bu durumda dosya derleyicinin belirlediği bir dizinde
 | 
						||
aranır. Derleyici install edilirken başlık dosyaları bir dizine çekilmektedir. Açısal ile dosya ismi belirtilirken o
 | 
						||
dizine bakılır. C'nin standart başlık dosyalarının açısal parantzeler içerisinde include edilmesi uygundur. eğer
 | 
						||
dosya ismi iki tırnak içerisnde belirtilirse derleyiciler onu önce bulunulan dizinde (ya da projenin yüklü dizinde)
 | 
						||
arar eğer orada bulamazlarsa sani açısal parantez ile belirtilmiş gibi derleyicinin belirlediği dizine de bakılır. Bu
 | 
						||
durumda bizim kendi dosyalarımız iki tırnak içerisinde include etmemiz daha uygundur. Çünkü onlar muhtemelen
 | 
						||
kendi dizinimizde bulunacaktır.
 | 
						||
C standartları #include komutunun semantiğini ana hatlarıyla belirtip çoğu şeyi derleyicileri yazanların isteğine
 | 
						||
bırakmıştır. Yani dosya ismi açısal parantez ile belirtildiğinde nerelere bakılacağı, iki tırnak içerisinde
 | 
						||
belirtildiğinde nerelere bakılacağı hep derleyicileri yazanların isteğine bırakılmıştır.
 | 
						||
Pek çok derleyicide include dosyaları aranırken programcının istediği dizinlere de bakılması sağlanmıştır. Örneğin
 | 
						||
Visual Studio IDE'sinde Projeye ayarlarında "C+C++/General/Additional Include Directories" seçenekleri ile
 | 
						||
önişlemcinin bizim belirlediğimiz dizilere de bakması sağlanabilir. gcc derleyicilerinde -I seçeneği ile dizin
 | 
						||
eklenebilmektedir. Örneğin:
 | 
						||
gcc -I /home/csd/Study/C -o sample sample.c
 | 
						||
include edilen bir dosyanın içerisinde de #include komutları bulunabilir. Önişlemci bunların hepsini düzgün
 | 
						||
biçimde açabilmektedir. Örneğin biz pek çok dosyayı "myheaders.h" dosyası içerisinde include etmiş olalım:
 | 
						||
/* myheaders.h */
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <math.h>
 | 
						||
 | 
						||
Asıl programımızda yalnızca "myheaders.h" dosyasını include edebiliriz:
 | 
						||
93
 | 
						||
 | 
						||
/* sample.c */
 | 
						||
#include "myheaders.h"
 | 
						||
...
 | 
						||
 | 
						||
Fakat include işlemlerinde döngüsellik olamaz. include edilecek dosyanın ismi ve uzuntası herhangi bir biçimde
 | 
						||
olabilir. Ancak C'de gelenek uzantının .h biçiminde olmasıdır.
 | 
						||
#define Komutu
 | 
						||
#define komutu text editörlerdeki "find and replace" işlemine benzer bir işlem yapmaktadır. Komutun genel biçimi
 | 
						||
şöyledir:
 | 
						||
#define STR1
 | 
						||
 | 
						||
STR2
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#define MAX
 | 
						||
#define MIN
 | 
						||
 | 
						||
100
 | 
						||
100 - 20
 | 
						||
 | 
						||
#define anahtar sözcüğünden sonra boşluk karakterleri atılır ve ilk boşluksuz yazı kümesi elde edilir. Buna STR1
 | 
						||
diyelim. Sonra önişlemci yine boşlukları atar ve satır sonuna kadarki tüm karakterleri elde eder. Buna da STR2
 | 
						||
diyelim. Sonra kaynak kodda STR1 gördüğü yere STR2 yazısını yerleştirir. Sonucu derleme modülüne verir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define MAX
 | 
						||
 | 
						||
100 - 50
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = MAX * 2;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada #define komutunda STR1 MAX, STR2 de 100 - 50'dir. Önişlemci MAX atomlarını 100 - 50 ile yer
 | 
						||
değiştirir. Derleme modülüne kod aşağıdaki gibi verilir:
 | 
						||
...stdio.h içeriği...
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = 100 - 50 * 2;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dolayısıyla bu program çalıştırıldığında ekrana 0 basılır.
 | 
						||
Önişlemci hesp yapmaz. Bir yazıyı başka bir yazıyla yer değiştirir. Örneğin:
 | 
						||
94
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#define MAX
 | 
						||
 | 
						||
(100 - 50)
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
a = MAX * 2;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada ekrana 100 basılacaktır.
 | 
						||
STR1 olarak yalnızca değişken ve anahtar sözcük atom kullanılır. Örneğin:
 | 
						||
#define +
 | 
						||
#define 100
 | 
						||
#define beep()
 | 
						||
#define MAX
 | 
						||
 | 
						||
200
 | 
						||
putchar('\a')
 | 
						||
+
 | 
						||
 | 
						||
/* geçerli değil */
 | 
						||
/* geçerli değil */
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Aşağıdaki kod tamamen geçerlidir:
 | 
						||
#include <stdio.h>
 | 
						||
#define ana
 | 
						||
main
 | 
						||
#define tam
 | 
						||
int
 | 
						||
#define eger
 | 
						||
if
 | 
						||
#define yazf
 | 
						||
printf
 | 
						||
#define geridon return
 | 
						||
tam ana()
 | 
						||
{
 | 
						||
tam a = 100;
 | 
						||
eger(a == 100)
 | 
						||
yazf("tamam\n");
 | 
						||
geridon 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir yazıyıya karşılık bir sayı karşı düşürülmesi durumunda bu yazıya sembolik sabit (symbolic constans) ya da
 | 
						||
makro (macro) denilmektedir. Örneğin:
 | 
						||
#define MAX
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
Buarada MAX bir sembolik sabittir.
 | 
						||
STR2'de başka makrolar bulundurulabilir. Önişlemci açımı özyinelemeli yapar. Yani açtığı makroyu yeniden açar.
 | 
						||
Ta ki açılacak bi,rşey kalmayana kadar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define MAX
 | 
						||
#define MIN
 | 
						||
 | 
						||
100
 | 
						||
(MAX - 50)
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int a;
 | 
						||
 | 
						||
95
 | 
						||
 | 
						||
a = MIN;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada MIN yerine önce (MAX - 50) yerleştirilir. Sonra bu da yeniden önişlemciye sokulur. Böylece (100 - 50)
 | 
						||
elde edilir. Burada #define'ların sırası farklı olsaydı da değişen birşey olmayacaktı. Yani önişlemci #define satırları
 | 
						||
üzerinde değişiklik yazpmaz.
 | 
						||
#define önişlemci komutu ile iki tırnak içerisindeki yazılarda değişiklik yapamayız. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define MAX
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf("MAX");
 | 
						||
 | 
						||
/* MAX çıkar */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define CITY
 | 
						||
int main()
 | 
						||
{
 | 
						||
printf(CITY);
 | 
						||
 | 
						||
"Ankara"
 | 
						||
 | 
						||
/* Ankara çıkar */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aşağıdaki makro geçerlidir:
 | 
						||
#define MYHEADER
 | 
						||
#include MYHEADER
 | 
						||
 | 
						||
<stdio.h>
 | 
						||
 | 
						||
#define komutu #include komutunda değişiklik yapabilmektedir.
 | 
						||
Eğer STR2 yazılmazsa bu durumda STR1 görülen yere boşluk yerleştirilir. Yani STR1 silinir. Örneğin aşağıdaki
 | 
						||
kodda derleme sorunu oluşmaz:
 | 
						||
#include <stdio.h>
 | 
						||
#define in
 | 
						||
int main()
 | 
						||
{
 | 
						||
in
 | 
						||
in in in in
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
#define komutu nereye yerleştirilmişse onun aşağısında etkili olur. Örneğin:
 | 
						||
96
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
#define SIZE
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
#define komutunda yerel, global gibi bir kavram yoktur. Komut nereye yazılmışsa dosyanın sonuna kadarki
 | 
						||
bölgede geçerli olur.
 | 
						||
Önişlemci #include komutu ile bir başlık dosyasını açtığında o dosyanın içini de önişleme sokar. Yani oradaki
 | 
						||
#define'lar, #include'lar da etki gösterir. Böylece biz birtakım sembolik sabit tanımlamalarını bir başlık dosyasında
 | 
						||
yapabiliriz. Onu include ettiğimizde o makrolar etkili olur.
 | 
						||
C standartlarına göre bir makro sabit ikinci kez farklı bir biçimde define edilirse tanımsız davranış oluşur. Örneğin:
 | 
						||
#define MAX
 | 
						||
...
 | 
						||
#define MAX
 | 
						||
 | 
						||
100
 | 
						||
200
 | 
						||
 | 
						||
Burada pek çok derleyici ikinci komuta kadar birincisini etkin yapar, ikincikomuttan sonra ikincisinin etkin olduğu
 | 
						||
düşünür. Fakat aslında derleyicinin burada ne yapacağı belli değildir. Bundan kaçınmak gerekir. Tabi makronun
 | 
						||
aynı biçimde birden fazla kez tanımlanması soruna yol açmaz.
 | 
						||
Ayrıca #define komutunun parametreli bir kullanımı da vardır. Bu konı kursumuzun sonlarına doğru ele alınacaktır.
 | 
						||
Sembolik sabitler geleneksel olarak büyük harflerle isimlendirilmektedir. Bu onların program içerisinde
 | 
						||
ayrımsanmasını kolaylaştırmaktadır.
 | 
						||
#define Komutuna Neden Gereksinim Duyulmaktadır?
 | 
						||
#define komutu kaynak kod üzerinde bazı değişiklikler yapılmasına olanak sağlar. Fakat kurusumuzun bu
 | 
						||
noktasında bu tür değişikliklerden nasıl fayda sağlanacağı açıklanmayacaktır. #define komutu okunabilirliği
 | 
						||
artırmak için sıkça kullanılır. Örneğin bir personel takip programında aşağdaki gibi bir satır bıuşunuyor olsun:
 | 
						||
if (n > 732) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Bu mu daha onunabilirdir (readable), yoksa aşağıdaki mi?
 | 
						||
#define PERSONEL_SAYISI
 | 
						||
...
 | 
						||
 | 
						||
732
 | 
						||
 | 
						||
if (n > PERSONEL_SAYISI) {
 | 
						||
...
 | 
						||
 | 
						||
97
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
İşte programımızda çeşitli sayıları yazısal biçimde ifade edersek okunabilirliği artırmış oluruz. #define komutu kod
 | 
						||
üzerinde değişiklikleri daha az zahmetle yapmamızı sağlar. Örneğin bir programın pek çok yerinde 100 sayısı
 | 
						||
kullanılıyor olsun. Biz bunu 200 ile yer değiştirmek isteyelim. Eğer 100'ü define edersek bunu tek yerden
 | 
						||
yapabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
int main()
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
/* ... */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyon Prototipleri
 | 
						||
Derleme işleminin bir yönü vardır ve bu yön yukarıdan aşağıya doğrudur. Derleyici çağrılan bir fonksiyonu
 | 
						||
gördüğünde çağrılama noktasına kadar bu fonksiyonun geri dönüş değerinin türünü tespit etmek zorundadır. Eğer
 | 
						||
çağrılan fonksiyon çağıran fonksiyonun daha yukarısında tanımlanmışsa çağrılam noktasına kadar derleyici bu
 | 
						||
tespit yapmış olur. Fakat eğer çağrılan fonksiyon çağıran fonksiyonun daha altında tanımlanmışsa bu durumda
 | 
						||
derleyici çağrılma noktasına kadar fonksiyonun geri dönüş değerinin türünü tespit edememiştir.
 | 
						||
Eğer derleyici çağrılma noktasına kadar fonksiyonun geri dönüş değerinin türünü tespit edememişse bu durumda
 | 
						||
fonksiyonun int geri dönüş değerine sahip olduğunu varsayarak kodu üretir. Eğer fonksiyonunun daha sonra başka
 | 
						||
bir geri dönüş değerine sahip olduğunu derleyici görürse geri dönüp ürettiği kodu düzeltmez. Bu durum error
 | 
						||
oluşturur. Fakat derleyici daha sonra fonksiyonun int geri dönüş değerine sahip olarak tanımlandığı görürse sorun
 | 
						||
oluşmaz.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
foo();
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Fakat örneğin:
 | 
						||
98
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
foo();
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int foo(void)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = div(10, 2.5);
 | 
						||
 | 
						||
/* error! */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
double div(double a, double b)
 | 
						||
{
 | 
						||
return a / b;
 | 
						||
}
 | 
						||
 | 
						||
İşte derleyiciye bir fonksiyonun geri dönüş değeri ve parametreleri hakkında bilgi veren bildirimlere "fonksiyon
 | 
						||
prototipleri" denilmektedir. Eğer çağrılan fonksiyon çağıran fonksiyonun daha aşağısında tanımlanmışsa biz
 | 
						||
çağırma işleminin yukarısına fonksiyon prototipini yerleştirmeliyiz. Prototip bildiriminin genel biçimi şöyledir:
 | 
						||
[geri dönüş değerinin türü] <fonksiyon ismi>([parametre bildirimi]);
 | 
						||
 | 
						||
Örneğin:
 | 
						||
double divide();
 | 
						||
double divide(double a, double b);
 | 
						||
divide(void);
 | 
						||
 | 
						||
geçerli prototip bildirimleridir. Prototipte parametre bildirimi yapmak iyi bir tekniktir. Çünkü derleyici bu durumda
 | 
						||
çağrılma ifadesinde parametre kontrolü ve uygun tür dönüştürmesini yapar. Prototip bildiriminde parametre
 | 
						||
değişkenlerinin yalnızca türleri belirtilebilir. Örneğin:
 | 
						||
double divide(double, double);
 | 
						||
 | 
						||
Fakat parametre değişkenlerinin isimlerini yazmak okunabilirliği artırır. Örneğin aşağıdakilerden hangisi daha
 | 
						||
okunabilirdir?
 | 
						||
double usal(double, double);
 | 
						||
double usal(double taban, double us);
 | 
						||
 | 
						||
Parametre değişkenlerinin isimlerinin hiçbir önemi yoktur. Hatta istenirse bazı parametre değişkenlerine isim verili
 | 
						||
bazılarına verilmeyebilir. Tabi iyi teknik tüm parametre değişkenlkerinin isimlendirilmesidir.
 | 
						||
99
 | 
						||
 | 
						||
Protoipteki geri dönüş değeri ve parametre türleriyle tanımlamadakilerin tam olarak aynı olması gerekir. Aksi
 | 
						||
halde kod geçerli olmaz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
double divide(float x, double y);
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = divide(10, 2.5);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
double divide(double a, double b)
 | 
						||
{
 | 
						||
return a / b;
 | 
						||
}
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Fakat parametre değişkenlerinin isimlerinin aynı olması zorunluluğu yoktur.
 | 
						||
Fonksiyon tanımlamasının ilk satırı alınıp kopyalanır ve sonuna noktalı virgül getirilirse prototip elde edilir.
 | 
						||
Örneğin:
 | 
						||
double divide(double a, double b)
 | 
						||
{
 | 
						||
return a / b;
 | 
						||
}
 | 
						||
 | 
						||
Bu fonksiyonun prototipi şöyledir:
 | 
						||
double divide(double a, double b);
 | 
						||
 | 
						||
Aynı protipi birden fazla kez bildirmek sorun oluşturmaz. Örneğin:
 | 
						||
double div(double, double);
 | 
						||
double div(double a, double b);
 | 
						||
double div();
 | 
						||
 | 
						||
Bunların hepsi bir arada bulunabilir.
 | 
						||
Küçük olmayan projelerde ve kodlarda fonksiyonlar nerede tanımlamış olursa olsun onların prototiplerini kaynak
 | 
						||
kodun tepesine yerleştirmek ya da bir başlık dosyasına yerleştirip onu include etmek iyi bir tekniktir.
 | 
						||
Eğer biz fonkisyonun prototini ya da tanımlamasını daha yukarıda oluşturmuşsak bu durumda çağrılma ifadesinde
 | 
						||
derleyici argümanları sayıca ve türce kontrol eder. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo(int a, int b);
 | 
						||
int main()
 | 
						||
{
 | 
						||
foo(100);
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Prototip bir bildirimdir, tanımlama değildir. Yani prototip oluşturduğunuzda biz fonksiyonu yazmış (tanımlamış)
 | 
						||
olmayız. Yalnızca derleyiciye bir ön bildirimde bulunmuş oluruz.
 | 
						||
Prototip bildiriminde parametre parantezinini boş bırakılmasıyla oraya void yazılması farklı anlamlara gelmektedir.
 | 
						||
eğer parametre parantezi boş bırakılırsa bu durum derleyicinin çağrılam ifadelerinde argüman kontrolü
 | 
						||
yapmayacağı anlamına gelir. Örneğin:
 | 
						||
void foo();
 | 
						||
 | 
						||
Böyle bir bildirimde biz foo'yu istediğimiz kadar çok argümanla çağırabiliriz. Derleyici bir kontrol uygulamaz.
 | 
						||
Ancak parametre parantezinin içine void yazılırsa bu durum fonksiyonun parametre almadığı anlamına gelir.
 | 
						||
Örneğin:
 | 
						||
void foo(void);
 | 
						||
 | 
						||
Biz artık foo'yu argümanla çağıramayız.
 | 
						||
Anahtar Notlar: Aslınd abu durum un tarihsel bazı gerekçeleri vardır. Eskiden C'de fonksiyon prototipleri yoktu. Zaten parametre
 | 
						||
parantezlerinin içi boş bırakılmak zorundydı. Sonra C'ye prototi kavramı eklendiği zaman eski kodlar geçerli olsun diye bu semantik
 | 
						||
muhafaza edildi. Eskiden yapılan parametre parantezinin içinin boş bırakıldığı bildirimlere prototip değil "fonklsiyon bildirimi"
 | 
						||
deniliyordu.
 | 
						||
 | 
						||
Fonksiyon tanımlamasında parametre parantezinin içinin boş bırakılmasıyla void yazılması arasında bir farklılık
 | 
						||
yoktur. Her iki durum da fonksiyonun parametreye sahip olmadığı anlamına gelir. Yani:
 | 
						||
void foo()
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
ile,
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
tanımlamaları tamamen eşdeğerdir.
 | 
						||
Fonksiyon prototip bildirimeleri nerede yapılamlıdır? Protip bildirimleri yerel ya da global düzeyde yapılabilir.
 | 
						||
Yine eğer yerel düzeyde yapılacaksa C90'da blokların başlarında yapılması zorunludur (Tabi C99 ve C++'ta
 | 
						||
blokların herhangi bir yerinde yapılabilir.) Eğer prototip bildirimi global düzeyde yapılırsa tüm dosya bundan
 | 
						||
etkilenir. Yerel düzeyde yapılrsa yalnızca o bloktaki kullanım bundan etkilenir. Yani başka bloklarda sanki
 | 
						||
bildirim hiç yapılmamış gibi etki gösterir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void bar(void)
 | 
						||
{
 | 
						||
void foo(int a, int b);
 | 
						||
 | 
						||
101
 | 
						||
 | 
						||
foo(10, 20);
 | 
						||
 | 
						||
/* prototip etki gösterir */
 | 
						||
 | 
						||
}
 | 
						||
int main()
 | 
						||
{
 | 
						||
foo(10, 20);
 | 
						||
 | 
						||
/* geçersiz! sanki protip yokmuş gibi etki gösterir*/
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void foo(int a, int b)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Fakat hemen her zaman fonksiyon prototip bildirimi glob al düzeyde yapılmaktadır.
 | 
						||
Derleyici bir fonksiyonun prototipini ya da tanımlamasını görmeden onun çağrıldığı görürse sanki onun prototipi
 | 
						||
aşağıdaki gibi yazılmış varsayar:
 | 
						||
int some_function();
 | 
						||
Bir fonksiyonun prototip bildirimin yaılmış olması onu çağırmayı zorunlu hale getirmez.
 | 
						||
Standart C Fonksiyonlarının Prototipleri
 | 
						||
Kaynak programımızda olmayan bir fonksiyonu çağırsak bile derleme aşamasından başarıyla geçilir. Eğer prototip
 | 
						||
yoksa derleyici fonksiyonun geri dönüş değerinin türünü int varsayar. Tabi bu durumda argüman kontrolü yapmaz.
 | 
						||
Eğer prototip varsa derleyici geri dönüş değerinin türünü anlar. Her iki durumda da derleme işlemi başarıyla geçilir.
 | 
						||
Fakat derleyici object kod içerisine linker için bir mesaj yazar. Mesajda "sevgili linker, ben bu fonksiyonu
 | 
						||
bulamadım. Sen onu diğer objectg modüllerde ve kütüphane dosyalarında ara. Bulursan oradan al. Bulamazsan
 | 
						||
seninde elinden birşey gelmez. Ne yapalım...". Bu mesajı okuyan linker fonksiyonu diğer modüllerde ve
 | 
						||
kütüphanelerde arar.
 | 
						||
Aslında link işlemi tek bir object modülle yapılmak zorunda değildir. Birden fazla object modül ve kütüphane
 | 
						||
dosyaları birlikte link edilip tek bir exe dosya elde edilebilir.
 | 
						||
 | 
						||
Uzantısı Windows'ta .lib ve .dll olan, UNIX/Linux sistemlerinde .a ve .so olan dosyalara kütüphane dosyaları denir.
 | 
						||
Kütüphaneler statik ve dinamik olmak üzere ikiye ayrılmaktadır. Uzantısı Windows'ta .lib, UNIX/Linux'ta .a olan
 | 
						||
dosyalara statik kütüphane dosyaları, uzantısı Windows'ta .dll, UNIX/Linux'ta .so olan dosyalara dinamik
 | 
						||
kütüphane dosyaları denilmektedir. Kütüphanelerin içerisinde derlenmiş halde fonksiyonlar bulunmaktadır.
 | 
						||
Eğer bir fonksiyon linker tarafından statik kütüphanelerde bulunursa oradan çekilir ve .exe dosyanın içerisine
 | 
						||
yazılır. Eğer fonksiyon dinamik kütüphane içerisinde bulunursa linker fonksiyonu oradan çekip exe dosyaya
 | 
						||
yazmaz. Linker exe dosya içerisine işletim sistemi için şöyle bir nor yazmaktadır: "Sevgili işletim sistemi bu
 | 
						||
programı çalıştırırken bu programın kullandığı dinamik kütüphaneleri de bul onları da belleğe yükle ki benim
 | 
						||
102
 | 
						||
 | 
						||
fonksiyonlarım çalışsın". Görüldüğü gibi statik link işleminde exe dosya zaten her şeyi içermektedir. Programı
 | 
						||
artık başka bir makinaya götürürken statik kütüphane dosyasını götürmememize gerek yoktur. Ancak dinamik link
 | 
						||
işleminde biz exe dosyayla birlikte onun kullandığı dinamik kütüphane dosyalarını da hedef makinaya taşımamaız
 | 
						||
gerekir.
 | 
						||
C'nin standart fonksiyonları da kütüphaneler içerisinde derlenmiş bir biçimde bulunmaktadır. Dolayısıyla bunlar
 | 
						||
linker tarafından ele alınırlar. C derleyicileri standart C fonksiyonlarının farkında değildir. Yani printf
 | 
						||
fonksiyonuna bizim foo fonksiyonuna yaptığından farklı bir muamele uygulamaz. Biz printf fonksiyonunu
 | 
						||
çağırdığımızda derleyici onu kaynak dosyada bulamayacağı için linker'a havale eder. Linker da onu baktığı
 | 
						||
kütüphane dosyalarında bulur. Aslında linker de standart C fonksiyonlarının farkında değildir. O yalnızca
 | 
						||
belirlenen kütüphanelere bakmaktadır. Standart C fonksiyonlarının yalnızca biz programcılar standart olduğunun
 | 
						||
farkındayızdır. Tabi onların kütüphane içerinde bulunduğundan emin oluruz.
 | 
						||
Standart C fonksiyonlarını derleyici tanımadığına göre onların geri dönüş değerlerinin türlerini ve parametrik
 | 
						||
yapılarını bilmez. Onlar çağrıldığında onların prototipini görmezse geri dönüş değerlerini int kabul eder. O halde
 | 
						||
standart C fonksiyonarının da prototiplerinin bulundurulması gerekir. Örneğin aşağıdaki programda sqrt
 | 
						||
fonksiyonun prototipi bulundurulmadığı için program başarılı derlenip link edilmiştir. Fakat yaznlış çalışır:
 | 
						||
#include <stdio.h>
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = sqrt(10);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Halbuki prototip eklersek doğru çalışacaktır:
 | 
						||
#include <stdio.h>
 | 
						||
double sqrt(double);
 | 
						||
int main()
 | 
						||
{
 | 
						||
double result;
 | 
						||
result = sqrt(10);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Buradan çıkan sonuç "standart fonksiyonlarının da prototiplerinin bulundurulması" gerektiğidir.
 | 
						||
İşte standart C fonksiyonlarının prototipleri gruplanarak çeşitli başlık dosyalarına yerleştirilmiştir. Biz onları elle
 | 
						||
yazmak yerine o dosyaları include edebiliriz. Örneğin bütün matematiksel fonksiyonların prototipleri <math.h>,
 | 
						||
bütün klavye, ekran ve dosya fonksiyonlarının prototipleri <stdio.h>, genel amaçlı fonksiyonların prototipleri
 | 
						||
<stdlib.h> dosyası içerisindedir. Başlık dosyaalarında fonksiyonların tanımlamaları yoktur. Yalnızca prototipleri
 | 
						||
vardır. Fonksiyonlar derlenmiş bir biçimde kütüphanede bulunmaktadır.
 | 
						||
Adres Kavramı
 | 
						||
CPU'nun doğrudan bağlı olduğu ana bellek (yani RAM) byte'lardan oluşmaktadır. Her byte da 8 bitten oluşur.
 | 
						||
Belleğin her bir byte'ına ilk byte 0 olmak üzere bir numara verilmiştir. Buna ilgili byte'ın fiziksel adresi denir.
 | 
						||
103
 | 
						||
 | 
						||
Bayte'ların fiziksel adresleri geleneksel olarak 16'lık sistemde gösterilmektedir. Örneğin:
 | 
						||
 | 
						||
Bellketeki nesnelerin de adresleri vardır. Çünkü onlar da yaşam süresi içerisinde RAM'de bir yerlerde
 | 
						||
bulunmaktadır. Örneğin:
 | 
						||
 | 
						||
Her nesnenin bir fiziksel adresi vardır. Bir byte'tan büyük olan nesnelerin fiziksel adresleri onların en düşük adres
 | 
						||
numaralarıyla belirtilir. Örneğin b'nin fiziksel adresi 001B0101'dir, c'nin 001B00103'tür. Şüphesiz yerel nesneler
 | 
						||
dinamik ömürlü olduğu için, yerel nesneler için ayrılan alan onların ömürleri bittiğinde artık başka yerel nesneler
 | 
						||
için kullanılabilir.
 | 
						||
Yazılımsal adres bilgisi iki bileşnli bir bilgidir. Adresin sayısal ve tür bileşenleri vardır:
 | 
						||
 | 
						||
Adresin sayısal bileşeni nesnenin bellekteki fiziksel adres numarasını belirtir. Tür bileşeni ise o fiziksel adresteki
 | 
						||
nesnenin türünü ifade eder. Örneğib:
 | 
						||
 | 
						||
104
 | 
						||
 | 
						||
Bundan sonra kursumuzda "adres" denildiğinde yazılımsal adres kavramı anlaşılmalıdır. (Donanımsal ya da
 | 
						||
fiziksel adres kavramı tek bileşenlidir ve yalnızca sayı belirtir.)
 | 
						||
C'de adresler tamamen ayrı bir tür belirtmektedir. Nasıl int, long, double gibi türler varsa adresleri tutabilen türler
 | 
						||
de vardır. C'de adres türleri (pointer types) adresin tür bileşeni ile ifade edilir. Adres türleri C'de adresin tür bileşeni
 | 
						||
ile ifade edilir. Yani örneğin bu türe "adres" denmez de "int türden adres (pointer to int)", "long türden adres" denir.
 | 
						||
C'de nasıl int türden, long türden, double türden sabit kavramı varsa adres sabiti kavramı da vardır. T türünden
 | 
						||
adres sabiti tür dönüştürme operatörü ile oluşturulur. Genel biçimi şöyledir:
 | 
						||
(<tür bileşeni> *) <sayısal bileşen>
 | 
						||
 | 
						||
Adresin sayısal bileşeni fiziksel adres numarası belirtir. Tür bileşeni ise o fiziksel adresteki bilginin türünü
 | 
						||
belirtmektedir.
 | 
						||
Örneğin:
 | 
						||
(int *)0x001B1010
 | 
						||
 | 
						||
Bu ifade int türden adres sabiti belirtir. Örneğin:
 | 
						||
(double *)0x001C2000
 | 
						||
 | 
						||
Bu ifade double türden adres sabiti anlamına gelir.
 | 
						||
Genel biçimdeki yıldız adres anlamına gelir. Adres sabitlerini yazarken tür bileşeninin 16'lık sistemde belirtilmesi
 | 
						||
zorunlu değildir. Fakat gelenek bu yöndedir.
 | 
						||
C'de nasıl int türünü tutan, long türünü tutan, double türünü tutan nesneler bildirilebiliyorsa adres bilgilerini tutan
 | 
						||
da nesneler bildirilebilir. Bunlara gösterici (pointer) denir.
 | 
						||
Diziler (Arrays)
 | 
						||
Elemanları aynı türden olan ve bellekte ardışıl bir biçimde bulunan veri yapılarına dizi denir (bu tanım
 | 
						||
ezberlenmelidir). Aralarında fiziksel ya da mantıksal ilişki bulunan birden fazla nesneni oluşturduğu topluluğa veri
 | 
						||
yapısı (data structure) denilmektedir. Dizi bir veri yapısıdır. Çünkü dizi dediğimizde birden fazla eleman (özel
 | 
						||
olarak bir eleman da olabilir) söz konusu edilir. Dizi elemanlarının hepsi aynı türdendir. Dizi elemanları bellekte
 | 
						||
ardışıl bir biçimde tutulur. Yani bir elelmandan sonra hiç boşluk olmadan diğer elemana geçilir. Halbuki örneğin:
 | 
						||
int a, b;
 | 
						||
 | 
						||
105
 | 
						||
 | 
						||
gibi bir bildirimde a ve b'nin bellekte ardışıl tutulacağının bir garantisi yoktur.
 | 
						||
Dizi bildiriminin genel biçimi şöyledir:
 | 
						||
<tür> <dizi ismi><[<uzunluk>]>;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int a[10];
 | 
						||
long b[20];
 | 
						||
char c[5];
 | 
						||
 | 
						||
gibi.
 | 
						||
Küme parantezi içerisindeki uzunluk belirten ifadenin tamsayı türlerine ilişkin sabit ifadesi olması zorunludur.
 | 
						||
Örneğin:
 | 
						||
int a[3 + 2];
 | 
						||
int n = 5;
 | 
						||
int b[n];
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli değil */
 | 
						||
 | 
						||
Derleyicinin dizi uzunluğunu daha derleme aşamasında bilmesi gerekir. Bu nedenle dizi unluklarının sabit ifadesi
 | 
						||
olması zorunlu tutulmuştur.
 | 
						||
Bir dizinin ismi o dizinin tamamını temsil eder. Örneğin:
 | 
						||
int a[10];
 | 
						||
 | 
						||
bildiriminde a 10 elemanlı bir dizi nesnesidir. Dizi türleri sembolik olarak önce tür sonra bir köşeli parantez
 | 
						||
içerisinde uzunluk ifadesiyle temsil edilmektedir. Örneğin yukarıda a'nın türü int[10] biçiminde ifade edilir. int[10]
 | 
						||
"10 elemanlı int türden dizi" anlamına gelir. Örneğin:
 | 
						||
char s[20];
 | 
						||
 | 
						||
Burada s, char[20] türündendir.
 | 
						||
Dizi bildirimi ile normal nesne bildirimi birlikte yapılabilir. Örneğin:
 | 
						||
int a[10], b, c[5];
 | 
						||
 | 
						||
Bu bildirimin eşdeğeri şöyledir:
 | 
						||
int a[10];
 | 
						||
int b;
 | 
						||
int c[5];
 | 
						||
 | 
						||
Dizi Elemanlarına Erişim ve [] Operatörü
 | 
						||
Dizi elemanlarına [] operatörüyle erişilir. Köşeli parantezlerin içerisine tamsayı türlerine ilişkin bir indeks ifadesi
 | 
						||
yazılmak zorundadır. Bu ifade sabit ifadesi olmak zorunda değildir. Dizinin ilk elemanı sıfırıncı ideks'li elemanıdır.
 | 
						||
Bu durumda son eleman (n elemanlı bir dizide) n - 1'inci indeksteki eleman olur. Örneğin:
 | 
						||
int a[10];
 | 
						||
 | 
						||
a[3] ifadesi int türdendir. a ifadesi ise int[10] türündendir.
 | 
						||
106
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10];
 | 
						||
int i;
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
a[i] = i * i;
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dizi Elemanlarına İlkdeğer Verilmesi
 | 
						||
Bir dizinin elemanlarına değer atanmamışsa elemanların içerisinde ne vardır? Dizi yerel ise dizinin tüm
 | 
						||
elemanlarında çöp değer, globalsa sıfır bulunur.
 | 
						||
Dizi elemanlarına ilkdeğer küme parantezleri ile verilir. Küme parantezlerinin içerisinde değerler ',' atomu ile
 | 
						||
ayrılarak yazılır. Örneğin:
 | 
						||
int a[3] = {1, 2, 3};
 | 
						||
 | 
						||
Verilen ilkdeğerler dizi elemanlarına sırasıyla yerleştirilmektedir:
 | 
						||
 | 
						||
Dizinin uzunluğundan fazla elemanına değer veremeyiz. Örneğin:
 | 
						||
int a[3] = {1, 2, 3, 4};
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Fakat dizinin az sayıda elemanına ilkdeğer verebiliriz. Bu durumda geri kalan elemanlar dizi ister yerel yerel olsun,
 | 
						||
ister global olsun derleyici tarafından sıfırlanır. Örneğin:
 | 
						||
int a[5] = {1, 2 };
 | 
						||
 | 
						||
/* Dizinin 2, 3 ve 4'üncü indeksli elemanları sıfırdır */
 | 
						||
 | 
						||
İlkdeğer verirken küme parantezlerinin içi boş bırakılamaz. Örneğin:
 | 
						||
int a[] = {};
 | 
						||
 | 
						||
Örneğin yerel bir dizinin tüm elemanlarını sıfırlamanın en kolay şöyledir:
 | 
						||
int a[10] = {0};
 | 
						||
 | 
						||
107
 | 
						||
 | 
						||
C'de (C90 ve C99 ve C11'de) küme parantezleri içerisinde verilenm ilkdeğerlerin sabit ifadesi olması zorunludur
 | 
						||
(ancak C++'ta sabit ifadesi olmayabilir). Örneğin:
 | 
						||
int n = 10;
 | 
						||
int a[3] = {5, n, 15};
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Bazı derleyiciler bir eklenti olarak küme parantezleri içerisinde sabit ifadesi olmayan ilkdeğer ifadelerini kabul
 | 
						||
edebilmektedir. Fakat bu standart bir özellik değildir.
 | 
						||
Diziye ilkdeğer verilirken küme parantezlerinin içi boş bırakılabilir. Bu durumda derleyici verilen ilkdeğerleri
 | 
						||
sayar ve dizinin o uzunlukta açılmış olduğunu kabul eder. Örneğin:
 | 
						||
int a[] = {1, 2, 3};
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Burada a dizisi 3 elemanlıdır.
 | 
						||
Tabi diziye ilkdeğer verilmiyorsa uzunluk belirtmek zorunludur. Örneğin:
 | 
						||
int a[];
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
C'de virgüllerle ayrılmış listelerde genel olarak (ileride başka konularda da karşılaşılacak) son elemandna sonra ','
 | 
						||
atomu bulundurulabilir. Bu yasak değildir. Örneğin:
 | 
						||
int a[3] = {1, 2, 3, };
 | 
						||
int b[] = {1, 2, 3, };
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
C90'da dizinin istediğimiz bazı elemanlarına ilkdeğer veremeyiz. Böyle bir sentaks yoktur. Ancak C99'da bu
 | 
						||
aşağıdaki sentaksla mümkündür:
 | 
						||
int a[10] = {10, [4] = 100, 200, [9] = 300};
 | 
						||
 | 
						||
C99'a göre burada dizinin sıfırıncı indeksli elemanında 10, 4'üncü indeksil elemanında 100, 5',nci indeksli
 | 
						||
elemanında 200 ve 9'uncu indeksli elemanında 300 vardır. Diğer elemanlarda sıfır bulunur. Bu özellik bazı C90
 | 
						||
derleyicilerinde bir eklenti (extension) olarak desteklenmektedir.
 | 
						||
Bir Dizi İçerisindeki En Büyük (ya da En Küçük) elemanın Bulunması Algoritması
 | 
						||
Bu problemin tek bir algoritmik çözümü vardır. Dizinin ilk elemanı enbüyük kabul edilip bir nesnede saklanır.
 | 
						||
Sonra geri kalan tüm elemanlara bakılır, daha büyüğü varsa nesne içerisindeki değer yer değiştirilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 10, 4, 6, 34, 32, -34, 39, 21, 9, 22 };
 | 
						||
int i, max;
 | 
						||
max = a[0];
 | 
						||
for (i = 1; i < SIZE; ++i)
 | 
						||
if (a[i] > max)
 | 
						||
max = a[i];
 | 
						||
printf("Max = %d\n", max);
 | 
						||
 | 
						||
108
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Yukarıdaki dizide en büyük ve en küçük eleman arasındaki farkı ekrana yazdırınız
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 10, 4, 6, 34, 32, -34, 39, 21, 9, 22 };
 | 
						||
int i, max, min;
 | 
						||
min = max = a[0];
 | 
						||
for (i = 1; i < SIZE; ++i)
 | 
						||
if (a[i] > max)
 | 
						||
max = a[i];
 | 
						||
else if (a[i] < min)
 | 
						||
min = a[i];
 | 
						||
printf("Degisim Araligi = %d\n", max - min);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dizilere İlişkin Çeşitli Algoritmik Örnekler
 | 
						||
Bir dizinin eleman toplamaı ve ortalaması şöyle bulunabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
5
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE];
 | 
						||
int i, total;
 | 
						||
double avg;
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
printf("%d. indeksli elemani giriniz:", i);
 | 
						||
scanf("%d", &a[i]);
 | 
						||
}
 | 
						||
total = 0;
 | 
						||
for (i = 0; i < SIZE; ++i)
 | 
						||
total += a[i];
 | 
						||
avg = (double)total / SIZE;
 | 
						||
printf("%f\n", avg);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: int türden bir dizi içerisindeki en fazla yinelenen değeri bulunuz (mod değeri). Test için aşağıdaki
 | 
						||
diziyi kullanınınz:
 | 
						||
int a[SIZE] = { 3, 7, 3, 6, 6, 8, 7, 8, 6, 4 };
 | 
						||
 | 
						||
109
 | 
						||
 | 
						||
İpucu: İç içe iki döngü kullanılabilir. Her değerin kaç tane tekrarlandığı bulunur ve bir değişkende saklanır, daha
 | 
						||
fazlası varsa yer değiştirilir. (Dizinin k'ıncı elemanından kaç tane olduğuna bakmak için baştan başlamaya gerek
 | 
						||
yoktur. k'ıncı indeksten başlamak yeterlidir. Çünkü zaten k'ıncı indekstenn önce o elemandan varsa biz onun
 | 
						||
sayısını bulmuş durumda oluruz).
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 3, 7, 3, 6, 6, 8, 7, 8, 6, 4 };
 | 
						||
int i, k;
 | 
						||
int count, max_count, max_count_val;
 | 
						||
max_count = 0;
 | 
						||
for (i = 0; i < SIZE; ++i) {
 | 
						||
count = 1;
 | 
						||
for (k = i + 1; k < SIZE; ++k)
 | 
						||
if (a[i] == a[k])
 | 
						||
++count;
 | 
						||
if (count > max_count) {
 | 
						||
max_count = count;
 | 
						||
max_count_val = a[i];
 | 
						||
}
 | 
						||
}
 | 
						||
printf("Mod = %d\n", max_count_val);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: int türden bir diziyi ters yüz ediniz ve elemanları yazdırınız
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
int i, temp;
 | 
						||
for (i = 0; i < SIZE / 2; ++i) {
 | 
						||
temp = a[SIZE - i - 1];
 | 
						||
a[SIZE - i - 1] = a[i];
 | 
						||
a[i] = temp;
 | 
						||
}
 | 
						||
for (i = 0; i < SIZE; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dizilerin sıraya dizilmesine İngilizce "sorting" denilmektedir ve 20'den fazla sıraya dizme yöntemi vardır. En basit
 | 
						||
yöntemlerden biri kabarcık sıraalaması (bubble sort) yöntemidir. Bu yöntemde dizinin yan yana elemanları
 | 
						||
110
 | 
						||
 | 
						||
karşılaştırılır, duruma göre yer değiştirilir. Tabi bu işlem bir kez yapılmaz. Bu işlem bir kez yapıldığında en büyük
 | 
						||
eleman sona gider. Örneğin:
 | 
						||
 | 
						||
Bu işlemi ikinci yaptığımızda sona kadar değil, bir önceye kadar gitmemiz yeterli olur. Böylece iç içe iki döngüyle
 | 
						||
algoritma gerçekleştirilebilir:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 10, 23, -5, 34, 77, 100, 32, 87, 22, 44};
 | 
						||
int i, k;
 | 
						||
int temp;
 | 
						||
for (i = 0; i < SIZE - 1; ++i)
 | 
						||
for (k = 0; k < SIZE - 1 - i; ++k) {
 | 
						||
if (a[k] > a[k + 1]) {
 | 
						||
temp = a[k];
 | 
						||
a[k] = a[k + 1];
 | 
						||
a[k + 1] = temp;
 | 
						||
}
 | 
						||
}
 | 
						||
for (i = 0; i < SIZE; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Seçerek Sıralama (Selection Sort) yönetminde dizinin en küçük elemanı bulunur, ilk elemanla yer değiştirilir.
 | 
						||
Örneğin:
 | 
						||
 | 
						||
Sonra dizi daraltılarak aynı işlem onun için de yapılır:
 | 
						||
 | 
						||
111
 | 
						||
 | 
						||
Algoritmada iki döngü bulunur. Dıştaki döngü diziyi daraltmakta kullanılır. İçteki döngü en küçük elemanı bulur:
 | 
						||
#include <stdio.h>
 | 
						||
#define SIZE
 | 
						||
 | 
						||
10
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[SIZE] = { 10, 23, -5, 34, 77, 100, 32, 87, 22, 44};
 | 
						||
int i, k;
 | 
						||
int min, minIndex;
 | 
						||
for (i = 0; i < SIZE - 1; ++i) {
 | 
						||
min = a[i];
 | 
						||
minIndex = i;
 | 
						||
for (k = i + 1; k < SIZE; ++k)
 | 
						||
if (a[k] < min) {
 | 
						||
min = a[k];
 | 
						||
minIndex = k;
 | 
						||
}
 | 
						||
a[minIndex] = a[i];
 | 
						||
a[i] = min;
 | 
						||
}
 | 
						||
for (i = 0; i < SIZE; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yazıların char Türden Dizilerde Saklanması
 | 
						||
Aslında yazı dediğimiz şey karakterlerden oluşan bir dizidir. Bilindiği gibi karakterler de aslında karakter
 | 
						||
tablosunda birer sayı belirtir. Bir yazının karakter numaralarını bir dizide saklarsak yazıyı saklamış oluruz.
 | 
						||
Programa dili ne olursa olsun yazılar arka planda hep böyle saklanmaktadır. Şüphesiz yazıları saklamak için en iyi
 | 
						||
tür char türüdür. char türü her sistemde 1 byte uzunluğundadır.
 | 
						||
Genellikle yazılar char türden dizilerin başından başlarlar fakat onların sonuna kadar devam etmezler. Yani
 | 
						||
genellikle yazılar yerleştirildikleri diziden küçük olurlar. Bu durumda yazının sonunu belirlemek gerekir. İşte C'de
 | 
						||
bunun için null karakter kullanılmaktadır. null karakter '\0' ile temsil edilir. Karakter, tablonun sıfır numaralı
 | 
						||
karakteridir ve sayısal değeri de sıfırdır. null karakterin bir görüntü karşılığı yoktur, zaten tabloya böyle bir amaç
 | 
						||
için yerleştirilmiştir.
 | 
						||
C'de bir char türden bir diziye yazı yerleştireceksek yazının sonuna (dizinin sonuna değil) '\0' karakteri koymalıyız.
 | 
						||
Çünkü C'de herkes tarafından yapılan böyle bir anlaşma vardır. Bu durumda n elemanlı charf türden bir diziye en
 | 
						||
112
 | 
						||
 | 
						||
fazla n - 1 elemanlı bir yazı yerleştirilebilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[10];
 | 
						||
int i;
 | 
						||
s[0] = 'a';
 | 
						||
s[1] = 'l';
 | 
						||
s[2] = 'i';
 | 
						||
s[3] = '\0';
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
putchar(s[i]);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aynı işlem ilşkdeğer verilerek de yapılabilirdi:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[10] = { 'a', 'l', 'i', '\0' };
 | 
						||
int i;
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
putchar(s[i]);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada diziye ilk değer verildiği için derleyici gerei kalan elemanları sıfırlar. Null karakter zaten sıfır olduğu için
 | 
						||
aşağıdaki iki bildirim eşdeğer olur:
 | 
						||
char s[10] = { 'a', 'l', 'i', '\0' };
 | 
						||
char s[10] = { 'a', 'l', 'i'};
 | 
						||
 | 
						||
char türden bir dizide null karakter görene kadar ilerlemek için şu kalıp kullanılabilir:
 | 
						||
for (i = 0; s[i] != '\0'; ++i) {
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
gets Fonksiyonu
 | 
						||
gets standart bir C fonksiyonudur. Aşağıdaki gibi kullanılır:
 | 
						||
gets(<dizi ismi>);
 | 
						||
 | 
						||
gets fonksiyonu kalvyeden bir yazı girilip ENTER tuşuna basılana kadar bekler. Girilen yazının karakterlerini tek
 | 
						||
tek verdiğimiz char türden diziye yerleştirir, yazının sonuna '\0' karakterini de ekler. gets dizinin diğer elemanlarına
 | 
						||
hiç dokunmaz.
 | 
						||
Örneğin:
 | 
						||
113
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int i;
 | 
						||
gets(s);
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
putchar(s[i]);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
puts Fonksiyonu
 | 
						||
puts fonksiyonu bir dizinin ismini (yani adresini) parametre olarak alır '\0' görene kadar tüm karakterleri yan yana
 | 
						||
basar, en sonunda imleci aşağı satırın başına geçirerek orada bırakır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int i;
 | 
						||
gets(s);
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Klavyeden gets fonksiyonuyla bir diziye bir yazı giriniz. Girilen yazının kaç karakterli olduğunu
 | 
						||
(yani yazının uzunluğunu) yazdırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int i;
 | 
						||
printf("Yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
;
 | 
						||
printf("%d\n", i);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Klavyeden gets fonksiyonuyla bir diziye bir yazı giriniz. Girilen yazıyı tersten yazdırınız.
 | 
						||
Çözüm:
 | 
						||
114
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int i;
 | 
						||
printf("Yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
;
 | 
						||
for (--i; i >= 0; --i)
 | 
						||
putchar(s[i]);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir dizideki yazıyı büyük harfe dönüştüren örnek:
 | 
						||
#include <stdio.h>
 | 
						||
#include <ctype.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int i;
 | 
						||
printf("Yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
s[i] = toupper(s[i]);
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: char türden bir dizi ve bir de char türden değişken bildiriniz. gets fonksiyonu ile klavyeden diziye
 | 
						||
okuma yapınız. Sonra da getchar fonksiyonu ile char nesneye okuma yapınız. Yazı içerisinde o karakterden kaç
 | 
						||
tane olduğunu yazdırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
char ch;
 | 
						||
int i, count;
 | 
						||
printf("Yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
printf("Bir karakter giriniz:");
 | 
						||
ch = getchar();
 | 
						||
for (count = 0, i = 0; s[i] != '\0'; ++i)
 | 
						||
if (s[i] == ch)
 | 
						||
++count;
 | 
						||
printf("%d\n", count);
 | 
						||
 | 
						||
115
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
printf Fonksiyonu İle Yazıların Yazdırılması
 | 
						||
printf fonksiyonunda %s format karakterine karşılık char türden bir dizi ismi (aslında adres) gelmelidir. Bu
 | 
						||
durumda printf null karakter görene kadar karakterleri yazar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
printf("Yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
printf("Girilen yazi: %s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yani:
 | 
						||
puts(s);
 | 
						||
 | 
						||
ile,
 | 
						||
printf("%s\n", s);
 | 
						||
 | 
						||
eşdeğerdir.
 | 
						||
char Türden Dizilere İki Tırnak İfadesi ile İlkdeğer Vermek
 | 
						||
C'de char türden bir diziye iki tırnak ile ilkdeğer verilebilir. Örneğin:
 | 
						||
char s[10] = "ankara";
 | 
						||
 | 
						||
Burada yazının karakterleri tek tek diziye yerleştirilir, sonuna null karakter eklenir. Tabi dizinin geri akalan
 | 
						||
elemanları da yine sıfırlanacaktır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100] = "ankara";
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İki tırnak ile ilkdeğer verilirken dizi uzunluğu yine belirtilmeyebilir. Örneğin:
 | 
						||
char s[] = "ankara";
 | 
						||
 | 
						||
Burada derleyici null karakteri de hesaba katar. Yani dizi 7 uzunlukta açılmış olacaktır. Aşağıdaki ilkdeğer verme
 | 
						||
geçersizdir:
 | 
						||
116
 | 
						||
 | 
						||
char s[4] = "ankara";
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Burada verilen ilkdeğerler dizi uzunluğundan fazladır. Aşağıdaki durum bir istisnadır:
 | 
						||
char s[6] = "ankara";
 | 
						||
 | 
						||
/* geçerli, fakat null karakter eklenmeyecek */
 | 
						||
 | 
						||
Standartlara göre iki tırnak içerisindeki karakterlerin sayısı tam olarak dizi uzunluğu kadarsa bu durum geçerlidir.
 | 
						||
Ancak '\0' karakter derleyici tarafından bu istisnai durumda diziye eklenmez.
 | 
						||
İki tırnağın içi boş olabilir. Bu durumda yalnızca null karakter ataması yapılır. Örneğin:
 | 
						||
char s[] = "";
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Dizi tek elemanlıdır ve o elemanda da '\0' karakter vardır.
 | 
						||
Standartlara göre '\0' karakteri kesinlikle hangi karakter dönüştürme tablosu kullanılıyor olursa olsun o tablonun
 | 
						||
sıfır numaralı karakteridir. Yani null karakterin sayısal değeri sıfırdır. Başka bir deyişle:
 | 
						||
s[n] = 0;
 | 
						||
 | 
						||
ile
 | 
						||
s[n] = '\0';
 | 
						||
 | 
						||
tamamen aynı etkiyi yapar. Eğer char dizisi yazısal amaçlı kullanılıyorsa '\0' kullanmak iyi bir tekniktir.
 | 
						||
char türden diziye iki tırnakla daha sonra değer atayamazyız. Yalnızca ilkdeğer olarak bunu yapabiliriz. Örneğin:
 | 
						||
char s[10];
 | 
						||
s = "ankara";
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Koşul Operatörü (Conditional Operator)
 | 
						||
Koşul operatörü C'nin üç operandlı (ternary) tek operatörüdür. Operatör "? :" temsil edilir. Genel kullanımı
 | 
						||
şöyledir:
 | 
						||
op1 ? op2 : op3
 | 
						||
 | 
						||
Burada op1, op2 ve op3 birer ifadedir. Koşul operatörü if deyimi gibi çalışan bir operatördür. Operatörün çalışması
 | 
						||
şöyledir: Önce soru işaretinin solundaki ifade (op1) yapılır. Bu ifade sıfır dışı bir değerse yalnızca soru işareti ile
 | 
						||
iki nokta üstü üste arasındaki ifade (op2) yapılır, sıfırsa iki nokta üst üstenin sağındaki ifade yapılır. Koşul
 | 
						||
operatörü bir operatör olduğu için bir değer üretir. Yani bu değer bir nesneye atanabilir, başka işlemlere sokulabilir.
 | 
						||
Koşul operatörünün ürettiği değer duruma göre op2 ya da op3 ifadelerinin değeridir. Örneğin:
 | 
						||
result = val > 0 ? 100 + 200 : 300 + 400;
 | 
						||
 | 
						||
Burada val > 0 ise yalnızca 100 + 200 ifadesi yapılır ve koşul operatöründen bu değer elde edilir. Değilse 300 +
 | 
						||
400 yaılır ve koşul operatöründen bu değer elde edilir. Burada yapılan aşağıdaki ile eşdeğerdir:
 | 
						||
if (val > 0)
 | 
						||
result = 100 + 200;
 | 
						||
else
 | 
						||
result = 300 + 400;
 | 
						||
 | 
						||
117
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int val;
 | 
						||
int result;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
scanf("%d", &val);
 | 
						||
result = val > 0 ? 100 + 200 : 300 + 400;
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Koşul operatörü elde edilen değerin bir nesneye atanacağı ya da b aşka bir ifadede kullanılacağı durumlarda tercih
 | 
						||
edilmelidir. Aşağıdaki örnek koşul operatörünün kötü bir kullanımına işaret eder:
 | 
						||
a > 0 ? foo() : bar();
 | 
						||
 | 
						||
Burada koşul operatörünün kullanılması kötü bir tekniktir. Koşul operatörünün üç durumda kullanılması tavsiye
 | 
						||
edilir:
 | 
						||
1) Bir karşılaştırma sonucunda elde edilen değerin bir nesneye atanması durumunda. Örneğin:
 | 
						||
max = a > b ? a : b;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
for (i = 1900; i < year; ++i)
 | 
						||
tdays += isleap(i) ? 366 : 365;
 | 
						||
 | 
						||
Burada isleap(i) sıfır dışı ise tdays'e 366 değilse 365 eklenmektedir. Aşağıda belli bir tarihin hangi güne karşı
 | 
						||
geldiğini bulan programı görüyorsunuz:
 | 
						||
#include <stdio.h>
 | 
						||
int isleap(int year)
 | 
						||
{
 | 
						||
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
 | 
						||
}
 | 
						||
int totaldays(int day, int month, int year)
 | 
						||
{
 | 
						||
int i;
 | 
						||
long tdays = 0;
 | 
						||
int montab[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 | 
						||
for (i = 1900; i < year; ++i)
 | 
						||
tdays += isleap(i) ? 366 : 365;
 | 
						||
montab[1] = isleap(year) ? 29 : 28;
 | 
						||
for (i = 0; i < month - 1; ++i)
 | 
						||
tdays += montab[i];
 | 
						||
tdays += day;
 | 
						||
 | 
						||
118
 | 
						||
 | 
						||
return tdays;
 | 
						||
}
 | 
						||
void printday(int day, int month, int year)
 | 
						||
{
 | 
						||
long tdays;
 | 
						||
tdays = totaldays(day, month, year);
 | 
						||
switch (tdays % 7) {
 | 
						||
case 0:
 | 
						||
printf("Pazar\n");
 | 
						||
break;
 | 
						||
case 1:
 | 
						||
printf("Pazartesi\n");
 | 
						||
break;
 | 
						||
case 2:
 | 
						||
printf("Sali\n");
 | 
						||
break;
 | 
						||
case 3:
 | 
						||
printf("Carsamba\n");
 | 
						||
break;
 | 
						||
case 4:
 | 
						||
printf("Persembe\n");
 | 
						||
break;
 | 
						||
case 5:
 | 
						||
printf("Cuma\n");
 | 
						||
break;
 | 
						||
case 6:
 | 
						||
printf("Cumartesi\n");
 | 
						||
break;
 | 
						||
}
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
printday(10, 9, 1993);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
2) Koşul operatörü fonksiyon çağrılarında argüman olarak kullanılabilir. Böylece elde edilen değer fonksiyonun
 | 
						||
parametre değişkenine atanmış olur. Örneğin:
 | 
						||
foo(val % 2 == 0 ? 100 : 200);
 | 
						||
 | 
						||
Bunun if eşdeğeri şöyledir:
 | 
						||
if (val % 2 == 0)
 | 
						||
foo(100);
 | 
						||
else
 | 
						||
foo(200);
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int val;
 | 
						||
printf("Bir sayi giriniz:");
 | 
						||
 | 
						||
119
 | 
						||
 | 
						||
scanf("%d", &val);
 | 
						||
printf(val % 2 == 0 ? "Cift\n" : "Tek\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
3) Koşul operatörü return deyiminde de kullanılabilir. Örneğin:
 | 
						||
return a % 2 == 0 ? 100 : 200;
 | 
						||
 | 
						||
Bu ifadenin eşdeğer if karşılığı şöyledir:
 | 
						||
if (a % 2 == 0)
 | 
						||
return 100;
 | 
						||
else
 | 
						||
return 200;
 | 
						||
 | 
						||
Koşul operatörü özel bir operatördür. Öncelik tablosundaki klasik kurallara tam uymaz. Ancak öncelik tablosunda
 | 
						||
hemen atama ve işlemli atama operatörlerinin yukarısında sağdan-sola grupta bulunur:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! (tür)
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
?:
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
= += -= *= /= %=, ... Sağdan-Sola
 | 
						||
,
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
Koşul operatörünün operandları şöyle ayrıştırılır: Soru işaretinden sola doğru gidilir. Koşul operatöründen daha
 | 
						||
düşük öncelikli operatör görülene kadarki ifade koşul operatörünün birinci operandını oluşturmaktadır. Soru işareti
 | 
						||
ile iki nokta üst üsete arasındaki ifade koşul operatörünün ikinci operandını oluşturur. İki nokta üst üstenin
 | 
						||
sağındaki ifade koşul operatörünün üçüncü operandını oluşturmaktadır. Örneğin:
 | 
						||
 | 
						||
Parantezler koşul operatörünün opearanlarını ayrıştırmakta kullanılabilir. Örneğin:
 | 
						||
a = (b > 0 ? 100 + 200 : 300) + 400;
 | 
						||
 | 
						||
Burada artık üç operatör vardır: Atama, koşul ve +. Yani + 400 koşul operatörünün dışındadır. b > 0 koşulu
 | 
						||
sağlansa da sağlanmasa da 400 ile toplama yapılacaktır.
 | 
						||
120
 | 
						||
 | 
						||
Koşul operatörü de iç içe kullanılabilir. Örneğin:
 | 
						||
result = a > b ? a > c ? a : c : b > c ? b : c;
 | 
						||
 | 
						||
Burada üç sayının en büyüğü bulunmaktadır. Tabi bu ifade karmaşık gözüktüğü için parantezler gerekmiyor olsa
 | 
						||
da sadelk oluşturmak için kullanılabilir. Örneğin:
 | 
						||
result = a > b ? (a > c ? a : c) : (b > c ? b : c);
 | 
						||
 | 
						||
sizeof Operatörü
 | 
						||
sizeof bir derleme zamanı (compile time) operatörüdür. Parantezlerinden dolayı acemi C programcıları onu
 | 
						||
fonksiyon sanabilmektedir. Operatörler derleme aşamasında derleyici tarafından kod üretilmesi için ele alınırlar.
 | 
						||
Oysa fonksiyonlar programın çalışma zamanı sırasında akışın oraya gitmesiyle işlem görürler.
 | 
						||
sizeof operatörünün kullanım biçimleri şöyledir:
 | 
						||
sizeof <ifade>
 | 
						||
sizeof(<tür>)
 | 
						||
sizeof <dizi ismi>
 | 
						||
 | 
						||
sizeof operatörünün operandı bir ifade ise, derleyici o ifadeyi yapmaz. Yalnızca o ifadenin türüne bakar. sizeof
 | 
						||
ifadenin türünün o sistemde kaç byte yer kapladığını belirtir. Derleyici sizeof operatörünü gördüğünde derleme
 | 
						||
aşamasında oraya sabir yerleştirir. Yani sanki sizeof yerine biz bir sabit yazmışız gibi işlem görür. sizeof operatörü
 | 
						||
tek operandlı önek bir operatördür ve öncelik tablosunun ikinci düzeyinde sağdan sola grupta bulunur:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
Dolayısıyla,
 | 
						||
sizeof a + b
 | 
						||
ifadesinde a'nın sizeof'u b ile toplanır. Fakat:
 | 
						||
sizeof (a + b)
 | 
						||
 | 
						||
iafdesinde a + b'nin sizeof'u elde edilmektedir. Programcılar genellikle sizeof operatörünü hep parantezlerle
 | 
						||
kullanmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int n;
 | 
						||
n = sizeof 1.2 + 5;
 | 
						||
printf("%d\n", n);
 | 
						||
 | 
						||
/* 13 */
 | 
						||
 | 
						||
121
 | 
						||
 | 
						||
n = sizeof (1.2 + 5); /* 8 */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sizeof operatörünü gören derleyici onun yerine o ifadenin türünün kaç byte olduğuna ilişkin sabit bir değer
 | 
						||
yerleştiri. Yerleştirdiği sabit size_t türündendir (tipik olarak unsigned int).
 | 
						||
n = sizeof 1.2 + 5;
 | 
						||
 | 
						||
ifadesi derleyici tarafından şu biçime dönüştürülür:
 | 
						||
n = 8u + 5;
 | 
						||
 | 
						||
Şüphesiz sizeof operatörünün operandı fonksiyon çağrısıysa fonksiyon işletilmez, onun geri dönüş değerinin türüne
 | 
						||
bakılır, o değer elde edilir:
 | 
						||
#include <stdio.h>
 | 
						||
double foo(void)
 | 
						||
{
 | 
						||
printf("Im am foo\n");
 | 
						||
return 1500.2;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int n;
 | 
						||
n = sizeof(foo());
 | 
						||
printf("%d\n", n);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada ekrana foo yazısı basılmaz, yalnızca 8 yazısı basılır.
 | 
						||
void değerin sizeof'u yoktur. Dolayısıyla sizeof operatörünün operandı void türden olamaz.
 | 
						||
sizeof operatörünün operandı doğrudan bir tür ismi olabilir. Bu durumda parantezler kullanılmak zorundadır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int n;
 | 
						||
n = sizeof(int);
 | 
						||
printf("%d\n", n);
 | 
						||
 | 
						||
/* 4 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sizeof operatörünün operandı bir dizi ismi olabilir. Bu durumda sizeof o dizinin bellekte kapladığı byte miktarını
 | 
						||
bize verir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
122
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[4] = { 1, 2 };
 | 
						||
char s[] = "ali";
 | 
						||
printf("%u\n", sizeof a);
 | 
						||
printf("%u\n", sizeof s);
 | 
						||
 | 
						||
/* 16 */
 | 
						||
/* 4 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Pekiyi sizeof operatörüne neden gereksinim duyulmaktadır? Bilindiği gibi C'de char dışındaki tüm türlerin
 | 
						||
uzunlukları derleyiciye bağlı olarak değişebilmektedir. Kodun taşınabilirliğini sağlamak için bazı durumlarda
 | 
						||
sizeof operatörünü kullanmak zorunda kalabiliriz. sizeof operatörüne olan gereksinim ileriki konularda açıkça
 | 
						||
görülecektir.
 | 
						||
Göstericiler (Pointers)
 | 
						||
Adres bilgilerinin saklanması için kullanılan nesnelere gösterici (pointer) denir. Bir adres bilgisi C'de int, long
 | 
						||
türlerde tutulamaz. Ancak gösterici denilen türlerde tutulabilir. Benzer biçimde göstericiler de adi birer int, long
 | 
						||
türleri tutamazlar. Ancak adres tıtarlar.
 | 
						||
Gösterici bildiriminin genel biçimi şöyledir:
 | 
						||
<tür> *<gösterici ismi>;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int *p;
 | 
						||
long *t;
 | 
						||
double *d;
 | 
						||
 | 
						||
Buradaki * adres türünü temsil eder. Çarpma gibi bir anlama gelmez.
 | 
						||
Bir gösterici her türden adres bilgisini tutamaz. Yalnızca tür bileşeni belirli olan olan adres bilgilerini tutar. Genel
 | 
						||
olarak:
 | 
						||
T *p;
 | 
						||
 | 
						||
gibi bir bildirimde p göstericisi tür bileşeni T olan adresleri tutabilir. Örneğin:
 | 
						||
*pi;
 | 
						||
 | 
						||
int
 | 
						||
 | 
						||
Burada pi int türünden göstericidir. int türden gösterici demek, tür bileşeni int olan adresleri tutan gösterici
 | 
						||
demektir.
 | 
						||
Anahtar Notlar: İngilizce T türünden gösterici "pointer to T" biçiminde yazılıp okunmaktadır.
 | 
						||
 | 
						||
Gösterici bildiriminde * farklı bir atomdur. Dolayısıyla öncesinde sonrasında birden fazla boşluk karakteri
 | 
						||
bulunabilir. Örneğin:
 | 
						||
int
 | 
						||
 | 
						||
*
 | 
						||
 | 
						||
pi;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Bazı programcılar * atomunu türe yapıştırmaktadır:
 | 
						||
int*
 | 
						||
 | 
						||
pi;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
123
 | 
						||
 | 
						||
Fakat gelenek (zaten daha mantıksal olduğu için) * atomunun gösterici ismine yapıştırılmasıdır:
 | 
						||
int *pi;
 | 
						||
 | 
						||
* atomu dekleratöre ilişkindir, türe ilişkin değildir. Örneğin aşağıdaki bildirim geçerlidir:
 | 
						||
int *pi, a;
 | 
						||
 | 
						||
Burada pi int türden bir göstericidir fakat a, adi bir int nesnedir. Örneğin:
 | 
						||
int *pi, *a;
 | 
						||
 | 
						||
Burada hem pi hem de a int türden göstericidir:
 | 
						||
int a, b[10], *c;
 | 
						||
 | 
						||
Burada a adi bir int, b 10 elemanlı bir int dizi, c ise inttürdne bir göstericidir.
 | 
						||
Bir göstericiye aynı türden bir adres bilgisi yerleştirilir:
 | 
						||
int *pi;
 | 
						||
pi = (long *) 0x1FC0;
 | 
						||
pi = 0x1FC0;
 | 
						||
 | 
						||
/* geçersiz! tür yanlış*/
 | 
						||
/* geçersiz! adi bir int */
 | 
						||
 | 
						||
Benzer biçimde biz nümerik türlere adres bilgisi atayamazyız. Örneğin:
 | 
						||
int a;
 | 
						||
a = (int *) 0x1FC0;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Dizi İsimlerinin Otomatik Adreslere Dönüştürülmesi
 | 
						||
C'de bir dizinin ismi dizinin taamamını temsil eder. Dizi isimleri işleme sokulduğunda otomatik olarak derleyici
 | 
						||
tarafından adrese dönüştürülür. Yani dizi isimleri aslında adres belirtmektedir. Dizi isimleriyle belirtilen adresin tür
 | 
						||
bileşeni dizinin aynı türden, sayısal bileşeni dizinin bellekteki başlangıcına ilişkin fiziksel adres numjarasından
 | 
						||
oluşur:
 | 
						||
 | 
						||
Burada a ismini işleme soktuğumuzda otomatik olarak bu dizinin başlangıç adresine ilişkin adres sabiti gibi işleme
 | 
						||
girer.
 | 
						||
Medemki dizi isimleri adres belirtmektedir, o halde bir dizi ismini biz aynı türden bir göstericiye atayabiliriz.
 | 
						||
Örneğin:
 | 
						||
124
 | 
						||
 | 
						||
int *pi;
 | 
						||
int a[3];
 | 
						||
pi = a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int *pi;
 | 
						||
char s[10];
 | 
						||
 | 
						||
pi = s;
 | 
						||
 | 
						||
/* geçersiz! s char türden adres belirtir */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int a[10];
 | 
						||
int b;
 | 
						||
b = a;
 | 
						||
 | 
						||
/* Geçersiz! a bir adres bilgisidir ve göstericiye yerleştirilebilir */
 | 
						||
 | 
						||
Bir göstericiye bir adres bilgisi atandığında aslında gösterici yalnızca adresin sayısal bileşenini tutar. (Tabi bu
 | 
						||
durum göstericiye adi bir sayı atıyabileceğemiz anlamına gelmiyor).
 | 
						||
Bir göstericiye bir adres adres bilgisi atandığında o göstericinin o adresi gösterdiği söylenir. Örneğin:
 | 
						||
 | 
						||
Burada pi göstericisi a dizisinin başlangıç adresini göstermektedir.
 | 
						||
& (Address Of) Operatörü
 | 
						||
& sembolü C'de "address of" isimli bir adres operatörü belirtir. & tek operandlı önek bir operatördür. Öncelik
 | 
						||
tablosunun ikinci düzeyinde sağdan sola grupta bulunmaktadır:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof & Sağdan-Sola
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
& operatörü operand olarak bir nesne alır. Operatör bize nesnenin bellekteki yerleşim adresini verir. & operatörü
 | 
						||
ile çekilen adresin sayısal bileşeni operand olan nesnenin bellekteki fiziksel adres numarasıdır. Tür bileşeni ise
 | 
						||
125
 | 
						||
 | 
						||
nesnenin türüyle aynı türdendir. Örneğin:
 | 
						||
 | 
						||
Dizi isimleri zaten adres belirtmektedir. Bu nedenle yanlışlıkla dizi isimlerine & uygulamamak gerekir. Fakat adi
 | 
						||
nesnelerin adreslerini almak için & operatörü kullanmak gerekir.
 | 
						||
& operatörüyle elde ettiğimiz adresler ancak aynı türdne bir göstericiye atanbilir.
 | 
						||
int a;
 | 
						||
int *pi;
 | 
						||
char *pc;
 | 
						||
pi = &a;
 | 
						||
pc = &a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
& operatörünün operandı bir nesne olabilir. Nesne belirtmeyen bir ifade olamaz. Çünkü yalnızca nesnelerin
 | 
						||
adresleri vardır. Örneğin:
 | 
						||
#define MAX
 | 
						||
 | 
						||
100
 | 
						||
 | 
						||
int *pi;
 | 
						||
pi = &100;
 | 
						||
pi = &MAX;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
* (Indirection) Operatörü
 | 
						||
* tek operandlı önek bir adres operatörüdür. * operatörünün operandı bir adres bilgisi olmak zorundadır. *
 | 
						||
operatörü operandı olan adresteki nesneye erişmekte kullanılır. * operatörüyle elde edilen nesnenin türü operanda
 | 
						||
olan adresin türüyle aynı türdendir. Örneğin:
 | 
						||
 | 
						||
pi göstericini kullandığımızda onun içerisindeki adres bilgisini kullanıyor oluruz. Ancak *pi ifadesi pi adresindeki
 | 
						||
nesneyi belirtir. Bu da şekilde görüldüğü gibi a'dır. Burada *pi ifadesi int türdendir. Çünkü pi adresi int türdendir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
126
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 100;
 | 
						||
int *pi;
 | 
						||
pi = &a;
 | 
						||
printf("%d\n", *pi);
 | 
						||
*pi = 150;
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 100 */
 | 
						||
/* 150 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char ch;
 | 
						||
char *pc;
 | 
						||
pc = &ch;
 | 
						||
*pc = 'x';
 | 
						||
putchar(ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[3] = { 10, 20, 30 };
 | 
						||
int *pi;
 | 
						||
pi = a;
 | 
						||
*pi = 100;
 | 
						||
printf("%d\n", a[0]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Buradada yapılan işlemleri şekilsel olarak şöyle ifade edilebilir:
 | 
						||
 | 
						||
p bir adres belirtiyor olsun. Aşağıdaki gibi bir atamadan bellekteki kaç byte etkilenir:
 | 
						||
127
 | 
						||
 | 
						||
*p = 0;
 | 
						||
İşte p eğer char türdense p adresinden itibaren 1 byte etkilenir. p int türdense 4 byte, double türdense 8 byte
 | 
						||
etkilenecektir.
 | 
						||
Aşğıdaki gibi bir gösterici bildirimi yapılmış olsun:
 | 
						||
int *pi;
 | 
						||
 | 
						||
Buaradan iki şey anlaşılır:
 | 
						||
1) Burada pi int * türündendir. (pi'yi kapatıp sola baktımızda int * görüyoruz).
 | 
						||
 | 
						||
2) pi göstericisini * operatörü ile kullanırsak int bir nesne elde ederiz (*pi'yi kapatıp sola baktığımızda int
 | 
						||
görüyoruz).
 | 
						||
 | 
						||
C'de adres türleri sembolik olarak T * ile ifade edilir. Örneğin:
 | 
						||
char *pc;
 | 
						||
Burada pc'nin türü char * biçimindedir. *pc ise char türdendir.
 | 
						||
* operatörü öncelik tablosunun ikinci düzeyinde sağdan sola grupta bulunur:
 | 
						||
()
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof & *
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
Örneğin:
 | 
						||
a = *pi * 50;
 | 
						||
Burada:
 | 
						||
İ1: *pi
 | 
						||
İ2: İ1 * 50
 | 
						||
İ3: a = İ2
 | 
						||
* operatörünün operandı gösterici olmak zorunda değildir. Adres belirten herhangi bir ifade olabilir. Yani *
 | 
						||
operatörünün operandı şunlar olabilir.
 | 
						||
128
 | 
						||
 | 
						||
1) Bir gösterici (*pi gibi)
 | 
						||
2) Bir adres sabiti. Örneğin:
 | 
						||
*(int *) 0x1FC0 = 100;
 | 
						||
3) & operatörü elde edilmiş bir adres (*&a gibi)
 | 
						||
4) Bir dizi ismi olabilir. Örneğin:
 | 
						||
int a[] = {1, 2, 3};
 | 
						||
*a = 100;
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
5) String ifadeleri (henüz görmedik)
 | 
						||
& ve * operatörlerinin sağdan sola aynı grupta olduğuna dikkat ediniz. Örneğin:
 | 
						||
int a = 100;
 | 
						||
*&a = 200;
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
Burada:
 | 
						||
İ1: &a
 | 
						||
İ2: *İ1
 | 
						||
İ3: İ2 = 200
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 100;
 | 
						||
*&a = 200;
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[3] = { 10, 20, 30 };
 | 
						||
int i;
 | 
						||
*a = 100;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
printf("%d\n", a[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
129
 | 
						||
 | 
						||
Adreslerin Artırılması ve Eksiltilmesi
 | 
						||
Adres bilgileri tamsayı türlerine ilişkin değerlerle toplama ve çıkartma işlemine sokulabilir. Yani, p bir adres
 | 
						||
bilgisi n de bir tamsayı türünden olmak üzere p + n ve p - n ifadeleri geçerlidir (Ancak n - p ifadesi geçerli değildir).
 | 
						||
Bir adxres bilgisine bir tamsayıyı topladığımızda ya da bir adres bilgisinden bir tamsayı çıkarttığımızda elde edilen
 | 
						||
ürün yine aynı türden bir adres bilgisi olur. Örneğin pi int türünden bir adres bilgisi (int *) olsun:
 | 
						||
pi + 1
 | 
						||
 | 
						||
ifadesi int türden bir adres bilgisi belirtir. C'de bir adres bilgisi 1 artırıldığında ya da 1 eksiltildiğinde adresin
 | 
						||
sayısal bileşeni o adresin türünün uzunluğu kadar artar ya da eksilir. Yani örneğin int türden bir göstericiyi 1
 | 
						||
artırdığımızda içindeki adres 32 bit sistemlerde 4 artacaktır. char türden bir gösterici bir adtırdığımızda içindeki
 | 
						||
adres 1 artacaktır.
 | 
						||
Bu durumda dizi elemanları ardışıl olduğuna göre biz dizinin başlangıç adresini aynı türden bir göstericiye
 | 
						||
yerleştirip sonra o göstericiyi artırarak dizinin tüm elemanlarına erişebiliriz:
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[3] = { 10, 20, 30 };
 | 
						||
int *pi;
 | 
						||
int i;
 | 
						||
pi = a;
 | 
						||
printf("%d\n", *pi);
 | 
						||
++pi;
 | 
						||
printf("%d\n", *pi);
 | 
						||
++pi;
 | 
						||
printf("%d\n", *pi);
 | 
						||
 | 
						||
/* 10 */
 | 
						||
/* 20 */
 | 
						||
/* 30 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
* operatörünün opeandı adres türünden olmak zorudadır. Örneğin:
 | 
						||
130
 | 
						||
 | 
						||
int a = 0x1Fc0;
 | 
						||
*a = 10;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
char türden bir dizinin adresini char türden bir göstericiye atadıktan sonra göstericiyi artıra artıra yazının tüm
 | 
						||
karakterlerine erişebiliriz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char *pc;
 | 
						||
pc = s;
 | 
						||
while (*pc != '\0') {
 | 
						||
putchar(*pc);
 | 
						||
++pc;
 | 
						||
}
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Köşeli parantez (Index) Operatörü
 | 
						||
Köşeli parantez operatörü tek operandlı sonek bir operatördür. Köşeli parantezler içerisinde tamsayı türlerine iliikin
 | 
						||
bir ifade bulunmak zorundadır. Köşeli parantez operatörü öncelik tablosunun tepe grubunda (..) operatörü ile
 | 
						||
soldan sağa aynı gruptadır:
 | 
						||
() []
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof & *
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
Köşeli parantez operatörünün operandı bir adres bilgisi olmak zorundadır. p bir adres belirten ifade olmak üzere:
 | 
						||
p[n] ile *(p + n) aynı anlamdadır. p[n] ifadesi p adresinden n ilerideki nesneyi temsil eder.
 | 
						||
Dizi isimleri dizilerin başlangıç adresini belirttiği için dizi elemanlarına köşeli parantez operatörü ile
 | 
						||
erişilebilmektedir. Örneğin:
 | 
						||
int a[3] = {10, 20, 30};
 | 
						||
a[2] ifadesi a adresinden 2 ilerinin (yani 2 * sizeof(*a) kadar byte ilerinin) içeriği anlamına gelir. Yani a[2] ile *(a
 | 
						||
+ 2) aynı anlamdadır.
 | 
						||
Pekiyi p[0] ne anlama gelir? Bu ifade p adresinden 0 ilerinin içeriği anlamına gelir. *(p + 0) ile ve dolayısyla *p ile
 | 
						||
aynı anlamdadır.
 | 
						||
Biz köşeli parantez operatörünü göstericilerle, dizi isimleriyle ve diğer adres belirten ifadelerle kullanabiliriz.
 | 
						||
Örneğin:
 | 
						||
131
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char *pc;
 | 
						||
int i;
 | 
						||
pc = s;
 | 
						||
for (i = 0; pc[i] != '\0'; ++i)
 | 
						||
putchar(pc[i]);
 | 
						||
putchar('\n');
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Köşeli parantez içerisindeki ifade negatif bir değer de belirtebilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[3] = { 10, 20, 30 };
 | 
						||
int *pi;
 | 
						||
pi = a + 2;
 | 
						||
printf("%d\n", pi[-2]);
 | 
						||
 | 
						||
/* 10 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Göstericilere İlkdeğer Verilmesi
 | 
						||
Göstericilere de bildirim sırasında '=' atomu ile ilkdeğer verebiliriz. Örneğin:
 | 
						||
int *pi = (int *)0x1FC0;
 | 
						||
 | 
						||
Burada adres p'ye atanmaktadır. Bildirimdeki yıldız operatör görevinde değildir. Tür belirtmek için
 | 
						||
kullanılmaktadır. Başka bir örnek:
 | 
						||
int a = 123;
 | 
						||
int *pi = &a;
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[3] = { 10, 20, 30 };
 | 
						||
int *pi = a;
 | 
						||
// p'ye dizinin başlangıç adresi atanıyor
 | 
						||
int i;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
printf("%d ", pi[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
132
 | 
						||
 | 
						||
Fonksiyon Parametresi Olarak Göstericilerin Kullanılması
 | 
						||
Bir fonksiyonun parametresi bir gösterici olabilir. Örneğin:
 | 
						||
void foo(int *pi)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
Bir fonksiyonun parametresi bir gösterici ise biz o fonksiyonu aynı türden bir adres bilgisi ile çağırmalıyız.
 | 
						||
Örneğin:
 | 
						||
int a = 10;
 | 
						||
...
 | 
						||
foo(a);
 | 
						||
foo(&a);
 | 
						||
 | 
						||
// geçersiz!
 | 
						||
// geçerli
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo(int *pi)
 | 
						||
{
 | 
						||
*pi = 200;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 123;
 | 
						||
foo(&a);
 | 
						||
printf("%d\n", a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada foo fonksiyonuna a'nın içindeki değer değil, a'nın adresi geçirilmiştir. Böylece fonksiyon içerisinde *pi
 | 
						||
dediğimizde aslında bu ifade main fonksiyonundaki a'ya erişir.
 | 
						||
 | 
						||
C'de bir fonksiyonun başka bir fonksiyonun yerel değişkenini değiştirebilmesi için onun adresini alması gerekir.
 | 
						||
Bunun için de fonksiyonun parametre değişkeninin gösterici olması gerekir. (Aslında tüm dillerde böyledir. Çünkü
 | 
						||
makinanın çalışma prensibinde durum böyledir.)
 | 
						||
İki yerel nesnenin içerisindeki değerleri değiştiren swap isimli bir fonksiyon yazmak isteyelim. Acaba bu
 | 
						||
fonksiyonu aşağıdaki gibi yazabilir miyiz?
 | 
						||
133
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
void swap(int a, int b)
 | 
						||
{
 | 
						||
int temp = a;
 | 
						||
a = b;
 | 
						||
b = temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int x = 10, y = 20;
 | 
						||
swap(x, y);
 | 
						||
printf("x = %d, y = %d\n", x, y);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yanıt hayır! Çünkü burada swap x ve y'nin içindekilerini değiştirmiyor, parametre değişkeni olan a ve b'nin
 | 
						||
içindekileri değiştiriyor. Bunun sağlanabilmesi için yerel nesnelerin adreslerinin swap fonksiyonuna aktarılması
 | 
						||
gerekir. Bu durumda swap fonksiyonunun paremetre değişkenleri birer gösterici olmalıdır:
 | 
						||
#include <stdio.h>
 | 
						||
void swap(int *a, int *b)
 | 
						||
{
 | 
						||
int temp = *a;
 | 
						||
*a = *b;
 | 
						||
*b = temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int x = 10, y = 20;
 | 
						||
swap(&x, &y);
 | 
						||
printf("x = %d, y = %d\n", x, y);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dizilerin Fonksiyonlara Parametre Yoluyla Aktarılması
 | 
						||
Dizi elemanları bellekte ardışıl bir biçimde tuutlduğuna göre biz bir diziyi yalnızca onun başlangıç adresi yoluyla
 | 
						||
fonksiyona aktarabiliriz. Böylece dizinin başlangıç adresini alan fonksiyon * ya da [ ] operatörü ile dizinin her
 | 
						||
elemanına erişebilir. Dizilerin fonksiyonlara parametre yoluyla aktarılmasında mecburen göstericilerden
 | 
						||
faydalanılmaktadır.
 | 
						||
#include <stdio.h>
 | 
						||
void foo(int *pi)
 | 
						||
{
 | 
						||
int k;
 | 
						||
for (k = 0; k < 10; ++k) {
 | 
						||
printf("%d ", *pi);
 | 
						||
++pi;
 | 
						||
}
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
 | 
						||
134
 | 
						||
 | 
						||
void bar(int *pi)
 | 
						||
{
 | 
						||
int k;
 | 
						||
for (k = 0; k < 10; ++k)
 | 
						||
printf("%d ", pi[k]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
 | 
						||
foo(a);
 | 
						||
bar(a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yukarıdaki fonksiyonlar dizinin uzunluğununun 10 olduğunu biliyor durumdalar. Biz bu fonksiyonlara ancak 10
 | 
						||
elemanlı dizilerin başlangıç adreslerini yollayabiliriz. eğer dizilerle çalışan fonksiyonların daha genel olmasını
 | 
						||
istiyorsak o fonksiyonlara ayrıca dizinin uzunluğunu geçirmemiz gerekir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo(int *pi, int size)
 | 
						||
{
 | 
						||
int k;
 | 
						||
for (k = 0; k < size; ++k) {
 | 
						||
printf("%d ", *pi);
 | 
						||
++pi;
 | 
						||
}
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
void bar(int *pi, int size)
 | 
						||
{
 | 
						||
int k;
 | 
						||
for (k = 0; k < size; ++k)
 | 
						||
printf("%d ", pi[k]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
 | 
						||
int b[5] = { 1, 2, 3, 4, 5 };
 | 
						||
foo(a, 10);
 | 
						||
bar(b, 5);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int getmax(int *pi, int size)
 | 
						||
{
 | 
						||
int max = pi[0];
 | 
						||
int i;
 | 
						||
 | 
						||
135
 | 
						||
 | 
						||
for (i = 1; i < size; ++i)
 | 
						||
if (pi[i] > max)
 | 
						||
max = pi[i];
 | 
						||
return max;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[5] = { 23, -4, 21, 34, 32 };
 | 
						||
int max;
 | 
						||
max = getmax(a, sizeof(a)/sizeof(*a));
 | 
						||
printf("%d\n", max);
 | 
						||
max = getmax(a, 3);
 | 
						||
printf("%d\n", max);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada getmax int bir dizinin en büyük elemanına geri dönmektedir.
 | 
						||
double bir dizinin standart sapmasını hesaplayan fonksiyon şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <math.h>
 | 
						||
double getavg(double *pd, int size)
 | 
						||
{
 | 
						||
double total;
 | 
						||
int i;
 | 
						||
total = 0;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
total += pd[i];
 | 
						||
return total / size;
 | 
						||
}
 | 
						||
double get_stddev(double *pd, int size)
 | 
						||
{
 | 
						||
double avg, total;
 | 
						||
int i;
 | 
						||
avg = getavg(pd, size);
 | 
						||
total = 0;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
total += pow(pd[i] - avg, 2);
 | 
						||
return sqrt(total / (size - 1));
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
double a[5] = { 1, 1, 1, 1, 2 };
 | 
						||
double result;
 | 
						||
result = get_stddev(a, 5);
 | 
						||
printf("%f\n", result);
 | 
						||
return 0;
 | 
						||
 | 
						||
136
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Örneğin bir diziyi kabarcık sıralaması (bubble sort) yöntemiyle sıraya dizen fonksiyon şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
void bsort(int *pi, int size)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < size - 1; ++i)
 | 
						||
for (k = 0; k < size - 1 - i; ++k)
 | 
						||
if (pi[k] > pi[k + 1]) {
 | 
						||
int temp = pi[k];
 | 
						||
pi[k] = pi[k + 1];
 | 
						||
pi[k + 1] = temp;
 | 
						||
}
 | 
						||
}
 | 
						||
void disp_array(int *pi, int size)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
printf("%d ", pi[i]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[] = { 4, 8, 2, 21, 43, 10, -5, 87, 9, 68 };
 | 
						||
bsort(a, 10);
 | 
						||
disp_array(a, 10);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: 10 elemanlı int türden bir dizi tanımlayınız. Bu dizinin elemanlarını ters yüz eden aşağıdaki
 | 
						||
prototipe sahip fonksiyonu yazarak test ediniz:
 | 
						||
void rev_array(int *pi, int size);
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
void rev_array(int *pi, int size)
 | 
						||
{
 | 
						||
int i, temp;
 | 
						||
for (i = 0; i < size / 2; ++i) {
 | 
						||
temp = pi[i];
 | 
						||
pi[i] = pi[size - i - 1];
 | 
						||
pi[size - i - 1] = temp;
 | 
						||
}
 | 
						||
}
 | 
						||
void disp_array(int *pi, int size)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
 | 
						||
137
 | 
						||
 | 
						||
printf("%d ", pi[i]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
rev_array(a, 10);
 | 
						||
disp_array(a, 10);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Gösterici Parametrelerinin Alternatif Biçimi
 | 
						||
C'de bir fonksiyonun parametre değişkeni bir gösterici ise bu alternatif olarak dizi sentaksıyla da
 | 
						||
belirtilebilmektedir. Bu alternatif sentaksta köşeli parantezlerin içi boş bırakılabilir. Fakat köşeli parantezlerin
 | 
						||
içerisine bir uzunluk yazılsa bile bunun hiçbir özel anlamı yoktur. Yani köşeli parantezlerin içinin boş
 | 
						||
bırakılmasıyla, onun içerisine bir uzunluk yazılması arasında bir fark yoktur. Örneğin:
 | 
						||
void foo(int *pi)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
bildirimi ile aşağıdaki tamamen eşdeğerdir:
 | 
						||
void foo(int pi[])
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
bununla da aşağıdaki eşdeğerdir:
 | 
						||
void foo(int pi[10])
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
Tabii bu eşdeğerlik yalnızca fonksiyon parametresi söz konusu olduğunda geçerlidir. Yani:
 | 
						||
int pi[];
 | 
						||
int pi[10];
 | 
						||
 | 
						||
/* geçersiz! Dizi uzunluğu belirtilmemiş! */
 | 
						||
/* pi gösterici değil 10 elemanlı bir dizi */
 | 
						||
 | 
						||
Kaynak Kodun Okunabilirliği ve Anlaşılabilirliği
 | 
						||
Kaynak kodun bakıldığında (ileride bizim tarafımızdan ya da başkaları tarafından) kolay anlaşılması önemlidir.
 | 
						||
Buna genel olarak okunabilirlik (readabilty) denilmektedir. Okunabilirliği sağlamak için bazı temel öğelere dikkat
 | 
						||
etmek gerekir:
 | 
						||
1) Kaynak kodda bazı kritik noktalara açıklama (yorum) yerleştirilebilir. Örneğin:
 | 
						||
if (delta >=
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
0) {
 | 
						||
 | 
						||
/ * kök var mı? */
 | 
						||
 | 
						||
138
 | 
						||
 | 
						||
Çok fazla açıklama bilgisi de okunabilirliği azaltmaktadır. Uygun yerlere, çok uzun olmayan ve genellikle soru
 | 
						||
cümleleri biçiminde açıklama yapmak uygun olur.
 | 
						||
2) Değişkenlere anlamlı ve telaffuz edilebilir isimler verilmelidir. İsimlendirme Türkçe olabilir. Ancak şu da
 | 
						||
unutulmamalıdır ki, maalesef uluslararası düzeyde herkes İngilizce isimlendirme beklemektedir. Fakat kodumuzu
 | 
						||
örneğin Internet ortamında uluslararası paylaymayacaksak isimlendirmeleri Türkçe yapabiliriz. Örneğin döngü
 | 
						||
değişkenleri için i, j, k, l, m, n gelenekseldir (Fortran zamanlarından gelme bir gelenek). Tabi bazen n, count gibi
 | 
						||
isimler kullanılabilir.
 | 
						||
3) Global değişkenlerin özel bir önekle (örneğin g_xxx biçiminde) başlatılması uygun olabilir. Örneğin g_count
 | 
						||
gibi.
 | 
						||
4) Değişken isimlendirmesinde harflendirme (capitalization) önemlidir. Harflendirme bir sözcük uzun olan
 | 
						||
isimlerin yazılış biçimlerini belirlemek anlamına gelir. Tipik olarak programda dillerinde üç harflendirme tarzı
 | 
						||
vardır:
 | 
						||
a) Pascal Tarzı Harflendirme (Pascal casting): Burada her sözcüğün ilk harfi büyük yazılır (bu tarz Pascal dilinde
 | 
						||
çok kullanıldığı için Pascal tarzı denilmektedir). Örneğin:
 | 
						||
CreateWindow
 | 
						||
GetWindowText
 | 
						||
NumberOfStudents
 | 
						||
...
 | 
						||
b) Klasik C Tarzı (C Style): Bu tarzda isimlerde büyük harf kullanılmaz. Sözcükleri ayırmak için alt tire kullanılır.
 | 
						||
Örneğin:
 | 
						||
create_window
 | 
						||
get_window_text
 | 
						||
number_of_students
 | 
						||
Bu tarz UNIX/Linux sistemlerinde C programlama dilinde baskın olarak kullanılmaktadır.
 | 
						||
c) Deve Notasyonu (Camel casting): Bu tarzda ilk sözcüğün tamamı küçük harflerle sonraki sözcüklerin yalnızca
 | 
						||
ilk harfleri büyük harflerle belirtilir. C++ QT framework'ünde, Java'da bu biçim tercih edilmektedir.
 | 
						||
Biz kursumuzda global değişkenleri g_ ile başlatarak harflendireceğiz. Fonksiyon isimlerini Klasik C Tarzında,
 | 
						||
yerel ve parametre değişkenlerini deve notasyonunda harflendireceğiz.
 | 
						||
Windows altında C programcıları fonksiyon isimlerini genel olarak Pascal tarzı harflendirmektedir.
 | 
						||
5) Okunabilirliği sağlamada en önemli unsurlardan biri de kaynak kodun genel düzenidir. Bunun için birkaç tarz
 | 
						||
kullanılmaktadır. Biz kursumuzda Ritchie/Kernighan tarzını kullanıyoruz. Bu tarzın anahtar noktaları şunlardır:
 | 
						||
a) Fonksiyonlar en soldaki sütuna dayalı olarak tanımlanırlar. Fonkisyon tanımlamaları arasında bir satır boşluk
 | 
						||
bırakılır.
 | 
						||
b) Bildirim satırlarından sonra bir satır boşluk bırakılır. Örneğin:
 | 
						||
 | 
						||
139
 | 
						||
 | 
						||
c) Üst üste birden fazla SPACE kullanılmaz. Bir SPACE yetmezse TAB kullanılır. Üst üste birdeb fazla TAB
 | 
						||
kullanılabilir.
 | 
						||
d) Blok içleri bir TAB içeriden yazılır. Örneğin:
 | 
						||
 | 
						||
e) İki operandlı operatörlerle operandlar arasında bir SPACE boşluk bırakılır. Ancak tek operandlı operatörlerle
 | 
						||
operand arasında boşluk bırakılmaz. (İstisna olarak iki operandlı nokta ve ok operatörleriyle operandlar arasında
 | 
						||
boşluk bırakılmaz.) Örneğin:
 | 
						||
 | 
						||
f) Anahtar sözcüklerden sonra 1 SPACE boşluk bırakılır. Ancak fonksiyon isimlerinden sonra bırakılmaz. Örneğin:
 | 
						||
 | 
						||
140
 | 
						||
 | 
						||
g) Yazıdaki paragraf duygusu programlamada boş satır bırakılarak verilir. Yani konudan konuya geçerken bir satır
 | 
						||
boşluk bırakılmalıdır.
 | 
						||
h) if deyiminin yazımı şöyledir:
 | 
						||
Bloksuz biçim:
 | 
						||
 | 
						||
Bloklu biçim:
 | 
						||
 | 
						||
else-if Merdiveni:
 | 
						||
 | 
						||
141
 | 
						||
 | 
						||
i) for deyimi şöyle yazılır:
 | 
						||
Bloksuz biçim:
 | 
						||
 | 
						||
Bloklu biçim:
 | 
						||
 | 
						||
j) while deyimi şöyle yazılır:
 | 
						||
Bloksuz biçim:
 | 
						||
 | 
						||
Bloklu biçim:
 | 
						||
 | 
						||
k) switch deyimi şöyle yazılır:
 | 
						||
142
 | 
						||
 | 
						||
l) Sembolik sabitleri yazarken STR1'ler ve STR'ler alta alta gelmelidir. Bunun için yeterli tablama yapılabilir.
 | 
						||
Örneğin:
 | 
						||
 | 
						||
m) goto etiketleri büyük harflerle oluşturulmalıdır ve bir TAB geride bulunmalıdır. Örneğin:
 | 
						||
 | 
						||
n) Virgül atomundan önce boşluk bırakılmaz, sonra bir SPACE boşluk bırakılır. Örneğin:
 | 
						||
 | 
						||
Yazıların Fonksiyonlara Parametre Yoluyla Aktarılması
 | 
						||
Yazıları fonksiyonlara parametre yoluyla aktarmak için yalnızca onların başlangıç adreslerinin fonksiyona
 | 
						||
143
 | 
						||
 | 
						||
geçirilmesi yeterlidir. Ayrıca onların uzunluklarının fonksiyonlara aktarılması gereksizdir. Çünkü yazıların
 | 
						||
sonunda null karakter olduğu için yazının başlangıç adresini alan fonksiyon null karakter görene kadar ilerleyerek
 | 
						||
yazının tüm karakterlerini elde edebilir. Tabi bu durumda fonksiyonun parametre değişkeni char türden gösterici
 | 
						||
olmalıdır. null karakter görene kadar yazının tüm karakterlerini dolaşmak için iki kalıp kullanılabilir:
 | 
						||
1)
 | 
						||
while (*str != '\0') {
 | 
						||
/* ... */
 | 
						||
++str;
 | 
						||
}
 | 
						||
 | 
						||
2)
 | 
						||
for (i = 0; str[i] != '\0'; ++i) {
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Örneğin aslında puts fonksiyonu bizden char türden bir adres alıp null karakter görene kadar karakterleri ekrana
 | 
						||
yazdırmaktadır. Aynı fonksiyonu biz de şöyle yazabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
void myputs(char *str)
 | 
						||
{
 | 
						||
while (*str != '\0') {
 | 
						||
putchar(*str);
 | 
						||
++str;
 | 
						||
}
 | 
						||
putchar('\n');
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
myputs(s);
 | 
						||
puts(s);
 | 
						||
myputs(s + 2);
 | 
						||
puts(s + 2);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf çalışması: Başlangıç adresiyle verilen bir yazıyı tersten yazdıran putrev fonksiyonu yazınız:
 | 
						||
void putsrev(char *str);
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
void putsrev(char *str)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; str[i] != '\0'; ++i)
 | 
						||
;
 | 
						||
for (--i; i >= 0; --i)
 | 
						||
putchar(str[i]);
 | 
						||
 | 
						||
144
 | 
						||
 | 
						||
putchar('\n');
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
putsrev(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Bir yazı içerisinde belli bir karakterden kaç tane olduğunu bulan ve o sayıya geri dönen aşağıdaki
 | 
						||
prototipe sahip sonksiyonu yazınız:
 | 
						||
int getch_count(char *str, char ch);
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
int getch_count(char *str, char ch)
 | 
						||
{
 | 
						||
int count = 0;
 | 
						||
while (*str != '\0') {
 | 
						||
if (*str == ch)
 | 
						||
++count;
 | 
						||
++str;
 | 
						||
}
 | 
						||
return count;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
int result;
 | 
						||
result = getch_count(s, 'a');
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonların Geri Dönüş Değerlerinin Adres Olması Durumu
 | 
						||
Bir fonksiyonun geri dönüş değeri bir adres bilgisi olabilir. Bu durumda T bir tür belirtmek üzere geri dönüş değeri
 | 
						||
T * biçiminde belirtilmelidir. Örneğin:
 | 
						||
int *foo(void)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
Burada foo fonksiyonu int türden bir adrese geri döner. Tabi böyle bir fonksiyonun geri dönüş değeri aynı türden
 | 
						||
bir göstericiye atanmalıdır. Örneğin:
 | 
						||
int *pi;
 | 
						||
...
 | 
						||
145
 | 
						||
 | 
						||
pi = foo();
 | 
						||
Örneğin bir dizideki en büyük elemanın adresiyle geri dönen getmax_addr isimli bir fonksiyonu yazacak olalım:
 | 
						||
#include <stdio.h>
 | 
						||
int *getmax_addr(int *pi, int size)
 | 
						||
{
 | 
						||
int *pmax = &pi[0];
 | 
						||
int i;
 | 
						||
for (i = 1; i < size; ++i)
 | 
						||
if (pi[i] > *pmax)
 | 
						||
pmax = &pi[i];
 | 
						||
return pmax;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10] = { 34, 21, 87, 45, 32, 67, 93, 22, 9, 2 };
 | 
						||
int *pmax;
 | 
						||
pmax = getmax_addr(a, 10);
 | 
						||
printf("%d\n", *pmax);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yukarıdaki örnekte dizinin en büyük elemanını değiştirmek isteyelim:
 | 
						||
#include <stdio.h>
 | 
						||
int *getmax_addr(int *pi, int size)
 | 
						||
{
 | 
						||
int *pmax = &pi[0];
 | 
						||
int i;
 | 
						||
for (i = 1; i < size; ++i)
 | 
						||
if (pi[i] > *pmax)
 | 
						||
pmax = &pi[i];
 | 
						||
return pmax;
 | 
						||
}
 | 
						||
void disp_array(int *pi, int size)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
printf("%d ", pi[i])
 | 
						||
;
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10] = { 34, 21, 87, 45, 32, 67, 93, 22, 9, 2 };
 | 
						||
int *pmax;
 | 
						||
disp_array(a, 10);
 | 
						||
*getmax_addr(a, 10) = 1000;
 | 
						||
disp_array(a, 10);
 | 
						||
return 0;
 | 
						||
 | 
						||
146
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Anahtar Notlar: C'de bir adresin sayısal bileşeni printf fonksiyonunda %p format karakteriyle yazdırılır. Örneğin:
 | 
						||
char s[10];
 | 
						||
printf("%p\n", s);
 | 
						||
C'de Standart String Fonksiyonları
 | 
						||
C'de ismi str ile başlayan strxxx biçiminde bir grup standart fonksiyon vardır. Bu fonksiyonlara string
 | 
						||
fonksiyonları denir. Bu fonksiyonlar bir yazının başlangıç adresini parametre olarak alırlar, onunla ilgili faydalı
 | 
						||
işlemler yaparlar. Bu fonksiyonların prototipleri <string.h> dosyası içerisinde bulunmaktadır.
 | 
						||
strlen fonksiyonu
 | 
						||
strlen fonksiyonu bir yazının karakter uzunluğunu bulmak için kullanılmaktadır:
 | 
						||
unsigned int strlen(char *str);
 | 
						||
Fonksiyon parametre olarak char türden bir yazının başlangıç adresini alır, geri dönüş değeri olarak bu yazının
 | 
						||
karakter uzunluğunu verir.
 | 
						||
Anahtar Notlar: strlen fonksiyonun orijinal prototipi şöyledir:
 | 
						||
size_t strlen(const char *str);
 | 
						||
size_t türü ve const göstericiler ileride ele alınacaktır.
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
unsigned n;
 | 
						||
printf("Bir yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
n = strlen(s);
 | 
						||
printf("%u\n", n);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strlen fonksiyonu aşağıdaki gibi yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
unsigned mystrlen(char *str)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; str[i] != '\0'; ++i)
 | 
						||
;
 | 
						||
 | 
						||
147
 | 
						||
 | 
						||
return i;
 | 
						||
}
 | 
						||
unsigned mystrlen2(char *str)
 | 
						||
{
 | 
						||
int count = 0;
 | 
						||
while (*str != '\0') {
 | 
						||
++count;
 | 
						||
++str;
 | 
						||
}
 | 
						||
return count;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
unsigned n;
 | 
						||
printf("Bir yazi giriniz:");
 | 
						||
gets(s);
 | 
						||
n = mystrlen(s);
 | 
						||
printf("%u\n", n);
 | 
						||
n = mystrlen2(s);
 | 
						||
printf("%u\n", n);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcpy Fonksiyonu
 | 
						||
Bu fonksiyon char türden bir dizi içerisindeki yazıyı başka bir diziye kopyalamakta kullanılır. Prototipi şöyledir:
 | 
						||
char *strcpy(char *dest, char *source);
 | 
						||
 | 
						||
Fonksiyon ikinmci parametresiyle belirtilen adresten başlayarak null karakter görene kadar (null karakter de dahil)
 | 
						||
tüm karakterleri birinci parametresi ile başlayan adresten itibaren kopyalar. Birinci parametresiyle verilen adresin
 | 
						||
aynısına geri döner. Uygulamada geri dönüş değerine ciddi bir biçimde gereksinim duyulmamaktadır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32] = "ankara";
 | 
						||
char d[32];
 | 
						||
strcpy(d, s);
 | 
						||
puts(d);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
148
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32] = "ankara";
 | 
						||
char d[32];
 | 
						||
strcpy(d, s + 2);
 | 
						||
puts(d);
 | 
						||
 | 
						||
/* kara */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcpy fonksiyonu aşağıdaki gibi yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *mystrcpy(char *dest, char *source)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; (dest[i] = source[i]) != '\0'; ++i)
 | 
						||
;
 | 
						||
return dest;
 | 
						||
}
 | 
						||
char *mystrcpy2(char *dest, char *source)
 | 
						||
{
 | 
						||
char *temp = dest;
 | 
						||
while ((*dest = *source) != '\0') {
 | 
						||
++dest;
 | 
						||
++source;
 | 
						||
}
 | 
						||
return temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32] = "ankara";
 | 
						||
char d[32];
 | 
						||
mystrcpy(d, s);
 | 
						||
puts(d);
 | 
						||
mystrcpy2(d, s);
 | 
						||
puts(d);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcat Fonksiyonu
 | 
						||
Bu fonksiyon bir yazının sonuna başka bir yazıyı eklemek için kullanılır. Prototipi şöyledir:
 | 
						||
char *strcat(char *dest, char *source);
 | 
						||
Fonksiyon ikinci parametresiyle belirtilen adresten başlayarak null karakter görene kadar tüm karakterleri (null
 | 
						||
karakter de dahil) birinci parametresiyle belirtilen yazının sonuna null karakter ezerek kopyalar. Fonksiyon birinci
 | 
						||
149
 | 
						||
 | 
						||
parametre ile belirtilen adresin aynısına geri döner.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32] = "ankara";
 | 
						||
char d[32] = "izmir";
 | 
						||
strcat(d, s);
 | 
						||
printf("%s\n", d);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcat fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *mystrcat(char *dest, char *source)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; dest[i] != '\0'; ++i)
 | 
						||
;
 | 
						||
for (k = 0; (dest[k + i] = source[k]) != '\0'; ++k)
 | 
						||
;
 | 
						||
return dest;
 | 
						||
}
 | 
						||
char *mystrcat2(char *dest, char *source)
 | 
						||
{
 | 
						||
char *temp = dest;
 | 
						||
while (*dest != '\0')
 | 
						||
++dest;
 | 
						||
while ((*dest = *source) != '\0') {
 | 
						||
++dest;
 | 
						||
++source;
 | 
						||
}
 | 
						||
return temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32] = "ankara";
 | 
						||
char d[32] = "izmir";
 | 
						||
mystrcat(d, s);
 | 
						||
printf("%s\n", d);
 | 
						||
mystrcat2(d, s);
 | 
						||
printf("%s\n", d);
 | 
						||
return 0;
 | 
						||
 | 
						||
/* izmirankaraankara*/
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: 1000 elemanlık char türden s imli bir dizi ve bunun yanı sıra 32 elemanlık k isimli bir dizi
 | 
						||
150
 | 
						||
 | 
						||
tanımlayınız. Bir döngü içerisinde gets fonksiyonuyla k dizisine okuma yapınız. Oradan da okunanları s dizisinin
 | 
						||
sonuna ekleyiniz. Ta ki gets fonksiyonunda kullanıcı hiçbirşey girmeyip yalnızca ENTER tuşuna basana kadar.
 | 
						||
Döngüden çıkınca s dizisini yazdırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[1000];
 | 
						||
char k[32];
 | 
						||
s[0] = '\0';
 | 
						||
for (;;) {
 | 
						||
printf("Isim giriniz:");
 | 
						||
gets(k);
 | 
						||
if (*k == '\0')
 | 
						||
break;
 | 
						||
strcat(s, k);
 | 
						||
}
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcmp Fonksiyonu
 | 
						||
strcmp fonksiyonu iki yazıyı karşılaştırmak amacıyla kullanılır. Karşılaştırma şöyle yapılır: Yazıların karakterleri
 | 
						||
eşit olduğu sürece devam edilir. İlk eşi,t olmayan karaktere gelindiğinde o karakterlerin hangisi karakter tablosunda
 | 
						||
daha büyük numaraya sahipse o yazı o yazıdan büyüktür. ASCII tablosu için şöyle örnekler verebiliriz:
 | 
						||
- "ali" yazısı "Ali" yazısından büyüktür.
 | 
						||
- "ali" yazısı "aliye" yazısından küçüktür.
 | 
						||
- "almanya" yazısı "ali" yazısından büyüktür.
 | 
						||
strcmp fonksiyonunun prototipi şöyledir:
 | 
						||
int strcmp(char *s1, char *s2);
 | 
						||
Fonksiyon birinci yazı ikinci yazıdan büyükse pozitif herhangi bir değer, küçükse negatif herhangi bir değer ve
 | 
						||
eşitse sıfır değerine geri döner.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[64];
 | 
						||
char passwd[] = "maviay";
 | 
						||
printf("Enter password:");
 | 
						||
gets(s);
 | 
						||
if (!strcmp(s, passwd))
 | 
						||
printf("Ok\n");
 | 
						||
 | 
						||
151
 | 
						||
 | 
						||
else
 | 
						||
printf("Invalid password\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonu şöyle yazabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int mystrcmp(char *s1, char *s2)
 | 
						||
{
 | 
						||
while (*s1 == *s2) {
 | 
						||
if (*s1 == '\0')
 | 
						||
break;
 | 
						||
++s1;
 | 
						||
++s2;
 | 
						||
}
 | 
						||
return *s1 - *s2;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[64];
 | 
						||
char passwd[] = "maviay";
 | 
						||
printf("Enter password:");
 | 
						||
gets(s);
 | 
						||
if (!mystrcmp(s, passwd))
 | 
						||
printf("Ok\n");
 | 
						||
else
 | 
						||
printf("Invalid password\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strcmp fonksiyonun büyük harf küçük harf duyarlılığı olmadan karşılaştırma yapan stricmp isimli bir versiyonu da
 | 
						||
vardır. Ancak stricmp standart bir fonksiyon değildir. Fakat Microsoft derleyicilerinde, Borland derleyicilerinde ve
 | 
						||
gcc derleyicilerinde bir eklenti biçiminde bulunmaktadır. stricmp fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int mystricmp(char *s1, char *s2)
 | 
						||
{
 | 
						||
while (tolower(*s1) == tolower(*s2)) {
 | 
						||
if (*s1 == '\0')
 | 
						||
break;
 | 
						||
++s1;
 | 
						||
++s2;
 | 
						||
}
 | 
						||
return tolower(*s1) - tolower(*s2);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[64];
 | 
						||
char passwd[] = "maviay";
 | 
						||
printf("Enter password:");
 | 
						||
 | 
						||
152
 | 
						||
 | 
						||
gets(s);
 | 
						||
if (!mystricmp(s, passwd))
 | 
						||
printf("Ok\n");
 | 
						||
else
 | 
						||
printf("Invalid password\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
NULL Adres Kavramı (NULL Pointer)
 | 
						||
NULL adres derleyici tarafından seçilmiş bir sayısal bileşene sahip olan özel bir adrestir. NULL adres C'de geçerli
 | 
						||
bir adres kabul edilmez. Başarısızlığı anlatmak için kullanılır. NULL adresin sayısal değeri standartlara göre
 | 
						||
derleyiciden derleyiciye değişebilir. Yaygın derleyicilerin hepsinde NULL adres 0 sayısal bileşenine sahip (yani
 | 
						||
belleğin tepesini belirten) adrestir. Fakat NULL adres standartlara göre farklı sistemlerde farklı biçimde olabilir.
 | 
						||
C'de 0 değerini veren tamsayı türlerine ilişkin sabit ifadeleri NULL adres sabiti olarak kullanılır. Örneğin:
 | 
						||
5-5
 | 
						||
0
 | 
						||
1-1
 | 
						||
gibi ifadeler aynı zamanda NULL adres sabiti anlamına gelmektedir. Biz bir göstericiye bu değerleri atadığımızda
 | 
						||
derleyici o sistemde hangi sayısal bileşen NULL adres belirtiyorsa göstericiye onu atar. Yani buradaki 0, 0 adresini
 | 
						||
temsil etmez. O sistemdeki NULL adresi temsil eder. Örneğin:
 | 
						||
int *pi = 0;
 | 
						||
Burada pi göstericisine int bir sıfır atanmıyor. O sistemde NULL adres neyse o değer atanıyor.
 | 
						||
NULL adres sabitini (yani sıfır değerini) her türden göstericiye atayabiliriz. Bu durumda o göstericinin içerisinde
 | 
						||
"NULL adres" bulunur. Örneğin:
 | 
						||
char *pc = 0;
 | 
						||
pc'nin içerisinde NULL adres vardır. NULL adres sabitinin türü yoktur. NULL adres sabiti (yani sıfır sayısı) her
 | 
						||
türden göstericiye atanabilir. Yukarıdaki örnekte pc göstericisi char türündendir. Fakat içerisinde NULL adres
 | 
						||
vardır.
 | 
						||
Örneğin bir sistgemde NULL adresin sayısal bileşeni 0xFFFF olsun. Biz bu sistemde göstericiye NULL adres
 | 
						||
atayabilmek için, göstericiye 0xFFFF atayamayız. Yine düz sıfır atarız. Bu düz sıfır zaten o sistemdeki NULL
 | 
						||
adres anlamına gelir. Örneğin:
 | 
						||
char *pc = 0;
 | 
						||
Burada pc'ye sıfır adresi atanmıyor, o sistemdeki NULL adres olan 0xFFFF sayısal bileşenine sahip olan adres
 | 
						||
atanıyor.
 | 
						||
Anahtar Notlar: Standartlara göre NULL adres sabiti aynı zamanda sıfır değerinin void * türüne dönüşsütülmüş biçimi de olabilir. Yani (void *)0 da
 | 
						||
NULL adres anlamına gelir.
 | 
						||
 | 
						||
Bir adresin NULL olup olmadığı == ve != operatörleriyle öğrenilebilir. Örneğin:
 | 
						||
if (pi == 0) {
 | 
						||
153
 | 
						||
 | 
						||
...
 | 
						||
}
 | 
						||
Burada pi'nin içerisindeki adresin sayısal bileşeninin sıfır olup olmadığına bakılmamaktadır. O sistemdeki NULL
 | 
						||
adres olup olmadığına bakılmaktadır. Örneğin ilgili sistemde NULL adres 0xFFFF sayısal değerine ilişkin olsun.
 | 
						||
Ve pi'in içerisinde NULL adres olduğunu varsayalım:
 | 
						||
if (pi == 0) {
 | 
						||
...
 | 
						||
}
 | 
						||
Burada if deyimi doğrudan sapar.
 | 
						||
if parantezinin içerisinde yalnızca bir adres ifadesi varsa bu adresin NULL adres olup olmadığına bakılır. Eğer
 | 
						||
adres NULL adres ise if deyimi yanlıştan sapar, NULL adres değilse doğrudan sapar. Örneğin:
 | 
						||
if (pi) {
 | 
						||
...
 | 
						||
}
 | 
						||
else {
 | 
						||
...
 | 
						||
}
 | 
						||
Bu örnekte ilgili sistemde NULL adresin 0xFFFF olduğunu düşünelim. Yukarıdaki if deyimi yanlıştan sapacaktır.
 | 
						||
Çünkü bu özel durumda adresin sayısal bileşeninin sıfır olup olmadığına değil, NULL adres olup olmadığına
 | 
						||
bakılmaktadır. Benzer biçimde C'de ! operatörünün operandı bir adres bilgisiyse bu operatör adres NULL adres ise
 | 
						||
1 değerini, NULL adres değilse 0 değerini üretir.
 | 
						||
* ya da köşeli parantez operatörleriyle NULL adresin içeriği elde edilmeye çalışılırsa bu durum tanımsız davranışa
 | 
						||
(undefined behavior) yol açar.
 | 
						||
C'de NULL adres sabiti daha okunabilir ifade edilsin diye NULL isimli bir sembolik sabitle temsil edilmiştir:
 | 
						||
#define NULL
 | 
						||
 | 
						||
0
 | 
						||
 | 
						||
Programcılar genellikle NULL adres sabiti için düz sıfır kullanmak yerine NULL sembolik sabitini kullanırlar.
 | 
						||
Örneğin:
 | 
						||
if (pi == NULL) {
 | 
						||
...
 | 
						||
}
 | 
						||
NULL sembolik sabiti <stdio.h> v e pek çok başlık dosyasında bulunmaktadır. NULL sembolik sabitini int türden
 | 
						||
bir sıfır olarak değil, NULL adres sabiti olarak kullanmalıyız.
 | 
						||
strchr Fonksiyonu
 | 
						||
Bu fonksiyon bir yazı içerisinde bir karakteri aramak için kullanılır. Eğer karakter yazı içerisinde bulunursa
 | 
						||
fonksiyon karakterin ilk bulunduğu yerin adresiyle geri döner. Eğer bulunamazsa NULL adresle geri döner. Bu
 | 
						||
fonksiyon null karakteri de arayabilmektedir.
 | 
						||
char *strchr(char *str, char ch);
 | 
						||
154
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char *str;
 | 
						||
str = strchr(s, 'k');
 | 
						||
if (str == NULL)
 | 
						||
printf("karakter yok!..\n");
 | 
						||
else
 | 
						||
printf("Buldu:%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strchr fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
char *mystrchr(char *str, char ch)
 | 
						||
{
 | 
						||
while (*str != '\0') {
 | 
						||
if (*str == ch)
 | 
						||
break;
 | 
						||
++str;
 | 
						||
}
 | 
						||
if (*str == '\0' && ch != '\0')
 | 
						||
return NULL;
 | 
						||
return str;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char *str;
 | 
						||
str = mystrchr(s, 'k');
 | 
						||
if (str == NULL)
 | 
						||
printf("karakter yok!..\n");
 | 
						||
else
 | 
						||
printf("Buldu:%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strrchr Fonksiyonu
 | 
						||
Fonksiyon tamamen strchr fonksiyonu gibidir. Ancak ilk bulunan karakterin değil son bulunan karakterin adresiyle
 | 
						||
geri döner. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char *str;
 | 
						||
 | 
						||
155
 | 
						||
 | 
						||
str = strrchr(s, 'i');
 | 
						||
if (str == NULL)
 | 
						||
printf("karakter yok!..\n");
 | 
						||
else
 | 
						||
printf("Buldu:%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strrchr fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *mystrrchr(char *str, char ch)
 | 
						||
{
 | 
						||
char *result = NULL;
 | 
						||
while (*str != '\0') {
 | 
						||
if (*str == ch)
 | 
						||
result = str;
 | 
						||
++str;
 | 
						||
}
 | 
						||
if (ch == '\0')
 | 
						||
return str;
 | 
						||
return result;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char *str;
 | 
						||
str = mystrrchr(s, 'i');
 | 
						||
if (str == NULL)
 | 
						||
printf("karakter yok!..\n");
 | 
						||
else
 | 
						||
printf("Buldu:%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strncpy Fonksiyonu
 | 
						||
Bu fonksiyon bir yazının ilk n karakterini başka bir diziye kopyalar. Prototipi şöyledir:
 | 
						||
char *strncpy(char *dest, char *source, unsigned n);
 | 
						||
Bu fonksiyonda eğer n değeri strlen(source) değerinden küçük ya da eşitse fonksiyon null karakteri hedef diziye
 | 
						||
eklemez. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
 | 
						||
156
 | 
						||
 | 
						||
strncpy(d, s, 3);
 | 
						||
puts(d);
 | 
						||
/* izmara */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Eğer n değeri strlen(source) değerinden büyükse ya da eşitse hedefe null karakter de kopyalanır. Üstelik geri kalan
 | 
						||
miktar kadar null karakter kopyalanır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
strncpy(d, s, 30);
 | 
						||
puts(d);
 | 
						||
/* izmir çıkacak fakat 24 tane null karakter d'ye eklenecek */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyon yine strcpy de olduğu gibi, kopyalamanın yapıldığı hedef adrese geri döner.
 | 
						||
strncpy fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *mystrncpy(char *dest, char *source, unsigned n)
 | 
						||
{
 | 
						||
char *temp = dest;
 | 
						||
while (n-- > 0) {
 | 
						||
*dest = *source;
 | 
						||
if (*source != '\0')
 | 
						||
++source;
 | 
						||
++dest;
 | 
						||
}
 | 
						||
return temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
mystrncpy(d, s, 30);
 | 
						||
puts(d);
 | 
						||
/* izmir çıkacak fakat 24 tane null karakter d'ye eklenecek */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strncat Fonksiyonu
 | 
						||
Bu fonksiyon bir yazının sonuna başka bir yazının ilk n karakterini ekler. Prototipi şöyledir:
 | 
						||
char *strncat(char *dest, char *source, unsigned n);
 | 
						||
157
 | 
						||
 | 
						||
Fonksiyon her zaman null karakteri ekler. Ancak n > strlen(source) ise null karakteri ekleyip işlemini sonlandırır.
 | 
						||
(Yani strcpy'de olduğu gibi geri kalan miktar kadar null karakter eklemez.) Başka bir deyişle eğer n > strlen(source)
 | 
						||
ise fonksiyon tamamen strcat gibi çalışır. Fonksiyon yine birinci parametresiyle belirtilen adresin aynısına geri
 | 
						||
döner. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
strncat(d, s, 3);
 | 
						||
puts(d);
 | 
						||
/* ankaraizm */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
strncat(d, s, 100);
 | 
						||
puts(d);
 | 
						||
/* ankaraizmir çıkar fakat dizi taşması olmaz */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strncat fonksiyonu şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char mystrncat(char *dest, char *source, unsigned n)
 | 
						||
{
 | 
						||
char *temp = dest;
 | 
						||
while (*dest != '\0')
 | 
						||
++dest;
 | 
						||
while ((*dest = *source) != '\0' && n > 0) {
 | 
						||
++dest;
 | 
						||
++source;
 | 
						||
--n;
 | 
						||
}
 | 
						||
/* if (n == 0) */
 | 
						||
*dest = '\0';
 | 
						||
return temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "izmir";
 | 
						||
char d[32] = "ankara";
 | 
						||
 | 
						||
158
 | 
						||
 | 
						||
mystrncat(d, s, 50);
 | 
						||
puts(d);
 | 
						||
/* ankaraizmir çıkar fakat dizi taşması olmaz */
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
strncmp Fonksiyonu
 | 
						||
Bu fonksiyon iki yazının ilk n karakterinmi karşılaştırmakta kullanılmaktadır. Fonksiyonun prototipi şöyledir:
 | 
						||
int strncmp(char *s1, char *s2, unsigned n);
 | 
						||
Fonksiyonun üçüncü parametresi ilk kaç karakterin karşılaştırılacağını belirtir. n sayısı büyükse iki yazıdan
 | 
						||
hangisinde null karakter görülürse işlem biter. Fonksiyonun geri dönüş değeri yine strcmp fonksiyonunda olduğu
 | 
						||
gibidir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char passwd[] = "maviay";
 | 
						||
char s[64];
 | 
						||
printf("Enter password:");
 | 
						||
gets(s);
 | 
						||
if (!strncmp(passwd, s, strlen(s)))
 | 
						||
printf("Ok\n");
 | 
						||
else
 | 
						||
printf("invalid password\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Adres Operatörleriyle ++ ve -- Operatörlerinin Birlikte Kullanılması
 | 
						||
1) * Operatörüyle Kullanım
 | 
						||
a) ++*p Kullanımı: Burada *p bir artırılır. Yani bu ifade *p = *p + 1 ile eşdeğerdir.
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 10;
 | 
						||
int *pi = &a;
 | 
						||
++*pi;
 | 
						||
printf("%d\n", a);
 | 
						||
 | 
						||
/* 11 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
b) *++p Durumu: Burada önce p bir artırılır sonra artırılmış adresin içeriğine erişilir. Örneğin:
 | 
						||
159
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[2] = { 10, 20 };
 | 
						||
int *pi = a;
 | 
						||
*++pi = 30;
 | 
						||
printf("%d\n", a[1]);
 | 
						||
 | 
						||
/* 30 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
c) *p++ Durumu: Burada p bir artırılır ancak ++ sonek durumunda olduğu için artırılmış adresin içeriğine erişilir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
char *mystrcpy(char *dest, char *source)
 | 
						||
{
 | 
						||
char *temp = dest;
 | 
						||
while ((*dest++ = *source++) != '\0')
 | 
						||
;
 | 
						||
return temp;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char d[32];
 | 
						||
mystrcpy(d, s);
 | 
						||
puts(d);
 | 
						||
 | 
						||
/* ankara */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Görüldüğü gibi strcpy işlemi aşağıdaki gibi kompakt yazılabilmektedir:
 | 
						||
while ((*dest++ = *source++) != '\0')
 | 
						||
;
 | 
						||
 | 
						||
Burada her defasında *source değeri *dest değerine atanır ve source ile dest bir artılmış olur.
 | 
						||
2) [] Operatörü İle Kullanım
 | 
						||
a) ++p[n] Durumu: Burada p[n] bir artırılır. Yani bu ifade p[n] = p[n] + 1 ile eşdeğerdir.
 | 
						||
b) p[n]++ Durumu: Burada p[n] bir artırılır ancak sonraki operatöre p[n]'in artırılmamış değeri sokulur.
 | 
						||
c) p[++n] Durumu: Burada n bir artırılır ve artırılmış indeksteki elemaana erişilir.
 | 
						||
d) p[n++] Durumu: Burada n bir artırılır fakat p[n] sonraki işleme sokulur. Örneğin:
 | 
						||
3) & Operatörü İle Kullanım
 | 
						||
160
 | 
						||
 | 
						||
& adres operatörü ile ++ ve -- operatörleri birlikte kullanılamazlar. Çünkü & operatörünün ürettiği değer nesne
 | 
						||
değildir. Örneğin:
 | 
						||
int a;
 | 
						||
++&a;
 | 
						||
 | 
						||
/* geçerli değil */
 | 
						||
 | 
						||
++ ve -- operatörleri nesne üretmezler. Yani ++a işleminde a bir artırılır, fakat ürün olarak a elde edilmez. a
 | 
						||
artırılmış değeri elde edilir. Benzer biçimde sonrek durumunda aynı şey söz konusudur. Örneğin:
 | 
						||
a++ = b; /* geçerli değil */
 | 
						||
Dolayısıyla &++a ve &a++ ifadeleri de anlamsızdır. Ancak nesnelerin adresleri alınabilir.
 | 
						||
Farklı Türden Adresin Bir Göstericiye Atanması
 | 
						||
C'de bir göstericiye farklı türden bir adres atanamaz. Örneğin:
 | 
						||
char s[] = "ankara";
 | 
						||
int *pi;
 | 
						||
pi = s;
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
Anahtar Notlar: Yukarıdaki atama C'de geçersizdir. Fakat bilindiği gibi geçersiz bir progrtamı C derleyicileri bir mesaj veremek koşuluyla derleyebilir.
 | 
						||
Yani bir derleyicinin yukarıdaki kodu derlemesi onun insafına kalmıştır.
 | 
						||
 | 
						||
Fakat bir göstericiye farklı türden bir adres tür dönüştürme operatörüyle atanabilir. Örneğin:
 | 
						||
char s[] = "ankara";
 | 
						||
int *pi;
 | 
						||
pi = (int *)s;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "aaaa";
 | 
						||
int *pi;
 | 
						||
pi = (int *)s;
 | 
						||
printf("%d\n", *pi);
 | 
						||
 | 
						||
/* buradaki sayı kaçtır? */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir adresi başka türden bir göstericiye tür dönüştürme operatöryle atamak çoğuı zaman anlamsızdır. Örneğin
 | 
						||
yukarıdaki kodda artık *pi ifadesi dört tane 'a' karakterinin oluşturduğu int değer olarak yorumlanır. Böyle bir şeyi
 | 
						||
neden yapmak isteyebilriz? Bazı durumlarda böylesi işlemlere gereksinim duyulabilmektedir.
 | 
						||
Adres Olmayan Bir Bilginin Bir Göstericiye Atanması
 | 
						||
Bazen elimizde bir tamsayı değeri vardır. Bu değeri sayısal bileşen anlamında bir göstericiye yerleştirmek
 | 
						||
161
 | 
						||
 | 
						||
isteyebiliririz. Örneğin:
 | 
						||
int *pi;
 | 
						||
int a = 0x1FC0;
 | 
						||
pi = a;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Bu tür atamalar da tür dönüştürme operatörüyle yapılabilmektedir:
 | 
						||
int *pi;
 | 
						||
int a = 0x1FC0;
 | 
						||
pi = (int *)a;
 | 
						||
 | 
						||
/* geçerli! */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int *pi;
 | 
						||
pi = 0x1FC0;
 | 
						||
pi = (int *)0x1FC0;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Dizi İsimleriyle Göstericilerin Karşılaştırılması
 | 
						||
Dizi isimleriyle göstericiler bazen kullanım bakımından yeni öğreneleri tereddütte bırakabilmektedir. Aralarındaki
 | 
						||
benzerlikler ve farklılıklar şöyledir:
 | 
						||
Açıklamalarda aşağıdaki iki bildirimi göz önünde bulundurunuz:
 | 
						||
int a[10];
 | 
						||
int *pi;
 | 
						||
- Dizi isimleri de göstericiler de birer adres belirtir. Yani ikisini de kullandığımızda biz adres bilgisi kullanmış
 | 
						||
oluruz. Fakat göstericiler bir nesne belirtir. Yani onun içerisine bir adres bilgisi atayabiliriz. Ancak dizi isimleri o
 | 
						||
dizilerin başlangıç adreslerini belirtir fakat nesne belirtmez. Biz bir dizi ismine birşey atayamayız. Örneğin:
 | 
						||
char s[10];
 | 
						||
char *pc;
 | 
						||
s = pc;
 | 
						||
pc = s;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
- Dizi isimleri dizilerin başlangıç adresi belirtir. Yani onların belirttiği adreste tahsis edilmiş bir alan vardır.
 | 
						||
Halbuki göstericilerin içerisindeki adresler değiştirilebilir. Göstericiler bellekte başka yerleri gösterebilir hale
 | 
						||
getirilebilir.
 | 
						||
Gösterici Hataları
 | 
						||
Bir gösterici bellekte potansiyel olarak her yeri gösterebilir. Fakat bir göstericiyi kullanarak * ya da []
 | 
						||
operatörleriyle bellekte bizim için tahsis edilmemiş (yani bize ait olmayan) alanlara erişmek normal bir durum
 | 
						||
değildir. Bu durum C'de tanımsız davranışa (undefined behavior) yol açmaktadır. elimiz bir gösterici olsun biz
 | 
						||
parmağımızla başkasına ait bir araziyi gösterebiliriz. Bu yasak değildir. Fakat oraya erişemeyiz (yani giremeyiz).
 | 
						||
Girersek başımıza ne geleceği belli değildir. Tabi biz kendi arazimizi göstererek oraya girebiliriz. İşte göstericiler
 | 
						||
162
 | 
						||
 | 
						||
de tıpkı böyledir. Biz göstericilerle kendi tanımladığımız yani bizim için ayrılan nesnelere erişmeliyiz.
 | 
						||
Peki bir yerin bizim için ayrıldığını nasıl anlarız? İşte tanımlama yoluyla oluşturduğumuz nesneler bize tahsis
 | 
						||
edilmiştir. (Yani onların tapusu bizdedir.) Biz o alanlara erişebiliriz. Örneğin:
 | 
						||
int a;
 | 
						||
char s[100];
 | 
						||
Burada &a'dan itibaren 4 byte, s'ten itibaren 100 byte bize ayrılmıştır. Biz bu alanları istediğimiz gibi kullanabiliriz.
 | 
						||
Anahtar Notlar: Bir fonksiyona biz bir adres geçirelim. Fonksiyon onun tahsis edilmiş bir adres olup olmadığını anlayamaz. Bir adresin tahsis edilmiş
 | 
						||
olup olmadığını anlamanın C'de resmi bir yolu yoktur.
 | 
						||
 | 
						||
Bizim için tahsis edilmemiş alanlara erişme işlemine "gösterici hatası" denilmektedir. Gösterici hataları maalesef
 | 
						||
deneyimli programcılar tarafından bile yanlışlıkla yapılabilmektedir. Gösterici hatalarının tipik ortaya çıkış biçimi
 | 
						||
şunlardır:
 | 
						||
1) İlkdeğer Verilmemiş Göstericilerin Yol Açtığı Gösterici Hataları: Bir göstericinin içerisinde rastgele bir değer
 | 
						||
varsa onu * ya da [] parantez operatörleriyle kullanamayız. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int *pi;
 | 
						||
*pi = 100;
 | 
						||
 | 
						||
/* 100 rastgele bir yere atanıyor */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s;
 | 
						||
gets(s);
 | 
						||
 | 
						||
/* Gösterici hatası! gets klavyeden girilen karakterleri nereye yerleştiriyor? */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
2) Dizi Taşmalarından Doğan Gösterici Hataları: Bir dizi için tam o kadar yer ayrılmıştır. Dizinin gerisi de ötesi de
 | 
						||
bizim tahsis edilmemiştir. Oralara erişmek gösterici hatalarına yol açar. Örneğin:
 | 
						||
int a[10];
 | 
						||
int i;
 | 
						||
...
 | 
						||
for (i = 0; i <= 10; ++i)
 | 
						||
a[i] = 0;
 | 
						||
 | 
						||
/* dikkat a[10] bizim için tahsis edilmemiş */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
char s[] = "ankara";
 | 
						||
char d[] = "istanbul";
 | 
						||
strcat(d, s);
 | 
						||
 | 
						||
/* dikkat d dizisi taşıyor! */
 | 
						||
 | 
						||
163
 | 
						||
 | 
						||
Örneğin:
 | 
						||
char s[10];
 | 
						||
gets(s);
 | 
						||
 | 
						||
Burada kullanıcı en fazla 9 karakter girmelidir. Yoksa dizi taşar.
 | 
						||
3) Ömrü Biten Nesnelerin Yol Açtığı Gösterici Hataları: Bir fonksiyon yerel bir nesnenin ya da dizinin adresi ile
 | 
						||
geri dönmemelidir. Çünkü fonksiyon bitince o fonksiyonun yerel değişkenleri boşaltılır. Dolayısıyla fonksiyonun
 | 
						||
bize verdiği adres artık tahsis edilmiş bir alanın adresi olmaz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
char *getname(void)
 | 
						||
{
 | 
						||
char name[128];
 | 
						||
printf("Adi Soyadi:");
 | 
						||
gets(name);
 | 
						||
return name;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s;
 | 
						||
s = getname();
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada getname fonksiyonu geri döndüğünde artık name isimli dizi yok edilecektir. Dolayısıyla main'de s
 | 
						||
göstericisine atanan adres tahsis edilmemiş bir alanın adresi olacaktır. Yukarıdaki fonksiyonda name global
 | 
						||
yapılırsa sorun kalmaz. Ya da fonksiyon şöyle düzenlenebilir:
 | 
						||
#include <stdio.h>
 | 
						||
void getname(char *s)
 | 
						||
{
 | 
						||
printf("Adi Soyadi:");
 | 
						||
gets(s);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[128];
 | 
						||
getname(s);
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
void Adresler ve void Göstericiler
 | 
						||
C'de void türden bir değişken tanımlanamaz. Ancak gösterici tanımlanabilir. Örneğin:
 | 
						||
164
 | 
						||
 | 
						||
void a;
 | 
						||
void *pv;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
void göstericiler türsüz göstericilerdir. void göstericiler * ya da [] operatörleriyle kullanılamazlar. Çünkü bu
 | 
						||
operatörler eriştiği yerdeki nesnenin türünü bilmek zorundadır. void göstericiler ya da void adresler artırılamazlar
 | 
						||
ve eksiltilemezler. Çünkü derleyici bu işlemlerde göstericinin sayısal bileşenini kaç artırıp kaç eksilteceğini
 | 
						||
bilememektedir. Peki void göstericiler ne işe yarar?
 | 
						||
void bir göstericiye herhangi türden bir adres doğrudan atanabilir. Tür dönüştürme operatörüne hiç gerek yoktur.
 | 
						||
Örneğin:
 | 
						||
void *pv;
 | 
						||
char s[10];
 | 
						||
int a[10];
 | 
						||
pv = s;
 | 
						||
pv = a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Benzer biçimde void bir adres de herhangi bir türden göstericiye atanabilir. Örneğin:
 | 
						||
void *pv;
 | 
						||
int *pi;
 | 
						||
char s[10];
 | 
						||
pv = s;
 | 
						||
...
 | 
						||
pi = pv;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
void göstericilere neden gereksinim duyulduğunu açıklayabilmek için memcpy fonksiyonu iyi bir örnek olabilir.
 | 
						||
memcpy fonksiyonu bir adresten bir adrese koşulsuz n byte kopyalamaktadır. Bu fonksiyon strcpy fonksiyonunu
 | 
						||
andırmakla birlikte ondan farklıdır. memcpy null karaktere bakmaz. Koşulsuz n byte kopyalar. Örneğin herhangi
 | 
						||
bir türden diziye aynı türden başka bir diziye memcpy ile kopyalayabiliriz. C'de başı memxxx biçiminde başlayan
 | 
						||
fonksiyonların prototipleri de <string.h> içerisindedir.
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
int b[10];
 | 
						||
int i;
 | 
						||
memcpy(b, a, 10 * sizeof(int));
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", b[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Biz memcpy fonksiyonuyla herhangi türden iki diziyi kopyalayabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
double a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
 | 
						||
165
 | 
						||
 | 
						||
double b[10];
 | 
						||
int i;
 | 
						||
memcpy(b, a, 10 * sizeof(double));
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%f ", b[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İşte memcpy gibi bir fonksiyonun bizden her türlü adresi kabul edebilmesi için onun parametrelerinin void
 | 
						||
gösterici olması gerekir. Gerçekten de memcpy fonksiyonunun prototipi şöyledir:
 | 
						||
void *memcpy(void *dest, void *source, unsigned n);
 | 
						||
Fonksiyon ikinci parametresiyle belirtilen adresten başlayarak birinci parametresiyle belirtilen adrese koşulsuz n
 | 
						||
byte kopyalar. Birinci parametresiyle belirtilen adresin aynısına geri döner.
 | 
						||
Peki biz memcpy gibi bir fonksiyonu nasıl yazabiliriz? void göstericiler artırılıp azaltılamadığına göre onların türü
 | 
						||
belirli bir göstericiye atanması gerekir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
void *mymemcpy(void *dest, void *source, unsigned n)
 | 
						||
{
 | 
						||
char *pcdest = dest;
 | 
						||
char *pcsource = source;
 | 
						||
while (n-- > 0)
 | 
						||
*pcdest++ = *pcsource++;
 | 
						||
return dest;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
double a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
double b[10];
 | 
						||
int i;
 | 
						||
mymemcpy(b, a, 10 * sizeof(double));
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%f ", b[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
memcpy fonksiyonuyla yazı kopyalaması da yapabiliriz. Örneğin:
 | 
						||
strcpy(d, s);
 | 
						||
ile
 | 
						||
memcpy(d, s, strlen(s) + 1);
 | 
						||
166
 | 
						||
 | 
						||
işlevsel olarak eşdeğerdir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[30] = "ankara";
 | 
						||
char d[30];
 | 
						||
strcpy(d, s);
 | 
						||
memcpy(d, s, strlen(s) + 1);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
memset isimli fonksiyon bir adresten başlayarak koşulsuz n byte'a belli bir değeri atar. Yani n tane byte'ı belli bir
 | 
						||
değerle doldurur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[10];
 | 
						||
int i;
 | 
						||
memset(a, 0, 10 * sizeof(int));
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[200];
 | 
						||
memset(s, 'a', 199);
 | 
						||
s[199] = '\0';
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
memset fonksiyonunun prototipi şöyledir:
 | 
						||
void *memset(void *dest, int val, unsigned n);
 | 
						||
Fonksiyonun birinci parametresi doldurulacak yerin adresini, ikinci parametresi doldurulacak değeri ve üçüncü
 | 
						||
parametresi kaç adet değerin doldurulacağını belirtir. memset fonksiyonunu şöyle yazabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
void *mymemset(void *dest, int val, unsigned n)
 | 
						||
 | 
						||
167
 | 
						||
 | 
						||
{
 | 
						||
unsigned char *pcdest = dest;
 | 
						||
while (n-- > 0)
 | 
						||
*pcdest++ = ch;
 | 
						||
return dest;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[200];
 | 
						||
mymemset(s, 'a', 199);
 | 
						||
s[199] = '\0';
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
memset fonksiyonunun strset isimli bir kardeşi vardır. strset char türden bir diziyi null karakter görene kadar belli
 | 
						||
bir karakterle doldurur:
 | 
						||
char *strset(char *dest, char ch);
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
strset(s, 'x');
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Anahtar Notlar: C'de strxxx biçiminde str ile başlayan fonksiyonlar char * türünden göstericileri parametre olarak alır ve null karakter görene kadar
 | 
						||
işlem yapar. Fakat memxxx biçiminde mem ile başlayan fonksiyonlar void * türünden parametre alır ve koşulsuz n byte için işlem yapar.
 | 
						||
strcpy --- memcpy
 | 
						||
strset --- memset
 | 
						||
strchr --- memchr
 | 
						||
 | 
						||
strset fonksiyonu yerine memset kullanılabilir. Yani:
 | 
						||
strset(s, ch);
 | 
						||
ile,
 | 
						||
memset(s, ch, strlen(s));
 | 
						||
işlevsel olarak eşdeğerdir.
 | 
						||
Yukarıda da belirtildiği gibi C'de void bir göstericiye her türden adres doğrudan atanabilir. Benzer biçimde void bir
 | 
						||
adres de her türlü göstericiye atanabilir. Ancak void adresin herhangi bir göstericiye atanması bazılarınca
 | 
						||
168
 | 
						||
 | 
						||
eleştirilmektedir. Çünkü bu sayede aslında yasak olan birşeyi yapabilir duruma gelmekteyiz (yani hülle yapılabilir):
 | 
						||
char *pc;
 | 
						||
int *pi;
 | 
						||
pi = pc;
 | 
						||
 | 
						||
/* yasak */
 | 
						||
 | 
						||
Fakat:
 | 
						||
char *pc;
 | 
						||
int *pi;
 | 
						||
void *pv;
 | 
						||
pv = pc;
 | 
						||
pi = pv;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
İşte C++'ta eleştirilen bu durum düzeltilmiştir. Şöyle ki: C++'ta yine void göstericiye her türden adres doğrudan
 | 
						||
atanabilir. Fakat void bir adres tür dönüştürmesi yoluyla başka türden bir göstericiye atanabilir. Böylece yukarıdaki
 | 
						||
kod C'de geçerli olduğu halde C++'ta geçerli değildir. Bazı C programcıları böylesi kodların her iki dilde de geçerli
 | 
						||
olmasını sağlamak için void adresleri atarken tür dönüştürmesi yaparlar. Örneğin:
 | 
						||
pi = (int *)pv;
 | 
						||
 | 
						||
/* Hem C'de hem de C++'ta geçerli */
 | 
						||
 | 
						||
Özetle C'de void göstericiler bir adresin yalnızca sayısal bileşenini tutmak için ve tür dönüştürmesine programcıyı
 | 
						||
zorlamamak için kullanılmaktadır. Eğer memcpy fonksiyonunun parametreleri char * türünden olsaydı. O zaman
 | 
						||
onu biz şöyle kullanmak zorunda kalırdık:
 | 
						||
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
int b[10];
 | 
						||
int i;
 | 
						||
memcpy((char *)b, (char *)a, 10 * sizeof(int));
 | 
						||
 | 
						||
void göstericiler C'ye 80'li yılların ikinci yarısında katılmıştır.
 | 
						||
Anahtar Notlar: Kursun bu noktasında koşul operatörü anlatılmıştır. Fakat notlarda "Diziler" konusundan sonraya eklenmiştir.
 | 
						||
 | 
						||
Göstericilerin Uzunlukları
 | 
						||
Bir gösterici kaç byte uzunluktadır? Göstericilerin uzunlukları onların türleriyle ilgili değildir. Çünkü göstericilerin
 | 
						||
türleri onların gösterdikleri yerdeki nesnenin uzunluğuyla ilişkilidir. Bir göstericinin uzunluğu o sistemin adres
 | 
						||
alanına bağlıdır. Örneğin teorik olarak 32 bit işlemcilere 4GB RAM, 64 bit işlemcilere 16EB RAM takılabilir. Bu
 | 
						||
durumda 32 bit sistemlerdeki göstericiler 4 byte, 64 bit sistemlerdeki göstericiler 8 byte yer kaplarlar. Biz
 | 
						||
kursumuzda şekilleri genel olarak 32 bit mikroişlemcilere göre çiziyoruz. Yani örneğin sizeof(char *) ile,
 | 
						||
sizeof(double *) aynı değerlerdedir.
 | 
						||
String İfadeleri
 | 
						||
C'de derleyici ne zaman iki tırnak içerisinde bir yazı görse önce o yazıyı char türden bir dizinin içerisine yerleştirir,
 | 
						||
sonuna null karakteri ekler ve iki tırnak yerine o dizinin başlangıç adresini insert eder. Yani C'de iki tırnak ifadeleri
 | 
						||
char türnden adres belirtmektedir. Böylece C'de iki tırnak ifdadeleri doğrudan char türdnen bir göstericiye
 | 
						||
atanmalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
169
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *str;
 | 
						||
str = "ankara";
 | 
						||
printf("%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Eşdeğeri aşağıdaki gibi düşünülebilir:
 | 
						||
#include <stdio.h>
 | 
						||
static char compiler_generated_name[] = "ankara";
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *str;
 | 
						||
str = compiler_generated_name;
 | 
						||
printf("%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu durumda örneğin biz char * parametreli bir fonksiyonu string ifadesiyle çağırabiliriz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned n;
 | 
						||
n = strlen("ankara");
 | 
						||
printf("%u\n", n);
 | 
						||
puts("ankara");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin char türden bir diziye sonradan (ilkdeğer vererek değil) bir yazı yerleştirecek olsak bunu karakter karakter
 | 
						||
yerleştirmek pek uygun olmaz. Bunun en pratik ve doğru yolu strcpy fonksiyonunu kullanmaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[32];
 | 
						||
strcpy(s, "ankara");
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
char türden bir dizi ismine iki tırnak ifadesini atayamayız. Çünkü dizi isimleri nesne belirtmez. Göstericler nesne
 | 
						||
belirtir. Örneğin:
 | 
						||
char s[32];
 | 
						||
170
 | 
						||
 | 
						||
char *ps;
 | 
						||
s = "ankara";
 | 
						||
ps = "ankara";
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
C'de ist,sna olarak char türden bir diziye iki tırnak ifadesi ile ilkdeğer verilirken kullanılan iki tırnak bir adres
 | 
						||
belirtmemektedir. Bu iki tırnak içindeki karakterleri tek tek diziye yerleştir ve sonuna null karakter ekle anlamına
 | 
						||
gelir. Yani:
 | 
						||
char s[32] = "ankara";
 | 
						||
bildiriminde bu iki tırnak ifadesi için derleyici bir adres insert etmeyecektir. Bu istisna durum aşağıdakiyle
 | 
						||
eşdeğerdir:
 | 
						||
char s[32] = {'a', 'n', 'k', 'a', 'r', 'a', '\0'};
 | 
						||
Bunun dışındaki iki tırnak ifadelerinin hepsi birer string belirtir ve bunlar için derleyici birer adres insert eder.
 | 
						||
Örneğin:
 | 
						||
char *ps = "ankara";
 | 
						||
Burada "ankara" bir adres belirtmektedir.
 | 
						||
C'de iki tırnak ifadeleri ile oluşturulan string'ler güncellenmeye çalışılmamalıdır. String karakterlerinin
 | 
						||
değiştirilmesi "tanımsız davranışa (undefined behavior)" yol açar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *str = "ankara";
 | 
						||
char s[] = "ankara";
 | 
						||
*str = 'x';
 | 
						||
*s = 'x';
 | 
						||
 | 
						||
/* undefined behavior */
 | 
						||
/* normal */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
char *str = "ankara";
 | 
						||
*strchr(s, 'k') = 'x';
 | 
						||
puts(s);
 | 
						||
*strchr(str, 'k') = 'x';
 | 
						||
puts(s);
 | 
						||
 | 
						||
/* undefined behavior */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
171
 | 
						||
 | 
						||
String'ler statik ömürlü nesnelerdir. Yani hangi fonksiyonda oluşturulmuş olursa olsunlar programın başından
 | 
						||
sonuna kadar bellekte kalırlar. Dolayısıyla bir fonksiyonun bir string'in başlangıç adresine geri dönmesi gösterici
 | 
						||
hatası değildir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *str;
 | 
						||
str = getstr();
 | 
						||
printf("%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aşağıdaki örnekte iki tırnak istisna olan duruma ilişkindir. Dolayısıyla burada dizinin adresiyle geri dönülmektedir.
 | 
						||
O da fonksiyon bittiğinde yok edilir.
 | 
						||
#include <stdio.h>
 | 
						||
char *getstr(void)
 | 
						||
{
 | 
						||
char s[] = "ankara";
 | 
						||
return s;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *str;
 | 
						||
str = getstr();
 | 
						||
/* dikkat! gösterici hatası */
 | 
						||
printf("%s\n", str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Özdeş string'ler için aynı yerin mi kullanılacağı ya da farklı yerler mi ayrılacağı C'de belirsiz (unspecied) davranış
 | 
						||
olarak ele alınmaktadır. Yani derleyici bu ikisinden birini yapabilir. Bu durum derleyicileri yazanların isteğine
 | 
						||
bırakılmıştır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s = "ankara";
 | 
						||
char *k = "ankara";
 | 
						||
printf(s == k ? "Evet\n" : "Hayir\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada derleyici bir tane "ankara" yazısı yerleştirip s ve k'ya onu atayabilir. Ya da iki farklı "ankara" yazısı
 | 
						||
yerleştirip s ve k'ya farklı adresler atayabilir. Bazı derleyiciler de derleyici ayarlarından programcı bunu
 | 
						||
değiştirebilmektedir.
 | 
						||
Boş bir string oluşturulabilir. Bu durumda derleyici diziye yalnızca null karakteri yerleştirir. Örneğin:
 | 
						||
172
 | 
						||
 | 
						||
char *s = "";
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
String'ler tek bir satır üzerine yazılmak zorundadır. Örneğin:
 | 
						||
s = "this is a
 | 
						||
test";
 | 
						||
Böyle bir yazım geçersizdir. Peki ya string bir satıra sığmıyorsa ne olacaktır? C'de yan yana iki string atomu (yani
 | 
						||
aralarında boşluk karakterlerinden başka bir karakter olmayan) derleme aşamasında derleyici tarafından
 | 
						||
birleştirilmektedir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s;
 | 
						||
s = "this is a"
 | 
						||
" test";
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yukarıdaki yazım tamamen geçerlidir.
 | 
						||
Anahtar Notlar: C'de tek bir tes bölü ve hemen arkasından ENTER ('\n') karakteri "aşağıdaki satırı bu starırla birleştir" anlamına gelir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int count;
 | 
						||
cou\
 | 
						||
nt = 10;
 | 
						||
printf("%d\n", count);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Yukarıdaki örnekte cou ve aşağısındaki satır birleştirilmiş ve count = 10 haline gelmiştir. Bu işlemin mümkün olması için '\'den sonra hemen '\n' karakterinin gelmesi gerekir.
 | 
						||
Bu özelliği biz string'ler için de kullanabiliriz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s;
 | 
						||
s = "ankara,\
 | 
						||
izmir";
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
String ifadeleri içerisinde '\' karakterleri '\' anlamına gelmez. Bunlar yanındak, karakterlerle başka bir karakter
 | 
						||
anlamına gelir. Dolayısıyla '\' karakterinin kendisi '\\' biçiminde ifade edilmelidir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
173
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *path = "c:\\windows\\temp";
 | 
						||
printf("%s\n", path);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Adreslerin Karşılaştırılması
 | 
						||
C'de adresler altı karşılaştırma operatörüyle de karşılaştırılabilir. Örnğin:
 | 
						||
int *p, *q;
 | 
						||
if (p > q) {
 | 
						||
...
 | 
						||
}
 | 
						||
Ancak karşılaştırılan adreslerin aynı türden olması ya da birinin void türden olması gerekir. Fakat C'de rastgeele iki
 | 
						||
adresin karşılaştırılması "tanımsız davranış" olarak değerlendirilir. İki adresin karşılaştırılabilmesi için bunların
 | 
						||
aynı dizinin ya da yapının elemanları olması gerekir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void putsrev(char *s)
 | 
						||
{
 | 
						||
char *end = s;
 | 
						||
while (*end != '\0')
 | 
						||
++end;
 | 
						||
--end;
 | 
						||
while (end >= s) {
 | 
						||
putchar(*end);
 | 
						||
--end;
 | 
						||
}
 | 
						||
putchar('\n');
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *s = "ankara";
 | 
						||
putsrev(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Gösterici Dizileri (Array of Pointers)
 | 
						||
Her elemanı bir gösterici olan dizilere gösterici dizileri denir. Gösterici dizileri T bir tür belirtmek üzere,
 | 
						||
T * <isim>[uzunluk];
 | 
						||
biçiminde bildirilir. Örneğin:
 | 
						||
int *a[10];
 | 
						||
Bu dizini her elemanı int türden bir göstericidir. Yani dizinin her elemanı int * türündendir. Örneğin:
 | 
						||
174
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int x = 10, y = 20, z = 30;
 | 
						||
int *a[3];
 | 
						||
int i;
 | 
						||
a[0] = &x;
 | 
						||
a[1] = &y;
 | 
						||
a[2] = &z;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
printf("%d\n", *a[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C'de en fazla karşımıza çıkan char türden göstericileridir. Yazıların başlangıç adresleri char türden bir gösterici
 | 
						||
dizisine yerleştirilirse dizi yazıları tutar hale gelir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names[5];
 | 
						||
int i;
 | 
						||
names[0] = "ali";
 | 
						||
names[1] = "veli";
 | 
						||
names[2] = "selami";
 | 
						||
names[3] = "ayse";
 | 
						||
names[4] = "fatma";
 | 
						||
for (i = 0; i < 5; ++i)
 | 
						||
printf("%s\n", names[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
175
 | 
						||
 | 
						||
Dizilere küme parantezleri içeriisnde ilkdeğer verebildiğimize göre aşağıdaki ilkdeğerleme de geçelidir:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names[5] = { "ali", "veli", "selami", "ayse", "fatma" };
 | 
						||
int i;
 | 
						||
for (i = 0; i < 5; ++i)
 | 
						||
printf("%s\n", names[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu dizinin sonuna NULL adres (Null karakter değil) yerleştirirsek, NULL adres görene kadar işlem yapabiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names[] = { "ali", "veli", "selami", "siracettin", "ayse", "fatma", NULL };
 | 
						||
int i;
 | 
						||
for (i = 0; names[i] != NULL; ++i)
 | 
						||
printf("%s\n", names[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: char türdne bir gösterici dizisinin içerisine birtakım isimlerin adresleri yerleştirimiştir. Gösterici
 | 
						||
dizisinin sonunda NULL adres vardır. char türden geniş bir dizi açınız, bu isimleri aralarına ',' karakteri koyarak tek
 | 
						||
bir dizide (tabi sonu null karakter ile bitmeli) birleştiriniz. Sonra da bu diziyi puts ile yazdırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names[] = { "ali", "veli", "selami", "siracettin", "ayse", "fatma", NULL };
 | 
						||
char str[100];
 | 
						||
int i;
 | 
						||
str[0] = '\0';
 | 
						||
for (i = 0; names[i] != NULL; ++i) {
 | 
						||
if (i != 0)
 | 
						||
strcat(str, ", ");
 | 
						||
 | 
						||
176
 | 
						||
 | 
						||
strcat(str, names[i]);
 | 
						||
}
 | 
						||
puts(str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sprintf Fonksiyonu
 | 
						||
sprintf fonksiyonu kullanım bakımından tamamen printf gibidir. Ancak ekrana yazmak yerine yazılacakları char
 | 
						||
türden bir dizinin içerisine yazar. Fonksiyonun birinci parametresi char türden dizinin adresidir. Fonksiyonun
 | 
						||
prototipi şöyledir:
 | 
						||
int sprintf(char *s, char *format, ...);
 | 
						||
Fonksiyonun birinci parametresi dizinin başlangıç adresini alır. Diğer parametreleri printf ile aynıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
int a = 10;
 | 
						||
double b = 12.345;
 | 
						||
sprintf(s, "a = %d, b = %f\n", a, b);
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Üç basamklı int türden bir sayıyı yazı biçiminde adresi aldığı char türdne bir dizinin içerisine
 | 
						||
yerleştiren num_to_text isimli fonksiyonu yazınız.
 | 
						||
char *num_to_text(int val, char *s);
 | 
						||
Fonksiyon birinci parametresiyle belirtilen değeri yazıya dönüştürerek ikinci parametresi ile aldığı diziye yerleştirir.
 | 
						||
Fonksiyon ikinci parametresiyle aldığı adresin aynısına geri döner.
 | 
						||
char s[100];
 | 
						||
num_to_text(983, s);
 | 
						||
puts(s);
 | 
						||
/* dokuz yüz seksen üç */
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *num_to_text(unsigned long val, char *s);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
unsigned long val;
 | 
						||
for (;;) {
 | 
						||
printf("Sayi:");
 | 
						||
scanf("%lu", &val);
 | 
						||
 | 
						||
177
 | 
						||
 | 
						||
if (val == 123)
 | 
						||
break;
 | 
						||
num_to_text(val, s);
 | 
						||
puts(s);
 | 
						||
/* dokuz yüz seksen üç */
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
char *num_to_text(unsigned long val, char *s)
 | 
						||
{
 | 
						||
char *ones[] = { "bir", "iki", "uc", "dort", "bes", "alti", "yedi", "sekiz", "dokuz" };
 | 
						||
char *tens[] = { "on", "yirmi", "otuz", "kirk", "elli", "altmis", "yetmis", "seksen", "doksan" };
 | 
						||
int one, ten, hundred;
 | 
						||
*s = '\0';
 | 
						||
hundred = val / 100;
 | 
						||
ten = val % 100 / 10;
 | 
						||
one = val % 10;
 | 
						||
if (!val) {
 | 
						||
strcat(s, "sifir");
 | 
						||
return;
 | 
						||
}
 | 
						||
if (hundred) {
 | 
						||
if (hundred != 1) {
 | 
						||
strcat(s, ones[hundred - 1]);
 | 
						||
strcat(s, " ");
 | 
						||
}
 | 
						||
strcat(s, "yuz");
 | 
						||
}
 | 
						||
if (ten) {
 | 
						||
if (hundred)
 | 
						||
strcat(s, " ");
 | 
						||
strcat(s, tens[ten - 1]);
 | 
						||
}
 | 
						||
if (one) {
 | 
						||
if (ten)
 | 
						||
strcat(s, " ");
 | 
						||
strcat(s, ones[one - 1]);
 | 
						||
}
 | 
						||
}
 | 
						||
 | 
						||
Soruyu genelleştirerek de çözebiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
char *num_to_text(unsigned long val, char *s);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
unsigned long val;
 | 
						||
for (;;) {
 | 
						||
printf("Sayi:");
 | 
						||
scanf("%lu", &val);
 | 
						||
if (val == 123)
 | 
						||
break;
 | 
						||
num_to_text(val, s);
 | 
						||
puts(s);
 | 
						||
/* dokuz yüz seksen üç */
 | 
						||
}
 | 
						||
 | 
						||
178
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
char *num_to_text(unsigned long val, char *s)
 | 
						||
{
 | 
						||
char *ones[] = { "bir", "iki", "uc", "dort", "bes", "alti", "yedi", "sekiz", "dokuz" };
 | 
						||
char *tens[] = { "on", "yirmi", "otuz", "kirk", "elli", "altmis", "yetmis", "seksen", "doksan" };
 | 
						||
char *others[] = { "bin", "milyon", "milyar", "trilyon", "katrilyon", "katrilyar" };
 | 
						||
int one, ten, hundred;
 | 
						||
int digits3[5], temp;
 | 
						||
int i;
 | 
						||
*s = '\0';
 | 
						||
temp = val;
 | 
						||
for (i = 0; val; ++i) {
 | 
						||
digits3[i] = val % 1000;
 | 
						||
val /= 1000;
 | 
						||
}
 | 
						||
val = temp;
 | 
						||
if (!val) {
 | 
						||
strcat(s, "sifir");
 | 
						||
return;
 | 
						||
}
 | 
						||
while (--i >= 0) {
 | 
						||
hundred = digits3[i] / 100;
 | 
						||
ten = digits3[i] % 100 / 10;
 | 
						||
one = digits3[i] % 10;
 | 
						||
if (hundred) {
 | 
						||
if (hundred != 1) {
 | 
						||
strcat(s, ones[hundred - 1]);
 | 
						||
strcat(s, " ");
 | 
						||
}
 | 
						||
strcat(s, "yuz");
 | 
						||
}
 | 
						||
if (ten) {
 | 
						||
if (hundred)
 | 
						||
strcat(s, " ");
 | 
						||
strcat(s, tens[ten - 1]);
 | 
						||
}
 | 
						||
if (one) {
 | 
						||
if (ten)
 | 
						||
strcat(s, " ");
 | 
						||
if (i != 1 || digits3[i] != 1)
 | 
						||
strcat(s, ones[one - 1]);
 | 
						||
}
 | 
						||
if (i != 0) {
 | 
						||
strcat(s, " ");
 | 
						||
strcat(s, others[i - 1]);
 | 
						||
strcat(s, " ");
 | 
						||
}
 | 
						||
}
 | 
						||
}
 | 
						||
 | 
						||
exit Fonksiyonu
 | 
						||
exit prototipi <stdlib.h> dosyasında olan standartd bir C fonksiyonudur. exit çağrıldığında program sonlanır. Yani
 | 
						||
sanki main bitmiş gibi bit etki oluşur. Bu durumda biz bir programı sonlandırmak için illa da akışın main'e geri
 | 
						||
dönmesini beklemek zorunda değiliz. Herhangi bir zaman herhangi bir fonksiyonda exit fonksiyonunu çağırırsak
 | 
						||
program sonlanır. Fonksiyonun prototipi şöyledir:
 | 
						||
179
 | 
						||
 | 
						||
#include <stdlib.h>
 | 
						||
void exit(int code);
 | 
						||
Fonksiyon parametre olarak programın exit kodunu alır. İşletim sistemlerinde her sonlanan programın bir exit kodu
 | 
						||
vardır. Bu exit kodunun kaç olduğunun bir önemi yoktur. Bu kod işletim sistemine iletilir. İşletim sistemi de birisi
 | 
						||
(genellikle üst proses) isterse bunu ona verir. Ancak geleneksel olarak başarılı sonlanmalar için sıfır değeri,
 | 
						||
başarısz sonlanmalar için sıfır dışı değerler kullanılmaktadır. Hatta okunabilirliği artırmak için <stdlib.h>
 | 
						||
içerisinde iki sembolik sabit bildirilmiştir:
 | 
						||
#define EXIT_SUCCESS
 | 
						||
#define EXIT_FAILURE
 | 
						||
 | 
						||
0
 | 
						||
1
 | 
						||
 | 
						||
Her ne kadar C standartlarında EXIT_SUCCESS ve EXIT_FAILURE sembolik sabitlerinin değerleri 0 ve 1 olarak
 | 
						||
kesin bir biçimde belirtilmemişse de derleyicilerin hemen hepsi EXIT_SUCCESS için 0 değerini,
 | 
						||
EXIT_FAILURE için 1 değerini kullanmaktadır.
 | 
						||
main fonksiyonu sonlandığında program sonlanmaktadır. O halde main fonksiyonun da return işlemi programın
 | 
						||
sonlanmasına yol açar. İşte C standartlarına göre main bittiğinde main fonksiyonunun geri dönüş değeri alınarak
 | 
						||
exit çağrılır. Yani program aslında her zaman exit ile sonlandırılır. O halde bir C programının şöyle çalıştırıldığı
 | 
						||
düşünülmelidir:
 | 
						||
exit(main());
 | 
						||
Ayrıca C'de main fonksiyonuna özgü olmak üzere, main fonksiyonunda hiç return kullanılmazsa sanki return 0
 | 
						||
kullanılmış gibi işlem görür.
 | 
						||
Prosesin Adress Alanı Ve Bölümleri
 | 
						||
İşletim sistemi çalıştıracağı progamı ikincil bellekten alarak fiziksel belleğe yükler. Bu program için bellekte belli
 | 
						||
bir yer ayırmaktadır. Buna prosesin adres alanı (address space) denir. Proseisn adres alanı içerisinde şu bölümler
 | 
						||
vardır:
 | 
						||
Stack Bölümü: Yerel ve parametre değişlkenlerinin yaratıldığı ve yok edildiği alana stack denir. Stack'te nesneler
 | 
						||
çok hızlı yaratılır ve yok edilir. Bloktan çıkıldığında o blok içerisinde tanımlanmış olan nesnelerin hepsi stack'ten
 | 
						||
atılır.
 | 
						||
Data Bölümü: İlkdeğer verilmiş global nesnelerin yaratıldığı bölümdür. Data bölümü programın başından sonuna
 | 
						||
kadar kalır. Burada yaratım ve boşaltım yapılmaz. Program çalışırken artık bu bölümde tahsisat yapılamaz.
 | 
						||
BSS Bölümü: İlkdeğer verilmemiş global nesnelerin yaratıldığı bölümdür. BSS bölümü programın başından
 | 
						||
sonuna kadar kalır. Burada yaratım ve boşaltım yapılmaz. Program çalışırken artık bu bölümde tahsisat yapılamaz.
 | 
						||
Heap Bölümü: Dinamik bellek fonksiyonlarıyla tahsisat yapılan bölümdür. Heap'te alan tahsisatı programın
 | 
						||
çalışma zamanı sırasında yapılır ve yok edilir. Tahsisat yapılırken ve serbest bırakılırken nerelerin boş ve doldu
 | 
						||
olduğu sorgulandığı için stack'teki tahsisata göre çok yavaştır. Ancak bu bölümde yapılan tahisatlar bloktan
 | 
						||
çıkıldığında otomatik geri bırakılmaz. Yani yaşamaya devam eder.
 | 
						||
Sistemlerde en küçük alan stack olma eğilimindedir. Bunu daha sonra data ve bss alanları sonra da heap alanı izler.
 | 
						||
En büyük alan heap olma eğilimindedir.
 | 
						||
Dinamik Bellek Yönetimi (Dynamic Memory Management)
 | 
						||
180
 | 
						||
 | 
						||
Bilindiği gibi C'de dizi uzunlukları sabit ifadesi biçimde verilmek zorundadır. Fakat bazen bir dizinin hangi
 | 
						||
uzunlukta açılacağı işin başında belli değildir. Ancak program çalışırken birtakım olaylar sonucunda dizinin
 | 
						||
uzunluğu bilinmektedir. İşte bu tür durumlarda programın çalışma zamanı sırasında dizi açmaya gereksinim
 | 
						||
duyulur. Programın çalışma zamanı sırasında bellek tahsis etme işlemine "dinamik bellek yönetimi" denilmektedir.
 | 
						||
C'de dinamik bellek yönetimi ismine "dinamik bellek fonksiyonları" denilen bir grup standart C fonksiyonuyla
 | 
						||
gerçekleştirilir. Bunlar malloc, calloc, realloc ve free fonksiyonlarıdır.
 | 
						||
Dinamik bellek fonksyionları bellekte ismine "heap" denilen bir alanda tahsisatlarını yaparlar. Heap alanının
 | 
						||
büyüklüğü sistemden sisteme değişebilmektedir. C standartlarında söylenmese de modern sistemlerde her
 | 
						||
programın heap'i ayrıdır. Yani bir orgramdaki dinamik tahsisatların başka bir programa etkisi yoktur. Program
 | 
						||
bitinci modern sistemlerde o programın kullandığı heap alanı da sisteme iade edilir.
 | 
						||
malloc Fonksiyonu
 | 
						||
malloc programın çalışma zamanı sırasında parametresiyle belirtilen sayıda byte kadar ardışıl alanı tahsis eder.
 | 
						||
Tahsis ettiği alanın başlangıç adresiyle geri döner. Prototipi şöyledir:
 | 
						||
#include <stdlib.h>
 | 
						||
void *malloc(unsigned n);
 | 
						||
malloc parametresiyle belirtilen miktarda byte kadar ardışıl alanı heap'te tahsis eder. Tahsis ettiği alanın başlangıç
 | 
						||
adresine geri döner. Eğer heap doluysa malloc tahsisatı yapamaz. Bu durumda NULL adrese geri döner. malloc ile
 | 
						||
tahsis edilen blokta çöp değerler vardır. Her ne kadar Windows gibi Linux gibi işletim sistemlerinde prosesin heap
 | 
						||
alanı çok büyükse de yine de her bellek tahsisatının başarısının kontrol edilmesi iyi bir tekniktir.
 | 
						||
malloc fonksiyonunun bize verdiği adres herhangi türden bir dizi olarak kulanılabilir. Örneğin biz malloc ile biz 32
 | 
						||
byte bir alan tahsis etmiş olalım. Orayı 4 elemanlı double bir dizi gibi de, 8 elemanlı int bir dizi gibi de, 32
 | 
						||
elemanlı char türden bir dizi gibi de kullanabiliriz. Ne de olsa tüm diziler bellekte ardışıl tutulmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int n, i;
 | 
						||
int *pi;
 | 
						||
printf("Kac elemanli bir int dizi olusturmak istiyorsunuz?");
 | 
						||
scanf("%d", &n);
 | 
						||
pi = (int *)malloc(n * sizeof(int));
 | 
						||
if (pi == NULL) {
 | 
						||
printf("cannot allocate memory!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < n; ++i) {
 | 
						||
printf("%d. elemani giriniz:", i + 1);
 | 
						||
scanf("%d", &pi[i]);
 | 
						||
}
 | 
						||
for (i = 0; i < n; ++i)
 | 
						||
printf("%d ", pi[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
 | 
						||
181
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
calloc Fonksiyonu
 | 
						||
Aslında calloc fonksiyonu taban bir fonksiyon değildir. Pek çok kütüphanede calloc fonksiyonu malloc
 | 
						||
fonksiyonunu çağıracak biçimde yazılmıştır. Prototipi şöyledir:
 | 
						||
#include <stdlib.h>
 | 
						||
void *calloc(unsigned count, unsigned size);
 | 
						||
calloc iki parametresinin çarpımı kadar arşılı alan tahsis eder. Geleneksel olarak birinci parametre veri yapısının
 | 
						||
eleman sayısı, ikinci parametre ise bir elemanın byte uzunluğu olarak girilir. Örneğin biz 20 elemanlı bir int dizi
 | 
						||
tahsis edecek olsak birinci parametreye 20, ikinci parametresizeof(int) biçiminde girilir. calloc fonksiyonu malloc
 | 
						||
fonksiyonundan farklı olarak tahsis ettiği alanı sıfırlar. Halbuki malloc Yine fonksiyon başarı durumunda tahsis
 | 
						||
edilen alanın başlangıç adresine, başarısızlık durumunda NULL adrese geri döner. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int *pim;
 | 
						||
int *pic;
 | 
						||
int i;
 | 
						||
if ((pim = (int *)malloc(10 * sizeof(int))) == NULL) {
 | 
						||
printf("cannot allocate memory!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if ((pic = (int *)calloc(10, sizeof(int))) == NULL) {
 | 
						||
printf("cannot allocate memory!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", pim[i]);
 | 
						||
printf("\n");
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", pic[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
calloc fonksiyonunu malloc kullanarak biz de yazabiliriz:
 | 
						||
void *mycalloc(unsigned count, unsigned size)
 | 
						||
{
 | 
						||
void *ptr;
 | 
						||
if ((ptr = malloc(count * size)) == NULL)
 | 
						||
return NULL;
 | 
						||
return memset(ptr, 0, count * size);
 | 
						||
}
 | 
						||
 | 
						||
realloc Fonksiyonu
 | 
						||
182
 | 
						||
 | 
						||
realloc daha önce tahsis edilmiş bir alanı büyütmek ya da küçültmek için kullanılır. Fonksiyonun prototipi şöyledir:
 | 
						||
#include <stdlib.h>
 | 
						||
void *realloc(void *ptr, unsigned newsize);
 | 
						||
Fonksiyonun birinci parametresi daha önce tahsis edilmiş alanın başlangıç adresini alır. İkinci parametre toplam
 | 
						||
yeni uzunluğu belirtir. Fonksiyon tipik olarak şöyle çalışmaktadır: eğer blok büyütülüyorsa realloc önce tahsis
 | 
						||
edilmiş alanın hemen altında istenilen kadar ilave yer var mı diye bakar. Eğer varsa orayı da tahsis eder. Böylece
 | 
						||
blok yer değiştirmemiş olur. Eğer önceki alanın hemen altında toplam yeni uzunluğu karşılayacak kadar boş yer
 | 
						||
yoksa realloc heap alanının başka bir yerinde toplam yeni uzunluk kadar yeni bir yer arar. Bulursa burayı tahsis
 | 
						||
eder. Eski alandaki bilgileri burayqa kopyalar. Eski alanı free hale getirir ve yeni alanın başlangıç adresiyle geri
 | 
						||
döner. Her ne kadar standartlarda realloc'un önce eski bloğun aşağısında yer arayıp aramayacağı belirtilmemişse de
 | 
						||
tipik çalışma böyledir. Örneğin realloc aslında eski bloğun altında boş yer olsa bile yine de bloğu taşıyabilir. Tabi
 | 
						||
realloc yine de başarısz olabilir. Yani ne eski yerin altında ne de herap'in başka bir yerinde toplam yeni uzunluğu
 | 
						||
karşılayacak kadar yer olmayabilir. Bu durumda realloc NULL adrese geri döneri Ayrıca bloğun küçültülmesi
 | 
						||
durumunda da blok yer değiştirebilir. Küçültme durumunda başarısızlık olmaz fakatg blok yer değiştirebilir.
 | 
						||
O halde realloc fonksiyonunun geri dönüş değeri her zaman değerlendirilmelidir. Örneğin:
 | 
						||
p = malloc(10);
 | 
						||
...
 | 
						||
realloc(p, 20);
 | 
						||
 | 
						||
/* hata! */
 | 
						||
 | 
						||
Burada realloc bloğun yerini değiştirebilir. Bu durumda p hala eski bloğu gösterir durumda olacaktır. Oysa şöyle
 | 
						||
yapılmalıydı:
 | 
						||
p = malloc(10);
 | 
						||
...
 | 
						||
p = realloc(p, 20);
 | 
						||
 | 
						||
/* doğru, fakat başarısızlık durumuna dikkat */
 | 
						||
 | 
						||
realloc başarız olduğunda eski bloğu free hale getirmemektedir. eğer programa devam edilecekse eski bloğun
 | 
						||
kaybedilmemesi uygun olur:
 | 
						||
p = malloc(10);
 | 
						||
...
 | 
						||
temp = realloc(p, 20);
 | 
						||
 | 
						||
/* doğru */
 | 
						||
 | 
						||
if (temp == NULL) {
 | 
						||
...
 | 
						||
}
 | 
						||
else
 | 
						||
p = temp;
 | 
						||
...
 | 
						||
realloc ile bloğun büyütüldüğü durumda bloğa ek yapılan alanda rastgele (çöp) değerler vardır. Yani burası da tıpkı
 | 
						||
malloc fonksiyonunda olduğu gibi sıfırlanmaz.
 | 
						||
realloc fonksiyonunun birinci parametresi NULL girilirse realloc malloc gibi davranır. Yani:
 | 
						||
p = malloc(n);
 | 
						||
ile,
 | 
						||
183
 | 
						||
 | 
						||
p = realloc(NULL, n);
 | 
						||
aynı işleve sahiptir. realloc kullanılması için daha önce kesinlikle bloğun dinamik bellek fonksiyonlarıyla tahsis
 | 
						||
edilmiş olması gerekir. Normal bir diziyi biz realloc ile büyütüp küçültemeyiz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#define BLOCK_ELEM_SIZE
 | 
						||
 | 
						||
5
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int *pi = NULL;
 | 
						||
int val, count, i;
 | 
						||
count = 0;
 | 
						||
for (;;) {
 | 
						||
printf("Sayi giriniz:");
 | 
						||
scanf("%d", &val);
 | 
						||
if (val == 0)
 | 
						||
break;
 | 
						||
if (count % BLOCK_ELEM_SIZE == 0) {
 | 
						||
pi = (int *)realloc(pi, (count + BLOCK_ELEM_SIZE) * sizeof(int) );
 | 
						||
if (pi == NULL) {
 | 
						||
printf("cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
}
 | 
						||
pi[count++] = val;
 | 
						||
}
 | 
						||
for (i = 0; i < count; ++i)
 | 
						||
printf("%d ", pi[i]);
 | 
						||
printf("\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
aynı işleve sahiptir. realloc kullanılması için daha önce kesinlikle bloğun dinamik bellek fonksiyonlarıyla tahsis
 | 
						||
edilmiş olması gerekir. Normal bir diziyi biz realloc ile büyütüp küçültemeyiz.
 | 
						||
Sınıf Çalışması: Bir döngü içerisinde sürekli isim isteyiniz. Girilen isimleri dinamik olarak büyütülen char türden
 | 
						||
bir diziye aralarına ',' koyarak ekleyiniz. "exit" yazıldığında döngüden çıkınız ve üm yazıyı tek bir puts ile
 | 
						||
yazdırınız.
 | 
						||
Çözüm:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names;
 | 
						||
char name[64];
 | 
						||
int count;
 | 
						||
count = 1;
 | 
						||
names = (char *)malloc(1);
 | 
						||
 | 
						||
184
 | 
						||
 | 
						||
if (names == NULL) {
 | 
						||
printf("cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
*names = '\0';
 | 
						||
for (;;) {
 | 
						||
printf("Isim giriniz:");
 | 
						||
gets(name);
 | 
						||
if (!strcmp(name, "exit"))
 | 
						||
break;
 | 
						||
if (count != 1)
 | 
						||
strcat(names, ", ");
 | 
						||
count += strlen(name) + 2;
 | 
						||
names = (char *)realloc(names, count);
 | 
						||
if (names == NULL) {
 | 
						||
printf("cannot allocate memory!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
strcat(names, name);
 | 
						||
}
 | 
						||
puts(names);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Önce malloc kullanmadan realloc fonksiyonunu malloc gibi kullanmak isteyebiliriz. Ancak bu durumda ilk
 | 
						||
tahsisattan önce names dizisini başında null karakter bulunması gerekir. Şöyle çözüm söz konusu olabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <string.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names = NULL;
 | 
						||
char name[64];
 | 
						||
int count, len;
 | 
						||
count = 1;
 | 
						||
for (;;) {
 | 
						||
printf("Isim giriniz:");
 | 
						||
gets(name);
 | 
						||
if (!strcmp(name, "exit"))
 | 
						||
break;
 | 
						||
if (count != 1)
 | 
						||
strcat(names, ", ");
 | 
						||
len = strlen(name) + 2;
 | 
						||
names = (char *)realloc(names, count + len);
 | 
						||
if (names == NULL) {
 | 
						||
printf("cannot allocate memory!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (count == 1)
 | 
						||
*names = '\0';
 | 
						||
count += len;
 | 
						||
strcat(names, name);
 | 
						||
}
 | 
						||
puts(names);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
185
 | 
						||
 | 
						||
free Fonksiyonu
 | 
						||
free fonksiyonu daha önce malloc, calloc ya da realloc fonksiyonlarıyla tahsis edilmiş alanı serbest bırakmak için
 | 
						||
kullanılır. Yani free'den sonra artık o alan sani hiç tahsis edilmemiş gibi olur. Fonksiynunun prototipi şöyledir:
 | 
						||
#include <stdlib.h>
 | 
						||
void free(void *ptr)
 | 
						||
Fonksiyon daha önce tahsis edilmiş olan bloğun başlangıç adresini alır ve o bloğun tamamını serbest bırakır.
 | 
						||
Bloğun bir kısmının serbest bırakılması söz konusu değildir.
 | 
						||
Modern sistemlerin hepsinde çalışan programların (proseslerin) heap alanları birbirlerinden farklıdır. Yani bir
 | 
						||
programdaki dinamik tahsisatlar diğer programın heap alanını azaltmazlar. Ancak yine de C standartlarında
 | 
						||
proseslerin heap alanlarının farklı olduğu belirtilmemiştir. Yani bazı küçük kapasiteli sistemlerde programdan
 | 
						||
çıkılsa bile heap tahsisatları kalıyor olabilir. Fakat mevcut sistemlerde bir program sonlandığında onun bütün
 | 
						||
tahsisatları otomatik free hale getirilmektedir.
 | 
						||
Örneğin:
 | 
						||
if ((ptr = malloc(100)) == NULL) {
 | 
						||
printf("cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
...
 | 
						||
free(ptr);
 | 
						||
Heap Organizasyonu Nasıl Yapılmaktadır?
 | 
						||
Heap alanının hangi büyüklükte olacağı ve heap olarak belleğin neresinin kullanılacağı sistemden sisteme
 | 
						||
değişebilmektedir. Ancak dinamik bellek fonksiyonları kendi aralarında hangi bölgelerin tahis edilmiş olduğunu
 | 
						||
bir tahsisat tablosu yoluyla tutmaktadır. Pek çok sistemde tahsisat algoritması olarak "boş blok bağlı listeleri"
 | 
						||
kullanılıyorsa da çok çeşitli tahsisat algoritmaları vardır. Burada bir fikir oluşsun diye bu fonksiyonların aaşağıdaki
 | 
						||
gibi bir tahsisat tablosu oluşturduğunu düşünebiliriz:
 | 
						||
 | 
						||
Yapılar (Structures)
 | 
						||
Elemanları bellekte ardışıl biçimde tutulan fakat farklı türlerden olabilen veri yapılarına "yapı (structure)"
 | 
						||
denilmektedir. Dizi ile yapılar birbilerine çok benzerler. Her ikisinde de elemanları ardışıl olarak bellekte
 | 
						||
tutulmaktadır. Fakat dizi elemanları aynı türden olduğu halde yapı elemanları farklı türlernden olabilmektedir.
 | 
						||
186
 | 
						||
 | 
						||
Yapılarla çalışırken önce yapıların bir şablonu bildirilir. Buna "yapı bildirimi" denir. Yapıl bildirimi bellekte yer
 | 
						||
kaplamaz. Yani tanımlama değildir. Yapı bildirimini gören derleyici yapı elemanlarının türlerini ve isimlerini
 | 
						||
öğrenir. Daha sonra bu yapı türünden gerçek nesnler tanımlanır. Yapı bildiriminin genel biçimi şöyledir:
 | 
						||
struct <isim> {
 | 
						||
<eleman bildirimleri>
 | 
						||
};
 | 
						||
Örneğin:
 | 
						||
struct DATE {
 | 
						||
int day, month, year;
 | 
						||
};
 | 
						||
struct COMPLEX {
 | 
						||
double real;
 | 
						||
double imag;
 | 
						||
};
 | 
						||
struct SAMPE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
Yapı isimlerini bazı programcılar büyük harflerle bazıları küçük harflerle harflendirmektedir. Biz kurusumuzda
 | 
						||
yapı isimlerini büyük harflerle harflendireceğiz. Yapı bildirimi ile derleyici yapı elemanlarının isimlerini ve
 | 
						||
türlerini öğrenir.
 | 
						||
Yapı bildirimleri yerel ya da global düzeyde yapılabilir. Eğer yapı bildirimi yerel düzeyde yapılmışsa o yapı ismi
 | 
						||
ancak o blokta kullanılabilir. Global olarak yapılmışsa her yerde kullanılabilir. Yapı bildirimleri genellikle global
 | 
						||
düzeyde yapılmaktadır. Çünkü çoğu kez yapıların farklı fonksiyonlarda kullanılması gerekmektedir.
 | 
						||
Her yapı bildirimi aynı zamanda bir tür de belirtmektedir. Yani bir yapı bildirimi ile biz bir tür de oluşturmuş
 | 
						||
oluruz. Yapı bildirimleriyle oluşturulan türler struct anahtar sözcüğü ve yapı ismiyle ifade edilir. Örneğin struct
 | 
						||
DATE gibi, struct SAMPLE gibi.
 | 
						||
Yapı bildirimi yapıldıktan sonra artık o yapı türünden nesneler tanımlanır. Örneğin:
 | 
						||
struct DATE d;
 | 
						||
struct SAMPLE s;
 | 
						||
Burada d "struct DATE" türünden, s ise "struct SAMPLE" türündendir.
 | 
						||
Yapılar bileşik nesnelerdir. Yani parçalardan oluşmaktadır. Örneğin:
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
187
 | 
						||
 | 
						||
struct SAMPLE s;
 | 
						||
 | 
						||
Burada s a, b, ve c parçalarından oluşan nesnein bütününü temsil eder.
 | 
						||
Yapı nesneleri genellikle bütünsel olarak kullanılmaz. Onların parçaalarına erişilip parçaları kullanılır. Yapı
 | 
						||
elemanlarına erişmek için nokta operatörü kullanılmaktadır.
 | 
						||
Nokta operatörü iki operandlı araek bir operatördür. Nokta operatörünün sol tarafındaki operand yapı nesnesinin
 | 
						||
bütünü, sağ taraftaki operand onun bir elemanı olmak zorundadır. Bu operatör sol taraftaki yapının sağ taraftaki
 | 
						||
elemanına erişmekte kullanılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
s.a = 10;
 | 
						||
s.b = 20;
 | 
						||
s.c = 12.4;
 | 
						||
printf("%d, %ld, %f\n", s.a, s.b, s.c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada s nesnesi struct SAMPLE türündendir. s.a ifadesi int türdendir. s.a ifadesi "s nesnesinin a parçası" anlamına
 | 
						||
gelir. s.b ifadesi long türden, s.c ifadesi ise double türdendir.
 | 
						||
C standartlarına göre yapı bildiriminde ilk yazılan eleman düşük adreste bulunacak biçimde eleman ardı
 | 
						||
şıllığı oluşturulur. Yani örneğimizde s'in a parçası en düşük adrestedir. Bunu b parçası izler onu da c parçası izler.
 | 
						||
Nokta operatörü öncelik tablosunun en yüksek düzeyinde soldan sağa grupta bulunur
 | 
						||
188
 | 
						||
 | 
						||
() [].
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof & *
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
Örneğin &s.a gibi bir ifadede s.a'nın adresi alınmaktadır. Çünkü nokta operatörü & operatöründen daha yüksek
 | 
						||
önceliklidir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
printf("%p, %p, %p\n", &s.a, &s.b, &s.c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
Anahtar Notlar: Anımsanacağı gibi Kernighan & Ritchie yazıam tarzında iki operandlı operatörlerle operandlar arasında birer boşluk karakteri
 | 
						||
bırakılıyordu. Fakat nokta ve -> operatörleri iki operandlı olmasına karşın bunlarla operandlar arasında boşluk karakteri bırakılmamaktadır.
 | 
						||
 | 
						||
Bir yapı nesnesinin elemanlarına henüz değer atanmadıysa içerisinde ne vardır? İşte eğer yapı nesnesi yerelse onun
 | 
						||
tüm elemanlarında çöp değerler, global ise sıfır değerleri bulunur.
 | 
						||
Bir yapı nesnesine küme parantezleri içerisinde ilkdeğer verebiliriz. (Tıpkı dizilerd eolduğu gibi) Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s = { 10, 20, 20.5 };
 | 
						||
printf("%d, %ld, %f\n", s.a, s.b, s.c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Tıpkı dizilerde olduğu gibi yapının az sayıda elemanına ilkdeğer verebiliriz. Bu durumda geri kalan elemanlar
 | 
						||
derleyici tarafından sıfırlanır. Fakat yapının fazla sayıda elemanına ilkdeğer vermeye çalışırsak derleme
 | 
						||
aşamasında error oluşur.
 | 
						||
Aynı türden iki yapı nesnesi birbirlerine atanabilir. Bu durumda yapının karşılıklı elemanları birbirlerine
 | 
						||
atanmaktadır. Örneğin:
 | 
						||
189
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s = { 10, 20, 30.5 };
 | 
						||
struct SAMPLE k;
 | 
						||
k = s;
 | 
						||
printf("%d, %ld, %f\n", s.a, s.b, s.c);
 | 
						||
printf("%d, %ld, %f\n", k.a, k.b, k.c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Atama işleminde yapıların türlerinin aynı olması gerekir. Tür ise isme bağlıdır. İçi aynı olan farklı isimli yapıları
 | 
						||
birbirlerine atayamayız.
 | 
						||
Yapı Elemanı Olarak Diziler
 | 
						||
Bir dizi bir yapının elemanı olabilir. Bu durumda yapının dizi elemnanına erişildiğinde bu ifade dizinin tamamını
 | 
						||
temsil eder, ifade içerisinde kullanıldığında ise yapı içerisindeki dizinin başlangıç adresini belirtir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON per;
 | 
						||
printf("Adi soyadi:");
 | 
						||
gets(per.name);
 | 
						||
printf("No:");
 | 
						||
scanf("%d", &per.no);
 | 
						||
printf("%s, %d\n", per.name, per.no);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
190
 | 
						||
 | 
						||
Böyle yapı nesnesine ilkdeğer de verebiliriz:
 | 
						||
struct PERSON per = {"Kaan Aslan", 123};
 | 
						||
Tabi buradaki iki tırnak ifadesi adres anlamına gelmez. Karakterlerin name dizisine kopyalanacağı anlamına gelir.
 | 
						||
Nokta operatöryle [] operatörünün aynı grupta soldan sağa öncelikli olduğuna dikkat ediniz. Örneğin:
 | 
						||
per.name[3]
 | 
						||
ifadesi per.name dizisinin 3. indeksli elemanı anlamına gelir. Yani:
 | 
						||
per -------> struct PERSON türündendir
 | 
						||
per.name -------> char * türündendir
 | 
						||
per.name[3] ------> char türündendir
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON per = { "Kaan Aslan", 123 };
 | 
						||
char ch;
 | 
						||
ch = per.name[3];
 | 
						||
putchar(ch);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapı Elemanı Olarak Göstericilerin Kullanılması
 | 
						||
Bir yapının elemanı bir gösterici olabilir. Örneğin:
 | 
						||
struct PERSON {
 | 
						||
char *name;
 | 
						||
int no;
 | 
						||
191
 | 
						||
 | 
						||
};
 | 
						||
struct PERSON per;
 | 
						||
Burada name elemanı bir göstericidir ve bu göstericinin tahsis edilmiş bir alanı gösteriyor olması gerekir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char *name;
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON per;
 | 
						||
per.name = "Kaan Aslan";
 | 
						||
per.no = 123;
 | 
						||
printf("%s, %d\n", per.name, per.no);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapı Türünden Adresler ve Göstericiler
 | 
						||
Bir yapı nesnesinin adresi alınabilir. Bu durumda elde edilen adresin sayısal bileşeni tüm yapı nesnesinin bellekteki
 | 
						||
başlangıç adresi, tür bileşeni ise po yapı türündendir. Bir yapı nesnesinin adresi aynı türden bir yapı göstericisne
 | 
						||
atanabilir. Örneğin:
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
struct SAMPLE s;
 | 
						||
struct SAMPLE *ps;
 | 
						||
ps = &s;
 | 
						||
 | 
						||
192
 | 
						||
 | 
						||
Bir yapı göstericisi ya da bir yapı türünden adres * operatörüyle kullanılırsa yapı nesnesinin tamamına erişilir. Yani
 | 
						||
yukarıdaki örnekte *ps ile s aynıdır. Burada ps struct SAMPLE * türünden *ps ise struct SAMPLE türündedir.
 | 
						||
 | 
						||
Yapı Göstericisi Yoluyla Yapı elemanlarına Erişilmesi
 | 
						||
p bir yapı türünden adres a da bu yapının bir elemanı olmak üzere bu adresin gösterdiği yerdeki yapı nesnesinin a
 | 
						||
elemanına erişmek için (*p).a ifadesi kullanılır. *p.a ifadesi geçersizdir. Çünkü nokta operatörü * operatöründen
 | 
						||
daha yüksek öncelikli olduğu için bu ifadede önce p.a yapılmaya çalışılır ki nokta operatörünün solundaki operand
 | 
						||
geçersi olur. Çünkü nokta operatörünün solundaki operand yapı nesnesinin kendisi olmalıdır, adresi olmamalıdır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
struct SAMPLE *ps;
 | 
						||
ps = &s;
 | 
						||
(*ps).a = 10;
 | 
						||
(*ps).b = 20;
 | 
						||
(*ps).c = 30;
 | 
						||
printf("%d, %ld, %f\n", (*ps).a, (*ps).b, (*ps).c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Ok (Arrow) Operatörü
 | 
						||
Ok operatörü -> karaketerleriyle elde edilir. Ok operatörü iki operandlı araek bir operatördür. Ok operatörünün
 | 
						||
193
 | 
						||
 | 
						||
solundaki operand bir yapı türünden adres, sağındaki operand o yapının bir elemanı olmak zorundadır. On
 | 
						||
operatörü sol taraftaki operandla belirtilen adresteki yapı nesnesinin sağ taraftaki operandla belirtilen elemanına
 | 
						||
erişmekte kullanılır. Yani:
 | 
						||
p->a
 | 
						||
ile
 | 
						||
(*p).a
 | 
						||
tamamen eşdeğerdir.
 | 
						||
Nokta operatörüyle ok operatörünün her ikisi de yapı elemanlarına erişmekte kullanılır. Nokta operatörü nesnenin
 | 
						||
kendisiyle ok operatörü adresiyle erişim sağlar. Ok operatörü öncelik tablosunun en üst düzeyinde soldan sağa
 | 
						||
grupta bulunur:
 | 
						||
( ) [ ] . ->
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ - ++ -- ! sizeof & *
 | 
						||
 | 
						||
Sağdan-Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
...
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
long b;
 | 
						||
double c;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
struct SAMPLE *ps;
 | 
						||
ps = &s;
 | 
						||
ps->a = 10;
 | 
						||
ps->b = 20;
 | 
						||
ps->c = 30;
 | 
						||
printf("%d, %ld, %f\n", ps->a, ps->b, ps->c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct POINT {
 | 
						||
int x, y;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
 | 
						||
194
 | 
						||
 | 
						||
{
 | 
						||
struct POINT pt;
 | 
						||
struct POINT *ppt;
 | 
						||
ppt = &pt;
 | 
						||
ppt->x = 10;
 | 
						||
ppt->y = 20;
 | 
						||
printf("(%d, %d)\n", ppt->x, ppt->y);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
s bir yapı türünden nesne a da bu yapının bir elemanı olmak üzere &s->a ifadesi geçersizdir. Çünkü & operatörü ->
 | 
						||
operatöründen daha düşük önceliklidir. Ancak (&s)->a ifadesi geçerlidir ve s.a ile eşdeğerdir.
 | 
						||
p bir yapı türünden adres ve a da bu yapının bir elelmanı olmak üzere &p->a ifadesi geçerlidir ve bu ifade p
 | 
						||
göstericisinin gösterdiği yerdeki nesnenin a parçasının adresi anlamına gelir.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON per = { "Ali Serce", 234 };
 | 
						||
struct PERSON *pper;
 | 
						||
pper = &per;
 | 
						||
printf("%s, %d\n", pper->name, pper->no);
 | 
						||
printf("%c\n", pper->name[2]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapıların Fonksiyonlara Parametre Yoluyla Aktarılması
 | 
						||
Yapıların fonksiyonlara aktarılmasında iki teknik kullanılır. Bunlardan birincisi nesnenin kopyalama yoluyla
 | 
						||
aktarılması tekniği, diğeri ise adres yoluyla aktarma tekniğidir. Nesnenin kendisinin aktarılması genel olarak kötü
 | 
						||
bir tekniktir. Adrtes yoluyla aktarma iyi bir tekniktir. Gerçekten de C'de yapı nesneleri hemen her zaman
 | 
						||
fonksiyonlara adres yoluyla aktarılır.
 | 
						||
2) Yapı Nesnelerinin Fonksiyonlara Kopyalama Yoluyla Aktarılması: Bu yöntemde fonksiyonun parametre
 | 
						||
değişkeni bir yapı türünden yapı nesnesidir. Fonksiyon da aynı yapı türünden bir nesneyle çağrılır. Aynı türden iki
 | 
						||
yapı nesnesi atanabildiğine göre bu çağırma geçerlidir. Ancak burada aktarım kopyalama yoluyla yapılmaktadır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
 | 
						||
195
 | 
						||
 | 
						||
void foo(struct PERSON per)
 | 
						||
{
 | 
						||
printf("%s, %d\n", per.name, per.no);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON x = { "Ali Serce", 234 };
 | 
						||
foo(x);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu yöntem genel olarak kötü bir tekniktir. Çünkü büyük bir yapının bu yöntemde tüm elemanlarını tek tek aktarım
 | 
						||
sırasında fonksiyona kopyalanır. Üstelik bu yöntemde fonksiyon içerisinde artık biz orijinal nesneye erişemeyiz.
 | 
						||
Tabi eğer yapı çok küçükse bu teknik kötü bir teknik olmaz. Bu durumda kullanılabilir.
 | 
						||
2) Yapı Nesnelerinin Fonksiyonlara Adres Yoluyla Aktarılması: Bu yöntemde fonksiyonun parametre
 | 
						||
değişkeni yapı türünden gösterici olur. Fonksiyon da aynı türden bir yapı nesnesinin adresiyle çağrılır. Bu
 | 
						||
kullanılması gereken doğru tekniktir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
void foo(struct PERSON *per)
 | 
						||
{
 | 
						||
printf("%s, %d\n", per->name, per->no);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON x = { "Ali Serce", 234 };
 | 
						||
foo(&x);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct DATE {
 | 
						||
int day;
 | 
						||
int month;
 | 
						||
int year;
 | 
						||
};
 | 
						||
void get_date(struct DATE *date)
 | 
						||
{
 | 
						||
printf("Gun:");
 | 
						||
scanf("%d", &date->day);
 | 
						||
printf("Ay:");
 | 
						||
scanf("%d", &date->month);
 | 
						||
printf("Yil:");
 | 
						||
scanf("%d", &date->year);
 | 
						||
 | 
						||
196
 | 
						||
 | 
						||
}
 | 
						||
void disp_date(struct DATE *date)
 | 
						||
{
 | 
						||
printf("%02d/%02d/%04d\n", date->day, date->month, date->year);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct DATE date;
 | 
						||
get_date(&date);
 | 
						||
disp_date(&date);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct COMPLEX {
 | 
						||
double real, imag;
 | 
						||
};
 | 
						||
void get_comp(struct COMPLEX *comp)
 | 
						||
{
 | 
						||
printf("Gercek Kisim:");
 | 
						||
scanf("%lf", &comp->real);
 | 
						||
printf("Sanal Kisim:");
 | 
						||
scanf("%lf", &comp->imag);
 | 
						||
}
 | 
						||
void disp_comp(struct COMPLEX *comp)
 | 
						||
{
 | 
						||
printf("%.0f+%.0fi\n", comp->real, comp->imag);
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct COMPLEX z;
 | 
						||
get_comp(&z);
 | 
						||
disp_comp(&z);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapılara Neden Gereksinim Duyulmaktadır?
 | 
						||
1) Yapılar olgular için mantıksal bir kap oluşturmaktadır. Yani tarih gibi, sanal sayılar gibi, şahıs bilgileri gibi
 | 
						||
birbirleriyle ilgili çok sayıda nesne bir yapıyla ifade edilirse daha kolay bir temsil yeteneği el edilir. Gerçekten de
 | 
						||
mantıksal bakımdan birbirleriyle bağlantılı nesnelerin yapılarla temsil edilmesi okunabilirliği ve anlaşılabilirliği
 | 
						||
artırmaktadır.
 | 
						||
2) Bir fonksiyona çok sayıda parametre aktarılacaksa onların tek tek aktarılmaları hem yazılımsal olarak zordur.
 | 
						||
Hem anlaşılır olmaktan çıkar hem de yavaştır. Bunun yerine çok sayıda parametre bir yapında toplanım tek bir
 | 
						||
parametre biçiminde fonksiyona geçirilebilir.
 | 
						||
3) Bir fonksiyonun tek bir geri dönüş değeri vardır. Eğer fonksiyon dış dünyaya çok sayıda değer iletecekse bu
 | 
						||
yapılarla sağlanabilir. Örneğin iletilecek değerler bir yapıyla ifade edilir. Fonksiyona yapı nesnesinin adresi
 | 
						||
197
 | 
						||
 | 
						||
geçirilir. Fonksiyon da bu nesnenin içini doldurur.
 | 
						||
Fonksiyonların Geri Dönüş Değerlerinin Yapı Olması Durumu
 | 
						||
Bir fonksiyonun geri dönüş değeri bir yapı türünden olabilir. Bu durumda return ifadesi de aynı türden bir yapı
 | 
						||
nesnesi olmalıdır. Aslında bu yöntem de C'de çoğu kez (yani yapı büyükse) iyi teknik kabul edilmez. Çünkü
 | 
						||
burada return işlemi sırasında geçici nesneye bir kopyalama yapılmakta ve geri dönüş değerinin atanması sırasında
 | 
						||
da aynı sorun oluşmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct COMPLEX {
 | 
						||
double real, imag;
 | 
						||
};
 | 
						||
void disp_comp(struct COMPLEX *comp)
 | 
						||
{
 | 
						||
printf("%.0f+%.0fi\n", comp->real, comp->imag);
 | 
						||
}
 | 
						||
struct COMPLEX add_comp(struct COMPLEX *z1, struct COMPLEX *z2)
 | 
						||
{
 | 
						||
struct COMPLEX result;
 | 
						||
result.real = z1->real + z2->real;
 | 
						||
result.imag = z1->imag + z2->imag;
 | 
						||
return result;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct COMPLEX z1 = { 3, 2 };
 | 
						||
struct COMPLEX z2 = { 1, 4 };
 | 
						||
struct COMPLEX result;
 | 
						||
result = add_comp(&z1, &z2);
 | 
						||
disp_comp(&result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Genellikle programcılar çoklu bilgiyi böyle almaktansa bir nesne verip fonksiyonun onun içerisine yerleştirme
 | 
						||
yapmasını tercih ederler. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct COMPLEX {
 | 
						||
double real, imag;
 | 
						||
};
 | 
						||
void disp_comp(struct COMPLEX *comp)
 | 
						||
{
 | 
						||
printf("%.0f+%.0fi\n", comp->real, comp->imag);
 | 
						||
}
 | 
						||
void add_comp(struct COMPLEX *z1, struct COMPLEX *z2, struct COMPLEX *result)
 | 
						||
{
 | 
						||
result->real = z1->real + z2->real;
 | 
						||
result->imag = z1->imag + z2->imag;
 | 
						||
}
 | 
						||
 | 
						||
198
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct COMPLEX z1 = { 3, 2 };
 | 
						||
struct COMPLEX z2 = { 1, 4 };
 | 
						||
struct COMPLEX result;
 | 
						||
add_comp(&z1, &z2, &result);
 | 
						||
disp_comp(&result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapılarda Hizalama (Alignment Kavramı)
 | 
						||
Modern 32 işlemcilerde bellek bağlantısı bir hamlede 4 byte bilgiyi çekeck biçimde yapımıştır. Benzer biçimde 64
 | 
						||
bit işlemcilerde de bellek bağlantısı bir hamlede 8 byte bilgiyi çekecek biçimde yapılmıştır. Böylece örneğin 32 bit
 | 
						||
işlemciler aslında 32 adres yoluna değil 30 adres yoluna sahiptir. Tabi makina komutları yine aynı biçimde byte
 | 
						||
ardeslemeli çalışmaktadır. Aşağıda 32 bit bir işlemcinin bellek erişimi resmedilmiştir:
 | 
						||
 | 
						||
Bu sistemlerde eğer 4 byte'lık bir bilgi (örneğin int türden bir bilgi) 4'ün katlarında değilse makina komutları göreli
 | 
						||
olarak daha yavaş çalışmaktadır. Çünkü bu 4 byte'ı işlemci iki bus erişimiyle elde eder:
 | 
						||
 | 
						||
Tabi bir byte bir bilgiye kaçın katlarında olursa olsun tek bus hareketiyle erişilebilmektedir. Peki 2 byte'lık bilgiler?
 | 
						||
Bunların da 2'nin katlarında olması gerekir. Örneğin 2'nin katlarında olmayan 2 byte'lık (örneğin short türden bir
 | 
						||
nesne) bir nesne aşağıdaki gibi gösterilebilir:
 | 
						||
 | 
						||
İşte derleyiciler işlemcilerin böyle çalıştıklarını bildiği için makina komutları daha hızlı çalışsın diye 4 byte'lık
 | 
						||
199
 | 
						||
 | 
						||
nesneleri 4'ün katlarına, 8 byte'lık nesneleri 8'in katlarına, 2 byte'lık nesneleri 2'nin katlarına, 1 byte'lık nesneleri
 | 
						||
1'in katlarına yerleştirmektedir. Derleyiciler bunlara yerel değişkenler için ve global değişkenler için dikkaet
 | 
						||
ederler. Yapı elemanları bellekte ardışıl olacağından ve ilk yazılan elemanın düşük adreste olması gerekeceğinden
 | 
						||
bu hizalama (alignement) yapılar için nasıl gerçekleştirilecektir? İşte derleyiciler yapı elemanlarının arasına
 | 
						||
boşluklar koyarak o elemanların belli adres katlarında olmasını sağlayabilmektedir. Örneğin:
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
int b;
 | 
						||
char c;
 | 
						||
int d;
 | 
						||
};
 | 
						||
struct SAMPLE s;
 | 
						||
32 bit bir işlemcide bu yap nesnesinin bellkete 10 byte yer kaplayacağı düşünülebilir. Ancak derleyiciler a ile b
 | 
						||
arasına ve c ile d arasına 3'er byte boşluk bırakarak int olan kısımların 4'ün katlarına gelmesini sağlayabilmektedir.
 | 
						||
Böylece bu yapı nesnesinin sizeof değerinin 16 çıkması programcıyı şaşırtmamalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
int b;
 | 
						||
char c;
 | 
						||
int d;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
printf("%u\n", sizeof(s));
 | 
						||
 | 
						||
/* 16 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Peki yukarıdaki yapıyı aşağıdaki gibi düzenleseydik ne olurdu?
 | 
						||
#include <stdio.h>
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
char b;
 | 
						||
int c;
 | 
						||
int d;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
printf("%u\n", sizeof(s));
 | 
						||
 | 
						||
/* 12 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Hizalama pek çok derleyicide derleyici seçeneklerinden yönetilebilmektedir. Örneğin Microsoft C derleyicilerinde
 | 
						||
hizalama Proje ayarlarında C-C++/Code Generation/Struct Member Alignment ile değiştirilebilmektedir. Eğer
 | 
						||
hizalama "1 byte hizalama moduna çekilirse derleyici yapı elemanlarını 1'in katlarına yerleştirmeye çalışır.
 | 
						||
200
 | 
						||
 | 
						||
Dolayısıyla elemanlar arasında hiç boşluk bırakmaz.
 | 
						||
C standartlarına hizalama kavramı bir kurala bağlanmamıştır. Standartlarda yalnızca derleyicinin yapı elemanları
 | 
						||
arasında boşluk bırakabileceği belirtilmiştir.
 | 
						||
Peki derleyicinin yapı elemanları arasında boşluk bırakabilmesi erişimde bir soruna yol açar mı? Yanıt hayır.
 | 
						||
Çünkü derleyici nerelere boşluk bıraktığını bildiği için ona göre erişimi yapmaktadır. Örneğin aşağıdaki yapı için
 | 
						||
derleyici 4 byte hizalama kullanmış olsun:
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
int b;
 | 
						||
char c;
 | 
						||
int d;
 | 
						||
};
 | 
						||
 | 
						||
Şimdi p göstericisinin bu yapıyı gösterdiğini düşünelim. Artık derleyici p->b ifadesiyle p adresinden 1 byte
 | 
						||
sonraya değil 4 byte sonraya erişir. Çünkü araya boşluk bıraktığını zaten kendisi bilmektedir. Yani yukarıdaki
 | 
						||
yapıyı biz şöyle düşünebiliriz:
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
char temp1, temp2, temp3;
 | 
						||
int b;
 | 
						||
char c;
 | 
						||
char temp4, temp5, temp6;
 | 
						||
int d;
 | 
						||
};
 | 
						||
 | 
						||
Yapılar İçin Dinamik Tahsisat Yapılması
 | 
						||
Mademki yapı elemanları bellekte ardışıl bir biçimde tutulmaktadır. O halde yapı nesneleri için de heap'te dinamik
 | 
						||
tahsisatlar yapılabilir. Dinamik tahsisat yaparken hizalama olasılığını göz önüne almak gerekir. Bu nedenle yapının
 | 
						||
byte uzunluğunun sizeof operatörü ile elde edilmesi uygun olur. sizeof operatörü derleyicinin o anda uyguladığı
 | 
						||
hizalamayı da hesaba katmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON *per;
 | 
						||
per = (struct PERSON *)malloc(sizeof(struct PERSON));
 | 
						||
if (per == NULL) {
 | 
						||
printf("cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
printf("Adi soyadi:");
 | 
						||
gets(per->name);
 | 
						||
printf("No:");
 | 
						||
scanf("%d", &per->no);
 | 
						||
printf("%s, %d\n", per->name, per->no);
 | 
						||
 | 
						||
201
 | 
						||
 | 
						||
free(per);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapı Dizileri
 | 
						||
Her elemanı bir yapı nesnesi olan dizilere yapı dizileri (array of structures) denilmektedir. Yapı dizileri de normal
 | 
						||
dizilerde olduğu gibi bildirilir. Örneğin:
 | 
						||
struct PERSON persons[3];
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON persons[3];
 | 
						||
int i;
 | 
						||
strcpy(persons[0].name, "Kaan Aslan");
 | 
						||
persons[0].no = 123;
 | 
						||
 | 
						||
202
 | 
						||
 | 
						||
strcpy(persons[1].name, "Ali Serce");
 | 
						||
persons[1].no = 234;
 | 
						||
strcpy(persons[2].name, "Necati Ergin");
 | 
						||
persons[2].no = 678;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
printf("%s, %d\n", persons[i].name, persons[i].no);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Anımsanacağı gibi dizi isimleri tüm diziyi temsil etmektedir. Ancak bunlar işleme sokulduğunda derleyici
 | 
						||
tarafından dizinin başlangıç adresine dönüştürülürler. Yani biz dizi isimlerini kullandığımızda aslında o dizilerin
 | 
						||
başlangıç adreslerini kullanıyor oluruz. Dizi isimleri kullanıldığında nesne belirtmezler.
 | 
						||
Biz bir yapı dizisinin ismini aynı yapı türünden bir yapı göstericisine atayabiliriz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON persons[3], *per;
 | 
						||
int i;
 | 
						||
strcpy(persons[0].name, "Kaan Aslan");
 | 
						||
persons[0].no = 123;
 | 
						||
strcpy(persons[1].name, "Ali Serce");
 | 
						||
persons[1].no = 234;
 | 
						||
strcpy(persons[2].name, "Necati Ergin");
 | 
						||
persons[2].no = 678;
 | 
						||
per = persons;
 | 
						||
for (i = 0; i < 3; ++i) {
 | 
						||
printf("%s, %d\n", per->name, per->no);
 | 
						||
++per;
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir yapı göstericini bir artırdığımızda göstericinin içerisindeki adres yapı uzunluğu kadar artmaktadır.
 | 
						||
Yapı dizilerine de küme parantezleri ile ilkdeğer verilebilir. Bu durumda eleman olan her yapı nesnesi için de ayrı
 | 
						||
bir küme parantezi kullanılır. Örneğin:
 | 
						||
struct PERSON persons[3] = {
 | 
						||
{ "Kaan Aslan", 123 }, { "Ali Serce", 345 }, { "Necati Ergin", 654 }
 | 
						||
};
 | 
						||
 | 
						||
Aslında bu tür durumlarda ,çteki küme parantezleri zorunlu değildir. Örneğin yukarıdaki ilk değer verme şöyle
 | 
						||
yapılabilirdi:
 | 
						||
203
 | 
						||
 | 
						||
struct PERSON persons[3] = {
 | 
						||
"Kaan Aslan", 123 , "Ali Serce", 345, "Necati Ergin", 654
 | 
						||
};
 | 
						||
 | 
						||
Fakat bu biçimde ilkdeğer verme hem okunabilirliği azaltmakta hem de aradaki bir değer unutulduğunda diğer tüm
 | 
						||
değerlerin yanlış elemanlara atanması gibi bir soruna yol açabilmektedir. Örneğin:
 | 
						||
struct POINT {
 | 
						||
int x;
 | 
						||
int y;
 | 
						||
};
 | 
						||
struct POINT points[5] = {
 | 
						||
{ 1, 2 }, { 4, 7 }, { 6, 8 }, { 7, 9 }, { 3, 4 }
 | 
						||
};
 | 
						||
 | 
						||
Burada 2 değerinin yazılmadığını düşünelim:
 | 
						||
struct POINT points[5] = {
 | 
						||
{ 1 }, { 4, 7 }, { 6, 8 }, { 7, 9 }, { 3, 4 }
 | 
						||
};
 | 
						||
 | 
						||
Artık dizinin ilk elemanının y değeri sıfır olacaktır. Ancak:
 | 
						||
struct POINT points[5] = {
 | 
						||
1, 4, 7 , 6, 8, 7, 9, 3, 4
 | 
						||
};
 | 
						||
 | 
						||
Burada tamamen bir kaydırma oluşmaktadır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct POINT {
 | 
						||
int x;
 | 
						||
int y;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct POINT points[5] = {
 | 
						||
{ 1, 2 }, { 4, 7 }, { 6, 8 }, { 7, 9 }, { 3, 4 }
 | 
						||
};
 | 
						||
int i;
 | 
						||
for (i = 0; i < sizeof(points) / sizeof(*points); ++i)
 | 
						||
printf("(%d, %d)\n", points[i].x, points[i].y);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir yapı dizisini de fonksiyona parametre yoluyla aktarabiliriz. Bunun için yine onun başlangıç adresini ve
 | 
						||
uzunluğunu fonksiyona geçiririz. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <string.h>
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
 | 
						||
204
 | 
						||
 | 
						||
void sort_persons_byname(struct PERSON *per, int size);
 | 
						||
void sort_persons_byno(struct PERSON *per, int size);
 | 
						||
void disp_persons(struct PERSON *per, int size);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON persons[] = {
 | 
						||
{ "Selami Hakyemez", 123 }, { "Ahmet Hamdi Tanpinar", 523 }, { "Hulisi Sen", 323 },
 | 
						||
{ "Siracettin Bilyap", 654 }, { "Ali Ipek", 234 }
 | 
						||
};
 | 
						||
sort_persons_byname(persons, 5);
 | 
						||
disp_persons(persons, 5);
 | 
						||
printf("-----------\n");
 | 
						||
sort_persons_byno(persons, 5);
 | 
						||
disp_persons(persons, 5);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void sort_persons_byname(struct PERSON *per, int size)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
struct PERSON temp;
 | 
						||
for (i = 0; i < size - 1; ++i)
 | 
						||
for (k = 0; k < size - 1 - i; ++k)
 | 
						||
if (strcmp(per[k].name, per[k + 1].name) > 0) {
 | 
						||
temp = per[k];
 | 
						||
per[k] = per[k + 1];
 | 
						||
per[k + 1] = temp;
 | 
						||
}
 | 
						||
}
 | 
						||
void sort_persons_byno(struct PERSON *per, int size)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
struct PERSON temp;
 | 
						||
for (i = 0; i < size - 1; ++i)
 | 
						||
for (k = 0; k < size - 1 - i; ++k)
 | 
						||
if (per[k].no > per[k + 1].no) {
 | 
						||
temp = per[k];
 | 
						||
per[k] = per[k + 1];
 | 
						||
per[k + 1] = temp;
 | 
						||
}
 | 
						||
}
 | 
						||
void disp_persons(struct PERSON *per, int size)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < size; ++i)
 | 
						||
printf("%s, %d\n", per[i].name, per[i].no);
 | 
						||
}
 | 
						||
 | 
						||
Yapı Bildirimiyle Nesne Tanımlamasının Birlikte Yapılması
 | 
						||
Bir yapı bildirilirken bildirim ';' ile kapataılmayıp bir değişken listesi de yazılırsa aynı zamanda o yapı türünden
 | 
						||
nesneler de tanımlanmış olur. Örneğin:
 | 
						||
struct POINT {
 | 
						||
int x, y;
 | 
						||
205
 | 
						||
 | 
						||
} pt1, pt2;
 | 
						||
Bu bildirimin aşağıdakinden hiçbir farkı yoktur.
 | 
						||
struct POINT {
 | 
						||
int x, y;
 | 
						||
};
 | 
						||
struct POINT pt1, pt2;
 | 
						||
Örneğin:
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} per = {"Kaan Aslan", 123}, *pper, persons[] = { {"Ali Serce", 123}, {"Ahmet
 | 
						||
Altintarti", 345} };
 | 
						||
Burada "per" struct PERSON türünden bir nesnedir, "pper" struct PERSON türünden bir göstericidir. persons ise
 | 
						||
struct PERSON türünden bir dizidir. Bu biçimde bildirilmiş değişkenler yapı global olarak bildirildiyse global,
 | 
						||
yerel olarak bildirildiyse yerel biçimdedir.
 | 
						||
C standartlarına göre eğer yapı bildirimi ile aynı zamanda o yapı türünden n esne tanımlanıyorsa bu durumda
 | 
						||
yapıya isim verilmeyebilir. Örneğin:
 | 
						||
struct {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} x, y;
 | 
						||
Ancak eğer yapı türündne değişken tanımlanmıyorsa yapıya isim verilmek zorudadır. Örneğin:
 | 
						||
struct {
 | 
						||
/* geçersin */
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
İsimsiz yapıların hepsi farklı bir tür kabul edilir. Yani örneğin:
 | 
						||
struct {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} x;
 | 
						||
struct {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} y;
 | 
						||
Burada x ve y derleyici tarafından aynı türden kabul edilmemektedir. Bu nedenle biz örneğin bunları birbirlerine
 | 
						||
atayamayız.
 | 
						||
İç İçe Yapı Bildirimleri
 | 
						||
206
 | 
						||
 | 
						||
Bir yapının elemanları başka yapı türünden olabilir. Bu tür durumlara "iç içe yapı bildirimleri" denilmektedir.
 | 
						||
Örneğin:
 | 
						||
struct DATE {
 | 
						||
int day, month, year;
 | 
						||
};
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
struct DATE bdate;
 | 
						||
int no;
 | 
						||
};
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
struct DATE {
 | 
						||
int day, month, year;
 | 
						||
};
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
struct DATE bdate;
 | 
						||
int no;
 | 
						||
};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct PERSON per = { "Sacit Bayram", { 12, 10, 1970 }, 2000 };
 | 
						||
printf("%s, %d/%d/%d, %d\n", per.name, per.bdate.day, per.bdate.month, per.bdate.year, per.no);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İç içe yapılarda ilkdeğer verilirken iç yapı ayrıca küme parantezine alınabilir ya da alınmayabilir. Ancak
 | 
						||
okunabilirlik bakımından iç yapıya ilişkin değerlerin küme parantezleri içerisine alınması tavsiye edilir.
 | 
						||
İç içe yapıların alternatif bildirim biçimi de vardır. Bu biçimde iç yapı dış yapının fiziksel olarak içerisinde
 | 
						||
bildirilir. Örneğin:
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
struct DATE {
 | 
						||
int day, month, year;
 | 
						||
} bdate;
 | 
						||
int no;
 | 
						||
};
 | 
						||
 | 
						||
207
 | 
						||
 | 
						||
Burada dış yapı bildirimi içerisinde hem iç yapı bildirilmiş hem de o yapı türünden değişken bildirimi yapılmıştır.
 | 
						||
Bu biçimdeki bildirimde içeride bildirilen yapı (örneğimizde struct DATE) ayrıca dış yapıdan bağımsız olarak da
 | 
						||
kullanılabilir. Örneğin:
 | 
						||
struct PERSON per;
 | 
						||
struct DATE date;
 | 
						||
/* geçerli */
 | 
						||
Yani bu iki iç içe yapı bildirim biçimi semantik olarak tamamen eşdeğerdir. Genel olarak birinci biçimin daha
 | 
						||
okunabilir olduğu söylenebilir.
 | 
						||
typedef Bildirimleri
 | 
						||
typedef anahtar sözcüğü bir tür isminin tam olarak yerini tutabilen alternatif isimler oluşturmak için kullanılır.
 | 
						||
typedef standartlarda bildirimdeki bir yer belirleyici (storage class specifier) biçiminde tanımlanmıştır. Bir
 | 
						||
bildirime typedef anahtar sözcüğü eklenirse bildirimdeki değişken ismi o değişkenin türünü belirten tür ismi haline
 | 
						||
gelir. Örneğin:
 | 
						||
int I;
 | 
						||
Burada I bir değişken ismidir ve int türdendir. Şimdi bildirime typedef ekleyelim:
 | 
						||
typedef int I;
 | 
						||
Burada I artık int türünü temsil eder. Örneğin:
 | 
						||
int a, b, c;
 | 
						||
ile,
 | 
						||
I a, b, c;
 | 
						||
tamamen eşdeğerdir. Örneğin:
 | 
						||
char *STR;
 | 
						||
Burada STR char * türünden bir değişkendir. Şimdi bildirimin başına typedef yerleştirelimr:
 | 
						||
typedef char *STR;
 | 
						||
Artık STR char * türünü temsil etmektedir. Yani:
 | 
						||
char *s;
 | 
						||
ile
 | 
						||
STR s;
 | 
						||
aynı anlamdadır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
208
 | 
						||
 | 
						||
typedef int I;
 | 
						||
typedef char *STR;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
I a;
 | 
						||
STR s = "Ankara";
 | 
						||
a = 10;
 | 
						||
printf("%d\n", a);
 | 
						||
printf("%s\n", s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int A, B, C;
 | 
						||
Burada A, B ve C int türden değişken isimleridir. Bildirimin başına typedef getirelim:
 | 
						||
typedef int A, B, C;
 | 
						||
Burada artık, A, B ve C int türünü temsil eden tür ismidir. Örneğin:
 | 
						||
int I, *PI;
 | 
						||
Burada I int türünden, PI da int * türündendir. Bildirimin başına typedef getirelimr:
 | 
						||
typedef int I, *PI;
 | 
						||
Burada artık I int türünü, PI da int * türünü temsil eder. Örneğin:
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
struct PERSON PER;
 | 
						||
Burada PER struct PERSON türündendir. Şimdi bildirimin başına typedef getirelim:
 | 
						||
typedef struct PERSON PER;
 | 
						||
Artık PER struct PERSON türünü belirten bir tür ismi haline gelmiştir. Yani örneğin:
 | 
						||
struct PERSON per;
 | 
						||
ile,
 | 
						||
PER per;
 | 
						||
aynı anlamdadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
209
 | 
						||
 | 
						||
struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
};
 | 
						||
typedef struct PERSON PER;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
PER per = { "Ali Serce", 123 };
 | 
						||
printf("%s, %d\n", per.name, per.no);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin aynı işlem şöyle de yapılabilirdi:
 | 
						||
typedef struct PERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} PER;
 | 
						||
PER per = {"Ali Serce", 123};
 | 
						||
Örneğin:
 | 
						||
struct {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} PER;
 | 
						||
Burada PER bildirilen yapı türünden bir nesnedir. Başına typedef getirelim:
 | 
						||
typedef struct {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} PER;
 | 
						||
Burada PER bu yapıyı temsil eder. Örneğin:
 | 
						||
PER x;
 | 
						||
PER y;
 | 
						||
x ve y aynı türdendir.
 | 
						||
Örneğin:
 | 
						||
int ARY[3];
 | 
						||
Burada ARY int[3] türündedir. Şimdi başına typedef getirelim:
 | 
						||
typedef int ARY[3];
 | 
						||
Burada artık ARY 3 elemanlı bir int dizi türü ismidir. Yani örneğin:
 | 
						||
210
 | 
						||
 | 
						||
int a[3];
 | 
						||
ile,
 | 
						||
ARY a;
 | 
						||
aynı anlamdadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
typedef int ARY[3];
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
ARY a = { 1, 2, 3 };
 | 
						||
int i;
 | 
						||
for (i = 0; i < 3; ++i)
 | 
						||
printf("%d\n", a[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
void FOO(int a);
 | 
						||
Burada FOO geri dönüş değeri void parametresi int olan bir fonksiyon prototipidir. Başına typedef getirelim:
 | 
						||
typedef void FOO(int a);
 | 
						||
Artık Foo geri dönüş değeri void parametresi int olan bir fonksiyon türünü temsil eder. Yani:
 | 
						||
FOO x, y;
 | 
						||
ile,
 | 
						||
void x(int a);
 | 
						||
void y(int a);
 | 
						||
aynı anlmadadır.
 | 
						||
typedef anahtar sözcüğünün bildirimde başa gelmesi zounlu değildir. Fakat değişken isminin solunda bulunmak
 | 
						||
zorudadır. Örneğin:
 | 
						||
int typedef I;
 | 
						||
typedef int I;
 | 
						||
int I typedef;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
typedef bildirimleri yerel ya da global düzeyde yapılabilir. Eğer yerel düzeyde yapılırsa o typedef ismi yalnızca o
 | 
						||
blokta kullanılabilir. Eğer global düzeyde yapılırsa her yerde kullanılabilir. typedef bidirimleri genellikle globak
 | 
						||
düzeyde yapılmaktadır. Hatta çoğu kez programcılar typedef bildirimlerini başlık dosyalarının içerisine yerleştirir.
 | 
						||
Bazı typedef bildirimleri ile yaratılmak istenen etki #define önişlemci komutuyla ya yaratılabilmektedir. Örneğin:
 | 
						||
#define I int
 | 
						||
211
 | 
						||
 | 
						||
I a, b, c;
 | 
						||
Fakat pek çok tür #defile ile oluşturulamaz. Örneğin:
 | 
						||
typedef int ARY[3];
 | 
						||
Bunun karşılığını #define ile oluşturamayız. Ya da örneğin:
 | 
						||
#define I int *
 | 
						||
I a, b;
 | 
						||
Burada yalnızca a gösterici olur. Halbuki:
 | 
						||
typedef int *I;
 | 
						||
I a, b;
 | 
						||
Burada hem a hem de b göstericidir. Karmaşık türler #define ile zaten oluşturulamaz. Ayrıca #defibe önişlemci
 | 
						||
aşamasına ilişkindir. Halbuki typedef derleme modülü tarafından değerlendirilir.
 | 
						||
typedef Bildirimlerine Neden Gereksinim Duyulmaktadır?
 | 
						||
1) typedef karmaşık türlerin daha kolay yazılmasını sağlar. Örneğin:
 | 
						||
char *PARY[5];
 | 
						||
PARY names = {"ali", "veli,", "selami", "ayse", "fatma"};
 | 
						||
2) typedef okunabilirliği artırmak için de kullanılabilmektedir. Yani bir türe onun kullanım amacına uygun bir isim
 | 
						||
verirsek kodu inceleyen kişiler onu daha iyi anlamlandırabilirler. Örneğin:
 | 
						||
typedef int BOOL;
 | 
						||
BOOL foo()
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
Burada biz foo'nun başarı ya da başarısızlık biçiminde bir geri dönüş değerine sahip olduğunu anlıyoruz.
 | 
						||
3) typedef taşınabilirliği artımak için kullanılabilir. Örneğin bir kütüphane oluşturan ekip bazı türlerin duruma göre
 | 
						||
sistemden sisteme değişebileceğini gördüğünden bunlara typedef ismi atayabilir.
 | 
						||
/* lib.h */
 | 
						||
typedef int HANDLE;
 | 
						||
HANDLE foo(void);
 | 
						||
212
 | 
						||
 | 
						||
Burada HANDLE int türdendir. Programcı fonksiyonu şöyle kullanır:
 | 
						||
HANDLE h;
 | 
						||
h = foo();
 | 
						||
Burada kütüphaneyi yazanlar başka bir sistem için HANDLE değerini long olarak typedef edebilirler. Biz de int
 | 
						||
yerine HANDLE kullandığımızda boşuna kodumuzu değiştirmek zorunda kalmayız. Yani değişiklikten kodumuz
 | 
						||
etkilenmemiş olur. Gerçekten de C/C++'ta yazılmış framework'ler ve kütüphanelerde typedef ile oluşturulmuş tür
 | 
						||
isimlerine çık sık rastlanır.
 | 
						||
C'de Standart Olarak Bildirilen Bazı typedef İsimleri
 | 
						||
C'de de bazı başlık dosyalarında bildirilmiş olan çeşitli typedef isimleri vardır. Bunlar taşınabilirlik sağlamak
 | 
						||
amacıyla oluşturulmuştur. Tabi bunları kullanmak için ilgili dosyasının include edilmesi gerekir. C'nin tüm standart
 | 
						||
typede isimleri <stddef.h> isimli bir dosyada bildirilmiştir. Bu standart typedef isimlerinin hepsinin sonu xxx_t ile
 | 
						||
biter. Fakat bazı typedef isimleri ayrıca başka başlık dosyalarında da bildirilmiş durumdadır. Bunların bazıları
 | 
						||
aşağıda açıklanmaktadır:
 | 
						||
size_t türü: Standartlara göre size_t işaretsiz bir tamsayı türü olarak typedef edilmek zorundadır. size_t tipik
 | 
						||
olarak ilgili sistemdeki bellek büyüklüğünü ifade edecek biçimde derleyicileri yazanlar tarafından uygun bir türe
 | 
						||
typedef edilir. Bazı sistemlerde size_t unsigned int olarak bazı sistemlerde de unsigned long int olarak karşımıza
 | 
						||
çıkabilmektedir. size_t C'de bazı fonksiyonların prototilerinde kullanılmıştır. Örneğin malloc fonksiyonunun
 | 
						||
standartlarda belirtilen prototipi şöyledir:
 | 
						||
void *malloc(size_t size);
 | 
						||
Yani malloc fonksiyonunun parametresi o sistemde derleyiciyi yazanlar size_t türünü nasıl typedef etmişlerse o
 | 
						||
türdendir. Örneğin strlen fonksiyonunun da orijinal prototipi şöyledir:
 | 
						||
size_t strlen(const char *str);
 | 
						||
C programcıları da kendi programlarında genellikle dizi uzunluklarını size_t ile fade ederler. size_t türü <stddef.h>
 | 
						||
dosyasıının yanı sıra <stdio.h>, <stdlib.h> gibi temel başlık dosyalarında typedef edilmiştir.
 | 
						||
ptrdiff_t Türü: C'de iki adres toplanamaz. Ancak aynı türden iki adres çıkartılabilir. İşlem sonucu bir tamsayı
 | 
						||
türündendir. Öyle ki, önce adreslerin sayısal bileşenleri çıkartılır, sonuç adresin türünün uzunluğuna bölünür. Yani
 | 
						||
C'de aynı türden iki adresi çıkarttığımızda sonuç sanki aradaki eleman sayısını vermektedir. Örneğin:
 | 
						||
int a[10];
 | 
						||
Burada &a[5] - &a[0] ifadesi bize 5'i verir. Aslında a dizisi hangi türden olursa olsun bu işlem 5 değerini verecektir.
 | 
						||
Aynı türden iki adresi çıkarttığımızda sonuç hangi türdendir? İşte standartlar sonucun ptrdiff_t türünden olacağını
 | 
						||
söyler. ptrdiff_t işaretli bir tamsayı türü olarak typedef edilmelidir. Pek çok derleyicide ptrfdiff_t int ya da long
 | 
						||
türdendir.
 | 
						||
C'de standart olan birkaç tür daha ileride ele alınacaktır.
 | 
						||
C'de Bildirim İşleminin Genel Biçimi
 | 
						||
Daha önce biz bildirimin genel biçimini şöyle görmüştük:
 | 
						||
<tür> <değişken listesi>;
 | 
						||
213
 | 
						||
 | 
						||
Aslında bildirimin genel biçimi şöyledir:
 | 
						||
[yer belirleyici anahtar sözcükler] [tür niteleyici anahtar sözcükler] [tür belirten sözcükler]
 | 
						||
<değişken listesi>;
 | 
						||
 | 
						||
Görüldüğü gibi bildirimde türün yanı sıra yer belirleyiciler (storage class specifers) ve tür niteleyiciler (type
 | 
						||
qualifiers) de kullanılmaktadır. Örneğin:
 | 
						||
 | 
						||
Genel olarak bildirimde yer belirleyicileri, tür niteleyicileri ve tür belirten sözcükler yer değiştirebilir. Örneğin
 | 
						||
aşağıdaki iki bildirim eşdeğerdir:
 | 
						||
static const int a = 10;
 | 
						||
const int static a = 10;
 | 
						||
Fakat genellikle programcılar önce yer belirleyici, sonra tür niteleyici en sonra da tür belirten anahtar sözcükleri
 | 
						||
yzamaktadır.
 | 
						||
C'de yer belirleyici (storage class Specifiers) olarak aşağıdaki 5 anahtar sözcük kullanılabilir:
 | 
						||
auto
 | 
						||
static
 | 
						||
register
 | 
						||
extern
 | 
						||
typedef
 | 
						||
Aslında typedef anahtar sözcüğünün yer belirleyici işlevi yoktur. Tasarımcılar typedef'i başka bir yere
 | 
						||
yerleştiremeyince buraya yerleştirmişlerdir. Bildirimde en fazla 1 tane yer belirleyici anahtar sözcük
 | 
						||
bulundurulabilir. C'de ayrıca iki de tür niteleyici (type qualifier) anahtar sözcük vardır:
 | 
						||
const
 | 
						||
volatile
 | 
						||
C90'da eğer bildirimde tür belirten sözcük kullanılmamışsa fakat yer belirleyici ya da tür niteleyici anahtar
 | 
						||
sözcüklerden en az biri kullanılmışsa bu durumda default olarak tür belirten sözcük int kabul edilmektedir. Yani
 | 
						||
örneğin:
 | 
						||
const a = 10;
 | 
						||
bildirimi geçerlidir. Burada a'nın türü int'tir. Ancak bildirimde yalnızca değişken ismi bulundurulamaz. Örneğin:
 | 
						||
a, b = 20;
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
Böyle bir bildirim geçersizdir. Halbuki C99 ve C11'de bildirimde mutlaka tür belirten sözcük bulundurulmak
 | 
						||
zorundadır.
 | 
						||
214
 | 
						||
 | 
						||
Yer Belirleyici Anahtar Sözcüklerin İşlevleri
 | 
						||
auto Belirleyicisi: auto aslında işlevi olmayan bir anahtar sözcük durumundadır. auto belirleyicisi yerel
 | 
						||
değişkenlerle ya da parametre değişkenleriyle kullanılabilir. Global değişkenlerle kullanılamaz. auto değişkenin
 | 
						||
ilgili blok bittiğinde bellekten atılacağını (yani stack'te yer ayrılacağını) vurgulamaktadır.
 | 
						||
extern Belirleyicisi: Aslında biz tek bir .c dosyasını derlemek zorunda değiliz. Bir proje çok uzun olabilir ve
 | 
						||
programcı bunu çeşitli dosyalara yaymak isteyebilir. Bu durumda .c dosyaları bağımsız olarak derlenir. Oluşan
 | 
						||
object modüller birlikte link edilerek çaıştırılabilen dosya (executable) oluşturulur. Örneğin projemiz a1.c a2.c a3.c
 | 
						||
biçiminde üç kaynak dosyadan oluşuyor olsun:
 | 
						||
 | 
						||
Görüldüğü gibi linker aslında birden fazla object modülü çalıştırılabilen dosyaya dönüştürebilmektedir. C
 | 
						||
standartlarında projeyi oluşturan kaynak dosyalara "translation unit" denilmektedir. Programcılar ise genellikle bıu
 | 
						||
dosyalara "modül" demektedir.
 | 
						||
Peki bir programı neden birden fazla kaynak dosyaya bölerek yazmak isteriz? Birinci neden karmaşıklığı
 | 
						||
azaltmaktır. Büyük bir projenin birden fazla dosyaya bölünmesi yönetlebilirliği artırır. Örneğin farklı kişiler garklı
 | 
						||
dosyalar üzerinde çalışabilirler. Sonra bunları birleştirebilirler. İkinci neden derleme zamanının azaltılmasıdır.
 | 
						||
Şöyle ki, örneğin 100000 satırlık bir projeyi tek bir kaynak dosya olarak organizae etmiş olalım. Proje içerisindeki
 | 
						||
bir satırı bile değiştirsek tüm projeyi yeniden derlememiz gerekir. Halbuki bunu 10000 satırlık 10 dosyaya ayırsak
 | 
						||
yalnızca değişikliğin yapıldığı modülü derlememiz yeterli olur. Tabi hep berber her defasında bir link işlemi yine
 | 
						||
yapılmak zorundadır. Tabi link işlemi derleme işlemine göre daha kısa bir işlemdir.
 | 
						||
B irden fazla modülle çalışma nasıl yapılmaktadır? Derleyicilerin komut satırlı biçimlerinde önce her modülü
 | 
						||
derleyip sonra birlikte link işlemi yapılabilir. gcc derleyicisinde -c seçeneği Microsoft'un cl derleyicisinde /c
 | 
						||
seçeneği yalnızca derleme yapar link etme işlemini yapmaz. Daha sonra yalnızca derlenmiş bu modüller link
 | 
						||
edilebilir. gcc programına object modülleri verirsek zaten o link işlemini yapmaktadır. Microsoft'un linker
 | 
						||
programı link.exe isimli programdır. Örneğin:
 | 
						||
gcc -c a1.c
 | 
						||
gcc -c a2.c
 | 
						||
gcc -c a3.c
 | 
						||
Buradan a1.o, a2.o ve a3.o dosyaları elde edilecektir. Sonra bunlar aşağıdaki gibi link edilebilirler:
 | 
						||
gcc -o app a1.o a2.o a3.o
 | 
						||
Bunun yerine aynı işlem şöyle de yapılabilirdi:
 | 
						||
gcc -o app a1.c a2.c a3.c
 | 
						||
Biz gcc'ye birden fazla .c dosyası verirsek gcc onları önce bağımsız olarak derler sonra object modülleri link
 | 
						||
215
 | 
						||
 | 
						||
ederek çalıştırılabilir (executable) dosyayı oluşturur.
 | 
						||
IDE'lerde projeye birden fazla kaynak dosya eklenirse bunlar otomatik olarak derlenir ve object modüller link
 | 
						||
edilerek çalıştırılabilir dosya elde edilir.
 | 
						||
Birden fazla dosya ile çalışırken bir dosyadaki global değişkenin diğer dosyalardan da erişilebilmesi gerekir.
 | 
						||
Ancak her dosya bağımsız olarak derlendiği için diğer dosyada global olarak tanımlanmış değişkenleri derleyici
 | 
						||
tanımaz. Yani derleyici bir dosya derlenirken hangi dosyaların projenin parçalarını oluşturduğunu bilmemektedir.
 | 
						||
Projeyi oluşturan her kaynak dosya bağımısız olarak derlenir. İşte başka bir müdlde global olarak tanımlanmış olan
 | 
						||
bir değişken kullanılacaksa onun için extern bildiriminin yapılması gerekir. Örneğin:
 | 
						||
extern int g_x;
 | 
						||
Global değişkenin yalnızca bir modülde global olarak tanımlanması fakat onun kullanıldığı tüm modüllerde extern
 | 
						||
olarak bildirilmesi gerekir. Global tanımlamanın hangi modülde yapıldığının bir önemi yoktur.
 | 
						||
extern bir tanımlama değil bildirim işlemidir. extern belirleyicisini gören derleyici değişkenin başka bir modülde
 | 
						||
tanımlandığını anlar.. Ona göre kod üretir. Object modül içerisine linker için şöyle bir mesaj yazar: "Sayın linker
 | 
						||
bu değişken başka bir modülde tanımlanmış. Ben durumu idare ettim. Fakat sen proje link edilirken bu değişkenin
 | 
						||
hangi modülde tanımlandığını bul, bunu onla ilişkilendir. Bu değişken o değişkendir." Linker da link işlemine
 | 
						||
sokulan tüm modüllere bakarak bu birleştirmeyi yapar.
 | 
						||
Burada dikkat edilmesi gerek durum global değişkenin yalnızca tek bir modülde global tanımlamasının yapılması
 | 
						||
gerekliliğidir. eğer global değişken birden fazla modülde global tanımlanırsa her modül sorunsuz derlenir fakat link
 | 
						||
aşamasında aynı isimli birden fazla global tanımlama yapılmış olması nedeniyle error oluşur. Benzer biçimde
 | 
						||
global değişken her modülde extern bildirilirse yine her modül başarılı olarak derlenir ancak link aşamasında
 | 
						||
global tanımlamaya rastlanmadığından error oluşur.
 | 
						||
Bir değişkenin başka modüllerden kullanılabilirliğine o değişkenin bağlama durumu (linkage) denilmektedir.
 | 
						||
Değişkenin bağlama durumu üç biçimde olabilir:
 | 
						||
1) Dışsal Bağlama Durumu (External Linkage): Burada değişken başka modüllerden kullanılabilir. Global
 | 
						||
değişkenler default olarak dışsal bağlamaya sahiptir. Global değişkenleri extern ile bildirirsek de dışsal bağlama
 | 
						||
oluşur. Fakat extern bir tanımlama değildir. Global değişkenin tek bir tanımlamasını olması gerektiği için tek bir
 | 
						||
modülde global tanımlama yapılmalı diğer modüllerde extern bildirilmelidir.
 | 
						||
2) İçsel Bağlama Durumu (Internal Linkage): İçsel bağlamaya sahiğ değişkenler yalnızca bir modülün her
 | 
						||
yerinde kullanılabilirler. Ancak farklı modüllerden kullanılamazlar. Bir global değişken static anahtar sözcüğü ile
 | 
						||
bildirilirse onun bağlaması içsel bağlama olur. Artık diğer modüllerden o kullanılamaz.
 | 
						||
Özetle C'de bir global değişken default olarak dışsal bağlamaya sahiptir. Ancak onu static anahtar sözcüğüyle
 | 
						||
bildirirsek içsel bağlamaya sahip olur.
 | 
						||
3) Bağlamasız Durum (No Linkage): C'de yerel değişkenlerin ve parametre değişkenlerinin bağlaması yoktur.
 | 
						||
Bir global değişken C'de aynı kaynak dosyada hem global olarak tanımlanmış olabilir hem de extern olarak
 | 
						||
bildirilmiş olabilir. Bu durum hata oluşturmaz. Değişkenin bağlama durumu dışsal bağlamadır. Bu durumda
 | 
						||
değişken için yer ayrılır. Yani extern bildiriminin bir etkisi olmaz. Örneğin:
 | 
						||
int g_a;
 | 
						||
 | 
						||
/* global tanımlama */
 | 
						||
 | 
						||
extern int g_a;
 | 
						||
 | 
						||
/* Ggobal bildirim */
 | 
						||
216
 | 
						||
 | 
						||
Buradaki etki aşağıdakiyle eşdeğerdir:
 | 
						||
int g_a;
 | 
						||
Bir global değişken extern belirleyicisi kullanılarak bildirilmiş olsun ve ilkdeğer de verilmiş olsun. Örneğin:
 | 
						||
extern int g_a = 10;
 | 
						||
C standartlarına göre artık bu bildirim aynı zamanda bir tanımlamadır. Yani değişken için yer ayrılır. Başka bir
 | 
						||
deyişle yukarıdaki bildirim aşağıdaki ile eşdeğerdir:
 | 
						||
int g_a = 10;
 | 
						||
extern bildirimi yerel bir blokta yapılabilir. Tabi bu durumda o değişken yerel bir değişken olmaz. Başka
 | 
						||
modüldeki global bir değişken anlamındadır. Fakat bu isim yalnızca o blokta kullanılabilir. Örneğin:
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
extern int a;
 | 
						||
...
 | 
						||
}
 | 
						||
void bar(void)
 | 
						||
{
 | 
						||
a = 10;
 | 
						||
}
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Burada yerel bloktaki extern belirleyicisi ile bildirilmiş değişken aslında yerel bir değişken değildir. Dışsal
 | 
						||
bağlamaya sahip muhtemelen başka modülde tanımlanmış bir global değişkendir. Ama biz bu ismi yalnızca foo
 | 
						||
bloğunda kullanabiliriz.
 | 
						||
C standartlarına göre farklı modüllerde aynı global değişkenin ilkdeğer verilmeden tanımlanması yapılabilir. Buna
 | 
						||
standartlarda "tentative declaration" denilmektedir. Bu durumda her ne kadar global değişkenin her modül için
 | 
						||
tanımlaması object modüle yazılsa da linker bunların tek bir kopyasını çalıştırılabilen dosyaya yazmaktadır.
 | 
						||
Dolayısıyla aşağıdaki kod geçerlidir:
 | 
						||
 | 
						||
Ancak ilkdeğer verme durumunda bu geçerlilik kaybolur. Aşağıdaki derlemelerde link aşamasında error oluşur:
 | 
						||
 | 
						||
217
 | 
						||
 | 
						||
Peki bir modülde tanımlanmış bir fonksiyonu diğer modüllerden çağırabilir miyiz? Fonksiyonlar teknik olarak
 | 
						||
global değişkenler gibidir. Fonksiyonların da default bağlama biçimleri dışsal bağlamadır. Yani onlar başka bir
 | 
						||
modülden kullanılabilirler. Ancak kullanmadan önce bir fonksiyonun tanımlamasının ya da prototip bildirimin
 | 
						||
derleyici tarafından görülmesi gerekir. Bu nednele biz bir modülde tanımladığımız fonksiyonu diğer modülden
 | 
						||
kullanmadan önce fonksiyon prototipini bulundurmalıyız. eğer prototip bulundurmadan foo gibi bir fonksiyonu
 | 
						||
çağırırsak derleyici onun aşağıdaki gibi bir prototipe sahip olduğunu varsayar:
 | 
						||
int foo();
 | 
						||
Örneğin:
 | 
						||
/* a1.c */
 | 
						||
#include <stdio.h>
 | 
						||
extern int g_x;
 | 
						||
void foo(void);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
g_x = 10;
 | 
						||
foo();
 | 
						||
printf("%d\n", g_x);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
/* a2.c */
 | 
						||
#include <stdio.h>
 | 
						||
int g_x;
 | 
						||
void foo()
 | 
						||
{
 | 
						||
g_x = 100;
 | 
						||
}
 | 
						||
 | 
						||
Prototiplerde extern belirleyicisinin kullanılmasına gerek yoktur. Fakat kullanılsa da sorun oluşmaz.
 | 
						||
Çok modüllü derlemelerde önerilen durum. Proje için ortak bir başlık dosyası hazırlamak ve tüm modeüllerden
 | 
						||
bunu include etmektir. Bu başlık dosyasında yer kaplayan hiçbir tanımlama bulunmamalıdır. Yalnızca #'li
 | 
						||
önişlemci bidirimleri, extern bildirimler, fonksiyon prototipleri, yapı bildirimleri vs. bulunmalıdır. Global
 | 
						||
değişkenlerin ayrıca bir dosyada global tanımlaması yapılmalıdır. Örneğin:
 | 
						||
/* project.h */
 | 
						||
#ifndef PROJECT_H_
 | 
						||
#define PROJECT_H_
 | 
						||
void foo(void);
 | 
						||
void bar(void);
 | 
						||
extern int g_a;
 | 
						||
extern int g_b;
 | 
						||
#endif
 | 
						||
 | 
						||
218
 | 
						||
 | 
						||
/* a1.c */
 | 
						||
#include <stdio.h>
 | 
						||
#include "project.h"
 | 
						||
int g_a;
 | 
						||
int g_b;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
foo();
 | 
						||
bar();
 | 
						||
printf("%d, %d\n", g_a, g_b);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
/* a2.c */
 | 
						||
#include <stdio.h>
 | 
						||
#include "project.h"
 | 
						||
void foo()
 | 
						||
{
 | 
						||
g_a = 10;
 | 
						||
}
 | 
						||
/* a3.c */
 | 
						||
#include <stdio.h>
 | 
						||
#include "project.h"
 | 
						||
void bar()
 | 
						||
{
 | 
						||
g_b = 20;
 | 
						||
}
 | 
						||
 | 
						||
static Belirleyicisi: static belirleyicisi yer değişkenlerle ya da global değişkenlerle kullanılabilir. Fakat parametre
 | 
						||
değişkenleriyle kullanılamaz. Static yerel ve static global değişkenler tamamen farklı anlamlara gelirler.
 | 
						||
static belirleyicisi yerel değişkenlerle kullanılırsa bu durum o değişkenin ömrünün static ömürlü olduğunu gösterir.
 | 
						||
Yani blok bittiğinde değişken yok edilmez. Program çalışmaya başladığında bellekte yer kaplar, program
 | 
						||
sonlanana kadar bellekte kalır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void foo(void)
 | 
						||
{
 | 
						||
static int count = 1;
 | 
						||
printf("%d\n", count);
 | 
						||
++count;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
foo();
 | 
						||
foo();
 | 
						||
foo();
 | 
						||
 | 
						||
219
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
static yerel dğeişkene ilkdeğer verilmemişse içerisinde sıfır bulunur. Verilen ilkdeğer derleme aşasında
 | 
						||
değerlendirilir. Program yüklendiğinde static yerel değişken o değerle başlar. Artık bloğa her girişte bu ilkdeğer
 | 
						||
tekrar atanmaz.
 | 
						||
Peki static yerel değişkenin global değişkenden ne farkı vardır? Bunların ömürleri aynı olmasına karşın faaliyet
 | 
						||
alanları farklıdır. static yerel değişkenler blok faaliyet alanı kuralına uyarlar. Global değişkenler ise dosya faaliyet
 | 
						||
alanına sahiptir.
 | 
						||
Bir fonksiyonun static yerel bir nesnenin ya da dizinin adresiyle geri dönmesinde bir sakınca yoktur. Çünkü
 | 
						||
programdan çıkılsa bile o nesne yaşamaya devam etmektedir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
char *getname(void)
 | 
						||
{
 | 
						||
static char name[64];
 | 
						||
printf("Adi soyadi:");
 | 
						||
gets(name);
 | 
						||
return name;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *nm;
 | 
						||
nm = getname();
 | 
						||
puts(nm);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
static anahtar sözcüğü global değişkenlerle kullanılırsa bu durumda global değişkenin bağlama durumu "içsel
 | 
						||
bağlama (internal linkage)" olur. Yani böyle deişkenler diğer modüllerden hiçbir biçimde kullanılamazlar. Başka
 | 
						||
bir modülde aynı isimli global nesne olsa bile bu durum soruna yol açmaz.
 | 
						||
Bir global değişken aynı modülde hem static hem de extern olarak bildirilebilir mi? Tabi bu durum saçmadır.
 | 
						||
Böyle bir şeyden kaçınmak gerekir. Ancak standartlara göre bir global değişken önce static ile sonra extern ile
 | 
						||
bildirilirse nesnenin içsel bağlamaya sahip olduğu kabul edilir. Fakat önce extern sonra static ile bildirilirse bu
 | 
						||
durum tanımsız davranışa yol açar.
 | 
						||
register Belirleyicisi: register belirleyicisi global değişkenlerle kullanılamaz. Yerel ya da parametre değişkenleri
 | 
						||
ile kullanılabilir. Aslında uzunca bir süredir bu belirleyicinin ciddi bir işlevi kalmamıştır. Çünkü derleyicilerin kod
 | 
						||
optimizasyonları son derece gelişmiş durumdadır. Dolayısıyla bu belirleyicinin sağlayacağı fayda tartışmalıdır.
 | 
						||
CPU içerisinde ismine "yazmaç (register)" denilen küçük bellek bölgeleri vardır. CPU ile RAM bağlantılıdır. CPU
 | 
						||
aritmetik ya da mantıksal ya da karşılaştırma işlemlerini yapmak için operandları yazmaçlardan alır. Dolayısıyla
 | 
						||
örneğin:
 | 
						||
c = a + b;
 | 
						||
gibi bir işlem aşağıdaki gibi makine komutlarıyla yapılmaktadır:
 | 
						||
220
 | 
						||
 | 
						||
mov
 | 
						||
mov
 | 
						||
add
 | 
						||
mov
 | 
						||
 | 
						||
reg1, a
 | 
						||
reg2, b
 | 
						||
reg1, reg2
 | 
						||
c, reg1
 | 
						||
 | 
						||
işte register anahtar sözcüğü söz konusu değişkenin RAM yerine CPU içerisindeki yazmaçlarda tutulmasını
 | 
						||
istemek için kullanılmaktadır. Böylece bu değişkenler işleme sokulurken boşuna yeniden yazmaçlara alınmak
 | 
						||
zorunda kalınmaz. Şüphesiz çok yoğun işlem gören (örneğin döngü değişkenleri gibi) nesnelerin yazmaçlarda
 | 
						||
tutulması anlamlı olur.
 | 
						||
register belirleyicisi bir emir değildir. Rica niteliğindedir. Yani biz bir nesneyi aşağıdaki gibi tanımlamış olalım:
 | 
						||
register int x;
 | 
						||
Derleyici bunu yazmaçlarda tutmak zorunda değildir. İsterse bunu normal nesnelerde olduğu gibi yine RAM'de
 | 
						||
tutabilir. Bunun için de programcıya herhangi bir bildirimde (uyarı gibi) bulunmaz. Zaten modern derleyiciler
 | 
						||
gerekli gördükleri nesneleri gerektiği kadar yazmaçlarda tutacak biçimde kod optimizasyonu uygulamaktadır. Bu
 | 
						||
belirleyiciyi kullanmak yerine derleyicinin optimizasyon mekanizmasına güvenmek daha uygun gözükmektedir.
 | 
						||
CPU'lardaki yazmaç miktarları CPU'dan CPU'ya değişebilmektedir. Genel olarak RISC tabanlı mimarilere sahip
 | 
						||
olan işlemcilerde CISC tabanlı mimarilere sahip olan işlemcilere göre daha fazla yazmaç bulunma eğilimindedir.
 | 
						||
Bir nesnenin programcının isteği ile yazmaçta tutulması eldeki yazmaç sayısını azaltabilir. Bunu farkeden derleyici
 | 
						||
bu belirleyiciyi hiç dikkate almayabilir.
 | 
						||
Tür Niteleyici Anahtar Sözcükler
 | 
						||
C'de tür niteleyici iki anahtar sözcük vardır: const ve volatile. Bunların her ikisi de bildirimde kullanılabilir.
 | 
						||
const Belirleyicisi
 | 
						||
const bir nesne ilkdeğer verilirerek bildirilir. Artık bundan sonra bu nesneye değer atanamaz. Eğer const bir
 | 
						||
nesneye değer atanmak istenirse derleme aşamsında "error" oluşur. Örneğin:
 | 
						||
const int x = 10;
 | 
						||
printf("%d\n", x);
 | 
						||
x = 20;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
const bir nesnenin ilkdeğer verilmeden tanımlanması C++'ta geçersizdir. C'de geçerli olsa da anlamsızdır. (İlkdeğer
 | 
						||
verilmediğine göre ve bundan sonra değer de atanamayacağından böyle bir tanımlamanın bir anlamı olamaz.)
 | 
						||
221
 | 
						||
 | 
						||
const anahtar sözcüğü dekleratör ilişkin değildir. Türe ilişkin kabul edilir. Yani:
 | 
						||
int const x = 10, y = 20;
 | 
						||
Burada hem x hem de y const durumdadır.
 | 
						||
Peki const belirleyicisinin ne faydası vardır? const temel olarak okunabilirliği ve anlaşılırlığı kuvvetlendirmek için
 | 
						||
kullanılır. Örneğin:
 | 
						||
const int personel_sayisi = 150;
 | 
						||
gibi bir tanımlamada koda bakan kişi program içerisinde personel sayısının değişmeyeceğini anlayabilir. const
 | 
						||
belirleyicisi kodu yazanın yanlışlıkla bir nesneye atama yapmasını da engelleyebilmektedir. Bu durumda derleme
 | 
						||
aşamasında hatayla karşılaşan programcı hatasını anlayabilir. const belirleyicisi bazı durumlarda derleyicilere bazı
 | 
						||
optimizasyonları yapmaya olanak sağlayabilir.
 | 
						||
const belirleyicisi ile #define sembolik sabitleri benzer amaçlarla kullanılabilmektedir. Örneğin:
 | 
						||
#define personel_sayisi 150
 | 
						||
const int personel_sayisi = 120;
 | 
						||
Fakat sembolik sabitler gerçekten bir sabittir. Halbuki const nesneler gerçekten birer nesnedir. Örneğin biz const
 | 
						||
nesnelerin adreslerini alabiliriz. Fakat sembolik sabitlerin adreslerini alamayız.
 | 
						||
Bir dizinin tamamı const olabilir. Bu durumda biz dizinin hiçbir elemanını değiştiremeyiz Örneğin:
 | 
						||
const int a[5] = {10, 20, 30, 40, 50};
 | 
						||
a[3] = 100;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
const bir dizinin tüm elemanları const bir nesne gibidir.
 | 
						||
Bir yapı nesnesi de const olabilir. Bu durumda yapının hiçbir elemanını değiştiremeyiz. Örneğin:
 | 
						||
struct COMPLEX {
 | 
						||
double real, imag;
 | 
						||
};
 | 
						||
const struct COMPLEX z = {3, 2};
 | 
						||
z.real = 5;
 | 
						||
z.imag = 7;
 | 
						||
 | 
						||
/* geçersiz! */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Yapı bildiriminde belli elemanlar const yapılabilir. Bu durumda yapı nesnesinin tamamaı const olmasa bile bu
 | 
						||
elemanları daha sonra değiştiremeyiz. Örneğin:
 | 
						||
struct SAMPLE {
 | 
						||
int a;
 | 
						||
const int b;
 | 
						||
222
 | 
						||
 | 
						||
};
 | 
						||
struct SAMPLE s = {10, 20};
 | 
						||
s.a = 30;
 | 
						||
s.b = 40;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli değil! */
 | 
						||
 | 
						||
Genellikle programcılar yapı elemanlarını const yapmazlar. Çünkü bu durum karmaşıklığa yol açmaktadır.
 | 
						||
const belirleyicisi en çok göstericilerle kullanılır. Böyle göstericilere const göstericiler denilmektedir.
 | 
						||
const Göstericiler
 | 
						||
Bir gösterici bildiriminde iki nesne söz konusu olur: Göstericinin kendisi ve onun gösterdiği yer. Bunların
 | 
						||
gangisinin const olacağına göre const göstericiler üçe ayrılmaktadır:
 | 
						||
1) Kendisi değil gösterdiği yer const olan const göstericiler
 | 
						||
2) Gösterdeiği yer değil kendisi const olan const göstericiler
 | 
						||
3) Hem kendisi hem de gösterdiği yer const olan const göstericiler
 | 
						||
En çok kullanılan ve okunabilirlik bakımından en faydalı olan const göstericiler kendisi değil gösteridği yer const
 | 
						||
olan const göstericilerdir. Bir const göstericinin hangi gruba girdiği const anahtar sözcüğünün yerine bakılarak
 | 
						||
karar verilir. Eüer const anahtar sözcüğü * atomunun solundaysa bu gösterdiği yer const olan const göstericidir.
 | 
						||
Örneğin:
 | 
						||
const int *pi;
 | 
						||
 | 
						||
/* int const *pi ile aynı */
 | 
						||
 | 
						||
Burada pi'nin kendisi const değildir. *pi yani pi'nin gösterdiği yer const durumdadır.
 | 
						||
Eğer const anahtar sözcüğü * atomunun sağına getirilirse bu durumda göstericinin kendisi const olur. Örneğin:
 | 
						||
int * const pi = ptr;
 | 
						||
Burada const pi'yi nitelemektedir. Yani burada pi const durumdadır. *pi const değildir. Nihayaet const anahtar
 | 
						||
sözcüğü iki yerde de bulunabilir:
 | 
						||
const int * const pi = ptr;
 | 
						||
Burada hem pi hem de *pi const durumdadır.
 | 
						||
Kendisi Değil Gösterdiği Yer const Olan const Göstericiler
 | 
						||
Bu tür göstericilerin bildirimlerinde const anahtar sözcüğü * atomunun soluna getirilir. Örneğin:
 | 
						||
const int *pi;
 | 
						||
Tabi aşağıdaki bildirim de eşdeğerdir:
 | 
						||
int const *pi;
 | 
						||
Burada göstericinin kendisi const değildir. Yani onun içerisine istedğimiz bir adresi istediğimiz zaman atayabiliriz.
 | 
						||
Ancak onun gösterdiği yere atama yapamayız. Örneğin:
 | 
						||
223
 | 
						||
 | 
						||
int a = 10, b = 20;
 | 
						||
const int *pi;
 | 
						||
pi = &a;
 | 
						||
*pi = 30;
 | 
						||
 | 
						||
/* geçerli, pi const değil */
 | 
						||
/* Geçersiz göstericinin gösterdiği yer const */
 | 
						||
 | 
						||
pi = &b;
 | 
						||
*pi = 40;
 | 
						||
 | 
						||
/* geçerli pi const değil */
 | 
						||
/* Geçersiz göstericinin gösterdiği yer const */
 | 
						||
 | 
						||
Bu tür const göstericilerde tam olarak göstericinin gösterdiği yer const değil o gösterici ile erişilen her yer const
 | 
						||
biçimdedir. Örneğin:
 | 
						||
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
const int *pi;
 | 
						||
pi = a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
pi[3] = 20;
 | 
						||
 | 
						||
/* geçersiz*/
 | 
						||
 | 
						||
pi[1000] = 30;
 | 
						||
 | 
						||
/* Dizi taşması var ama derleme aşamasında yiner error oluşur */
 | 
						||
 | 
						||
pi += 500;
 | 
						||
*pi = 40;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
Gösteridiği yer const olan const göstericiler en çok karşımıza fonksiyon parametresi olarak çıkarlar. Örneğin:
 | 
						||
void foo(const int *pi)
 | 
						||
{
 | 
						||
/* ... */
 | 
						||
}
 | 
						||
 | 
						||
Burada foo bizden int türden bir adres alır. Parametre const bir gösterici olduğuna göre foo aldığı adresteki nesneyi
 | 
						||
değiştirmez. Onu kullanabilir ama değiştirmez. Böylece fonksiyona bakan kişi fonksiyonun adresini verdiği
 | 
						||
nesneyi değiştirip değiştirmeyeceini anlar. Gösterici parametrelerdeki const'luk çok önemlidir. Örneğin strlen
 | 
						||
fonksiyonunun orijinal prototipinde parametre const bir göstericidir:
 | 
						||
size_t strlen(const char *str);
 | 
						||
Bu prototipiten biz şunu anlarız: Biz strlen fonksiyonuna char türden bir dizinin başlangıç adresini vereceğiz.
 | 
						||
Fonksiyon bu adresteki yazıyı değiştirmeyecek. Yalnızca onu okuyacak.
 | 
						||
Göstericilerde const'luk çok kararlı bir biçimde kullanılmalıdır. Böylece bir fonksiyonun parametresinin const
 | 
						||
olmayan bir gösterici olduğunu gördüğümüzde artık biz onun verdiğimiz adresteki bilgiyi değiştireceğini anlarız.
 | 
						||
(Çünkü değiştirmeyecek olsaydı zaten onu yazan göstericiyi const yapardı.). Çeşitli örnekler verelim:
 | 
						||
- strcpy fonksiyonun orijinal prototipinde birinci parametre const olmayan bir gösterici, ikinci parametre const bir
 | 
						||
göstericidir:
 | 
						||
char *strcpy(char *dest, const char *source);
 | 
						||
Bu prototipe baktığımızda bizim fonksiyona iki char türden adres göndereceğimiz anlaşılır. Fonksiyon birinci
 | 
						||
argümanla gönderdiğimiz adresteki bilgiyi değiştirecektir. Ancak ikinci argümanla gönderdiğimzi adresteki bilgiyi
 | 
						||
değiştirmeyecektir.
 | 
						||
- strchr fonksiyonun parametresi const bir göstericidir:
 | 
						||
224
 | 
						||
 | 
						||
char *strchr(const char *s, int c);
 | 
						||
- strcat fonksiyonunun birinci parametresi const olmayan bir gösterici ikinci parametresi const bir göstericidir:
 | 
						||
char *strcat(char *dest, const char *source);
 | 
						||
- Bir diziyi sıraya dizen sort isimli bir fonksiyon yazacak olsak onun parametresi const gösterici olamaz:
 | 
						||
void sort(int *pi, size_t size);
 | 
						||
- puts fonksiyonun parametresi const bir gösterici, gets fonksiyonun parametresi const olmayan bir göstericidir:
 | 
						||
void puts(const char *str);
 | 
						||
char *gets(char *str);
 | 
						||
Yapı nesnelerinin adresini alan fonksiyonların gösterici parametreleri const ise fonksiyonlar bizden aldıkları yapı
 | 
						||
neesnesinin elemanlarını kullanırlar fakat değiştirmezler. Fakat bu fonksiyonların gösterici parametreleri const
 | 
						||
olmayan bir göstericiyse bu durumda fonksiyon yapı nesnesinin içini doldurmaktadır. Yani eğer fonksiyonun yapı
 | 
						||
gösterici parametresi const ise nesnenin içini biz doldururuz o kullanır. eğer const olmayan bir gösteric ise o
 | 
						||
doldurur biz kullanırız. Çeşitli örnekler verelim:
 | 
						||
- Örneğin sistemin zamanını alan GetSystemTime isimli bir fonksiyon olsun. Bu fonksiyon bizden TIME isimli bir
 | 
						||
yapı nesnesinin adresini alsın onun içerisine sistem zamanını yerleştirsin. Bu durumda fonksiyonun gösterici
 | 
						||
parametresi const olmayan bir gösterici olmalıdır:
 | 
						||
struct TIME {
 | 
						||
int hour, minute, second;
 | 
						||
};
 | 
						||
void GetSystemTime(struct TIME *t);
 | 
						||
...
 | 
						||
struct TIME t;
 | 
						||
GetSystemTime(&t);
 | 
						||
- Sistem zamanını bizim belirdiğimiz zaman olacak biçimde değiştiren SetSystemTime fonksiyonunun yapı
 | 
						||
gösterici parametresi const gösterici olmalıdır:
 | 
						||
void SetSystemTime(const TIME *t);
 | 
						||
- Veritabanından birisini ismiyle arayıp bulursa bulduğu kişinin bilgilerini PERSON isimli bir yapı nesnesine
 | 
						||
yerleştren fonksiyonun parametrelerinin const'luk durumu şöyledir:
 | 
						||
BOOL FindPerson(const char *name, struct PERSON *per);
 | 
						||
- Seri portun setting değerlerini alıp bize veren fonksiyonun yapı gösterici parametresi const olmayan bir gösterici
 | 
						||
olmalıdır:
 | 
						||
void GetSerialPortSettings(struct SERIAL *serial);
 | 
						||
- Bunun tam tersi işlemini yapan fonksiyonun parametresi const gösterici olmalıdır:
 | 
						||
void SetSerialPortSettings(const struct SERIAL *serial);
 | 
						||
225
 | 
						||
 | 
						||
Fonksiyonların gösterici parametrelerindeki const'luğa çok önem verilmelidir. Maalesef acemi C programcıları
 | 
						||
bunlara önem vermezler ve acemilikleri hemen anlaşılır.
 | 
						||
Fonksiyonun gösterici olmayan parametrelerinin const'luğunun hiçbir önemi yoktur. Bunların const yapılması da
 | 
						||
amaç bakımından faydasız ve saçmadır. Örneğin:
 | 
						||
void foo(const int x);
 | 
						||
Burada bildirim geçersiz değildir. Ancak böyle const bildirimin kimseye hiçbir faydası yoktur. Şöyle ki:
 | 
						||
Fonksiyonun parametre değişkenini değiştirip değiştirmeyeceği bizi ilgilendirmez. Önemli olan bizden aldığı
 | 
						||
nesnelerin değerlerini değiştirip değiştirmeyeceğidir. Bu nedenle parametrelerdeki const'luk yalnızca göstericiler
 | 
						||
söz konusu olduğunda anlamlı olur.
 | 
						||
const Nesnelerin Adresleri ve const Adres Dönüştürmeleri
 | 
						||
const bir nensenin adresini bir göstericiye atayıp o nesneyi derleyiciyi kandırarak değiştebilir miyiz? Örneğin:
 | 
						||
const int a = 10;
 | 
						||
int *pi;
 | 
						||
pi = &a;
 | 
						||
*pi = 20;
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
İşte bu durumu engellemek için C'ye şöyle bir kural eklanmeiştir: "const bir nesnenin adresi ancak gösterdiği yer
 | 
						||
const olan const bir göstericiye atanabilir." O halde yukarıdaki örnekte zaten biz a'nın adresini pi'ye atayamayız.
 | 
						||
Peki pi'yi const yapalım:
 | 
						||
const int a = 10;
 | 
						||
const int *pi;
 | 
						||
pi = &a;
 | 
						||
*pi = 20;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
Bir fonksiyonun adresini aldığı nesneyi değiştirmediği halde onun parametresinin const gösterici yapılmamasının
 | 
						||
önemli bir dezavantajı vardır. Biz artık o fonksiyonu const bir nesnenin adresiyle çağıramayız. Örneğin bir int dizi
 | 
						||
içerisindeki sayıları ekrana yazdıran disp isimli fonksiyonun normal olarak gösterici parametresinin const olması
 | 
						||
gerekir. Fakat programcının kötü teknik uygulayarak bunu const yapmadığını düşünelim:
 | 
						||
void disp(int *pi, size_t size);
 | 
						||
Biz artık bu fonksiyonu const bir adresle çağıramayız:
 | 
						||
const int a[] = {1, 2, 3};
 | 
						||
disp(a, 3);
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
const nesnelerinin adresleri const adreslerdir. const bir adres ancak gösterdiği yer const olan göstericilere atanabilir.
 | 
						||
Tabi const olmayan bir adresin const bir göstericiye atanmasının hiçbir sakınscası yoktur. Örneğin biz coonst
 | 
						||
olmayan x nesnesinin adresini const bir göstericiye atayabiliriz. Çünkü zatane x'in değiştirilmemesi konusunda bir
 | 
						||
çekince yoktur. const anahtar sözcüğü türe ilişkin olduğu için başka bir deyişle T bir tür belirtmek üzere const T *
 | 
						||
türünden T * türüne otomatik dönüştürme yoktur. Fakat T * türünden const T * türüne otomatik dönüştürme vardır.
 | 
						||
226
 | 
						||
 | 
						||
Bazen const bir adresin const'luğunu kaldırmak gerekebilir. Bu durumda const T * türünden T * türüne tür
 | 
						||
dönüştürme operatörü ile dönüştürme yapabiliriz. Yani const T * türünden T * türüne otomatik dönüştürme yoktur
 | 
						||
fakat tür dönüştürme operatörü ile dönüştürme yapılabilir.
 | 
						||
C'de (ve tabi C++'ta) const bir nesnenin adresini tür dönüştürme operatörüyle const olmayan bir göstericiye atayıp
 | 
						||
orayı değiştirmeye çalışırsak bu durum tanımsız davranışa (undefined behavior) yol açar. (Gerçekten de statik
 | 
						||
ömürlü const nesnelerin Windows ve Linux'ta güncellenmeye çalışılması programın çökmesine yol açmaktadır.)
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
const int a = 20;
 | 
						||
int b = 30;
 | 
						||
int *pi;
 | 
						||
const int *pci;
 | 
						||
pi = &a;
 | 
						||
pi = (int *)&a;
 | 
						||
*pi = 40;
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
/* geçerli */
 | 
						||
/* geçerli ama tanımsız davranışa yol açar */
 | 
						||
 | 
						||
pci = &b;
 | 
						||
pi = pci;
 | 
						||
pi = (int *)pci;
 | 
						||
*pi = 50;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz */
 | 
						||
/* geçerli */
 | 
						||
/* normal yani tanımsız davranış değil */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
const int g_a = 10;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int *pi;
 | 
						||
pi = (int *)&g_a;
 | 
						||
*pi = 20;
 | 
						||
printf("%d\n", g_a);
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* tanımsız davranış, Windows'ta ve Linux'ta program çöker */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Gösterdiği Yer Değil Kendisi const Olan const Göstericiler
 | 
						||
Daha önce de belirtildiği gibi böyle const göstericiler seyrek kullanılmaktadır. Bunların bildirimlerinde const
 | 
						||
anahtar sözcüğü * atomunun sağına getirilir. Örneğib:
 | 
						||
char s[50];
 | 
						||
char d[50];
 | 
						||
char * const pc = s;
 | 
						||
*pc = 'x';
 | 
						||
pc = d;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz */
 | 
						||
227
 | 
						||
 | 
						||
Hem Gösterdiği Yer Hem de Kendisi const Olan const Göstericiler
 | 
						||
Bunlar hepten seyrek kullanılırlar. Burada const beelirleyicisi hem * atomunun soluna hem de sağına getirilir.
 | 
						||
Örneğin:
 | 
						||
char s[50];
 | 
						||
char d[50];
 | 
						||
const char * const pc = s;
 | 
						||
pc = d;
 | 
						||
*pc = 'x';
 | 
						||
 | 
						||
/* geçerli değil */
 | 
						||
/* geçerli değil */
 | 
						||
 | 
						||
Derleyicilerin Kod Optimizasyonları ve volatile Belirleyicisi
 | 
						||
Kodun çalışmasında hiçbir değişikliğin olmaması koşuluyla derleyiciler programcının yazdığı kodu yeniden
 | 
						||
düzenleyebilirler. Bundan amaç kodun daha hızlı çalışması (speed optimization) ya da daha az yer kaplamasıdır
 | 
						||
(size optimization). Derleyicinin kodun daha hızlı çalışması ya da daha az yer kaplaması için kodu yeniden
 | 
						||
düzenlemesine kod optimizasyonu denilmektedir.
 | 
						||
Pek çok kod optimizasyon teması vardır. Örneğin "ortak alt ifadelerin elimine edilmesi (common subexpression
 | 
						||
elimination)" denilen teknikte derleyici ortak alt ifadeleri yeniden yapmamak için bir yerde (muhtemelen bir
 | 
						||
yazmaçta) toplar. Örneğin:
 | 
						||
x = a + b + c;
 | 
						||
y = a + b + d;
 | 
						||
z = a + b + e;
 | 
						||
deyimlerinde derleyici her defasında a + b işlemini yapmamak için bunun toplamını bir yazmaçta saklayabilir ve
 | 
						||
her defasında onu kullanabilir:
 | 
						||
reg = a + b;
 | 
						||
x = reg + c;
 | 
						||
y = reg + d;
 | 
						||
z = reg + e;
 | 
						||
Derleyici kullanılmayan nenseleri sanki tanımlanmamış gibi elemine edebilir. Döngü içerisindeki gereksiz ifadeleri
 | 
						||
döngünü dışına çıkartabilir vs.
 | 
						||
Derleyiciler nesneleri ikide bir yeniden yazmaçlara çekmemek için belli büre yazmaçlarda tutabilirler. Örneğin:
 | 
						||
x = g_a + 10;
 | 
						||
y = g_a + 20;
 | 
						||
z = g_a + 30;
 | 
						||
Burada derleyici her defasında a'yı yeniden RAM'den yazmaca çekmeyebilir. Onu bir kez çekip sonraki erişimlerde
 | 
						||
o yazmacı kullanabilir. Fakat bzen özellikle çok thread'li uygulamalarda ya da kesme (interrupt) uygulamalarında
 | 
						||
bu istenmeyebilir. Şöyle ki:
 | 
						||
 | 
						||
228
 | 
						||
 | 
						||
Okla gösterilen noktada başka bir kaynak tarafından g_a değiştirildiğinde derleyicinin ürettiği kod bu değişikliği
 | 
						||
anlayamaz. Halbuki bazı durumlarda programcı bu değişikliğin kod tarafından anlaşılmasını isteyebilir. İşte
 | 
						||
volatile belirleyicisi buna sağlamak için kullanılmaktadır:
 | 
						||
volatile int g_a;
 | 
						||
volatile bir nesne kullanıldığında derleyici her zaman onu yeniden belleğe başvurarak alır. Yani onu optimizasyon
 | 
						||
amaçlı yazmaçlarda bekletmez. Örneğin aşağıdaki bir döngü söz konusu olsun:
 | 
						||
 | 
						||
Başka bir akışın (örneğin bir thread'in) bu g_falg nesnesini 0 yaparak döngüyü sonlandırabilme olanağı olduğunu
 | 
						||
düşünelim. eğer derleyici g_flag nesnesini bir kez yazmaca çekip hep onu yazmaçtan kullanırsa diğer thread g_flag
 | 
						||
nesnesini sıfıra çekse bile döngüden çıkılmaz. Bunu engellemek için g_flag volatile yapılmalıdır:
 | 
						||
volatile int g_flag;
 | 
						||
Nesnenin volatile olduğunu gören derleyici artık o neesnenin değerini her zaman belleğe başvurarak elde eder.
 | 
						||
volatile bir gösterici söz konusu olabilir. Aslında tıpkı const'luk durumunda olduğu gibi volatile belirleyicisinde de
 | 
						||
gösterici üç biçimde olabilir:
 | 
						||
1) Kendisi değil gösteridği yer volatile olan volatile göstericiler
 | 
						||
2) Gösterdiği yer değil kendisi volatile olan volatile göstericiler
 | 
						||
3) Hem kendisi hem de gösterdiği yer volatile olan volatile göstericiler
 | 
						||
Yine en çok kullanılan volatile göstericiler "kendisi değil gösterdiği yer volatile olan volatile göstericilerdir". Yine
 | 
						||
229
 | 
						||
 | 
						||
volatile anahtar sözcüğünün getirildiği yere göre bunlar değişebilmektedir:
 | 
						||
volatile int *pi;
 | 
						||
int * volatile pi;
 | 
						||
volatile int * volatile pi;
 | 
						||
Gösteridği yer volatile olan bir göstgericinin anlamı nedir?
 | 
						||
volatile int *pi;
 | 
						||
Burada *pi ya da pi[n] gibi ifadelerle erişen nesneler geçici süre yazmaçlarda bekletilmezler. Bunlar her defasında
 | 
						||
yeniden belleğe başvurularak oradan alınırlar. volatile bir nesnenin adresi ancak gösterdiği yer volatile olan bir
 | 
						||
göstericiye atanabilir. Örneğin:
 | 
						||
volatile int a;
 | 
						||
int *pi;
 | 
						||
volatile int *piv;
 | 
						||
pi = &a;
 | 
						||
piv = &a;
 | 
						||
 | 
						||
/* geçerli değil */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
volatile olma durumu const olma durumuyla aynı mantığa sahiptir. Yani T bir tür belirtmek üzere volatile T *
 | 
						||
türünden T * türüne otomatik dönüştürme yoktur ancak T * türünden volatile T * türüne doğurdan dönüştürme
 | 
						||
vardır. Örneğin:
 | 
						||
int a;
 | 
						||
volatile int *piv;
 | 
						||
piv = &a;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
Tabi tür dönüştürme operatörüyle volatile'lığı atarak volatile T * türünden T * türüne dönüştürme yapılabilir.
 | 
						||
const ve volatile belirleyicilerine kısaca İngilizce "cv qualifiers" denilmektedir. const ve volatile birlikte
 | 
						||
kullanılabilir. Örneğin:
 | 
						||
const volatile int g_a = 10;
 | 
						||
enum Türleri ve Sabitleri
 | 
						||
enum türleri C'de bir grup sembolik sabiti kolay bir biçimde oluşturmak için kullanılmaktadır. enum bildiriminin
 | 
						||
genel biçimi şöyledir:
 | 
						||
enum [isim] {<enum sabit listesi>};
 | 
						||
enum sabit listesi ',' atomuyla ayrılan isimlerden oluşur. Örneğin:
 | 
						||
enum COLORS {Red, Green, Blue, Yellow};
 | 
						||
enum DAYS {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
 | 
						||
İlk enum sabitinin değeri sıfırdır. Sonraki her sabit öncekinden bir fazladır. Örneğin:
 | 
						||
230
 | 
						||
 | 
						||
Enum sabitlerine İnglizce "enumerator" da denilmektedir. Enum sabitleri nesne belirtmezler. Derleyici enum
 | 
						||
sabitleri kullanıldığında onlar yerine koda gerçekten o sabitin değerini yerleştirir. Yani enum sabitleri #define
 | 
						||
oluşturulmuş sembolik sabitlere benzetilebilir. Ancak #define önişlemciye ilişkindir. Halbuki enum türleri ve
 | 
						||
sabitleri derleme aşamasına ilişkindir.
 | 
						||
Enum bildirimi global ya da yerel düzeyde yapılabilir. Bu durumda enum sabitlerinin de faaliyet alanı global ya da
 | 
						||
yerel olmaktadır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
enum {Red, Green, Blue};
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int color, city;
 | 
						||
enum {Adana, Ankara, Eskisehir};
 | 
						||
color = Green;
 | 
						||
printf("%d\n", color);
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
city = Ankara;
 | 
						||
printf("%d\n", city);
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void Foo()
 | 
						||
{
 | 
						||
int color, city;
 | 
						||
color = Green;
 | 
						||
printf("%d\n", color);
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
city = Ankara;
 | 
						||
printf("%d\n", city);
 | 
						||
 | 
						||
/* geçersiz */
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Bir enum sabitine '=' atomu ile bir değer verilebilir. Bu durumda sonrakiler onu izler. Aynı değere sahip birden
 | 
						||
fazla enum sabiti bulunabilir. Örneğin:
 | 
						||
enum COLORS {Red = 10, Green, Blue = -5, Yellow, Magenta = 9, Purple};
 | 
						||
 | 
						||
Burada Red = 10, Green = 11, Blue = -5, Yellow = -4, Magenta = 9, Purple = 10 değerlerini almaktadır.
 | 
						||
C'de enum türleri int türü gibi değerlendirilir. Bu durumda enum sabitleri de sanki int türden sabitlermiş gibi ele
 | 
						||
alınacaklardır. enum türleri işlem öncesinde yine büyük türe dönüştürülürler. Yani tamamen birer tamsayı türü gibi
 | 
						||
değerlendirilirler.
 | 
						||
enum türünden nesneler bildirilebilir. Örneğin:
 | 
						||
231
 | 
						||
 | 
						||
enum COLORS color;
 | 
						||
color = Red;
 | 
						||
color = 10;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
 | 
						||
enum türünden nesneler sanki int türündenmiş gibi değerlendirilirler. Böylece bir enum türünden nesneye diğer
 | 
						||
sayısal türleri doğrudan atayabiliriz. Farklı enum türünden değeri de birbirlerine doğrudan atayabiliriz.
 | 
						||
Anahtar Notlar: C++'ta, C# ve Java'da enum türleri farklı birer tür belirtmektedir. Yani bir tamsayı türü enum türüne doğrudan atanamaz.
 | 
						||
enum türlerine aynı türden enum sabitleri atanabilir. Fakat C'de enum türleri sanki int türü gibi değerlendirilmektedir.
 | 
						||
 | 
						||
Enum'lar yukarıda da belirtildiği gibi aynı türden bir dizi sembolik sabiti kolay bir biçimde ifade etmek için
 | 
						||
kullanılmaktadır.
 | 
						||
C'de Dosya İşlemleri
 | 
						||
İşletim sistemi tarafından organize edilen ikincil belleklerde tanımlanmış bölgelere dosya (file) denilmektedir.
 | 
						||
Dosyaların isimleri vardır. Özellikleri vardır. Dosyaların erişim hakları vardır. Pek çok sistemde her dosyaya
 | 
						||
herkes erişemez.
 | 
						||
Modern sistemlerde dosyalar dizinlerin (directories) içerisinde bulunur. Aynı dizin içerisinde aynı isimli birden
 | 
						||
fazla dosya bulunamaz. Fakat farklı dizinlerde aynı isimli dosyalar bulunabilmektedir: Dosya isimlerinin Windows
 | 
						||
sistemlerinde büyük harf-küçük harf duyarlılığı yoktur. Fakat Unix/Linux sistemlerinde vardır. Yani örneğin
 | 
						||
"test.txt" dosyası ile "Test.txt" dosyası Windows'ta aynı isimli isimli dosyalardır. Fakat UNIX/Linux sistemlerinde
 | 
						||
bunlar farklı isimli dosyalardır.
 | 
						||
Bir dosyanın yerini belirten yazısal ifadelere yol ifadeleri (path) denilmektedir. Yol ifadeleri mutlak (absolute) ve
 | 
						||
göreli (relative) olmak üzere ikiye ayrılır. Yol ifadesinin ilk karakteri Windows'ta '\', UNIX/Linux'ta '/' ise böyle
 | 
						||
yol ifadelerine mutlak (absolute) yol ifadeleri denilmektedir. Mutlak yol ifadeleri kök dizinden itibaren yer belirtir.
 | 
						||
Örneğin:
 | 
						||
"/a/b/c.txt"
 | 
						||
"a/b/c.txt"
 | 
						||
"a.txt"
 | 
						||
 | 
						||
/* mutlak */
 | 
						||
/* göreli */
 | 
						||
/* göreli */
 | 
						||
 | 
						||
Windows'ta dizinler sürücüler (drives) içerisindedir. Her sürücünün ayrı bir kökü (root) vardır. Halbuki
 | 
						||
UNIX/Linux sistemlerinde toplamda tek bir kök vardır. Bu sistemlerde sürücü kavramı yoktur. Başka bir aygıt bu
 | 
						||
sistemlere takıldığında onun kökü bir dizinin altında gözükür. Bu işleme "mount işlemi" denilmektedir.
 | 
						||
Windows'ta sürücü bir harf ve ':' karakterindne oluşur. Örneğin C:, D:, E: gibi...
 | 
						||
İşletim sistemlerinde çalışmakta olan programlara "process" ya da "task" denilmektedir. Her prosesin bir çalışma
 | 
						||
dizini (current working directory) vardır. İşte göreli yol ifadeleri prosesin çalışma dizininden itibaren yer belirtir.
 | 
						||
Örneğin prosesimizin çalışma dizini "/temp" olsun:
 | 
						||
"/a/b/c.txt" biçimindeki yol ifadesi mutlaktır. Ve her zaman kökten itibaren yer beliritir. Fakat "a/b/c.txt"
 | 
						||
biçimindeki yol ifadesi görelidir. Bu durumda bu yol ifadesi "/temp/a/b/c.txt" biçiminde ele alınır. Örneğin "a.txt"
 | 
						||
yol ifadesi görelidir ve prosesin çalışma dizininde aranır.
 | 
						||
Pekiyi prosesin çalışma dizini neresidir? Prosesin çalışma dizini program çalışırken değiştirilebilir. Fakat program
 | 
						||
çalışmaya başladığında default olarak çalıştırılabilen dosyanın bulunduğu yerdedir. Visual Studio idesinden
 | 
						||
çalıştırılan programların çalışma dizini proje dizinidir.
 | 
						||
Windows sistemlerinde mutlak yol ifadelerinde sürücü belirtilmemişse prosesin çalışma dizini hangi sürücüye
 | 
						||
232
 | 
						||
 | 
						||
ilişkinse o mutlak yol ifadesinin o sürücüye ilişkin olduğu anlaşılır. Örneğin prosesin çalışma dizini "E:\Study"
 | 
						||
olsun. Biz de "\A\B\C.txt" biçiminde bir yol ifadesi belirtelim. Bu yol ifadesi E'nin kökünden itibaren yer
 | 
						||
belirtecektir. Tabi mutla k yol ifadeleri sürücü içeriyorsa her zaman o sürücü dikkate alınır.
 | 
						||
C'de Dosya İşlemleri
 | 
						||
C'de dosya işlemleri prototipleri <stdio.h> içerisinde bulunan standart C fonksiyonlarıyla yapılmaktadır. Bütün
 | 
						||
dosya fonksiyonlarının isimleri f harfi başlar. Dosya işlemleri aslında işletim sisteminin sunduğu sistem
 | 
						||
fonksiyonlarıyla yapılmaktadır. Standart C fonksiyonları bu işletim sisteminin sistem fonksiyonlarını çağırarak
 | 
						||
işlemini yapar.
 | 
						||
C'de tipik olarak dosya işlemleri 3 aşamda yürütülür:
 | 
						||
1) Önce dosya açılır
 | 
						||
2) Sonra dosyadan okuma yazma yapılır
 | 
						||
3) En sononda dosya kapatılır
 | 
						||
Dosyanın kapatılması zorunlu değildir. Çünkü program bittiğinde exit işlemi sırasında zaten tüm açık dosyalar
 | 
						||
kapatılmaktadır. Tabi biz dosyayı çeşitli sebeplerden dolayı program bitmeden erken kapatmak isteyebiliriz.
 | 
						||
Dosyanın Açılması
 | 
						||
Dosyayı açmak için fopen fonksiyonu kullanılır. Fonksiyonun prototipi şöyledir:
 | 
						||
FILE *fopen(const char *path, const char *format);
 | 
						||
Fonksiyonun birinci parametresi açılacak dosyanın yol ifadesini belirtir. İkinci parametre açış modunu belirtir.
 | 
						||
Açış modu aşağıdakilerden biri biçiminde olabilir:
 | 
						||
Açış Modu
 | 
						||
"r"
 | 
						||
"r+"
 | 
						||
"w"
 | 
						||
"w+"
 | 
						||
"a"
 | 
						||
"a+"
 | 
						||
 | 
						||
Anlamı
 | 
						||
Olan dosyayı açmak için kullanılır. Eğer dosya yoksa fonksiyon başarısız olur. Böyle
 | 
						||
açılmış dosyalardan yalnızca okuma yapabiliriz.
 | 
						||
Olan dosyayı açmak için kullanılır. Eğer dosya yoksa fonksiyon başarısız olur. Böyle
 | 
						||
açılmış dosyalardan hem okuma hem de yazma yapabiliriz (+ okuma yazma anlamına
 | 
						||
gelir.)
 | 
						||
Dosya yoksa yaratılır ve açılır. Dosya varsa sıfırlanır ve açılır. Yalnızca yazma yapılabilir.
 | 
						||
Dosya yoksa yaratılır ve açılır. Dosya varsa sıfırlanır ve açılır. Hem okuma hem de yazma
 | 
						||
yapılabilir.
 | 
						||
Dosya yoksa yaratılır ve açılır. Dosya varsa olan dosya açılır. Dosyadan okuma yapamayız.
 | 
						||
Fakat her yazılan sona ekleme anlamına gelir. (Yani dosyanın ortasına yazma diye bir
 | 
						||
durum yoktur. Her yazma işlemi ekleme anlamına gelir.)
 | 
						||
Dosya yoksa yaratılır ve açılır. Dosya varsa olan dosya açılır. Dosyadan okuma yapabiliriz.
 | 
						||
Fakat her yazılan sona ekleme anlamına gelir. (Yani dosyanın ortasına yazma diye bir
 | 
						||
durum yoktur. Her yazma işlemi ekleme anlamına gelir.)
 | 
						||
 | 
						||
fopen fonksiyonu başarı durumunda FILE isimli bir yapı türünden adrese geri döner. fopen açılan dosyanın
 | 
						||
bilgilerini stdio içerisinde bildirilmiş ve FILE ismiyle typedef edilmiş bir yapı nesnesinin içerisine
 | 
						||
yerleştirmektedir ve o o yapı nesnesinin adresine geri dönmektedir. Programcı FILE yapısının elemanlarını bilmek
 | 
						||
zoruda değildir. Ancak FILE isminin bir yapı belirttiğini bilmelidir. Fonksiyon başarısız olabilir. Bu durumda
 | 
						||
fopen NULL adrese geri döner. Mutlaka başarı kontrolü yapılmalıdır. FILE türünden adrese biz "dosya bilgi
 | 
						||
gösterici" diyeceğiz. İngilizce genellikle bu adrese "file stream" denilmektedir.
 | 
						||
233
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
if ((f = fopen("test.dat", "w")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dosyanın Kapatılması
 | 
						||
Dosyayı kapatmak için fclose fonksiyonu kullanılır. Fonksiyonun prototipi şöyledir:
 | 
						||
int fclose(FILE *f);
 | 
						||
Fonksiyon parametre olarak fopen fonksiyonundan alınan FILE yapı adresini (yani dosya bilgi göstericisini)
 | 
						||
almaktadır. Fonksiyon başarı durumunda sıfır değerine başarıszlık durumunda -1 değerine geri döner. Dosyanın
 | 
						||
kapatılmasının başarısının kontrol edilmesine gerek yoktur. Zaten dosya başarılı bir biçimde açılmışsa kesinlikle
 | 
						||
başarılı kapatılır.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
if ((f = fopen("test.dat", "w")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dosya Göstericisi (File Pointer) Kavramı
 | 
						||
Dosya byte'lardan oluşan bir topluluktur. Dosyanın içerisinde ne oolursa olsun dosya işletim sistemi için bir byte
 | 
						||
yığınıdır. Dosyanın her bir byte'ına ilk byte sıfır olmak üzere bir numara verilmiştir. Bu numaraya ilgili byte'ın
 | 
						||
offset'i denir. Örneğin aşağıdaki çizimde her bir X bir byte'ı gösteriyor olsun. Tüm bu byte'ların bir offset2i vardır.
 | 
						||
 | 
						||
Dosya göstericisi bellekte bir adres belirten daha önce gördüğümüz gibi bir gösterici değildir. Buradaki isim
 | 
						||
234
 | 
						||
 | 
						||
tamamen kavramsal olarak verilmiştir. Dosya göstericisi bir offset belirtir. Bir imleç görevindedir. Tüm okuma ve
 | 
						||
yazma işlemleri dosya göstericisinin gösterdiği yerden itibaren yapılır.
 | 
						||
Örneğin dosya göstericisi 2'inci offset'i gösteriyor olsun:
 | 
						||
 | 
						||
Şimdi biz dosyadan 2 byte okuyacak olalım. İşte okuma işlemi dosya göstericisinin gösterdiği yerden itibaren
 | 
						||
yapılır. Yani 2'inci ve 3'üncü offset'teki byte'lar okunur. Benzer biçimde yazma işlemi de her zaman dosya
 | 
						||
göstericisinin gösterdiği yerden itibaren yapılır. Okuma ve yazma işlemleri dosya göstericisini okunan ya da
 | 
						||
yazılan byte miktarı kadar ilerletir. Örneğin dosyaya YY ile temsil edilen iki byte'ı yazmış olalım:
 | 
						||
 | 
						||
Dosya ilk kez açıldığında dosya göstericisi sıfırıncı offset'i gösteriyor durumdadır.
 | 
						||
EOF Durumu
 | 
						||
Dosyanın sonında özel hiçbir karakter ya da byte yoktur. Dosya göstericisinin dosyanın son byte'ından sonraki
 | 
						||
byte'ı göstermesi durumuna EOF durumu denilmektedir. Örneğin:
 | 
						||
 | 
						||
Dosya gösterisici EOF durumundaysa artık o dosyada olan bir byte'ı göstermiyordur. Bu nedenle EOF durumundan
 | 
						||
okuma yapılamaz. Dosya göstericisi EOF durumundaysa okuma fonksiyonları başarısız olur. Ancak EOF
 | 
						||
durumunda yazma yapılabilir. Bu durum dosyaya ekleme yapma anlamına gelir.
 | 
						||
Pekiyi bir dosyayı açıp byte byte okuduğumuzu düşünelim. Ne olur? Biz sırasıyla byte'ları okuruz. Dosya
 | 
						||
göstericisi en sonında EOF durumuna gelir. Artık okuma yapamayız.
 | 
						||
fgetc Fonksiyonu
 | 
						||
fgetc dosyadan bir byte okuyan standart C fonksiyonudur. Prototipi şöyledir:
 | 
						||
int fgetc(FILE *f);
 | 
						||
Fonksiyon parametre olarak fopen fonksiyonundan elde edilen dosya bilgi göstericisini alır. Dosya göstericisinin
 | 
						||
gösterdiği yerdeki byte'ı okur. Okuduğu byte ile geri döner. Geri dönüş değeri int rüdendir. Okuma başarılıysa int
 | 
						||
bilginin yüksek anlamlı byte'larında sıfır bulunur. En düşük anlamlı byte'ında okunan değer bulunur. fgetc IO
 | 
						||
235
 | 
						||
 | 
						||
hatası nedeniyle ya da EOF nedeniyle okumayı yapamazsa EOF denilen bir değere geri döner: EOF değeri <stdio.h>
 | 
						||
içerisinde -1 olarak define edilmiştir:
 | 
						||
#define EOF
 | 
						||
 | 
						||
-1
 | 
						||
 | 
						||
Pekiyi fgetc okuduğu byte'a geri döndüğüne göre neden geri dönüş değeri char değil de int türdendir. Eğer fgetc'in
 | 
						||
geri dönüş değeri char olsaydı bu durumda dosyadan FF numaralı byte'ı okuduğunda FF = -1 olduğundan fgetc'in
 | 
						||
başarısız mı olduğu yoksa gerçekten FF numaralı byte'ı mı okuduğu anlaşılamazdı. Halbuki fonksiyonun geri
 | 
						||
dönüş değeri int olduğu için bu sorun oluşmamaktadır. Yani:
 | 
						||
00 00 00 FF ---> FF numaralı byte okunmuş
 | 
						||
FF FF FF FF --> başarısız olunmuş
 | 
						||
Örneğin bir dosyayı sonuna kadar okuyup yazdıran örnek program şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int ch;
 | 
						||
if ((f = fopen("Sample.c", "r")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
feof ve ferror Fonksiyonları
 | 
						||
Bir dosya fonksiyonu (örneğin fgetc gibi) başarısız olduğunda bunun nedeni dosya sonuna gelinmiş olması olabilir
 | 
						||
ya da daha ciddi (genellikle biz artık şey yapamayız) IO hatası olabilir. fgetc fonksiyonu her iki durumda da EOF
 | 
						||
değeriyle geri dönmektedir. Dosya sonuna gelmiş olmak gayet normal bir durumdur. Halbuki bir disk hatası ciddi
 | 
						||
bir soruna işaret eder. İşte bizim fgetc gibi bir fonksiyonun neden EOF'a geri döndüğünü tespit edebilmemeiz
 | 
						||
gerekir. Bunun için feof ve ferror fonksiyonlarından faydalanılır:
 | 
						||
int feof(FILE *f);
 | 
						||
int ferror(FILE *f);
 | 
						||
feof fonksiyonu dosya göstericisinin EOF durumundaolup olmadığını tespit eder. Eğer dosya göstericisi EOF
 | 
						||
durumundaysa fonksiyon sıfır dışı herhangi bir değere, EOF durumunda değilse 0 değerine geri döner. Ancak bu
 | 
						||
fonksiyon son okuma işlemi başarısız olduğunda kullanılmalıdır. Şöyle ki: Biz dosyadaki son byte'ı fgetc ile
 | 
						||
okuduğumuzda dosya göstericisi EOF'a çekildiği halde feof sıfıra geri döner. Bundan sonra bir daha fgetc
 | 
						||
uygularsak fgetc başarısz olur. Arık feof sıfır dışı değere geri döner.
 | 
						||
ferror ise son okuma ya da yazma işleminde IO hatası oluşup oluşmadığını belirlemek için kullanılmaktadır. Eğer
 | 
						||
son dosya işlemi IO hatası nedeniyle balarısız olmuşsa ferror sıfır dışı bir değere, değilse sıfır değerine geri döner.
 | 
						||
Özellikle son işlemin başarısı iyi bir teknik bakımından kontrol edilmelidir. Örneğin:
 | 
						||
236
 | 
						||
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int ch;
 | 
						||
if ((f = fopen("Sample.c", "r")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
if (ferror(f)) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin aşağıdaki gibi dosya sonuna kadar okuma işlemi hatalıdır:
 | 
						||
while (!feof(f)) {
 | 
						||
ch = fgetc(f);
 | 
						||
putchar(ch);
 | 
						||
}
 | 
						||
 | 
						||
Çünkü burada son byte okunduktan sonra henüz feof sıfır dışı değer vermez. Dolayısıyla program fazladan bir byte
 | 
						||
okunmuş gibi davranır. Ayrıca burada IO hatası için bir tespit yapılmamıştır. Doğru teknik şöyledir:
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
if (ferror(f)) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
 | 
						||
fputc Fonksiyonu
 | 
						||
fputc dosyaya dosya göstericisinin gösterdiği yere bir byte yazan en temel yazma fonksiyonudur. Prototipi şöyledir:
 | 
						||
int fputc(int ch, FILE *f);
 | 
						||
Fonksiyon birinci parametresiyle belirtilen int değerin en düşün anlamlı byte'ını yazar. Baaşarı durumunda yazdığı
 | 
						||
değer, başarısızlık durumunda EOF değerine geri döner.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
 | 
						||
237
 | 
						||
 | 
						||
FILE *f;
 | 
						||
char s[] = "this is a test, yes this is a test!";
 | 
						||
int i;
 | 
						||
if ((f = fopen("test.txt", "w")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; s[i] != '\0'; ++i)
 | 
						||
if (fputc(s[i], f) == EOF) {
 | 
						||
printf("cannot write file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Dosya kopyalayan örnek bir program şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *fs, *fd;
 | 
						||
int ch;
 | 
						||
if ((fs = fopen("sample.c", "r")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if ((fd = fopen("xxx.c", "w")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(fs)) != EOF)
 | 
						||
if (fputc(ch, fd) == EOF) {
 | 
						||
printf("cannot write file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (ferror(fs)) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
printf("success...\n");
 | 
						||
fclose(fs);
 | 
						||
fclose(fd);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fread ve fwrite Fonksiyonları
 | 
						||
fread ve fwrite fonksiyonları getc ve fputc fonksiyonlarının n byte okuyan ve n byte yazan genel biçimleridir. fread
 | 
						||
dosya göstericisinin gösterdiği yerden itibaren n byte'ı okuyarak bellekte verilen adresten itibaren bir diziye
 | 
						||
yerleştirir. fwrite ise tam tersini yapmaktadır. Yani bellekte verilen bir adresten başlayarak n byte'ı dosya
 | 
						||
238
 | 
						||
 | 
						||
göstericisinin gösterdiği yerden itibaren dosyaya yazar. Fonksiyonların prototipleri şöyledir:
 | 
						||
size_t fread(void *ptr, size_t size, size_t count, FILE *f);
 | 
						||
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *f);
 | 
						||
fread fonksiyonun birinci parametresi bellekteki tarnsfer adresidir. Fonksiyon ikinci ve üçüncü parametrelerin
 | 
						||
çarpımı kadar byte okur (yani size * count kadar). Son parametre okuma işleminin yapılacağı dosyayı
 | 
						||
belirtmektedir. Genellikle ikinci parametre okunacak dizinin bir elemanının byte uzunluğu olarak, ikinci parametre
 | 
						||
ise okunacak eleman uzunluğu olarak girilir. Örneğin dosyada int bilgiler olsun ve biz 10 int değeri okumak
 | 
						||
isteyelim:
 | 
						||
int a[10];
 | 
						||
fread(a, sizeof(int), 10, f);
 | 
						||
fread fonksiyonu başarı durumunda okuyabildiği parça sayısına geri döner. Başarıszlık durumunda sıfır değerine
 | 
						||
geri dönmektedir. fread fonksiyonu ile dosyada olandan daha fazla byte okunmak istenebilir. Bu durumda fread
 | 
						||
okuyabildiği kadar byte'ı okur ve okuyabildiği parça sayısına geri döner. Örneğin dosya göstericisinin gösterdiği
 | 
						||
yerden itibaren dosyada 20 byte kalmış olsun. Biz de aşağıdaki gibi bir okuma yapmış olalım:
 | 
						||
fread(a, sizeof(int), 10, f);
 | 
						||
Biz buarad 40 byte okuma talep etmiş olabiliriz. Ancak fread kalan 20 byte'ın hepsini okur ve 5 değerine geri öner.
 | 
						||
Yani fread toplam okunan byte sayısının ikinci parametrede belirtilen değere bölümüne geri dönmektedir.
 | 
						||
fwrite fonksiyonu da tamamen aynı biçimde çalışır. Tek fark fwrite dosyadan belleğe okuma değil bellekten
 | 
						||
dosyaya yazma yapmaktadır. fwrite da başarı durumunda yazılan parça sayısına başarısızlık durumunda sıfır
 | 
						||
değerine geri döner.
 | 
						||
Örneğin 10 elemanlı bir int dizi dosyaya tek hamlede fwrite fonksiyonuyla şöyle yazdırılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 | 
						||
if ((f = fopen("Sample.dat", "wb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (fwrite(a, sizeof(int), 10, f) != 10) {
 | 
						||
printf("cannot write file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şimdi de yazdığımız sayıları tek hamlede fread ile okuyalım:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
239
 | 
						||
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int a[10];
 | 
						||
int i;
 | 
						||
if ((f = fopen("Sample.dat", "rb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (fread(a, sizeof(int), 10, f) != 10) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
printf("%d ", a[i]);
 | 
						||
printf("\n");
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şimdi de bir dosyadan belli bir miktar okuyup diğerine yazarak dosya kopyalaması yapmaya çalışalım:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#define BUF_SIZE
 | 
						||
 | 
						||
512
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *fs, *fd;
 | 
						||
char buf[BUF_SIZE];
 | 
						||
size_t n;
 | 
						||
if ((fs = fopen("sample.c", "rb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if ((fd = fopen("xxx.c", "wb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((n = fread(buf, 1, BUF_SIZE, fs)) > 0)
 | 
						||
if (fwrite(buf, 1, n, fd) != n) {
 | 
						||
printf("cannot write file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (ferror(fs)) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
printf("success...\n");
 | 
						||
fclose(fs);
 | 
						||
fclose(fd);
 | 
						||
 | 
						||
240
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Yapıların Dosyalara Yazılması ve Okunması
 | 
						||
Yapı elemanları bellekte ardışıl bir biçimde tutulduğuna göre, biz bir yapıyı tek hamlede fwrite fonksiyonuyla
 | 
						||
yazıp, fread fonksiyonuyla okuyabiliriz. Örneğin struct PERSON türünden per isimli bir yapı nesnesi olsun:
 | 
						||
fwrite(&per, sizeof(struct PERSON), 1, f);
 | 
						||
Klavyeden girilen değerlerden oluşan yapı nesneleri fwrite fonksiyonuyla dosyaya yazdırılmaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <string.h>
 | 
						||
typedef struct tagPERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} PERSON;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
PERSON per;
 | 
						||
if ((f = fopen("person.dat", "wb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (;;) {
 | 
						||
printf("Adi soyadi:");
 | 
						||
gets(per.name);
 | 
						||
if (!strcmp(per.name, "exit"))
 | 
						||
break;
 | 
						||
printf("No:");
 | 
						||
scanf("%d", &per.no);
 | 
						||
getchar();
 | 
						||
/* tamponu boşaltmak için, şimdilik takılmayın */
 | 
						||
if (fwrite(&per, sizeof(per), 1, f) != 1) {
 | 
						||
printf("cannot write file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aynı dosyadan kayıtları okuyan program da şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
typedef struct tagPERSON {
 | 
						||
char name[32];
 | 
						||
int no;
 | 
						||
} PERSON;
 | 
						||
int main(void)
 | 
						||
 | 
						||
241
 | 
						||
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
PERSON per;
 | 
						||
if ((f = fopen("person.dat", "rb")) == NULL) {
 | 
						||
printf("cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while (fread(&per, sizeof(per), 1, f) > 0)
 | 
						||
printf("%s, %d\n", per.name, per.no);
 | 
						||
if (ferror(f)) {
 | 
						||
printf("cannot read file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fprintf ve fscanf fonksiyonları
 | 
						||
fprintf fonksiyonu printf gibi çalışan fakat dosyaya yazdırma amaçlı kullanılan standart C fonksiyonudur.
 | 
						||
İlk parametresi dışında diğer parametreleri printf fonksiyonu ile aynıdır. fprintf fonksiyonunun prototipi aşağıdaki
 | 
						||
gibidir.
 | 
						||
int fprintf(FILE *f, const char *format, ...);
 | 
						||
Fonksiyonun birinci paramtresi yazılacak dosyayı belirtmektedir. Fonksiyonun ikinci parametresi formatlanması
 | 
						||
istenen yazıdır. printf fonksiyonu ile aynıdır. fprintf fonksiyonu aşağıdaki gibi kullanılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int i;
 | 
						||
if ((f = fopen(file_name, "w")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 10; ++i)
 | 
						||
fprintf(f, "sayı=%d\n", i + 1);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada fprintf fonksiyonu ile formatlı bir şekilde dosyaya yazma yapılmıştır. Yukarıda yazma yapılan dosyanın
 | 
						||
içeriği okunarak ekrana aşağıdaki gibi bir programla yazdırılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
 | 
						||
242
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int i, ch;
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
if (ferror(f)) {
 | 
						||
printf("can not read file...!");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fprintf fonksiyonu ile dosyaya formatlı bir bilgi yazılmaktadır. Aşağıdaki programlar fprintf ve fwrite
 | 
						||
fonksiyonlarının farkını göstermektedir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int val, ch;
 | 
						||
if ((f = fopen(file_name, "w")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
val = 1234;
 | 
						||
fprintf(f, "%d\n", val);
 | 
						||
fclose(f);
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
 | 
						||
243
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int val, ch;
 | 
						||
if ((f = fopen(file_name, "w")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
val = 1234;
 | 
						||
fwrite(&val, sizeof(int), 1, f);
 | 
						||
fclose(f);
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Anahtar Notlar: Bilindiği fwrite ve fread fonksiyonları dosyaya ikili olarak bilgi yazıp okumaktadırlar:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int val, i;
 | 
						||
if ((f = fopen(file_name, "wb")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 4; ++i)
 | 
						||
fwrite(&i, sizeof(int), 1, f);
 | 
						||
fclose(f);
 | 
						||
if ((f = fopen(file_name, "rb")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while (fread(&val, sizeof(int), 1, f) > 0)
 | 
						||
printf("%d\n", val);
 | 
						||
fclose(f);
 | 
						||
 | 
						||
244
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fscanf fonksiyonu scanf fonksiyonu gibi ancak dosyadan okuma yapma amaçlı kullanılmaktadır. fscanf fonksiyonu
 | 
						||
dosyadan formatlı olarak okuma yapar. Fonksiyonun prototipi aşağıdaki gibidir:
 | 
						||
int fscanf(FILE *f, const char *format, ...);
 | 
						||
Fonksiyonun birinci parametresi okuma yapılacak dosyayı belirtmektedir. Diğer parametreleri scanf fonksiyonu ile
 | 
						||
aynıdır.
 | 
						||
fscanf fonksiyonu ile dosyadan okuma işlemi aşağıdaki gibi yapılabilir.
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int a, b;
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fscanf(f, "%d %d", &a,
 | 
						||
 | 
						||
&b);
 | 
						||
 | 
						||
printf("a=%d\nb=%d\n", a, b);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fscanf fonksiyonun geri dönüş değeri dosyadan okunarak bellekteki alanlara yazılan değer sayısıdır. Hiç bir yazma
 | 
						||
yapılmadıysa fonksiyon 0(sıfır) değerine geri döner. Eğer ilk alana atama yapılmadan dosyanın sonuna gelinmişse,
 | 
						||
ya da bir hata oluşmuşsa fonsiyon EOF değerine geri döner. Aşağıdaki program her bir satırında 4 adet sayı olan
 | 
						||
bir dosyadan okuma yapmaktadır ve her bir dörtlü grubun toplamını bulmaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int a, b, c, d;
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while (fscanf(f, "%d %d %d %d", &a, &b, &c, &d) != EOF)
 | 
						||
printf("%d\n", a + b + c + d);
 | 
						||
if (ferror(f)) {
 | 
						||
 | 
						||
245
 | 
						||
 | 
						||
printf("can not read file....!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
fscanf fonksiyonu ile her okunan bilgi dönüştürülerek bir bellek alanına aktarılmak zorunda değildir. Böyle
 | 
						||
okumalarda % ile format karakteri arasına * konularak okunan değerin devre dışı bırakılması (ama okunması)
 | 
						||
sağlanabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int a, b;
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fscanf(f, "%d%*s%d", &a, &b);
 | 
						||
if (ferror(f)) {
 | 
						||
printf("can not read file....!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
printf("a=%d\n", a);
 | 
						||
printf("b=%d\n", b);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada dosya içerisinde örneğin
 | 
						||
10 ------ 20
 | 
						||
gibi bir bilgiden yalnızca 10 ve 20 sayıları okunmaktadır.
 | 
						||
Sınıf Çalışması: Formatı aşağıdaki gibi olan bir dosya içerisinde dosya formatının geçerliliğini kontrol etmeden
 | 
						||
yaş ortalamasını bulan program
 | 
						||
Dosya formatı:
 | 
						||
isim mert yas 18
 | 
						||
isim oguz yas 40
 | 
						||
isim serkan yas 35
 | 
						||
isim mustafa yas 42
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
246
 | 
						||
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int age, sum = 0, count = 0;
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while (fscanf(f, "%*s%*s%*s%d", &age) != EOF) {
 | 
						||
sum += age;
 | 
						||
count++;
 | 
						||
}
 | 
						||
if (ferror(f)) {
 | 
						||
printf("can not read file....!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
printf("Yas Ortalamasi:%lf\n", (double) sum / count);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Formatı aşağıdaki gibi olan bir dosya içerisinde dosya formatının geçerliliğini kontrol etmeden
 | 
						||
değerlerin ortalamasını bulan programı yazınız.
 | 
						||
Firma1 30 40 56
 | 
						||
Firma2 10 20 35
 | 
						||
Firma3 10 20 33
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#define MAX_LEN
 | 
						||
 | 
						||
32
 | 
						||
 | 
						||
typedef struct tagCOMPANYINFO {
 | 
						||
char name[MAX_LEN];
 | 
						||
int val1, val2, val3;
 | 
						||
} COMPANYINFO;
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "test.txt";
 | 
						||
int i;
 | 
						||
COMPANYINFO companies[3];
 | 
						||
if ((f = fopen(file_name, "r")) == NULL) {
 | 
						||
printf("can not open file...%s\n", file_name);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 3; ++i) {
 | 
						||
 | 
						||
247
 | 
						||
 | 
						||
fscanf(f,
 | 
						||
"%s
 | 
						||
&companies[i].val3);
 | 
						||
}
 | 
						||
 | 
						||
%d
 | 
						||
 | 
						||
%d
 | 
						||
 | 
						||
%d",
 | 
						||
 | 
						||
companies[i].name,
 | 
						||
 | 
						||
&companies[i].val1,
 | 
						||
 | 
						||
&companies[i].val2,
 | 
						||
 | 
						||
if (ferror(f)) {
 | 
						||
printf("can not read file....!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 3; ++i) {
 | 
						||
double avg = (companies[i].val1 + companies[i].val2 + companies[i].val3) / 3.;
 | 
						||
printf("%s ---> %lf\n", companies[i].name, avg);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aşağıdaki program rasgele ad, soyad ve not belirleyerek grades.txt isimli dosyaya formatlı bir şekilde yazma
 | 
						||
yapmaktadır.
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#include <time.h>
 | 
						||
void exit_sys(const char *msg);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char file_name[] = "grades.txt";
 | 
						||
int i;
 | 
						||
int number_of_students;
 | 
						||
char *names[8] = {"oguz", "mert", "durukan", "boran", "mustafa", "serkan", "onur", "latif"};
 | 
						||
char *snames[8] = { "aksoy", "kaya", "vural", "altan", "karpuz", "sert", "mulaim", "naif" };
 | 
						||
if ((f = fopen(file_name, "w")) == NULL)
 | 
						||
exit_sys("can not open file\n");
 | 
						||
srand((unsigned)time(NULL));
 | 
						||
number_of_students = rand() % 20 + 30;
 | 
						||
for (i = 0; i < number_of_students; ++i)
 | 
						||
fprintf(f, "%s %s %d\n", names[rand() % 8], snames[rand() % 8], rand() % 101);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void exit_sys(const char *msg)
 | 
						||
{
 | 
						||
printf(msg);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
 | 
						||
Sınıf Çalışması: Yukarıda oluşturulan grades.txt dosyasını kullanarak belirli bir geçme değeri belirleyip, geçenleri
 | 
						||
pass.txt kalanları fail.txt isimli dosyaya formatlı olarak yazdıran programı yazınız.
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
 | 
						||
248
 | 
						||
 | 
						||
#include <time.h>
 | 
						||
#define PASS_GRADE 50
 | 
						||
void exit_sys(const char *msg);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *fgrades, *fpass, *ffail;
 | 
						||
char grades_file_name[] = "grades.txt";
 | 
						||
char pass_file_name[] = "pass.txt";
 | 
						||
char fail_file_name[] = "fail.txt";
 | 
						||
char name[20], sname[20];
 | 
						||
int grade;
 | 
						||
if ((fgrades = fopen(grades_file_name, "r")) == NULL)
 | 
						||
exit_sys("can not open file\n");
 | 
						||
if ((fpass = fopen(pass_file_name, "w")) == NULL)
 | 
						||
exit_sys("can not open file\n");
 | 
						||
if ((ffail = fopen(fail_file_name, "w")) == NULL)
 | 
						||
exit_sys("can not open file\n");
 | 
						||
while (fscanf(fgrades, "%s%s%d", name, sname, &grade) != EOF)
 | 
						||
if (grade < PASS_GRADE)
 | 
						||
fprintf(ffail, "%s %s %d\n", name, sname, grade);
 | 
						||
else
 | 
						||
fprintf(fpass, "%s %s %d\n", name, sname, grade);
 | 
						||
if (ferror(fgrades))
 | 
						||
exit_sys("can not read file\n");
 | 
						||
fclose(fgrades);
 | 
						||
fclose(fpass);
 | 
						||
fclose(ffail);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
void exit_sys(const char *msg)
 | 
						||
{
 | 
						||
printf(msg);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
 | 
						||
Metin ve İkili Dosyalar
 | 
						||
C de bir dosya metin (text) ya da ikili (binary) modda açılabilmektedir. Açılış modunda hiç belirtme yapılmassa
 | 
						||
default olarak dosya text modda açılır. Programcı isterse açılış mod yazısının sonuna "t" ekleyerek de açılışı text
 | 
						||
modda yaptırabilir. Örneğin:
 | 
						||
if (f = fopen("test.txt", "w") == NULL)
 | 
						||
/*...*/
 | 
						||
ya da örneğin
 | 
						||
if (f = fopen("test.txt", "wt") == NULL)
 | 
						||
/*...*/
 | 
						||
Bir dosyayı binary modda açmak için mod yazısının "b" getirilmelidir. Örneğin:
 | 
						||
249
 | 
						||
 | 
						||
if (f = fopen("test.txt", "wb") == NULL)
 | 
						||
/*...*/
 | 
						||
Text ve binary modda açılan dosyalar için Windows ve Unix/Linux sistemlerinde farklılıklar bulunmaktadır. Bir
 | 
						||
dosya text modda açılmışsa ve çalışılan sistem windows ise yazma yapan herhangi bir fonksiyon Line feed (LF)
 | 
						||
('\n') karakterini yazdığında aslında dosyaya Carriage Return (CR)('\r') ve LF karakterlerinin ikisi birden yazılır.
 | 
						||
Benzer şekilde dosyadan okuma yapan fonksiyonlar çalışılan sistem windows ise ve dosya text modda açılmışsa
 | 
						||
CRLF karakterlerini yanyana gördüğünde yalnızca LF olarak okuma yaparlar. Aşağıdaki program bir text modda
 | 
						||
açılmış bir dosyaya beş adet LF karakteri yazmaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int i;
 | 
						||
if ((f = fopen("test.txt", "w")) == NULL) {
 | 
						||
printf("can not open file");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 5; ++i)
 | 
						||
fputc('\n', f);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu program Windows sistemlerinde çalıştırıldığında yazma yapılan dosya 10 byte olacaktır. Çünkü her LF yazması
 | 
						||
aslında CR ve LF karakterlerinin ikisinin birden yazılması demektir. Unix/Linux sistemlerinde text dosya ve binary
 | 
						||
dosya arasında hiç bir fark yoktur. Yani yukarıdaki program Unix/Linux sistemlerinde çalıştırıldığında 5 byte
 | 
						||
olacaktır. Çünkü beş adet LF karakteri dosyaya yazılmıştır.
 | 
						||
Aşağıdaki program az önce üretilmiş olan dosya içerisindeki karakterleri okumaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int i;
 | 
						||
int ch;
 | 
						||
if ((f = fopen("test.txt", "rb")) == NULL) {
 | 
						||
printf("can not open file");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
printf("%d\n", ch);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
 | 
						||
250
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
Bu program çalıştırıldığında binary modda olduğundan CRLF çiftlerini okuyacaktır.
 | 
						||
Metin dosyaları için Windows sistemlerinde diğer sistemlere bir fark daha vardır. Windows sistemlerinde text
 | 
						||
modunda okuma yapılırken 26 numaralı Ctrl+Z karakteri sonlandırıcı karakter olarak kabul edilir. Örneğin
 | 
						||
aşağıdaki program bir exe dosyanın tüm karakterini text modda okumaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int i;
 | 
						||
int ch;
 | 
						||
long count = 0;
 | 
						||
if ((f = fopen("Sample.exe", "r")) == NULL) {
 | 
						||
printf("can not open file");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
count++;
 | 
						||
printf("Count=%ld\n", count);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu program windows sistemlerinde çalıştırıldığında dosya içerisinde ctrlz karakteri görüldüğünde okuma
 | 
						||
sonlanacaktır. Ancak aynı program Unix/Linux sistemlerinde çalıştırıldığında herhangi sonlanma olmayacaktır.
 | 
						||
Dosya Göstericisinin Konumlandırılması ve fseek Fonksiyonu
 | 
						||
Anımsanacağı gibi dosya açıldığında dosya göstericisi 0'ıncı offset'tedir. Okuma yazma işlemleriyle dosya
 | 
						||
göstericisi otomatik ilerletilmektedir. Ancak biz isteresek dosya göstericisini fseek fonkisyonuyla istediğimiz
 | 
						||
offset'e konumlandırabiliriz. fseek fonksiyonunun prototipi şöyledir:
 | 
						||
#include <stdio.h>
 | 
						||
int fseek(FILE *stream, long offset, int whence);
 | 
						||
Fonksiyonun birinci parametres, konumlandırılacak dosyaya ilişkin dosya bilgi göstericisini alır. İ;kinci parametre
 | 
						||
dosya opffset'ini belirtmektedir. Üçüncü parametre konumlandırma orijinini belirtir. Üçüncü parametre üç
 | 
						||
değerden birisi olabilir: 0, 1 veya 2. Bu değerler aynı zamanda aşağıdaki gibi define da edilmiştir:
 | 
						||
#define SEEK_SET
 | 
						||
#define SEEK_CUR
 | 
						||
#define SEEK_END
 | 
						||
 | 
						||
0
 | 
						||
1
 | 
						||
2
 | 
						||
 | 
						||
Eğer üçüncü paramere sıfır girilirse ikinci parametre dosyanın başından itibaren offset belirtir. Yani ikinci
 | 
						||
parametre >= 0 olmak zorundadır. Örneğin:
 | 
						||
251
 | 
						||
 | 
						||
fseek(f, 100, SEEK_SET);
 | 
						||
Burada dosya göstericisi 100'üncü offset'e konumlandırılmıştır. Eğer üçüncü parametre 1 girilirse konumlandırma
 | 
						||
dosya göstericisinin gösterdiği yerden itibaren yapılır. Tabi bu durumda ikinci parametre sıfır, pozitif ya da negatif
 | 
						||
olabilir. Örneğin:
 | 
						||
fseek(f, 1, SEEK_CUR);
 | 
						||
Bu işlemle dosya göstericisi neredeyse onun 1 ilerisine konumlandırılır. Örneğin:
 | 
						||
fseek(f, -10, SEEK_CUR);
 | 
						||
Burada dosya göstericisi 10 geriye konumlandırılmaktadır. Eğer son parametre 2 girilirse bu durumda
 | 
						||
konumlandırma EOF'dan itibaren yapılır. Tabi bu durumda ikinci parametre <= 0 girilmek zorundadır. Örneğin:
 | 
						||
fseek(f, 0, SEEK_END);
 | 
						||
Bu durumda dosua göstericisi EOF'a konumlandırılır. Yani artık yazma yapmak istediğimizde dosyaya ekleme
 | 
						||
yapmış oluruz. Fonksiyon konumlandırma başarılıysa 0 değerine, başarısızsa EOF (-1) değerine geri döner.
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
if ((f = fopen("Test.txt", "r+")) == NULL) {
 | 
						||
printf("can not open file");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
fseek(f, 0, SEEK_END);
 | 
						||
fprintf(f, "Yes, this is a test");
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada dosya göstericisi dosyanın sonuna çekilip yazma yapılmıştır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char s[10];
 | 
						||
if ((f = fopen("Test.txt", "r+")) == NULL) {
 | 
						||
printf("can not open file");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
 | 
						||
252
 | 
						||
 | 
						||
fseek(f, 10, SEEK_SET);
 | 
						||
fgets(s, 5, f);
 | 
						||
puts(s);
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada dosya göstericisi dosyanın 10'uncu offset'ine çekilip okuma yapılmıştır.
 | 
						||
C'de okumadan yazmaya yazmadan okumaya geçişte mutlaka fseek ya da fflush işlemi yapılmalıdır.
 | 
						||
stdin, stdout ve stderr Dosyaları
 | 
						||
Bir C programında stdin, stdout ve stderr FILE * türünden dosya bilgi göstericisi belirtmektedir. Bu isimler
 | 
						||
<stdio.h> dosyasında bildirilmiştir. (Dolayısıyla bu isimler anahtar sözcük değildir. Bunları kullanabilmemiz için
 | 
						||
<stdio.h> dosyasını include etmeliyiz.
 | 
						||
stdin dosyasına "standard input" dosyası, stdout dosyasına "standart output" dosyası, stderr dosyasına ise "standard
 | 
						||
error dosyası" denilmektedir.
 | 
						||
C standartlarında "klavye" ve "ekran" lafı edilmemiştir. Standartlara göre stdout ekrana ya da istenirse başka
 | 
						||
aygıtlara yönlendirilebilir. Örneğin printf stdout nereye yönlendirilmişse oraya yazar. stdout eğer yazıcıya
 | 
						||
yönlendirilmişse printf de yazıcıya yazdıracaktır. Bugün kullandığımız masaüstü ve dizüstü bilgisayarlarda stdout
 | 
						||
default olarak ekrana yönlendirilmiş durumdadır. Fakat biz onu değiştirebiliriz. Örneğin standartlara göre getchar,
 | 
						||
gets fonksiyonlar stdin dosyasından okuma yapar. stdin de bilgisayarlarımızda default olarak klavyeye
 | 
						||
yönlendirilmiş durumdadır. Fakat biz onu başka aygıtlara yönlendirebiliriz. Örneğin:
 | 
						||
printf(.....);
 | 
						||
çağrısının aslında,
 | 
						||
fprintf(stdout, ....);
 | 
						||
çağrısından hiçbir farkı yoktur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
fprintf(stdout, "This is a test\n");
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Benzer biçimde örneğin getchar aslında fgetc(stdin) ile aynı işlemi yapar.
 | 
						||
stderr dosyası hata mesajlarının yazdırırlacağı dosyayı temsil eder. Default durumda stderr dosyası da ekrana
 | 
						||
yönlendirilmiştir. Ancak biz onu dosyaya ya da başka aygıta yönlendirebiliriz. Programın hata msajlarının stdout
 | 
						||
yerine fprintf fonksiyonuyla stderr dosyasına yazdırılması iyi bir tekniktir. Örneğin:
 | 
						||
if ((p = malloc(size)) == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
253
 | 
						||
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
Windows'ta ve UNIX/Linux sistemerinde stdout dosyasını yönlendirmek için komut satırında > işareti kullanılır.
 | 
						||
Örneğin:
 | 
						||
sample > x.txt
 | 
						||
Benzer biçimde stderr dosyası 2> sembolüyle yönlendirilir. Örneğin:
 | 
						||
sample 2> y.txt
 | 
						||
Burada stdout dosyasına yazılanlar yine ekrana çıkar fakat stderr dosyasına yazılanlar y.txt dosyasına yazılır. İki
 | 
						||
yönlendirme beraber de kullanılabilir:
 | 
						||
sample > x.txt 2> y.txt
 | 
						||
stdin dosyasını yönlendirmek için < sembolü kullanılmaktadır. Örneğin:
 | 
						||
sample < x.txt
 | 
						||
Buarada artık stdin dosyasından okuma yapıldığında sanki x.txt dosyasındakiler klavyeden girilmiş gibi işlem
 | 
						||
gerçekleşir.
 | 
						||
Bit Operatörleri (Bitwise Operators)
 | 
						||
Bit operatörleri sayıların karşılıklı bitleri üzerinde işlem yapan operatörlerdir. Bit operatörleri şunlardır:
 | 
						||
~
 | 
						||
<<
 | 
						||
>>
 | 
						||
&
 | 
						||
|
 | 
						||
^
 | 
						||
 | 
						||
Bit Not (Bitwise Not)
 | 
						||
Sola Öteleme (left shift)
 | 
						||
Sağa Öteleme (right shift)
 | 
						||
Bit And (Bitwise And)
 | 
						||
Bit Or (Bitwise Or)
 | 
						||
Bit Exor (Bit Exclusive Or)
 | 
						||
 | 
						||
Bit And ve Bit Or Operatörleri
 | 
						||
Tek ampsersand Bit And, Tek çubuk Bit Or operatörü anlamına gelir. (Anımsanacağı gibi bunlardan iki tane yan
 | 
						||
yana getirilirse mantıksal And ve Mantıksal Or operatörleri anlaşılır.) Bu operatörler sayının karşılıklı bitlerini And
 | 
						||
ve Or işlemine sokarlar. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned char a = 0x5F;
 | 
						||
unsigned char b = 0xC4;
 | 
						||
unsigned c;
 | 
						||
c = a & b;
 | 
						||
printf("%02X\n", c);
 | 
						||
 | 
						||
/* 0x44 */
 | 
						||
 | 
						||
c = a | b;
 | 
						||
 | 
						||
254
 | 
						||
 | 
						||
printf("%02X\n", c);
 | 
						||
 | 
						||
/* 0xDF */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Soru: Bir sayının n'inci bitinin durumunu belirleyiniz.
 | 
						||
Yanıt: Sayı tüm bitleri sıfır n'inci 1 olan bir sayıyla Bit And işlemine sokulur. Sonuç sıfırsa n'inci sıfır, sonuç
 | 
						||
sıfırdan farklıysa n'inci bir 1'dir.
 | 
						||
Soru: Sayının diğer bitlerine dokunmadan n'inci bitini 0 yapınız.
 | 
						||
Yanıt: O sayıyı bütün bitleri 1 olan n'inci biti 0 olan bir sayıyla Bit And işlemine sokarız.
 | 
						||
Soru: Sayının diğer bitlerine dokunmadan n'inci bitini 1 yapınız.
 | 
						||
Yanıt: O sayıyı bütün bitleri 0 olan n'inci biti 1 olan bir sayıyla Bit Or işlemine sokarız.
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned char a = 0x7F;
 | 
						||
a &= 0xDF;
 | 
						||
printf("%02X\n", a);
 | 
						||
 | 
						||
/* 0x5F */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bit Exor Operatörü
 | 
						||
Eğer bitler aynıysa 0 değerini veren bitler farklıysa 1 değerini veren işleme EXOR (exclusive Or) işlemi
 | 
						||
denilmektedir. EXOR işleminin doğruluk tablosu şöyledir:
 | 
						||
a
 | 
						||
0
 | 
						||
0
 | 
						||
1
 | 
						||
1
 | 
						||
 | 
						||
b
 | 
						||
0
 | 
						||
1
 | 
						||
0
 | 
						||
1
 | 
						||
 | 
						||
a^b
 | 
						||
0
 | 
						||
1
 | 
						||
1
 | 
						||
0
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned char a = 0x9A;
 | 
						||
unsigned char b = 0x7C;
 | 
						||
unsigned char c;
 | 
						||
 | 
						||
255
 | 
						||
 | 
						||
c = a ^ b;
 | 
						||
printf("%02X\n", c);
 | 
						||
 | 
						||
/* 0xE6 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Exor geri dönüşümlü bir operatördür. Bu nedenle şifreleme işlemlerinde çok kullanılır. Yani a ^ b = c ise, c ^ b = a
 | 
						||
ve c ^ a = b'dir. Böylece bir dosyanın byte'ları birtakım değerlerle Exor çekilerek bozulmuşsa yine aynı değerlerle
 | 
						||
exor çekilerek düzeltilebilir. (Yani değeri bozan anahtarla açan anahtar aynı olabilmektedi.) Örneğin Exor ile
 | 
						||
şifreleme için şöyle bir örnek verilebilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
char passwd[128];
 | 
						||
unsigned seed;
 | 
						||
int i;
 | 
						||
int ch;
 | 
						||
printf("Enter password:");
 | 
						||
gets(passwd);
 | 
						||
seed = 0;
 | 
						||
for (i = 0; passwd[i] != '\0'; ++i)
 | 
						||
seed = seed * 31 + passwd[i];
 | 
						||
if ((f = fopen("test.txt", "r+")) == NULL) {
 | 
						||
fprintf(stderr, "cannot open file!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
srand(seed);
 | 
						||
while ((ch = fgetc(f)) != EOF) {
 | 
						||
ch ^= rand() % 256;
 | 
						||
fseek(f, -1, SEEK_CUR);
 | 
						||
fputc(ch, f);
 | 
						||
fseek(f, 0, SEEK_CUR);
 | 
						||
}
 | 
						||
fclose(f);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Exor işleminde 0 etkisiz elemandır. 1 ise tersleme işlemi yapar.
 | 
						||
Soru: Bir sayının diğer bitlerine dokunmadan n'inci bitini onun tersiyle yer değiştiriniz.
 | 
						||
Yanıt: Sayı tüm bitleri 0 olan n'inci biti 1 olan bir sayıyla Exor çekilir.
 | 
						||
Bit Not Operatörü
 | 
						||
C'de ~ sembolü tek operandlı önek bir bit operatörüdür. Sayının 0 olan bitlerini 1, 1 olan bitlerini sıfır yapar.
 | 
						||
Örneğin :
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
256
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned a = 0, b;
 | 
						||
b = ~a;
 | 
						||
printf("%u\n", b);
 | 
						||
 | 
						||
/* 4294967295 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
&, |, ^ ve ~ Operatörlerinin Öncelik Tablosundaki Yerleşimi
 | 
						||
Bit operatörlerinden ~ operatörü tek operandlı olduğu için öncelik tablosunun ikinci düzeyinde sağdan-sola grupta
 | 
						||
bulunur. & ve | operatörleri mantıksal operatörlerden daha önceliklidir.
 | 
						||
( ) [ ] -> .
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
+ - ++ -- ! (tür), sizeof * & ~
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
&
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
^
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
|
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
?:
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
= += -= *= /= %= |= &= ^=
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
,
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
& ve | operatörleri karşılaştırma operatörlerinden daha düşük önceliklidir. Maalesef programcılar aşağıdaki gibi
 | 
						||
hataları sık yapmaktadır:
 | 
						||
if (a & 0x80 != 0)
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
{
 | 
						||
 | 
						||
Burada sayının en yüksek anlamlı bitinin 1 olup olmadığında bakılmak istenmiştir. Ancak != operatörü &
 | 
						||
operatöründen daha öncelikli olduğu için kullanım hatalıdır. İşlemin şöyle yapılması gerekirdi:
 | 
						||
if ((a & 0x80) != 0)
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
{
 | 
						||
 | 
						||
Bazı derleyiciler bu tür durumlarda uyarı ile programcının dikkatini çekmektedir.
 | 
						||
Sola ve Sağa Öteleme Operatörleri
 | 
						||
257
 | 
						||
 | 
						||
C'de << operatörüne sola öteleme (left shift), >> operatörüne ise sağa öteleme (right shift) operatörü denilmektedir.
 | 
						||
Bu operatörler iki operandlı araek operatörlerdir.
 | 
						||
Öteleme operatörlerinin sol tarafındaki operand'lar ötelenecek değeri, sağ tarafındaki operand'lar öteleme miktarını
 | 
						||
belirtir. Sola ötelemede her bir bir sola kaydırlır. En soldaki bit kaybedilir, en sağdan sıfırla besleme yapılır.
 | 
						||
Örneğin:
 | 
						||
 | 
						||
Sayıyı 1 kez sola ötelemek onu ikiyle çarpmak anlamına gelir. Örneğin:
 | 
						||
a = 0x02;
 | 
						||
b = a << 1;
 | 
						||
 | 
						||
/* 0000 0010 */
 | 
						||
/* 0000 0100 */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned int a = 2, b;
 | 
						||
b = a << 1;
 | 
						||
printf("%d\n", b);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
İşaretli syaılarda sola öteleme işlemi sırasında taşma olabilir. Eğer sola ötelemede taşma olursa tanımsız davranış
 | 
						||
(undefined behavior) oluşur. Örneğin:
 | 
						||
int a = -2147483647, b;
 | 
						||
b = a << 1;
 | 
						||
/* Bu sayının iki katı int sınırlarının dışında! Tanımsız davranış! */
 | 
						||
 | 
						||
Örneğin:
 | 
						||
int a = -1, b;
 | 
						||
b = a << 1;
 | 
						||
 | 
						||
/* sorun yok b = -2 */
 | 
						||
 | 
						||
Sağa ötelemede ise sayının bütün bitleri bir sağa kaydırılır. en sağdaki bit kaybedilir. En soldan sıfır ile besleme
 | 
						||
yapılır. Sayıyı bir kez sağa ötelemek 2'ye tam bölmek anlamına gelir.
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
 | 
						||
258
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned int a = 100, b;
 | 
						||
b = a >> 2;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* 25 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Eğer sayı işaretliyse ve negatifse en soldan besleme 0 ile yapılırsa sayı pğozitif hale gelir. Sayının negatifliğinin
 | 
						||
korunması için en soldan 1 ile beslenmesi gerekir. İşte standartlara göre işaretli negatif sayıların sağa
 | 
						||
ötelenmesinde en soldan 0'la mı besleme yapılacağı yoksa 1 ile mi besleme yapılacağı (başka bir deyişle işaret
 | 
						||
bitinin korunup korunmayacağı) derleyicileri yazanların isteğine bırakılmıştır. Bu nedenle işaretli sayıların sağa
 | 
						||
ötelenmesine dikkat edilmelidir. Örneğin Microsoft derleyhicileri ve gcc derleyicileri işaret bitini korumaktadır:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = -2, b;
 | 
						||
b = a >> 2;
 | 
						||
printf("%d\n", b);
 | 
						||
 | 
						||
/* -1 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Biz sola ya da sağa istediğimiz kadar öteleme yapabiliriz. Kuralları özetlemek gerekirse:
 | 
						||
- İşaretsiz sayıların sola ve sağa ötelenmesinde tanımsız davranış ya da derleyiciye bağlı davarnış gözükmez. Sola
 | 
						||
ötelemede taşma olsa bile yüksek alnmlı bit atılır.
 | 
						||
- İşaretli sayıların sola ötelenmesinde taşma olursa tanımsız davranış oluşur.
 | 
						||
- İşaretli negatif sayıların sağa ötelenmesinde işaret bitinin korunup korunmayacağı derleyicileri yazanların isteğine
 | 
						||
bırakılmıştır.
 | 
						||
Soru: Sayının n'inci, 1'leyiniz . Fakat n'in değerini bilmiyoruz. (Örneğin n klavyeden giriliyor.)
 | 
						||
Yanıt: Mademki n değeri bilinmiyor işlem şöyle yapılabilir (a ilgili sayı olsun, n de bit numarasını göstersin):
 | 
						||
b = a | 1 << n;
 | 
						||
Soru: Sayının n'inci, 0 layınız. Fakat n'in değerini bilmiyoruz. (Örneğin n klavyeden giriliyor.)
 | 
						||
Yanıt: İşlem şöyle yapılabilir:
 | 
						||
b = a & ~(1 << n )
 | 
						||
Soru: Klavyeden bir n değeri isteyiniz. Öyle bir sayı oluşturunuz ki, son n biti 1 olsun diğer bitleri 0 olsun.
 | 
						||
Örneğin n = 4 için şöyle bir sayı oluşturulmalıdır:
 | 
						||
0000 1111
 | 
						||
n = 2 için şöyle bir sayı oluşturulmalıdır:
 | 
						||
259
 | 
						||
 | 
						||
0000 0011
 | 
						||
Yanıt: İşlem şöyle yapılabilir:
 | 
						||
a = ~(~0U << n);
 | 
						||
Soru: Klavyeden bir n değeri isteyiniz. Öyle bir sayı oluşturunuz ki ilk n biti 1 olsun diğer bitleri 0 olsun. Örneğin
 | 
						||
n = 4 için şöyle bir sayı oluşturulmalıdır:
 | 
						||
1111 0000
 | 
						||
Yanıt:
 | 
						||
a = ~(~0U >> n)
 | 
						||
Soru: İşaretsiz bir sayı var. 1 olan bitlerinin kaç tane olduğunu bulunuz.
 | 
						||
Yanıt: Eğer döngüsüz yapılacaksa bir look-up table oluşturulur. 1 byte'lık sayılar için bu mümkün olsa da 2
 | 
						||
byte'lık ve daha uzun sayılar için sorunludur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
unsigned n, count;
 | 
						||
printf("Sayi giriniz:");
 | 
						||
scanf("%d", &n);
 | 
						||
count = 0;
 | 
						||
do {
 | 
						||
if (n & 1)
 | 
						||
++count;
 | 
						||
n >>= 1;
 | 
						||
} while (n);
 | 
						||
printf("%d\n", count);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Öteleme operatörleri öncelik tablosunda artimetik operatörlerle karşılaştırma operatörleri arasında bulunur. Öncelik
 | 
						||
tablonun son şekli şöyledir:
 | 
						||
( ) [ ] -> .
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
+ - ++ -- ! (tür), sizeof * & ~
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
* /%
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
+ -
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
<<
 | 
						||
 | 
						||
>>
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
< > <= >=
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
== !=
 | 
						||
 | 
						||
Soldan-Sağa
 | 
						||
 | 
						||
&
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
^
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
260
 | 
						||
 | 
						||
|
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
&&
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
||
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
?:
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
= += -= *= /= %= |= &= ^=
 | 
						||
 | 
						||
Sağdan Sola
 | 
						||
 | 
						||
,
 | 
						||
 | 
						||
Soldan Sağa
 | 
						||
 | 
						||
Programların Komut Satırı Argümanları
 | 
						||
Bir programı komut satırından çalıştırırken programdan isminden sonra girilen yazılara komut satırı argümanları
 | 
						||
(command line arguments) denilmektedir. Standartlara göre C'de main fonksiyonunun geri dönüş değeri int olmak
 | 
						||
zorundadır. main fonksiyonunun parametresi ya void olmak zorundadır ya da main iki parametreye sahip olabilir.
 | 
						||
Parametrelerden biri int türden diğeri char ** türündendir. Bu parametre char *[] biçiminde de ifade edilemektedir.
 | 
						||
Yani main fonksiyonu aşağıdaki iki biçimden biri olarak tanımlanmak zorundadır:
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
İkinci biçimdeki parametre değişken isimleri istenildiği gibi alınabilir. Ancak argc (argument counter) ve argv
 | 
						||
(argument variable list) isimleri gelenekseldir.
 | 
						||
main fonksiyonuna bu argümanları işletim sistemi ve derleyicilerin giriş kodları geçirmektedir. Birinci parametre
 | 
						||
komut satırına girilen boşlukla ayrılmış yazıların sayısını belirtir (programın ismi de dahil). Örneğin:
 | 
						||
ls -l -i
 | 
						||
Burada argc 3 olarak geçirilir. Yalnızca programın ismini yazarak programı çalıştırmak isteseydik argc değeri 1
 | 
						||
olurdu. argv ise komut satırı argümanlarına ilişkin yazıların başlangıç adreslerinin bulunduğu diziyi belirtir. Yani
 | 
						||
argv'nin her elemanı sırasıyla programın isminden başlayarak bir argümanı bize verir. Dizinin sonunda NULL
 | 
						||
adresin bulunacağı garanti edilmektedir. Örneğin:
 | 
						||
 | 
						||
261
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; i < argc; ++i)
 | 
						||
printf("%s\n", argv[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aynı döngü şöyle de kurulabilirdi:
 | 
						||
#include <stdio.h>
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; argv[i] != NULL; ++i)
 | 
						||
printf("%s\n", argv[i]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Visual Studio IDE'sinde komut satırı argümanları proje ayarlarında "Debugging / Command Arguments"
 | 
						||
kısmından girilebilir.
 | 
						||
Örneğin type (ya da cat) işlemini yapan bir program şöyle yazılabilir:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
FILE *f;
 | 
						||
int ch;
 | 
						||
if (argc == 1) {
 | 
						||
printf("usage: <mytype> <path>\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if (argc > 2) {
 | 
						||
fprintf(stderr, "too many arguments!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
if ((f = fopen(argv[1], "r")) == NULL) {
 | 
						||
fprintf(stderr, "cannot open file: %s\n", argv[1]);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
 | 
						||
262
 | 
						||
 | 
						||
}
 | 
						||
while ((ch = fgetc(f)) != EOF)
 | 
						||
putchar(ch);
 | 
						||
if (ferror(f)) {
 | 
						||
fprintf(stderr, "IO error!\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Programın başında komut satırı argümanlarının kontrol edilmesiyle sık karşılaşılmaktadır. Yukarıda da gördüğünüz
 | 
						||
gibi önce program uygun komut satırı argümanlarıyla çalıştırılmış mı diye bakılmıştır.
 | 
						||
Komut satırı argümanlarının birer yazı biçiminde bize verildiğine dikkat ediniz. Örneğin biz n tane sayının
 | 
						||
toplamını yazdıran add isimli bir program yazmak isteyelim:
 | 
						||
./add 10 20 30 40
 | 
						||
Önce bu tazıları sayıya dönüştürmemiz gerekir. Bunun için ise atoi, atol ya da atof fonksiyonları kullanılmaktadır.
 | 
						||
atoi, atol ve atof Fonksiyonları
 | 
						||
Prototipi <stdlib.h> dosyasında bulunan bu standart C fonksiyonları bizden sayı belirten bir yazıyı parametre
 | 
						||
olarak alır ve onu gerçekten sayı olarak bize verir:
 | 
						||
int atoi(const char *str);
 | 
						||
long atol(const char *str);
 | 
						||
double atof(const char *str);
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = "123.45";
 | 
						||
double d;
 | 
						||
d = atof(s);
 | 
						||
printf("%f\n", d);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bu fonksiyonlar yazının başındaki boşluk karakterlerini (white space) atarlar. İlk uygun olmayan karakter
 | 
						||
gördüklerinde işlemini sonlandırırlar. Hiçbir uygun karaktger bulunamazsa bu fonksiyonlar 0 ile geri dönmektedir.
 | 
						||
atoi fonksiyonu şöyle yazılabilir.
 | 
						||
#include <stdio.h>
 | 
						||
#include <ctype.h>
 | 
						||
int myatoi(const char *str)
 | 
						||
{
 | 
						||
int val = 0;
 | 
						||
int sign = 1;
 | 
						||
 | 
						||
263
 | 
						||
 | 
						||
while (isspace(*str))
 | 
						||
++str;
 | 
						||
if (*str == '-') {
 | 
						||
sign = -1;
 | 
						||
++str;
 | 
						||
}
 | 
						||
while (isdigit(*str)) {
 | 
						||
val = val * 10 + *str - '0';
 | 
						||
++str;
 | 
						||
}
 | 
						||
return val * sign;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[] = " -1205";
 | 
						||
int i;
 | 
						||
i = myatoi(s);
 | 
						||
printf("%d\n", i);
 | 
						||
 | 
						||
/* 12 */
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Şimdi yukarıda belirtilen add programını yazalım:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
double total = 0;
 | 
						||
int i;
 | 
						||
for (i = 1; i < argc; ++i)
 | 
						||
total += atof(argv[i]);
 | 
						||
printf("%f\n", total);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
itoa ve ltoa Fonksiyonları
 | 
						||
itoa fonksiyonu atoi fonksiyonun, ltoa fonksiyonu da atol fonksiyonun ters işlemini yapan fonksiyonlardır. Ancak
 | 
						||
bu fonksiyonlar standart C fonksiyonları değildir. Dolayısıyla bazı C derleyicilerinde bulunmayabilirler. Örneğin
 | 
						||
bu fonksiyonlar Microsoft ve Borland derleyicilerinde varken, gcc derleyicilerinde bulunmamaktadır. Bunun
 | 
						||
yerine standart sprintf sonlsiyonu kullanılabilir.
 | 
						||
sprintf Fonksiyonu
 | 
						||
sprintf fonksiyonu printf fonksiyonunun char türden bir diziye yazan versiyonudur. Nasıl fprintf fonksiyonu
 | 
						||
printf'in dosyaya yazan versiyonuysa sprintf de diziye yazan versiyonudur. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
 | 
						||
264
 | 
						||
 | 
						||
char s[100];
 | 
						||
int a = 10, b = 20;
 | 
						||
sprintf(s, "a = %d, b = %d", a, b);
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sprintf fonksiyonun birinci parametresi char türden bir adrestir. Diğer parametreleri printf ile aynıdır. Genel olarak
 | 
						||
bir sayıyı yazıya dönüştürmek için de sprintf kullanılabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100];
 | 
						||
double d = 12.345;
 | 
						||
sprintf(s, "%f", d);
 | 
						||
puts(s);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sscanf Fonksiyonu
 | 
						||
Bu fonksiyon scanf'in char türden diziden okuyan versiyonudur. Yani fscanf nasıl dosyadan okuyorsa, sscanf de
 | 
						||
diziden girilenleri sanki klavyeden (stdin dosyasından) girilmiş gibi ele alır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char s[100] = "
 | 
						||
int a, b;
 | 
						||
 | 
						||
123 456
 | 
						||
 | 
						||
";
 | 
						||
 | 
						||
sscanf(s, "%d%d", &a, &b);
 | 
						||
printf("a = %d, b = %d\n", a, b);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
sscanf fonksiyonunun birinci parametresi char türden dizinin adresini alır. Diğer parametreleri scanf fonksiyonunda
 | 
						||
olduğu gibidir. Dolayısıyla bu fonksiyon da yazıyı sayıya dönüştüren atoi, atol ve atof fonksiyonlarının daha genel
 | 
						||
bir biçim olarak kullanılabilir.
 | 
						||
Gösteriyi Gösteren Göstericiler (Pointers to Pointer)
 | 
						||
Göstericiler de birer nesne olduğuna ve bellekte yer kapladığına göre onların da adresleri alınabilir. Bir göstericinin
 | 
						||
adresi alınırsa nasıl bir göstericiye yerleştirilmelidir. İşte T türünden bir göstericinin adresi T* türünden bir
 | 
						||
göstericiye yerleştirilebilir. Böyle göstericiler iki tane * atomu ile bildirilirler. Örneğin:
 | 
						||
int **ppi;
 | 
						||
Burada ppi bir gösterixiyi gösteren göstericidir. Yani gösterici türünden göstericidir. Biz ppi'yi * operatörüyle
 | 
						||
kullanırsa (*ppi'yi kapatıp sola bakın) elde ettiğimiz nesne int * türündendir. Yani bir göstericidir. Yani:
 | 
						||
ppi, int ** türündendir.
 | 
						||
265
 | 
						||
 | 
						||
*ppi, int * türündendir.
 | 
						||
**ppi, int türdendir.
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 10;
 | 
						||
int *pi;
 | 
						||
int **ppi;
 | 
						||
pi = &a;
 | 
						||
ppi = π
 | 
						||
printf("%d\n", **ppi);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Anımsanacağı gibi bir dizinin ismini bir ifadede kullandığımızda aslında o dizinin adresini kullanmış oluruz. Başka
 | 
						||
bir deyişle bir dizinin ismi o dizinin ilk elemanın adresi gibidir. O halde bir gösterici dizisinin ismi de o gösterici
 | 
						||
dizisinin ilk elemanın adresi gibi olacağına göre iki yıldızlı bir göstericiye atanabilir. Örneğin:
 | 
						||
char *s[10];
 | 
						||
Burada s ifadesi char ** türündendir. Yani char türden bir göstericiyi gösteren göstericiye atanabilir:
 | 
						||
char **ppc;
 | 
						||
ppc = s;
 | 
						||
Örneğin biz bir gösterici dizini bir fonksiyona geçirmek istesek fonksiyonun parametre değişkeni göstericiyi
 | 
						||
gösteren gösterici olmalıdır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
void disp_names(char **ppnames);
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
char *names[] = { "ali", "veli", "selami", "ayse", "fatma", NULL };
 | 
						||
disp_names(names);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
266
 | 
						||
 | 
						||
void disp_names(char **ppnames)
 | 
						||
{
 | 
						||
int i;
 | 
						||
for (i = 0; ppnames[i] != NULL; ++i)
 | 
						||
printf("%s\n", ppnames[i]);
 | 
						||
}
 | 
						||
 | 
						||
Fonksiyonun göstericiyi gösteren gösterici parametresi yine dizi formunda belirtilebilir. Yani örneğin:
 | 
						||
void disp_names(char **ppnames)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
ile,
 | 
						||
void disp_names(char *ppnames[])
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
aynı anlamdadır. Ya da örneğin:
 | 
						||
int main(int argc, char *argv[])
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
ile,
 | 
						||
int main(int argc, char **argv)
 | 
						||
{
 | 
						||
...
 | 
						||
}
 | 
						||
aynı anlamdadır.
 | 
						||
Bazen fonksiyona bir gösterici bir göstericinin adresi gönderilir. Fonksiyon da o göstericinin içerisine birşeyler
 | 
						||
yazabilir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
void mallocstr(char **ptr, size_t size)
 | 
						||
{
 | 
						||
*ptr = (char *)malloc(size);
 | 
						||
if (*ptr == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
 | 
						||
267
 | 
						||
 | 
						||
char *str;
 | 
						||
mallocstr(&str, 32);
 | 
						||
gets(str);
 | 
						||
puts(str);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir göstericiyi gösteren göstericiyi 1 artırdığımızda içindeki adresin sayısal değeri kaç artar? Yanıt bir gösterici
 | 
						||
kadar artar. Örneğin argv char ** türünden olsun. ++argv yaptığımızda argv'nin içerisindeki adres bir gösterici
 | 
						||
(yani 32 bit sistemlerde 4 byte, 64 bit sistemlerde 8 byte) artacaktır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int main(int argc, char **argv)
 | 
						||
{
 | 
						||
while (*argv != NULL)
 | 
						||
puts(*argv++);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bilindiği gibi C'de void * türünden bir göstericiye biz her türden adresi atayabiliriz. Ancak void ** türünden
 | 
						||
göstericiye her adresi atayamayız. Yalnızca void * türünden göstericinin adresini atayabiliriz. Örneğin:
 | 
						||
int a;
 | 
						||
int *pi;
 | 
						||
void *pv;
 | 
						||
void **ppv;
 | 
						||
pi = &a;
 | 
						||
pv = pi;
 | 
						||
ppv = &pv;
 | 
						||
ppv = π
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* geçersiz! */
 | 
						||
 | 
						||
int * türünün void * türüne atanması normal ve geçerlidir. Ancak bu int ** türünün void ** türüne atanabileceği
 | 
						||
anlamına gelmez. void ** türüne biz yalnızca void ** türünden bir adresi atayabiliriz. Biz aslında int ** türünü de
 | 
						||
istersek void * türünden bir göstericiye atayabiliriz. Örneğin:
 | 
						||
int a;
 | 
						||
int *pi;
 | 
						||
int **ppi;
 | 
						||
void *pv;
 | 
						||
int **ppi2;
 | 
						||
pi = &a;
 | 
						||
ppi = π
 | 
						||
pv = ppi;
 | 
						||
ppi2 = (int **) pv;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* geçerli */
 | 
						||
/* tür dönüştürmesi zorunluı değildir */
 | 
						||
 | 
						||
Gösterici dizileri için dinamik tahsisatlar yapabiliriz. Örneğin önce 5 elemanlı char türden bir gösterici dizisini
 | 
						||
dinamik olarak tahsis edelim. Sonra da dizinin her elemanının 32 byte'lık char türden dinamik tahsis edilmiş bir
 | 
						||
diziyi göstermesini sağlayalım:
 | 
						||
268
 | 
						||
 | 
						||
char **ppc;
 | 
						||
int i;
 | 
						||
if ((ppc = (char **)malloc(5 * sizeof(char *))) == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
for (i = 0; i < 5; ++i)
 | 
						||
if ((ppc[i] = (char *)malloc(32)) == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
...
 | 
						||
for (i = 0; i < 5; +i)
 | 
						||
free(ppc[i]);
 | 
						||
free(ppc);
 | 
						||
Soru: Yukarıdaki tahsisat sistemini tek malloc ile oluşturunuz. Böylece tek free ile sistem serbest bırakılsın.
 | 
						||
Yanıt: Sistem için gereken tüm bellek 5 * sizeof(char *) + 5 * 32 byte kadardır. Önce bu tahsis edilir. Sonra da
 | 
						||
gösterici dizisinin elemanlarının kendi alanlarını göstermesi sağlanır:
 | 
						||
char **ppc;
 | 
						||
char *pc;
 | 
						||
int i;
 | 
						||
if ((ppc = (char **)malloc(5 * (sizeof(char *) + 32))) == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
pc = (char *)(ppc + 5);
 | 
						||
for (i = 0; i < 5; ++i) {
 | 
						||
ppc[i] = pc;
 | 
						||
pc += 32;
 | 
						||
}
 | 
						||
...
 | 
						||
free(ppc);
 | 
						||
 | 
						||
269
 | 
						||
 | 
						||
Tabi böyle bir sistemde 32 byte'lık diziler daha sonra realloc ile büyütülü küçültülemezler.
 | 
						||
C'de aslında göstericiyi gösteren göstericiyi gösteren göstericiler ve daha çok kademeli göstericiler de bildirilebilir.
 | 
						||
Ancak üç yıldızlı göstericilerin bile pek bir kullanım alanı yoktur. Biz bir göstericiyi gösteren göstericinin adresini
 | 
						||
üç yıldızlı bir göstericiye yerleştirebiliriz. Örneğin:
 | 
						||
int a = 10;
 | 
						||
int *pi;
 | 
						||
int **ppi;
 | 
						||
int ***pppi;
 | 
						||
int ****ppppi;
 | 
						||
pi = &a;
 | 
						||
ppi = π
 | 
						||
pppi = &ppi;
 | 
						||
ppppi = &pppi;
 | 
						||
Çok Boyutlu Diziler
 | 
						||
Çok boyutlu dizler bazı olayları temsil ederken okunabilirliği artırmak için kullanılabilmektedir. Örneğin bir
 | 
						||
satranç tahtasını temsil etmek için iki boyutlu bir dizi kullanabiliriz. Bir matematiksel bir matrisi temsil etmek için
 | 
						||
yine iki boyutlu bir diziden faydalanabiliriz. Dizilerin boyut sayıları fazla olabilse de pratikte en çok kullanılan çok
 | 
						||
boyutlu diziler iki boyutlu dizilerdir. İki boyutlu dizilere matris de denilmektedir.
 | 
						||
C'de çok boyutlu diziler aslında bellekte tek boyutlu diziymiş gibi tutulmaktadır. Zaten bellek çok boyutlu değildir.
 | 
						||
Tek boyutludur. Çok boyutlu diziler aslında "dizi dizileri" gibi düşünülmelidir. Örneğin 4x3'lik bir matris aslında
 | 
						||
her elemanı 3 elemanlık dizi olan 4'lik dizi gibidir. C'de çok boyutlu diziler birden fazla köşeli parantezle
 | 
						||
bildirilirler. Örneğin:
 | 
						||
 | 
						||
270
 | 
						||
 | 
						||
Bildirimdeki ilk köşeli parantez dizi dizisinin uzunluğunu, ikinci köşeli parantez eleman olan dizilerin uzunluğunu
 | 
						||
belirtir. Örneğin:
 | 
						||
 | 
						||
Çok boyutlu bir diziye birden fazla küme paranteziyle ilkdeğer verilebilir. Örneğin:
 | 
						||
int a[4][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[4][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10, 11, 12 } };
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < 4; ++i) {
 | 
						||
for (k = 0; k < 3; ++k)
 | 
						||
printf("%d ", a[i][k]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Aslında iç küme parantezleri ayrıca kullanılmak zorunda değildir. Örneğin:
 | 
						||
int a[4][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
 | 
						||
 | 
						||
Fakat bir elemanın yazılması unutulduğunda diğer elemanlar birer kayabilir. Bu nedenle iç küme parantezlerinin
 | 
						||
belirtilmesi tavsiye edilir.
 | 
						||
Biz matriste erişim için yalnızca ilk köşeli parantezi kullanırsak (örneğin a[2] gibi) bu ifade bir nesne belirtmez.
 | 
						||
Dizi dizisinin o indise ilişkin dizisinin başlangıç adresini belirtir. Örneğin:
 | 
						||
 | 
						||
271
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[4][3];
 | 
						||
int *pi;
 | 
						||
pi = a[2];
 | 
						||
pi[0] = 10;
 | 
						||
pi[1] = 20;
 | 
						||
pi[2] = 30;
 | 
						||
 | 
						||
/* geçerli */
 | 
						||
/* a[2][0] = 10 */
 | 
						||
/* a[2][1] = 20 */
 | 
						||
/* a[2][1] = 30 */
 | 
						||
 | 
						||
printf("%d, %d, %d\n", a[2][0], a[2][1], a[2][2]);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir matrisin yani dizisinin ismi yine o dizi dizisinin başlangıç adresini belirtir. Bir dizi dizisinin başlangıç adresi
 | 
						||
dizi göstericisine atanabilir. Gösterdiği yer bir dizi olan göstericiye dizi göstericisi (pointer to array) denir. Dizi
 | 
						||
göstericileri şöyle bildirilir:
 | 
						||
<tür> (*<gösterici_ismi>)[<sütun uzunluğu>];
 | 
						||
Örneğin:
 | 
						||
int (*pa)[4];
 | 
						||
Burada pa'nın gösterdiği yerde (yani *pa) int[4] türünden bir nesne başka bir dyişle 4 elemanlı bir dizi vardır. *pa
 | 
						||
bir nesne değildir, sanki bir dizinin ismi gibidir. İşte int[N][M] türünden bir matrisin ismi böyle bir göstericiye
 | 
						||
atanabilir. Örneğin:
 | 
						||
 | 
						||
272
 | 
						||
 | 
						||
Aşağıdaki gibi bir matris bildirimi yapılmış olsun:
 | 
						||
int a[4][3];
 | 
						||
Burada a'yı ve a[i]'yi atayacağımız göstericiler nasıl olmalıdır?
 | 
						||
 | 
						||
Aşağıdaki gibi bir dizi göstericisi bildirilmiş olsun:
 | 
						||
int (*pa)[3];
 | 
						||
Burada pa'yı bir artırdığımızda pa'nın içerisindeki adres 3 * sizoef(int) kadar artar.
 | 
						||
Bir matirisin ismini atayacağımız dizi göstericisinin sütun uzunluğuyla matrisin sütun uzunluğu aynı olmak
 | 
						||
zorundadır. Örneğin:
 | 
						||
 | 
						||
İkiden fazla boyut söz konusu oılduğunda aynı prensip söz konusudur. Örneğin:
 | 
						||
int a[3][4][5];
 | 
						||
Burada a "dizi dizisi dizisidir". a adresini atayacağımız dizi göstericisi de iki köşeli parantez içermelidir:
 | 
						||
int (*pa)[4][5];
 | 
						||
pa = a;
 | 
						||
273
 | 
						||
 | 
						||
Yani birinci boyut dışındaki bütün boyutların uzunlukları belirtilmek zorundadır. Burada *pa nesne değildir.
 | 
						||
pa[i][k]'da nesne değildir. Ancak pa[i][k][j] bir nesnedir. Pekiyi a[i]'yi atayacağımız gösterici nasıl olmalıdır? Yanit:
 | 
						||
int (*pa)[5];
 | 
						||
biçiminde olmalıdır. Çünkü a[i] bir matrisin adresi gibidir. O da yukarıdaki gibi bir göstericiye atanabilir.
 | 
						||
Pekiyi bir matrisi fonksiyona parametre olarak nasıl geçirebiliriz:
 | 
						||
#include <stdio.h>
 | 
						||
void disp(int(*pa)[3], int size)
 | 
						||
{
 | 
						||
int i, k;
 | 
						||
for (i = 0; i < size; ++i) {
 | 
						||
for (k = 0; k < 3; ++k)
 | 
						||
printf("%d ", pa[i][k]);
 | 
						||
printf("\n");
 | 
						||
}
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a[4][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10, 11, 12 } };
 | 
						||
disp(a, 4);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Göstericiyi Gösteren Göstericiler ve Matrisler
 | 
						||
Matrisel bir sistem oluşturabilmek için iki seçenek söz konusu olabilir:
 | 
						||
1) Önce göstericiyi gösteren gösterici için sonra da onun elemanı olan göstericiler için tahsisat yapmak. Örneğin:
 | 
						||
int **ppi;
 | 
						||
int i;
 | 
						||
ppi = (int **)malloc(4 * sizeof(int *));
 | 
						||
for (i = 0; i < 4; ++i)
 | 
						||
ppi[i] = (int *) malloc(3 * sizeof(int));
 | 
						||
 | 
						||
2) Doğrudan çok boyutlu bir dizi kullanmak.
 | 
						||
274
 | 
						||
 | 
						||
int ppi[4][3];
 | 
						||
 | 
						||
Şüphesiz ikinci biçim bellekte daha az yer kaplamaktadır. Ancak birinci biçimde matrisin her satırı aynı uzunlukta
 | 
						||
olmak zorunda değildir.
 | 
						||
Bir matris için dinamik tahisat yapılabilir. Örneğin:
 | 
						||
int(*pa)[3];
 | 
						||
pa = (int(*)[3])malloc(4 * 3 * sizeof(int));
 | 
						||
 | 
						||
Tabii typedef bildirimi ile türler daha sade gösterilebilir. Örneğin:
 | 
						||
typedef int(*PA3)[3];
 | 
						||
PA3 pa;
 | 
						||
pa = (PA3)malloc(4 * 3 * sizeof(int));
 | 
						||
 | 
						||
Makrolar
 | 
						||
#define önişlemci komutunun parametrik kullanımına makro denilmektedir. Makro yazılırken #define komutunda
 | 
						||
STR1 yazısı parantez içerir. Parantez içerisindekiler makro parametrelerini belirtir. Makro parametreliyse biz onu
 | 
						||
kullanırken sanki bir fonksiyon gibi argüman gireriz. Örneğin:
 | 
						||
 | 
						||
Burada square(10) ifadesini önişlemci 10 * 10 biçiminde açmaktadır. a makro parametresidir.
 | 
						||
#include <stdio.h>
 | 
						||
#define square(a)
 | 
						||
 | 
						||
a * a
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int result;
 | 
						||
result = square(10);
 | 
						||
 | 
						||
275
 | 
						||
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define square(a)
 | 
						||
 | 
						||
a * a
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int result;
 | 
						||
int x = 10;
 | 
						||
result = square(x);
 | 
						||
/* result = x * x */
 | 
						||
printf("%d\n", result);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Bir makro fonksiyon gibi işlev görmelidir. Kodu inceleyen kişi onun bir fonksiyon mu yoksa makro mu olduğunu
 | 
						||
anlamak zorunda kalmamalıdır. Oysa yukarıdaki square tam olarak bir fonksiyon gibi kullanılamaz. Örneğin:
 | 
						||
 | 
						||
Burada önişlemci 5 - 2 ifadesini a kabul edip bunu 5 - 2 * 5 - 2 biçiminde açar. Buradaki problemi ortadan
 | 
						||
kaldırmak için makro parametreleri paranteze alınmalı ve makro en dıştan ayrıca paranteze alınmalıdır. Örneğin:
 | 
						||
#define square(a)
 | 
						||
 | 
						||
((a) * (a))
 | 
						||
 | 
						||
Şimdi artık square tam olarak b ir fonksiyon etkisi yaratır. En dıştan paranteze alınmasının nedeni yüksek öncelikli
 | 
						||
opetaörlerden tüm ifadenin etkilenmesini sağlamaktır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define max(a, b)
 | 
						||
 | 
						||
((a) > (b) ? (a) : (b))
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 3, b = 4, c;
 | 
						||
c = max(a, b);
 | 
						||
/* c = ((a) > (b) ? (a) : (b)) */
 | 
						||
printf("%d\n", c);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define beep() putchar('\7');
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
 | 
						||
276
 | 
						||
 | 
						||
beep();
 | 
						||
beep();
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Makro daha karmaşık olabilir. Bu durumda birden fazla satıra yazmak okunabiliği artırır. Anımsanacağı gibi \
 | 
						||
karakterini hemen ENTER (LF) izlerse derleyici onun aşağısındaki satırı aynı satır gibi kabul eder. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#define check(a)
 | 
						||
 | 
						||
if ((a) == 0) {
 | 
						||
\
 | 
						||
fprintf(stderr, "Error\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
\
 | 
						||
}
 | 
						||
 | 
						||
\
 | 
						||
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int a = 0;
 | 
						||
check(a);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Ancak çok satırlı ve deyim içeren makrolar yazılırken dikkat edilmelidir. Yukarıdaki check(a); işlemi aslında
 | 
						||
önişlemci tarafından şöyle açılır:
 | 
						||
if ((a) == 0) {
 | 
						||
fprintf(stderr, "Error\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
};
 | 
						||
 | 
						||
Buradaki son noktalı virgül check çağrısının sonundaki noltalı virgüldür. Gerçi bu noktalı virgül derleme modülü
 | 
						||
tarafından boş deyim kabul edileceğinden çoğu kez soruna yol açmaz. Fakat aşağıdaki gibi bir kullanımda soruna
 | 
						||
yol açacaktır:
 | 
						||
 | 
						||
Bu nedenle çok satırlı makroların sanki do-while deyimiymiş gibi yazılması gerekmektedir. Örneğin:
 | 
						||
 | 
						||
277
 | 
						||
 | 
						||
Artık if içerisinde çağrılan makro şöyle açılacaktır:
 | 
						||
 | 
						||
Böylece check fonksiyonun sonundaki noktalı virgül do-while'ın noktaı virgülü olur, boş deyim olmaz.
 | 
						||
Bazı makroları çağırırken ++ ve -- operatörlerinin argümanlarda kullanılmaması gerekir. Örneğin:
 | 
						||
#define square(a)
 | 
						||
 | 
						||
((a) * (a))
 | 
						||
 | 
						||
Biz böyle bir makroyu aşağıdaki gibi çağırmış olalım:
 | 
						||
int x = 3, y;
 | 
						||
y = square(++x);
 | 
						||
Burada square bir fonksiyon olsaydı hiçbir sorun ortaya çıkmazdı. Ancak makro olarak yazıldığında aynı ifadede
 | 
						||
birden fazla ++x gözüküyor olur. Böyle bir kod derleyici için tanımsız davranışa yol açacaktır.
 | 
						||
Makro mu Fonksiyon mu?
 | 
						||
Bir fonksiyonun çağrılması sırasında küçük bir maliyet söz konusudur. Bu maliyeti oluşturan etkenler şunlardır:
 | 
						||
- Fonksiyon çağrılırken kullanılan CALLve RET makina komutları
 | 
						||
- Fonksiyona girişte ve çıkışta gereken bazı makina komutları (örneğin stack frame düzüenlemesi için gereken
 | 
						||
komutlar).
 | 
						||
- Parametrelerin aktarımı için gereken makina komutları
 | 
						||
Yukarıdaki makina komutları ortalama 10 civarındadır. Oysa bazen birer satırlık fonksiyonlar yazmak isteyebiliriz.
 | 
						||
Örneğin:
 | 
						||
double square(double a)
 | 
						||
{
 | 
						||
return a * a;
 | 
						||
}
 | 
						||
İşte bir iki satırlık küçük fonksiyonların fonksiyon olarak yazılması onların çağrılması sırasında göreli (çoğu zaman
 | 
						||
bunun hiçbir önemi yoktur) zaman kaybına yol açabilmektedir. Bu tür fonksiyonların fonksiyon olarak değil de
 | 
						||
makro olarak organize edilmesi uygun olur. Büyük kodların makro olarak tanımlanması kodu ciddi biçimde
 | 
						||
büyütebilmektedir. O halde çok küçük fonksiyonlar makro olarak diğerleri normal fonksiyon olarak yazılabilirler.
 | 
						||
278
 | 
						||
 | 
						||
Şüphesiz makrolar başlık dosyalarına yerleştirilmelidir. Onları çağıracak kişi onların fonksiyon mu makro mu
 | 
						||
olduğunu bilmek zorunda değildir. Örneğin getchar fonksiyonu bazı derleyicilerde makro olarak yazılmıştır.
 | 
						||
#define getchar() fgetc(stdin)
 | 
						||
Diğer Önişlemci Komutları
 | 
						||
Biz şimdiye kadar #include ve #define komutlarını gördük. Bu bölümde diğer önemli bazı önişlemci komutları
 | 
						||
görülecektir.
 | 
						||
#if, #else, #elif ve #endif Komutu
 | 
						||
#if önişlemci komutunun yanında tamsayı türlerine ilişkin bir sabit ifadesi bulunmak zorundadır. Örneğin:
 | 
						||
#if MAX > 10
 | 
						||
....
 | 
						||
#else
 | 
						||
...
 | 
						||
#endif
 | 
						||
Önişlemci #if komutunun yanındaki ifadenin sayısal değerini hesaplar. Bu değer sıfır dışı bir değerse #else'e kadar
 | 
						||
kısım derleme modülüne verilir, sıfır ise #else ile #endif arasındaki kısım derleme modülüne verilir.
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
#define SIZE 10000
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
#if SIZE < 1000
 | 
						||
int a[SIZE];
 | 
						||
#else
 | 
						||
int *a;
 | 
						||
if ((a = (int *)malloc(SIZE * sizeof(int))) == NULL) {
 | 
						||
fprintf(stderr, "cannot allocate memory!..\n");
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
#endif
 | 
						||
...
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada SIZE değeri 1000'den küçükse dizi normal olarak büyükse dinamik olarak tahsis edilmiştir. Komutta #else
 | 
						||
kısmı olmak zorunda değildir.
 | 
						||
#if komutu bir önişlemci komutu olduğuna göre işleme sokulması derleme modülünden önce yapılır. #elif komutu
 | 
						||
#else #if etkisi yaratır ancak bu durumda tek bir #endif yetmektedir. Örneğin:
 | 
						||
#define SYSTEM 2
 | 
						||
#if SYSTEM == 1
 | 
						||
/* ... */
 | 
						||
#elif SYSTEM == 2
 | 
						||
/* ... */
 | 
						||
#elif SYSTEM == 3
 | 
						||
/* ... */
 | 
						||
 | 
						||
279
 | 
						||
 | 
						||
#endif
 | 
						||
 | 
						||
#ifdef, #else, #endif Komutu
 | 
						||
#ifdef komutunu bir değişken ismi izlemek zorundadır. Bu değişken ismi bir sembolik sabit ya da makro ismidir.
 | 
						||
Önişlemci böyle bir sembolik sabitin ya da makronun daha önce #define komutu ile define edilip edilmediğine
 | 
						||
bakar. (Ancak onun kaç olarak define edildiğine bakmaz). Eğer o sembolik sabit ya da makro daha yukarıda define
 | 
						||
edilmişse önişlemci #else'e kadar olan kısmı derleme modülüne verir, define edilmemişse #else ile #endif
 | 
						||
arasındaki kısmı derleme modülüne verir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define TEST
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
#ifdef TEST
 | 
						||
printf("TEST define edilmis!\n");
 | 
						||
#else
 | 
						||
printf("TEST define edilmemis!\n");
 | 
						||
#endif
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C derleyicileri bir sembolik sabiti derleme sırasında define edebilme olanacağını vermiştir. Örneğin gcc ve cl
 | 
						||
derleyicilerinde -D seçeneği ile bu yapılabilmektedir:
 | 
						||
gcc -o sample -D TEST sample.c
 | 
						||
Visual Studio IDE'sinde aynı etki proje seçeneklerinde "C-C++/Preprocessor/Preprocessor Definitions" menüsüyle
 | 
						||
yapılabilmektedir.
 | 
						||
#ifdef çeşitli platformlar için farklı kodların derleme dahil edilmesi amacıyla çok sık kullanılır. Örneğin:
 | 
						||
#ifdef UNIX
 | 
						||
....
 | 
						||
#else
 | 
						||
...
 | 
						||
#endif
 | 
						||
 | 
						||
Komutta #else kısmı olmak zorunda değildir.
 | 
						||
#ifndef, #else, #endif Komutu
 | 
						||
Bu komut #ifdef komutunun tam tersi işlem görmektedir. Yani yanındaki sembolik sabit ya da makro define
 | 
						||
edilmemişse #else'e kadar olan kısım #define edilmişse #else ile #endif arasındaki kısım derleme modülüne verilir.
 | 
						||
#ifndef include korumalarında (include guards) çok sık kullanılmaktadır. Aynı başlık dosyasının ikinci include
 | 
						||
edilmesi pek çok derleme hatasının oluşmasına yol açabilir. (Örneğin aynı yapının iki kez bildirilmesi, aynu
 | 
						||
enum'un ikinci kez bildirilmesi, aynı typedef bildiriminin ikinci kez yapılması geçersizdir.) Bazen kontrolümüz
 | 
						||
dışında aynı dosyanın birden fazla kez include edilme durumu oluşabilmektedir. Örneğin a.h dosyası ve b.h
 | 
						||
dosyaları kendi içerisinde x.h dosyasnı include etmiş olsun. Biz de bir uygulamada mecburen a.h ve b.h dosyalarını
 | 
						||
include edersek x.h dosyası iki kez include edilmiş olur. Büyük projelerde bu olasılık çok fazladır. include
 | 
						||
koruması şöyle oluşturulur:
 | 
						||
/* x.h dosyası */
 | 
						||
280
 | 
						||
 | 
						||
#ifndef X_H_
 | 
						||
#define X_H_
 | 
						||
...
 | 
						||
...
 | 
						||
...
 | 
						||
#endif
 | 
						||
Burada önişlemci x.h dosyasını ilk kez açtığında X_H_ sembolik sabiti henüz define edilmediği için #endif'e kadar
 | 
						||
kısmı yani bütün dosya içeriğini derleme modülüne verir. İkinci kez aynı dosyayı açtığında artık X_H_ sembolik
 | 
						||
sabiti define edilmiş olacağı için artık dosya içeriğini derleme modülüne vermez. Bu örnekteki X_H_ makro ismi
 | 
						||
dosya isminden hareketle uydurulmuş herhangi bir isimdir. Tüm include dosyaları böyle bir koruma ile
 | 
						||
oluşturulmalıdır (çünkü onların her zaman dolaylı olarak birden fazla kez include edilmesi mümkün
 | 
						||
olabilmektedir.)
 | 
						||
#error Komutu
 | 
						||
#error komutu önişlemci tarafından derlem işlemini fatal error ile sonlandırır. Komutun yanında bir yazı bulunur.
 | 
						||
Bu yazı ekrana hata mesajı olarak yazdırılır (Yazının iki tırnak içerisinde olması gerekmez.) Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#ifndef LINUX
 | 
						||
#error this program only compiles in Linux
 | 
						||
#endif
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
#pragma Komutu
 | 
						||
#pragma komutu standart bir önişlemci komutudur. #pragma anahtar sözcüğünü başka bir komut sözcüğü izler. Bu
 | 
						||
komut sözcüğü standart değildir. Yani #pragma komutu standarttır ancak onun yanındaki komut sözcüğü
 | 
						||
derleyiciden derleyiciye değişebilmektedir. Her derleyicinin pragma komutları diğerinden farklı olabilir. Prgama
 | 
						||
komutları belli bir derleyiciye özgü işlemlerin yaptırılması için kullanılır. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#pragma pack(1)
 | 
						||
struct SAMPLE {
 | 
						||
char a;
 | 
						||
int b;
 | 
						||
char c;
 | 
						||
int d;
 | 
						||
};
 | 
						||
#pragma pack()
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
struct SAMPLE s;
 | 
						||
 | 
						||
281
 | 
						||
 | 
						||
printf("%u\n", sizeof(s));
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
Burada pack pragma komutu her derleycide olmak zorunda değildir. Microsoft derleyicilerinde ve gcc
 | 
						||
derleyicilerinde bu komut hizalama sağlamak için kullanılabilir.
 | 
						||
Derleyicilerin pragma komutlarının listesi onların referans dokümanlarından öğrenilebilir.
 | 
						||
## (Token Pasting) Önişlemci Operatörü
 | 
						||
Bu önişlemci komutu iki yazıyı birleştirmek için kullanılır. Örneğin:
 | 
						||
#define Name(x, y)
 | 
						||
 | 
						||
x##y
 | 
						||
 | 
						||
struct Name(CSD, Sample) {
 | 
						||
...
 | 
						||
};
 | 
						||
Önişlemci bu yapı bildirimini şöyle açar:
 | 
						||
struct CSDSample {
 | 
						||
...
 | 
						||
};
 | 
						||
Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#define Name(x) CSD##x
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
int Name(x), Name(y);
 | 
						||
CSDx = 10;
 | 
						||
CSDy = 20;
 | 
						||
printf("%d, %d\n", CSDx, CSDy);
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
C'nin Önceden Tanımlanmış Sembolik Sabitleri ve Makroları
 | 
						||
C önişlemcisi bazı sembolik sabit ve makro isimlerini hiç define edilmediği halde define edilmiş gibi kabul
 | 
						||
etmektedir. Bunlara önceden tanımlanmış (predefined) sembolik sabitler ve makrolar denir. Bunların hepsinin
 | 
						||
başında ve sonun iki alt tire vardır.
 | 
						||
__FILE__: Önişlemci bu sembolik sabiti gördüğünde bunun yerine iki tırnak içerisinde bunun bulunduğu kaynak
 | 
						||
dosyanın ismini yerleştirir. Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int foo(void)
 | 
						||
{
 | 
						||
 | 
						||
282
 | 
						||
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
if (!foo()) {
 | 
						||
fprintf(stderr, "Error in file: %s\n", __FILE__);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
__LINE__: Önişlemci bu sembolik sabiti gördüğünde bu sembolik sabit hangi satıra yerleştirilmişse onun satır
 | 
						||
numarasını yazar (iki tırnak içerisinde değil). Örneğin:
 | 
						||
#include <stdio.h>
 | 
						||
#include <stdlib.h>
 | 
						||
int foo(void)
 | 
						||
{
 | 
						||
return 0;
 | 
						||
}
 | 
						||
int main(void)
 | 
						||
{
 | 
						||
if (!foo()) {
 | 
						||
fprintf(stderr, "Error in file: %s\n in line %d\n", __FILE__, __LINE__);
 | 
						||
exit(EXIT_FAILURE);
 | 
						||
}
 | 
						||
return 0;
 | 
						||
}
 | 
						||
 | 
						||
__DATE__ ve __TIME__: Önişlemci bu makrolar yerine iki tırnak içerisinde derleme işleminin yapıldığı tarihi ve
 | 
						||
zamanı yerleştirir.
 | 
						||
__STDC__: Bu makro eğer standart C derleyicisinde çalışılıyorsa define edilmiş kabul edilir, değilse define
 | 
						||
edilmemiş kabul edilir. Örneğin:
 | 
						||
#ifndef __STDC__
 | 
						||
error this program cannot compile with non standard C compiler
 | 
						||
#endif
 | 
						||
Yukarıdaki önceden tanımlanmış sembolik sabitlerin dışında derleyicilerin kendilerine özgü önceden tanımlanmış
 | 
						||
başka sembolik sabitleri ve makroları da vardır. Örneğin _WIN32 ve _WIN64 sembolik sabitleri Microsoft C
 | 
						||
derleyicilerine özgüdür:
 | 
						||
#ifndef _WIN64
 | 
						||
#error this program can only compile in Windows 64 platform
 | 
						||
#endif
 | 
						||
 | 
						||
283
 | 
						||
 | 
						||
284
 | 
						||
 | 
						||
 |