From 6de7b78d912b53f0cca0c0e96d93cbf879cef3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20G=C3=B6r?= Date: Tue, 18 Mar 2025 13:39:41 +0300 Subject: [PATCH] 2025-examples --- c-basic/CSD-C-Basic-Book/C.txt | 13278 ---------------- .../CSD-C-KaanAslan-links.txt | 29 - c-basic/cli_arg.c | 12 - c-basic/do_while_example_01.c | 27 - c-basic/double_example_01.c | 16 - c-basic/for_example_01.c | 11 - c-basic/hello.c | 8 - c-basic/putchar.c | 12 - c-basic/scanf_example_01.c | 13 - c-basic/scanf_example_02.c | 15 - c-basic/while_example_01.c | 12 - c-basic/while_example_02.c | 13 - 12 files changed, 13446 deletions(-) delete mode 100644 c-basic/CSD-C-Basic-Book/C.txt delete mode 100644 c-basic/CSD-C-Basic-Book/CSD-C-KaanAslan-links.txt delete mode 100644 c-basic/cli_arg.c delete mode 100644 c-basic/do_while_example_01.c delete mode 100644 c-basic/double_example_01.c delete mode 100644 c-basic/for_example_01.c delete mode 100644 c-basic/hello.c delete mode 100644 c-basic/putchar.c delete mode 100644 c-basic/scanf_example_01.c delete mode 100644 c-basic/scanf_example_02.c delete mode 100644 c-basic/while_example_01.c delete mode 100644 c-basic/while_example_02.c diff --git a/c-basic/CSD-C-Basic-Book/C.txt b/c-basic/CSD-C-Basic-Book/C.txt deleted file mode 100644 index 04c1015..0000000 --- a/c-basic/CSD-C-Basic-Book/C.txt +++ /dev/null @@ -1,13278 +0,0 @@ -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 -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ü] ([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 -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ü] ([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: -([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 -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: - ; - -Ö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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 - 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 -#include -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 -#include -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 -#include -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 -#include -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 -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 -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 -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 -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 -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 -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: -[±]<ü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 -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 -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 -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 -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 -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 -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 -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 -int main() -{ -int a = 3, b; -b = a++ == 3; -printf("a = %d, b = %d\n", a, b); -return 0; -} - -Örneğin: -#include -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 -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 = b ifadesi ile a = a 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 () - -[ -else - -] -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 -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 -#include -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 -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 -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 -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 -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 -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 -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 -#include -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 -#include -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 -#include -int main(void) -{ -char ch; -while ((ch = getch()) != 'q') -putchar(ch); -return 0; -} - -Örneğin: -#include - -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 () - -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 -int main(void) -{ -int i = 0; -while (i < 10) { -printf("%d\n", i); -++i; -} - -57 - - return 0; -} - -Örneğin: -#include -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 -#include -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 -#include -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 -#include -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 -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 -int main(void) -{ -int i = 0; -while (i++ < 10) -printf("%d ", i); /* 1'den 10'a kadar */ -putchar('\n'); -return 0; -} - -Örneğin: -#include -int main(void) -{ -int i = 10; -while (--i) - -59 - - printf("%d ", i); /* 9'dan 1'e kadar */ -putchar('\n'); -return 0; -} - -Örneğin: -#include -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 - -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 - -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 -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 -#include -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 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 -#include -#include -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 -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 dosyasının include edilmesi gerekir. Örneğin: -#include -#include -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 -#include -#include -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]) - - -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) - -Örneğin: -#include -int main(void) -{ -int i; -for (i = 0; i < 10; ++i) -printf("%d\n", i); -return 0; -} - -Örneğin: -#include -int main(void) -{ -int i; -for (i = 0; i < 10; ++i) -printf("%d ", i); -printf("\n"); -return 0; -} - -Örneğin: -#include -int main(void) - -65 - - { -int i; -for (i = 0; i < 10; i += 2) -printf("%d ", i); -printf("\n"); -return 0; -} - -Örneğin: -#include -#include -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 -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 -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 -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 -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 -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 -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 -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) { - -ifade3; -} - -aslında aşağıdaki for döngüsüyle eşdeğerdir: -for (ifade1; ifade2; ifade3) - -#include -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 -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 -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 -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) - - -ile, -{ -bildirim; -for (;ifade2; ifade3) - -} - -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 -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 -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 -#include -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 -#include -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 -#include -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 -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 -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 -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 () { -case : -.... -case : -.... -case : -.... -[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 -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 -#include -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 -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 -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 -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 -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 -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 -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 ; -.... -: - -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 -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 -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 -#include -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 -#include -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 -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 -#include -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 -dosyasının include edilmesi gerekir. Fonksiyon sıfır parametresiyle çağrılmalıdır. Örneğin: -#include -#include -#include -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 -#include -#include -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 -#include -#include -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 -#include -#include -#include - -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 -#include -#include -#include -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 -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 -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 - -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 -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 -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 -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 -int main() -{ -double a; -a = 10 / 4; -printf("%f\n", a); - -/* 2.0 */ - -return 0; -} - -Fakat örneğin: -#include -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 -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: -() 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 -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 -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 -#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 -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 -#include -#include - -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 -#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 -#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 -#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 -#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 -#define MAX - -100 - -int main() -{ -printf("MAX"); - -/* MAX çıkar */ - -return 0; -} - -Örneğin: -#include -#define CITY -int main() -{ -printf(CITY); - -"Ankara" - -/* Ankara çıkar */ - -return 0; -} - -Aşağıdaki makro geçerlidir: -#define MYHEADER -#include MYHEADER - - - -#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 -#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 -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 -#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 -int main() -{ -foo(); - -/* geçersiz */ - -return 0; -} -void foo(void) -{ -/* ... */ -} - -Fakat örneğin: -98 - - #include -int main() -{ -foo(); - -/* geçerli */ - -return 0; -} -int foo(void) -{ -/* ... */ -} - -Örneğin: -#include -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ü] ([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 -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 -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 -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 -int main() -{ -double result; -result = sqrt(10); -printf("%f\n", result); -return 0; -} - -Halbuki prototip eklersek doğru çalışacaktır: -#include -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 , -bütün klavye, ekran ve dosya fonksiyonlarının prototipleri , genel amaçlı fonksiyonların prototipleri - 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: -( *) - -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: - <[]>; - -Ö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 -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 -#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 -#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 -#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 -#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 -#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 -#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 -#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 -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 -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(); - -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 -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 -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 -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 -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 -#include -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 -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 -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 -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 -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 -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 -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 -sizeof() -sizeof - -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 -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 -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 -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 - -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: - *; - -Ö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 - -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 -int main(void) -{ -char ch; -char *pc; -pc = &ch; -*pc = 'x'; -putchar(ch); -return 0; -} - -Örneğin: -#include -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 -int main(void) -{ -int a = 100; -*&a = 200; -printf("%d\n", a); -return 0; -} - -Örneğin: -#include -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -#include -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 -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 -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 -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 -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 -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 -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 -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 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 -#include -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 -#include -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 -#include -int main(void) -{ -char s[32] = "ankara"; -char d[32]; -strcpy(d, s); -puts(d); -return 0; -} - -Örneğin: -148 - - #include -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 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 -#include -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 -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -int main(void) -{ -char s[] = "izmir"; -char d[32] = "ankara"; -strncat(d, s, 3); -puts(d); -/* ankaraizm */ -return 0; -} - -Örneğin: -#include -#include -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 -#include -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 -#include -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 -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 -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 -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 -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 -int main(void) -{ -int *pi; -*pi = 100; - -/* 100 rastgele bir yere atanıyor */ - -return 0; -} - -Örneğin: -#include -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 -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 -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 içerisindedir. -#include -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 - -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 -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 -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 -#include -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 -int main(void) -{ -char *str = "ankara"; -char s[] = "ankara"; -*str = 'x'; -*s = 'x'; - -/* undefined behavior */ -/* normal */ - -return 0; -} - -Örneğin: -#include -#include -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 - -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 -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 -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 -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 -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 -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 - -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 -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 * [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 -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 -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 -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 -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 -#include -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 -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 -#include -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 -#include -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 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 -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 -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 -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 -#include -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 -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 -#include -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 -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 -#include -#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 -#include -#include -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 -#include -#include -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 -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 { - -}; -Ö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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -#include -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 -#include -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 -#include -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 -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 -#include -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 -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 - -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 - -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 -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 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ü -dosyasıının yanı sıra , 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: - ; -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] -; - -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 -extern int g_x; -void foo(void); -int main(void) -{ -g_x = 10; -foo(); -printf("%d\n", g_x); -return 0; -} - -/* a2.c */ -#include -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 -#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 -#include "project.h" -void foo() -{ -g_a = 10; -} -/* a3.c */ -#include -#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 -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 -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 -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 -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 ',' 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 -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 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 -#include -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 -#include -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 -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 -#include -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 -#include -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 -#include -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 -#include -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 -#include -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 - -239 - - #include -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 -#include -#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 -#include -#include -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 -#include -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 -#include -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 -#include - -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 -#include -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 -#include - -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 -#include -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 -#include -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 -#include -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 -#include -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 - -246 - - #include -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 -#include -#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 -#include -#include -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 -#include - -248 - - #include -#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 -#include -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 -#include -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 -#include -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 -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 -#include -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 -#include -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 - dosyasında bildirilmiştir. (Dolayısıyla bu isimler anahtar sözcük değildir. Bunları kullanabilmemiz için - 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 -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 -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 -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 -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 -#include -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 - -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 -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 - -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 -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 -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 -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 -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 -#include -int main(int argc, char *argv[]) -{ -FILE *f; -int ch; -if (argc == 1) { -printf("usage: \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 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 -#include -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 -#include -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 -#include -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 -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 -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 -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 -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 -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 -#include -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 -#include -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 -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 -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: - (*)[]; -Ö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 -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 -#define square(a) - -a * a - -int main(void) -{ -int result; -result = square(10); - -275 - - printf("%d\n", result); -return 0; -} - -Örneğin: -#include -#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 -#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 -#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 -#include -#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 -#include -#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 -#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 -#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 -#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 -#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 -#include -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 -#include -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 - - diff --git a/c-basic/CSD-C-Basic-Book/CSD-C-KaanAslan-links.txt b/c-basic/CSD-C-Basic-Book/CSD-C-KaanAslan-links.txt deleted file mode 100644 index 70b2091..0000000 --- a/c-basic/CSD-C-Basic-Book/CSD-C-KaanAslan-links.txt +++ /dev/null @@ -1,29 +0,0 @@ -1 - What is UNIX ? What does it mean ? - -https://en.wikipedia.org/wiki/Unix -https://en.wikipedia.org/wiki/Unix#:~:text=In%201970%2C%20the%20group%20coined,of%20the%20final%20spelling%20Unix. - -2 - When was the first UNIX developed and which hardware was the first platform for the UNIX. - -https://en.wikipedia.org/wiki/History_of_Unix -https://www.britannica.com/technology/UNIX -https://www.redhat.com/en/blog/unix-linux-history - -3 - What is B programming language and also C programming language ? - -https://en.wikipedia.org/wiki/B_(programming_language) -https://en.wikipedia.org/wiki/C_(programming_language) - -4 - What is a "personal computer"(PC) ? - -https://en.wikipedia.org/wiki/Personal_computer - -Merhaba arkadaşlar - -Bugünkü çalışmamızda kaynak toplama hakkında nasıl yol alabileceğimizi göstermek istiyorum. - -Kaan Aslan hocamız C dilini UNIX işletim sistemiyle bağlantılı olarak anlatmış ve UNIX işletim sisteminin doğuşu ile bağlantılı olarak tasvir etmiştir. - -Wikipedia ve diğer kaynaklardan yararlanıp bilgilerimizi zenginleştireceğiz. - -Teşekkürler. diff --git a/c-basic/cli_arg.c b/c-basic/cli_arg.c deleted file mode 100644 index c2c359b..0000000 --- a/c-basic/cli_arg.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -int main(int argc, char *argv[]) -{ - - int i; - - for (i = 0; i < argc; ++i) - - printf("%s\n", argv[i]); - - return 0; -} diff --git a/c-basic/do_while_example_01.c b/c-basic/do_while_example_01.c deleted file mode 100644 index 77f177f..0000000 --- a/c-basic/do_while_example_01.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -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; -} diff --git a/c-basic/double_example_01.c b/c-basic/double_example_01.c deleted file mode 100644 index 1fea556..0000000 --- a/c-basic/double_example_01.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -int main() -{ - double a,b,c; - printf("enter double number a:\n"); - scanf("%lf", &a); - printf("enter double number b:\n"); - scanf("%lf", &b); - - c = a + b; - - printf("%f", c); - - return 0; -} diff --git a/c-basic/for_example_01.c b/c-basic/for_example_01.c deleted file mode 100644 index e190604..0000000 --- a/c-basic/for_example_01.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -int main(void){ - int i; - - for (i = 0; i < 10; ++i) - printf("%d\n", i); - - return 0; - -} diff --git a/c-basic/hello.c b/c-basic/hello.c deleted file mode 100644 index bb8dc10..0000000 --- a/c-basic/hello.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main() -{ - printf("Hello World !\n"); - - return 0; -} diff --git a/c-basic/putchar.c b/c-basic/putchar.c deleted file mode 100644 index 82e8722..0000000 --- a/c-basic/putchar.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int main() -{ - char ch; - - while ((ch = getch()) != 'q') - putchar(ch); - - return 0; -} diff --git a/c-basic/scanf_example_01.c b/c-basic/scanf_example_01.c deleted file mode 100644 index 6d7e75b..0000000 --- a/c-basic/scanf_example_01.c +++ /dev/null @@ -1,13 +0,0 @@ -#include - -int main() -{ - int a; - int b; - - printf("Enter two numbers:\n"); - scanf("%d%d", &a, &b); - printf("a = %d, b = %d\n", a, b); - - return 0; -} diff --git a/c-basic/scanf_example_02.c b/c-basic/scanf_example_02.c deleted file mode 100644 index c4cb062..0000000 --- a/c-basic/scanf_example_02.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -int main() -{ - int a; - int b; - - printf("Enter number a:\n"); - scanf("%d", &a); - printf("Enter number b:\n"); - scanf("%d", &b); - printf("a = %d, b = %d\n", a, b); - - return 0; -} diff --git a/c-basic/while_example_01.c b/c-basic/while_example_01.c deleted file mode 100644 index c85a2ee..0000000 --- a/c-basic/while_example_01.c +++ /dev/null @@ -1,12 +0,0 @@ -#include - -int main() -{ - int i = 0; - - while (i < 10) { - printf("%d\n", i); - ++i; - } - return 0; -} diff --git a/c-basic/while_example_02.c b/c-basic/while_example_02.c deleted file mode 100644 index cd8ec74..0000000 --- a/c-basic/while_example_02.c +++ /dev/null @@ -1,13 +0,0 @@ -#include - -int main(void) -{ - int i = 10; - - while (i) { - printf("%d\n", i); - --i; - } - - return 0; -}