+ Konu Cevaplama Paneli
1. Sayfa - Toplam 2 Sayfa var 1 2 SonuncuSonuncu
Gösterilen sonuçlar: 1 ile 10 ve 19

Konu: Şimdi de C Hakkında 450 Sayfalık E-Kitap Okuyalım

  1. #1
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb Şimdi de C Hakkında 450 Sayfalık E-Kitap Okuyalım

    NE KADAR KİTAP O KADAR CEVAP
    Bu E-kitap C ye farklı bir gözle bakmaktadır; değişik bir yöntemle anlatmaktadır.
    İşte E-kitabınız:

    1. Bölüm PROGRAMLAMA VE C

    Yazılım Nedir

    Yazılım (software) programlama ve programlamayla ilgili konuların geneline verilen isimdir. Yazılım denince ilk olarak aklımıza programlama dilleri, bu diller kullanılarak yazılmış kaynak programlar ve çeşitli amaçlar için oluşturulmuş dosyalar gelir.
    Donanım Nedir

    Donanım (hardware) : Bilgisayarın elektronik kısmı, yapısına verilen isimdir.
    Yazılımın Sınıflandırılması

    Yazılımı uygulama alanlarına göre 5 gruba ayırabiliriz :

    1. Bilimsel ve mühendislik yazılımları (scientific & engineering software).
    Bilimsel ve mühendislik konularındaki problemlerin çözülmesinde kullanılan programlardır. Bu tür programlarda veri miktarı göreli olarak düşüktür ancak matematiksel ve istatistiksel algoritmalar yoğun olarak kullanılabilir.Tamamen hesaplama ağırlıklı işlemler içerir. Bu tür programlar ağırlıklı olarak bilgisayarın Merkezi İşlem Birimini (CPU) kullanırlar. Elektronik devrelerin çözümünü yapan programları, istatistik analiz paketlerini bu tür programlara örnek olarak verebiliriz.

    2. Mesleki yazılımlar (Business software).
    Veri tabanı ağırlıklı yazılımlardır. Genel olarak verilerin yaratılması, işlenmesi ve dosyalarda saklanması ile ilgilidir. Bu tür programlara örnek olarak stok kontrol programları, müşteri takip programları, muhasebe programlarını verebiliriz.

    3. Yapay zeka yazılımları (artificial intelligence software).
    İnsan davranışlarını taklit etmeyi amaçlayan yazılımlardır. Örnek olarak robot yazılımları, satranç ya da briç oynatan programlar vs. verilebilir.

    4. Görüntüsel yazılımlar.
    Görüntüsel işlemlerin ve algoritmaların çok yoğun olarak kullanıldığı programlardır. Örnek olarak oyun ve animasyon yazılımlarını verebiliriz. Bu yazılımlar ağırlıklı olarak bilgisayarın grafik arabirimini kullanırlar.

    5. Sistem yazılımları (system software):
    Bilgisayarın elektronik yapısını yöneten yazılımlardır. Derleyiciler, haberleşme programları, işletim sistemi birer sistem yazılımıdır. Örneğin text editörü de bir sistem yazılımıdır. Uygulama programlarına göre daha düşük seviyeli işlem yaparlar.
    Programlama Dillerinin Sınıflandırılması

    Programlama dillerini çeşitli açılardan sınıflandırabiliriz. En sık kullanılan sınıflandırmalar:

    1. Seviyelerine göre sınıflandırma.
    2. Uygulama alanlarına göre sınıflandırma.
    Bilgisayar Dillerinin Seviyelerine Göre Sınıflandırması ve Seviyelerine Göre Bilgisayar Dillerinin Gelişimi

    Bir programlama dilinin seviyesi deyince o programlama dilinin insan algısına olan yakınlığının derecesini anlıyoruz. Bir programlama dili insan algılasına ne kadar yakınsa o kadar yüksek seviyeli demektir (high level). Yine bir programlama dili bilgisayarın elektronik yapısına ve çalışma biçimine ne kadar yakınsa o kadar düşük seviyeli (low level) demektir. Yüksek seviyeli dillerle çalışmak programcı açısından kolaydır. Algoritma yoktur. Bu dillerde yalnızca nelerin yapılacağı programa bildirilir ama nasıl yapılacağı bildirilmez. Genel olarak programlama dilinin seviyesi yükseldikçe , o dilin öğrenilmesi ve o dilde program yazılması kolaylaşır.

    Bir bilgisayar yalnızca kendi makine dilini doğrudan anlayabilir. Makine dili bilgisayarın doğal dilidir ve bilgisayarın donanımsal tasarımına bağlıdır. Bilgisayarların geliştirilmesiyle birlikte onlara iş yaptırmak için kullanılan ilk diller de makine dilleri olmuştur. Bu yüzden makine dillerine 1. kuşak diller de diyebiliriz.

    Makine dilinin programlarda kullanılmasında karşılaşılan iki temel problem vardır. Makine dilinde yazılan kodlar doğrudan makinanın işlemcisine, donanım parçalarına verilen komutlardır. Değişik bir CPU kullanıldığında ya da bellek organizasyonu farklı bir şekilde yapıldığında artık program çalışmayacak ve programın tekrar yazılması gerekecektir. Çünkü makine dili yalnızca belirli bir CPU ya da CPU serisine uygulanabilir. Makine dili taşınabilir (portable) değildir. Diğer önemli bir problem ise, makine dilinde kod yazmanın çok zahmetli olmasıdır.Yazmanın çok zaman alıcı ve uğraştırıcı olmasının yanı sıra yazılan programı okumak ya da algılamak da o denli zordur. Özellikle program boyutu büyüdüğünde artık makine dili programlarını geliştirmek, daha büyütmek iyice karmaşık bir hale gelir.

    Başlangıçta yalnızca makine dili vardı. Bu yüzden makine dilleri 1. kuşak diller olarak da isimlendirilir. Yazılımın ve donanımın tarihsel gelişimi içerisinde makine dilinden, insan algılamasına çok yakın yüksek seviyeli dillere (4. kuşak diller) kadar uzanan bir süreç söz konusudur. Bu tarihsel süreci ana hatlarıyla inceleyelim :

    1950 li yılların hemen başlarında makine dili kullanımın getirdiği problemleri ortadan kaldırmaya yönelik çalışmalar yoğunlaştı. Bu yıllarda makine dilleri bilgisayarın çok sınırlı olan belleğine yükleniyor ve programlar böyle çalıştırılıyordu. İlk önce makine dilinin algılanma ve anlaşılma zorluğunu kısmen de olsa ortadan kaldıran bir adım atıldı. Sembolik makine dilleri geliştirildi. Sembolik makine dilleri (Assembly languages) yalnızca 1 ve 0 dan oluşan makine dilleri yerine İngilizce bazı kısaltma sözcüklerden oluşuyordu. Sembolik makine dillerinin kullanımı kısa sürede yaygınlaştı. Ancak sembolik makine dillerinin makine dillerine göre çok önemli bir dezavantajı söz konusuydu. Bu dillerde yazılan programlar makine dilinde yazılan programlar gibi bilgisayarın belleğine yükleniyor ancak programın çalıştırılma aşamasında yorumlayıcı (interpreter) bir program yardımıyla sembolik dilin komutları, bilgisayar tarafından komut komut makine diline çevriliyor ve oluşan makine kodu çalıştırılıyordu. Yani bilgisayar, programı çalışma aşamasında önce yorumluyarak makine diline çeviriyor daha sonra makine diline çevrilmiş komutları icra ediyordu. Bu şekilde çalıştırılan programların hızı neredeyse 30 kat yavaşlıyordu.

    Bu dönemde özellikle iki yorumlayıcı program öne çıkmıştı: John Mauchly nin UNIVAC 1 için yazdığı yorumlayıcı (1950) ve John Backus tarafından 1953 yılında IBM 701 için yazılan "Speedcoding" yorumlama sistemi. Bu tür yorumlayıcılar makine koduna göre çok yavaş çalışsalar da programcıların verimlerini artırıyorlardı. Ama özellikle eski makine dili programcıları yorumlayıcıların çok yavaş olduklarını, yalnızca makine dilinde yazılanların gerçek program deneceğini söylüyorlardı.

    Bu sorunun da üstesinden gelindi. O zamanlar için çok parlak kabul edilebilecek fikir şuydu: Her defasında yazılan kod, kodun çalıştırılması sırasında makine diline çevireceğine, geliştirilecek bir başka program sembolik dilinde yazılan kodu bir kez makine diline çevirsin ve artık program ne zaman çalıştırılmak istense, bilgisayar yorumlama olmaksızın yalnızca makine kodunu çalıştırsın. Bu fikiri geliştiren Grace Hopper isimli bir bayandı. Grace Hopper'ın buluşuna "compiler" derleyici ismi verildi. (Grace Hopper aynı zamanda Cobol dilini geliştiren ekipten biridir, bug(böcek) sözcüğünü ilk olarak Grace Hopper kullanmıştır.) Artık programcılar sembolik sözcüklerden oluşan Assembly programlarını kullanıyor. Yazdıkları programlar derleyici tarafından makine koduna dönüştürülüyor ve makine kodu eski hızından birşey kaybetmeksizin tam hızla çalışıyordu. Assembly diller 2. kuşak diller olarak tarihte yerini aldı.

    Assembly dillerinin kullanılmaya başlamasıyla bilgisayar kullanımı hızla arttı. Ancak en basit işlemlerin bile bilgisayara yaptırılması için bir çok komut gerekmesi, programlama prosesini daha hızlı bir hale getirmek için arayışları başlatmış, bunun sonucunda da daha yüksek seviyeli programlama dilleri geliştirilmeye başlanmıştır.

    Tarihsel süreç içinde Assembly dillerinden daha sonra geliştirilmiş ve daha yüksek seviyeli diller 3. kuşak diller sayılmaktadır. Bu dillerin hepsi algoritmik dillerdir. Bugüne kadar geliştirilmiş olan yüzlerce yüksek seviyeli programlama dilinden yalnızca pek azı bugüne kadar varlıklarını sürdürebilmiştir:

    FORTRAN dili (FORmula TRANslator) kompleks matematiksel hesaplamalar gerektiren mühendislik ve bilimsel uygulamalarda kullanılmak üzere 1954 - 1957 yılları arasında IBM firması için John Backus tarafından geliştirilmiştir. FORTRAN dili, yoğun matematik hesaplamaların gerektiği bilimsel uygulamalarda halen yaygın olarak kullanılmaktadır. FORTRAN dilinin FORTRAN IV ve FORTRAN 77 olmak üzere iki önemli versiyonu bulunmaktadır. Doksanlı yılların başlarında FORTRAN - 90 isimli bir versiyon için ISO ve ANSI standartları kabul edilmiştir. FORTRAN dili 3. seviye dillerin en eskisi kabul edilmektedir.

    COBOL (COmmon Business Oriented Language) 1959 yılında, Amerika'daki bilgisayar üreticileri, özel sektör ve devlet sektöründeki bilgisayar kullanıcılarından oluşan bir grup tarafından geliştirilmiştir. COBOL'un geliştirilme amacı veri yönetimi ve işlemenin gerektiği ticari uygulamalarda kullanılacak taşınabilir bir programlama dili kullanmaktır. COBOL dili de halen yaygın olarak kullanılmaktadır.

    ALGOL (The ALGOritmick Language) 1958 yılında Avrupa'da bir konsorsiyum tarafından geliştirilmeye başlanmıştır. IBM Firması FORTRAN dilini kendi donanımlarında kullanılacak ortak programlama dili olarak benimsediğinden, Avrupa'lılar da alternatif bir dil geliştirmek istemişlerdi. ALGOL dilinde geliştirilen bir çok prensip modern programlama dillerinin hepsinde kullanılmaktadır.

    60'lı yılların başlarında programlama dilleri üzerinde yapılan çalışmalar yapısal programlama kavramını gündeme getirmiştir. Bu dillerden bazılarına kısaca göz atalım:

    PASCAL dili 1971 yılında akademik çevrelere yapısal programlama kavramını tanıtmak için Profesör Niclaus Wirth tarafından geliştirilmiş (Dilin yaratıcısı, dile matematikçi ve filozof Blaise Pascal'ın ismini vermiştir.) ve bu dil kısa zaman içinde üniversitelerde kullanılan programlama dili haline gelmiştir.
    Pascal dilinin ticari ve endüstriyel uygulamaları desteklemek için sahip olması gereken bir takım özelliklerden yoksun olması bu dilin bu uygulamalarda fazla kullanılmamasına yol açmıştır. Modula ve Modula-2 dilleri Pascal dili baz alınarak geliştirilmiştir.

    BASIC dili 1960'lı yılların ortalarında John Kemeney ve Thomas Kurtz tarafından geliştirilmiştir. Her ne kadar BASIC isminin "Beginner's All_purpose Symbolic Instruction Code" sözcüklerinin baş harflerinden oluşturulduğu söylense de, bu sözcüklerin daha sonradan uydurulduğu açıktır. Yüksek seviyeli dillerin en eski ve en basit olanlarından biridir.Tüm basitliğine karşın, bir çok ticari uygulamada kullanılmıştır. BASIC dili de ANSI tarafından standartlaştırılmıştır. Ancak BASIC dilinin ilave özellikler içeren bir sürü versiyonu söz konusudur. Örneğin Microsoft firmasının çıkarttığı Visual Basic diline Nesne Yönelimli Programlamaya ilişkin birçok özellik eklenmiştir. Ayrıca BASIC dilinin bazı versiyonları uygulama programlarında (Örneğin MS Excel ve MS Word'de) kullanıcının özelleştirme ve otomatikleştirme amacıyla yazacağı makroların yazılmasında kullanılan programlama dili olarak da genel kabul görmüştür.

    ADA dili ise Amerikan Savunma Departmanı (Department of Defence -DoD) desteği ile 70 li yıllar ve 80'li yılların başlarında geliştirilmiştir. Dod dünyadaki en büyük bilgisayar kullanıcılarından biridir. Bu kurum farklı yazılımsal gereksinimleri karşılamak için çok sayıda farklı programlama dili kullanıyordu ve tüm gereksinmelerini karşılayacak bir dil arayışına girdi. Dilin tasarlanması amacıyla uluslararası bir yarışma düzenledi. Yarışmayı kazanan şirket (CII-Honeywell Bull of France) Pascal dilini baz olarak alan çalışmalar sonucunda Ada dilini geliştirdi. Ada dilinin dökümanları 1983 yılında yayımlanmıştır.(Ada ismi, şair Lord Byron'un kızı olan Lady Ada Lovelace'ın isminden alıntıdır. Ada Lovelace delikli kartları hesap makinalarında ilk olarak kullanılan Charles Babbage'in yardımcısıydı. Charles Babbage hayatı boyunca "Fark makinası" (Difference Engine) ve "Analitik Makine" (Analytical Engine) isimli makinaların yapımı üzerinde çalıştı ama bu projelerini gerçekleştiremeden öldü. Yine de geliştirdiği tasarımlar modern bilgisayarların atası kabul edilmektedir. Ada Lovelace Charles Babbage'ın makinası için delikli kartları ve kullanılacak algoritmaları hazırlıyordu. Bu bayanın 1800'lü yılların başında ilk bilgisayar programını yazdığı kabul edilmektedir.) Ada dili genel amaçlı bir dildir, ticari uygulamalardan roketlerin yönlendirilmesine kadar birçok farklı alanda kullanılmaktdır. Dilin önemli özelliklerinden bir tanesi gerçek zaman uygulamalarına (real-time applications / embedded systems) destek vermesidir. Başka bir özelliği de yüksek modülaritesi nedeniyle büyük programların yazımını kolaylaştırmasıdır. Ancak büyük ve karmaşık derleyicilere ihtiyaç duyması, C, Modula-2 ve C++ dillerine karşı rekabetini zorlaştırmıştır.

    Çok yüksek seviyeli ve genellikle algoritmik yapı içermeyen programların görsel bir ortamda yazıldığı diller ise 4. kuşak diller olarak isimlendirilirler. Genellikle 4GL olarak kısaltılırlar. (fourth generation language). İnsan algısına en yakın dillerdir. RPG dili 4. kuşak dillerin ilki olarak kabul edilebilir.Özellikle küçük IBM makinalarının kullanıcıları olan şirketlerin, rapor üretimi için basit bir dil istemeleri üzerine IBM firması tarafından geliştirilmiştir.

    Programlama dillerini seviyelerine göre 5 ana gruba ayırabiliriz:

    1. Çok yüksek seviyeli diller ya da görsel diller (visual languages):
    Access, Foxpro, Paradox, Xbase, Visual Basic, Oracle Forms.

    2. Yüksek seviyeli diller (Bunlara algoritmik diller de denir):
    Fortran, Pascal, Basic, Cobol.

    3. Orta seviyeli programlama dilleri:
    Ada, C. Orta seviyeli diller daha az kayıpla makine diline çevrilebildiğinden daha hızlı çalışır.

    4. Alçak seviyeli programlama dilleri:
    Sembolik makine dili (Assembly language).

    5. Makine dili:
    En aşağı seviyeli programlama dili. (Saf makine dili tamamen 1 ve 0 lardan oluşuyor.)
    Uygulama Alanlarına Göre Sınıflandırma

    1. Bilimsel ve mühendislik uygulama dilleri:
    Pascal, C (C programlama dili üniversitelerdeki akademik çalışmalarda da yoğun olarak kullanılıyor.), FORTRAN

    2. Veri tabanı dilleri:
    XBASE, (Foxpro, Dbase, CA-Clipper), Oracle Forms, Visual Foxpro.

    3. Genel amaçlı programlama dilleri:
    Pascal, C, Basic.

    4. Yapay zeka dilleri:
    Prolog, Lisp.

    5. Simulasyon dilleri
    GPSS, Simula 67

    6. Makro Dilleri (Scripting languages)
    awk, Perl, Python, Tcl, JavaScript.

    7. Sistem programlama dilleri:
    Sembolik makine dilleri, BCPL, C, C++, occam.
    Günümüzde sistem yazılımların neredeyse tamamının C dili ile yazıldığını söyleyebiliriz.
    Örnek vermek gerekirse UNIX işletim sisteminin % 80'i C dili ile geri kalanı ise sembolik makine dili ile yazılmıştır. Bu işletim sistemi ilk olarak BELL labaratuarlarında oluşturulmuştur. Kaynak kodları gizli tutulmamış, böylece çeşitli kollardan geliştirilmesi mümkün olmuştur. Daha sonra geliştirilen UNIX bazlı işletim sistemi uygulamalarına değişik isimler verilmiştir.

    C bilimsel ve mühendislik alanlarına kullanılabilen genel amaçlı bir sistem programlama dilidir.
    Programlama Dillerinin Değerleme Ölçütleri

    Kaynaklar şu an halen kullanımda olan yaklaşık 1000 - 1500 programlama dilinin varlığından söz ediyor. Neden bu kadar fazla programlama dili var? Bu kadar fazla programlama dili olmasına karşın neden halen yeni programlama dilleri tasarlanıyor? Bir programlama dilini diğerine ya da diğerlerine göre daha farklı kılan özellikler neler olabilir? Bir programlama dilini tanımlamak istesek hangi sıfatları kullanabiliriz? Programlama dilleri hakkındaki bu sorulara yanıt verebilmemiz için elimizde değerlendirme yapmamıza olanak sağlayacak ölçütler olmalıdır. Bu ölçütleri kısaca inceleyelim:

    Verimlilik (efficiency)

    Bu özelliğe programın hızlı çalışma özelliği diyebiliriz. Programın çalışma hızı pek çok faktöre bağlıdır. Algoritmanın da hız üzerinde etkisi vardır. Çalışmanın yapıldığı bilgisayarın da doğal olarak hız üzerinde etkisi vardır. Verimliliği bir programlama dilinde yazılmış bir programın hızlı çalışması ile ilgili bir kavram olarak düşünebiliriz. Bu açıdan bakıldığında C verimli bir dildir.
    Veri türleri ve yapıları (data types and structures)

    Çeşitli veri türlerini (tamsayı, gerçek sayı, karakter...) ve veri yapılarını (diziler, yapılar vs.) destekleme yeteneğidir. Veri yapıları, veri türlerinin oluşturduğu mantıksal birliklerdir. Örneğin C ve Pascal dilleri veri yapıları bakımından zengin dillerdir.
    Alt programlama yeteneği (Modularity)

    Bir bütün olarak çözülmesi zor olan problemlerin parçalara ayrılması ve bu parçaların ayrı ayrı çözümlenmesinden sonra parçalar arasındaki koordinasyonun sağlanması programada sık başvurulan bir yöntemdir. Bir programlama dili buna olanak sağlayacak araçlara sahipse alp programlama yeteneği vardır diyebilirriz. Alt programlama yeteneği bir programlama dilinin, programı parçalar halinde yazmayı desteklemesi anlamına gelir. (C modülaritesi çok yüksek bir dildir)

    Alt programlama Yapısal Programlama tekniği'nin de ayrılmaz bir parçasıdır. Alt programlamanın getirdiği bazı önemli avantajlar vardır. Alt programlar kodu küçültür. Çok tekrarlanan işlemlerin alt programlar kullanılarak yazılması çalışabilir programın kodunu küçültür. Çünkü alt programlar yalnızca bir kere çalışabilir kod içine yazılırlar. Ama program kodu alt programın olduğu yere atlatılarak bu bölgenin defalarca çalıştırılması sağlanabilir.
    Alt programlama algılamayı kolaylaştırır, okunabilirliği artırır. Alt programlama kaynak kodun test edilebilirliğini artırır. Kaynak kodun daha kolay güncelleştirilmesi ve yeniden kullanılabilme olanağını artırır. Alt programlamanın en önemli avantajlarından biri de genel amaçlı kodlar yazarak bu yazılan kodları birden fazla projede kullanabilmektir. (reusability)
    C alt programlama yeteneği yüksek bir dildir. C'de alt programlara fonksiyon denir. Fonksiyonlar C Dili'nin yapıtaşlarıdır.
    Yapısallık (structural programming support)

    Yapısallık bir programlama tekniğidir. Bugün artık hemen hemen bütün programlama dilleri yapısal programlamayı az çok destekleyecek bir şekilde geliştirilmiştir. Yapısal Programlama fikri 1960'lı yıllarda geliştirilmiştir. Yapısal programlama tekniği dört ana ilke üzerine kurulmuştur :

    1. Böl ve üstesinden gel (divide and conquer)
    Yapısal programlama tekniğinde, tek bir bütün olarak çözüm getirmek zor olan programlar, daha küçük ve üstesinden daha kolay gelinebilecek parçalara bölünürler. Bu parçalar fonksiyon, prosedür, subroutine, alt program vs. olarak isimlendiriler. Alt program yapısının getirdiği avantajlar modularite konusunda yukarıda açıklanmıştır.

    2. Veri gizleme (Data hiding)
    Yapısal programlama tekniğinde, programın diğer parçalarından ulaşılamayan, yalnızca belli bir faaliyet alanı olan, yani kodun yalnızca belli bir kısmında faaliyet gösterecek değişkenler tanımlanabilir. Bu tür değişkenler genel olarak "yerel değişkenler" (local variables) olarak isimlendirilirler. Değişkenlerin faaliyet alanlarının kısıtlanabilmesi hata yapma riskini azalttığı gibi, programların daha kolay değiştirilebilmesini ve program parçalarının başka programlarda tekrar kullanabilmesini de sağlar. Alt programların, ve daha geniş şekliyle modüllerin, bir işi nasıl yaptığı bilgisi, o alt programın ya da modülün kullanıcısından gizlenir. Kullanıcı için (client) alt programın ya da modülün işi nasıl yaptığı değil, ne iş yaptığı önemlidir.

    3. Tek giriş ve Tek çıkış (single entry single exit)
    Yapısal programlama tekniğini destekleyen dillerde her bir altprogram parçasına girmek için tek bir giriş ve tek bir çıkış mekanizması vardır. Bu mekanizma programın yukarıdan aşağı olarak akışı ile uyum halindedir. Program parçalarına ancak tek bir noktadan girilebilir.

    4. Döngüler ve diğer kontrol yapıları.
    Artık hemen hemen kullanımda olan bütün programlama dilleri az ya da çok Yapısal Programlama tekniğini desteklemektedir. Zira bu teknik 60'lı yıllar için devrim niteliğindeydi.

    Esneklik (flexibility)

    Esneklik programlama dilinin programcıyı kısıtlamaması anlamına gelir.Esnek dillerde birçok işlem, hata yapma riski artmasına karşın rağmen kullanıcı için serbest bırakılmıştır. Programcı bu serbestlikten ancak yetkin bir programcıysa bir fayda sağlayabilir. Fakat programcı deneyimsiz ise bu esneklikten zarar görebilir.
    Öğrenme ve öğretme kolaylığı (pedagogy)

    Her programlama dilini öğrenmenin ve öğrenilen programlama dilinde uygulama geliştirebilmenin zorluğu aynı değildir. Genel olarak programlama dillerinin seviyesi yükseldikçe, öğrenme ve bu programlama dilini başkalarına öğretme kolaylaşır, öğrenme için harcanacak çaba ve zaman azalır. Bugün yaygın olarak kullanılan yüksek seviyeli programlı dillerinin bu derece popüler olmasının önemli bir nedeni de bu dillerin çok kolay öğrenilebilmesidir. Ne yazık ki C öğrenimi zor ve zahmetli bir dildir.
    Genellik (generality)

    Programlama dillerinin çok çeşitli uygulamalarda etkin olarak kullanılabilmesidir. Örneğin COBOL mühendislik uygulamalarında tercih edilmez zaten ticari uygulamalar için tasarlanmıştır, Clipper ya da FOXPRO veri tabanı dilleridir. Oysa PASCAL, BASIC daha genel amaçlı dillerdir. C dili de bir sistem programlama dili olarak doğmasına karşın, güçlü yapısından dolayı, kısa bir süre içinde, genel amaçlı bir dil haline gelmiştir.
    Giriş / Çıkış (input / output, I / O facility) kolaylığı

    Sıralı, indeksli ve rasgele dosyalara erişme, veritabanı kayıtlarını geri alma, güncelleştirme ve sorgulama yeteneğidir. Veritabanı programlama dillerinin (DBASE, PARADOX vs.) bu yetenekleri diğerlerinden daha üstündür ve bu dillerin en tipik özelliklerini oluşturur. Fakat C giriş çıkış kolaylığı kuvvetli olmayan bir dildir. C'de veri tabanlarının yönetimi için özel kütüphanelerin kullanılması gerekir.
    Okunabilirlik (readability)

    Okunabilirlik, kaynak kodun çabuk ve iyi bir biçimde algılanabilmesi anlamına gelen bir terimdir. Kaynak kodun okunabilirliğinde sorumluluk büyük ölçüde programı yazan kişidedir. Fakat yine verimlilik de olduğu gibi dillerin bir kısmında okunabilirliği güçlendiren yapı ve mekanizmalar bulunduğu için bu özellik bir ölçüde dilin tasarımına da bağlıdır. En iyi program kodu, sanıldığı gibi "en zekice yazılmış fakat kimsenin anlayamayacağı" kod değildir.
    Birçok durumda iyi programcılar okunabilirliği hiçbirşeye feda etmek istemezler. Çünkü okunabilir bir program kolay algılanabilme özelliğinden dolayı seneler sonra bile güncelleştirmeye olanak sağlar. Birçok kişinin ortak kodlar üzerinde çalıştığı geniş kapsamlı projelerde okunabilirlik daha da önem kazanmaktadır.
    C de okunabilirlik en fazla vurgulanan kavramlardan biridir. Biz de kursumuz boyunca okunabilirlik konusuna sık sık değineceğiz ve C programlarının okunabilirliği konusunda bazı temel prensipleri benimseyeceğiz.
    Taşınabilirlik (portability)

    Bir sistem için yazılmış olan kaynak kodun başka bir sisteme götürüldüğünde, hatasız bir biçimde derlenerek, doğru bir şekilde çalıştırılabilmesi demektir.
    Taşınabilirlik standardizasyon anlamına da gelir. Programlama dilleri (ISO International Standard Organization) ve ANSI (American National Standard Institute) tarafından standardize edilirler. 1989 yılında standartlaştırma çalışmaları biten C Dili, diğer programlama dillerinden daha taşınabilir bir programlama dilidir.
    Nesne Yönelimlilik (object orientation)

    Nesne yönelimlilik de bir programlama tekniğidir.
    Yapısal programlama Tekniği 1960 yılarında gündeme gelmişken, Nesne Yönelimli Programlama Tekniği 1980'li yıllarda popüler olmuştur.

    Bu teknik kaynak kodların çok büyümesi sonucunda ortaya çıkan gereksinim yüzünden geliştirilmiştir. C dilinin geliştirildiği yıllarda, akla gelebilecek en büyük programlar ancak onbin satırlar mertebesindeydi, ancak kullanıcıların bilgisayar programlarından beklentilerinin artması ve grafik arayüzünün artık etkin olarak kullanılmasıyla, bilgisayar programlarının boyutu çok büyümüş, yüzbin satırlarla hatta milyon satırlarla ölçülebilir hale gelmiştir.
    Nesne yönelimli programlama Tekniği, herşeyden önce büyük programların yazılması için tasarlanmış bir tekniktir. C dilinin yaratıldığı yıllarda böyle bir tekniğin ortaya çıkması söz konusu değildi, çünkü zaten programlar bugünkü ölçülere göre çok küçüktü.
    Nesne yönelimli programlama Tekniğinin yaygın olarak kullanılmaya başlanmasıyla birlikte bir çok programlama dilinin bünyesine bu tekniğin uygulanmasını kolaylaştırıcı araçlar eklenek, yeni versiyonları oluşturulmuştur. Örneğin C'nin nesne yönelimli programlama tekniğini uygulayabilmek için Bjarne Stroustrup tarafından geliştirilmiş haline C++ denmektedir. C++ dili C dili baz olarak alınıp, geliştirilmiş yeni bir programlama dilidir. C++ dilini iyi öğrenebilmek için öncelikle C dilini çok iyi öğrenmek gerekir.
    Pascal diline eklemeler yapılarak Delphi dili, Cobol dilinden yenilemesiyle OOCobol, Ada dilinin yenilenmesiyle ise ADA 95 dilleri geliştirilmiştir.
    Bazı programlama dilleri ise doğrudan N.Y.P.T'ni destekleyecek şekilde tasarlanarak geliştirilmiştir. Örneğin JAVA dili C++ dilinin basitleştirilmiş biçimi olup daha çok Internet uygulamalarında kullanılmaktadır. Başka bir örnek olarak da Eiffel dili verilebilir.
    C Nasıl bir Programlama Dilidir?

    Bütün bunlardan sonra yukarıda açıkladığımız kavramları da kullanarak C dilini aşağıdaki şekilde tanımlayabiliriz :
    C orta seviyeli bir programlama dilidir. Yapısal diğer programlama dillerine göre C dilinin seviyesi daha düşüktür. C dili hem yüksek seviyeli dillerin, kontrol deyimleri, veri yapıları gibi avantajlarını bünyesinde barındırıyor, aynı zamanda bitsel operatörler gibi makine kodu deyimlerini yansıtan operatörlerlere sahip. Yani hem makinaya yakın hem de insan algılamasına. Zaten çok tercih edilmesinin en önemli nedenlerinden biri de bu.

    C bir sistem programlama dilidir. Sistem Programlama ne anlama geliyor? Donanımın yönetilmesi, kontrolu ve denetimi için yazılan, doğrudan donanımla ilişkiye giren programlara sistem programı diyoruz. Örneğin, işletim sistemleri, derleyiciler, yorumlayıcılar, aygıt sürücüleri (device drivers), bilgisayarların iletişimine ilişkin programlar, otomasyon programları, sistem programlarıdır. Diğer uygulama programlarına destek veren yazılımlar da çoğunlukla sistem programları olarak ele alınırlar.
    C'den önce sistem programları assembly dillerle yazılıyordu.Sistem programlarının yazılmasında hemen hemen alternatifsiz olduğunu söyleyebiliriz. Bugün cep telefonlarından, uçaklara kadar her yerde C kodları çalışmaktadır. Örneğin Boeing uçaklarında 100.000 satırdan fazla C kodu çalıştığı bilinmektedir.


    C algoritmik bir dildir. C'de program yazmak için yalnızca dilin sentaks ve sementik yapısını bilmek yetmez genel bir algoritma bilgisi de gerekir.
    C diğer dillerle kıyaslandığında taşınabilirliği çok yüksek olan bir dildir. Çünkü 1989 yılından bu yana genel kabul görmüş standartlara sahiptir. İfade gücü yüksek , okunabilirlik özelliği güçlü bir dildir.
    C çok esnektir. Diğer dillerde olduğu gibi programcıya kısıtlamalar getirmez.
    Güçlü bir dildir. Çok iyi bir biçimde tasarlanmıştır. C'ye ilişkin operatörlerin ve yapıların bir çoğu daha sonra başka programlama dilleri tarafından da benimsenmiştir.
    C verimli bir dildir. Seviyesinden dolayı hızlı çalışır. Verimlilik konusunda assembly diller ile rekabet edebilir.
    C doğal bir dildir. C bilgisayar sisteminin biçimiyle uyum içindedir.
    C küçük bir dildir. Yeni sistemler için derleyici yazmak zor değildir.
    C'nin eğitimi diğer bilgisayar dillerine göre daha zordur.
    C Programlama Dili'nin Tarihi

    C dilinin tarihini incelediğimizde C dilinin UNIX işletim sisteminin bir yan ürünü olarak doğduğunu söyleyebiliriz. UNIX işletim sisteminin orjinal ilk versiyonunu Bell Labaratuarları'nda çalışan Ken Thompson tek başına yazmıştı ve UNIX'in bu ilk versiyonu DEC PDP-7 isimli bilgisayarda çalışıyordu. DEC PDP-7 ilk mini bilgisayarlardan biriydi ve ana belleği yalnızca 16 K (16 MB değil!). Yıllardan 1969'du.

    Zamanının diğer işletim sistemleri gibi UNIX de assembly dilinde yazılmıştı. Assembly dilinde yazılan programları geliştirmek çok zor ve zahmetli olduğundan, Thompson UNIX işletim sistemini daha geliştirebilmek için, makine dilinden daha yüksek seviyeli bir dile gereksinim duydu. Bu amaçla küçük bir programlama dili tasarladı. Kendi dilini tasarlarken Thompson, 1960 yıllarının ortalarında Martin Richards tarafından geliştirilmiş BCPL dilinden yola çıktı. (BCPL = Business Common Programming Language. Bu dil de CPL = Cambridge Programming Language'den türetilmiştir. CPL'in kaynağı da tüm zamanların en eski ve en etkili dillerinden biri olan ALGOL 60'dır. ALGOL 60 Pascal, ADA, Modula2 dillerinin de atasıdır, bu dillere bu yüzden C dilinin kuzenleri de diyebiliriz. Aşağıda ALGOL 60 dil ailesi görülmektedir:

    Algol 60






    Algol 68 Algol W Simula 67 BCPL




    C


    Pascal


    C++




    Java
    Modula-2 Ada Delphi




    Oberon


    Thompson geliştirdiği bu dilin ismini B koydu. Dennis Ritchie UNIX projesine katılınca B dilinde programlamaya başladı. B dili daha da geliştirilmişti ve artık daha yeni teknoloji olan PDP-11 bilgisayarlarda çalışıyordu. Thompson UNIX işletim sisteminin bir kısmını B dilinde tekrar yazdı. Artık 1971 yılına gelindiğinde B dilinin PDP-11 bilgisayarlar ve UNIX işletim sisteminin geliştirilmesi için çok uygun olmadığı iyice ortaya çıktı. Bu yüzden Ritchie B programlama dilinin daha ileri bir versiyonunu geliştirmeye başladı. Oluşturduğu dili ilk önce NB (new B) olarak isimlendirdi. Ama geliştirdiği dil B dilinden iyice kopmaya ve ayrı bir karakter göstermeye başlayınca dilin ismini de C olarak değiştirdi. 1973 yılında UNIX işletim sisteminin büyük bir kısmı C dili ile tekrar yazıldı.


    Ken Thompson ve Dennis Ritchie Unix İşletim Sistemi üzerinde çalışırken (Yıl: 1972)

    C'nin evrimi ve gelişmesi 70'li yıllarda da devam etti. Geniş kitleler tarafından tanınması ve kullanılmaya başlaması 1978 yılında Dennis Ritchie ve Brian Kernighan tarafından yazılan "The C Programming Language" kitabı ile olmuştur. Bu kitap aynı zamanda yazılım konusunda yazılan en iyi eserlerden biri olarak değerlendirilmektedir. C'nin standardize edilmesine kadar olan dönemde bu kitap çoğunluğun benimsediği genel kabul gören gayriresmi bir standard vazifesi de görmüştür.

    1970'li yıllarda C programcılarının sayısı azdı ve bunlardan çoğu UNIX kullanıcılarıydı. Ama artık 80'li yıllar gelince C nin kullanımı UNIX sınırlarını aştı, ve farklı işletim sistemlerinde çalışan derleyiciler piyasaya çıktı, C dili de IBM PC'lerde yoğun olarak kullanılmaya başladı.

    C'nin artan popülaritesi problemleri de beraberinde getirdi. Derleyici yazan kişiler, referans olarak Ritchie ve Kernighan'ın kitabını esas alıyorlardı ama söz konusu kitapta bazı noktalar çok da detaylı bir biçime açıklanmamıştı. Özellikle hangi noktaların C dilinin bir özelliği hangi noktaların ise UNIX işletim sisteminin bir özelliği olduğu o kadar açık olmadığı için bir takım karışıklıklar ortaya çıkıyordu. Böylece derleyici yazanların ürünlerinde de farklılıklar ortaya çıkıyordu. Ayrıca kitabın yayınlanmasından sonra da dilde bir takım geliştirmeler, iyileştirmeler, değişiklikler yapıldığı için, birbirinden çok farklı derleyiciler piyasada kullanılmaya başlanmıştı.

    Artık C dilinin standardizasyonu neredeyse zorunlu bir hale gelmişti! C'nin standardizasyon çalışmaları 1983 yılında ANSI (American National Standards Institute ) gözetiminde ve desteğinde başladı. Yapılan birçok değişiklikten sonra standart çalışmaları 1988 yılında sona erdi ve 1989 yılının Aralık ayında ANSI C standardı Jim Brodie başkanlğında X3.159 - 1989 numarasıyla resmi olarak onaylandı. 1990 yılında ise ISO/IEC 9899 - 1990 numarasıyla ISO (International Standards Organization) tarafından standardizasyonu kabul edildi. Standardizasyonu tamamlandıktan sonra C yüksek oranda taşınabilir bir sistem programlama dili haline gelmiştir. Günümüzde de sistem programlarının (derleyiciler, editörler, işletim sistemleri) çoğu C dili ile yazılmaktadır.
    Fotoğraflar

    Dennis M. Ritchie







    2. SAYI SİSTEMLERİ

    Günlük hayatta 10’luk sayı sistemini kullanıyoruz. 10 luk sistemde bir sayının değeri aslında her bir basamak değerinin 10 sayısının ilgili kuvvetiyle çarpımlarının toplanmasıyla elde edilir.

    Örneğin 1273 = (3 * 1) + (7 * 10 ) + (2 * 100) + (1 * 1000)

    Ancak bilgisayar sistemlerinde bütün bilgiler ikilik sistemde(binary system) ifade edilir.
    Genel olarak sayı sistemi kaçlıksa o sayı sisteminde o kadar sembol bulunur.
    Örneğin 10’luk sistemde 10 adet sembol vardır ve bu semboller 0, 1, 2, 3, 4, 5, 6, 7, 8, 9’dur.
    Aynı şekilde ikilik sayı sisteminde yalnızca iki adet sembol bulunur. Yani yalnızca 0 ve 1.

    Bir sayıyı başka bir sayı sisteminde ifade etmek o sayının değerini değiştirmez. Yalnızca sayının gösteriliş biçimi değişir. Örneğin onluk sayı sisteminde sayısal değeri 32 olan büyüklüğü çeşitli farklı sayı sistemlerinde farklı biçimlerde gösterebiliriz ama sayının büyüklüğünü değiştirmiş olmayız.

    İkilik sistemde her bir basamağa 1 bit denir. Bit kelimesi binary digit sözcüklerinden türetilmiştir.

    Örneğin 1011 sayısı 4 bittir. (Ya da 4 bit uzunluğundadır).

    11011001 sayısı 8 bittir.
    8 bitlik bir büyüklük bir byte olarak isimlendirilir.

    1 kilobyte 1K = 1024 byte dır. (yani 210 byte)
    1 mega byte 1 MB = 1024 Kilo byte dır. (yani 220 byte)
    1 gigabyte 1 GB = 1024 MB dır. (yani 230 byte)
    1 terabyte 1 TB = 1024 GB dır. (yani 240 byte)
    1 petabyte 1PB = 1024 TB dır. (yani 250 byte)
    1 exabyte 1EB = 1024 PB dır. (yani 260 byte)
    1 zettabyte 1ZB = 1024 EB dir.( yani 270 byte)
    1 yottabyte 1YB = 1024 ZB dır.( yani 280 byte)

    Kilo büyüklük olarak 1000 kat anlamına gelmektedir, ancak bilgisayar alanında Kilo 2'nin 1000'e en yakın kuvveti olan 210 yani 1024 kat olarak kullanılır.

    4 bit 1 Nybble (Nibble şeklinde de yazılır)
    8 bit 1 byte
    16 bit 1 word
    32 bit 1 double word
    64 bit 1 quadro word

    olarak da isimlendirilmektedir.

    ikilik sisteme ilişkin genel işlemler

    i. İkilik sistemdeki bir sayının 10 luk sistemde ifade edilmesi:
    ikilik sayı sisteminde ifade edilen bir sayının 10’luk sistemdeki karşılığını hesaplamak için en sağdan başlayarak bütün basamakları tek tek 2’nin artan kuvvetleriyle çarpılır. Örneğin :

    1 0 1 1 = 1 * 20 + 1 * 21 + 0 * 22 + 1 * 23 = 11
    0010 1001 = (1 * 1) + (1 * 8) + (1 * 32) = 41

    Bu arada sık kullanılan iki terimi de açıklayalım. İkilik sayı sisteminde yazılan bir sayının en solundaki bit, yukarıdaki örnekten de görüldüğü gibi en yüksek sayısal değeri katıyor. Bu bite en yüksek anlamlı bit (most significant digit) diyeceğiz ve bu bit için bundan sonra MSD kısaltmasını kullanacağız.
    İkilik sayı sisteminde yazılan bir sayının en sağındaki bit, yine yukarıdaki örnekten de görüldüğü gibi en düşük sayısal değeri katıyor. Bu bite en düşük anlamlı bit (least significant digit) diyeceğiz ve bu bit için bundan sonra LSD kısaltmasını kullanacağız.
    Örnek :
    0101 1101 sayısı için
    MSD = 0
    LSD = 1

    İkilik sayı sisteminde yazılan bir sayının belirli bir basamağından (bitinden) söz ettiğimizde, hangi bitten söz edildiğinin doğru bir şekilde anlaşılması için basamaklar numaralandırılır.
    8 bitlik bir sayı için, sayının en sağındaki bit (yani (LSD) sayının 0. bitidir. Sayının en solundaki bit (yani MSD) sayının 7. bitidir.

    ii. 10’luk sistemdeki bir sayının 2’lik sistemde ifadesi :

    Sayı sürekli olarak 2 ye bölünür. Her bölümden kalan değer( yani 1 ya da 0) oluşturulacak sayının 0. bitinden başlayarak, basamaklarını oluşturacaktır. Bu işleme 0 sayısı elde edilinceye kadar devam edilir. Örnek:
    87 sayısını ikilik sayı sisteminde ifade etmek isteyelim:
    87 / 2 = 43 (kalan 1) Sayının 0. biti 1
    43 / 2 = 21 (kalan 1) Sayının 1. biti 1
    21 / 2 = 10 (kalan 1) Sayının 2. biti 1
    10 / 2 = 5 (kalan 0) Sayının 3. biti 0
    5 / 2 = 2 (kalan 1) Sayının 4. biti 1
    2 / 2 = 1 (kalan 0) Sayının 5. biti 0
    1 / 2 = 0 (kalan 1) Sayının 6. biti 1

    87 = 0101 0111

    İkinci bir yöntem ise 10 luk sistemde ifade edilen sayıdan sürekli olarak 2'nin en büyük kuvvetini çıkarmaktır. 2’nin çıkarılan her bir kuvveti için ilgili basamağa 1 değeri yazılır. Bu işleme 0 sayısı elde edilene kadar devam edilir. Örnek:
    Yine 87 sayısını ikilik sayı sisteminde ifade etmek isteyelim:
    87'den çıkarılabilecek, yani 87'den büyük olmayan ikinin en büyük kuvveti nedir? Cevap 64. O zaman 64 = 26 olduğuna göre 6.bit 1 değerini alacak.
    87 - 64 = 23. Şimdi 23'den çıkarılabilecek ikinin en büyük kuvvetini bulalım. Bu say1 16'dır. Yani 24 'dür. O zaman sayımızın 4. biti de 1 olacak.
    23 - 16 = 7. 7'den çıkarılabilecek ikinin en büyük kuvveti 4'dür ve 4 = 22 'dir. Sayımızınj 2. biti de 1 olacak.
    7 - 4 = 3.
    3 - 2 = 1 (2 = 21 ) Sayımızın 1. biti 1 olacak.
    1 - 1 = 0 (1 = 20 ) Sayımızın 0. biti 1 olacak.

    1 değeri olmayan tüm bitleri 0 bitiyle doldurarak sayımızı ikilik sistemde ifade edelim:

    87 = 0101 0111

    iii. İkilik sistemde ifade edilen bir sayının 1’e tümleyeni. Sayının tüm bitlerinin tersinin alınmasıyla elde edilir. Yani sayıdaki 1’ler 0 ve 0’lar 1 yapılır.
    Bir sayının 1’e tümleyeninin 1’e tümleyeni sayının yine kendisidir.

    iv. İkilik sistemde ifade edilen bir sayının 2’ye tümleyeninin bulunması:

    Önce sayının 1’e tümleyeni yukarıdaki gibi bulunur. Daha sonra elde edilen sayıya 1 eklenirse sayının 2’ye tümleyeni bulunmuş olur. 2'ye tümleyeni bulmak için daha daha pratik bir yol daha vardır : Sayının en solundan başlayarak ilk defa 1 biti görene kadar (ilk görülen 1 dahil) sayının aynısı yazılır, daha sonraki tüm basamaklar için basamağın tersi yazılır. (Yani 1 için 0 ve 0 için 1) Örneğin :

    1110 0100 sayısının ikiye tümleyeni 0001 1100 dır.
    0101 1000 sayısının ikiye tümleyeni 1010 1000 dır.

    Bir sayının ikiye tümleyeninin ikiye tümleyeni sayının kendisidir. (Deneyiniz)

    8 bitlik bir alana yazılacak en büyük tam sayı kaçtır?
    1111 1111 = 255 dir. (1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255)

    8 bitlik bir alana yazılacak en küçük tam sayı kaçtır?
    0000 0000 = 0’dır.

    Negatif bir tamsayı ikilik sistemde nasıl gösterilir?

    Negatif tamsayıların da ifade edildiği ikilik sayı sistemine işaretli ikilik sayı sistemi (signed binary system) denir. İşaretli ikilik sayı siteminde, negatif sayıları göstermek için hemen hemen tüm bilgisayar sistemlerinde aşağıdaki yol izlenir:

    Sayının en yüksek anlamlı biti işaret biti (sign bit) olarak kabul edilir. Ve bu bit 1 ise sayı negatif, bu bit 0 ise sayı pozitif olarak değerlendirilir. İkilik sistemde bir negatif sayı aynı değerdeki pozitif sayının ikiye tümleyenidir. Örnek olarak, ikilik sistemde yazacağımız –27 sayısı yine ikilik sistemde yazılan 27 sayısının ikiye tümleyenidir.

    Pozitif olan sayıların değerini tıpkı işaretsiz sayı sisteminde olduğu gibi elde ederiz:

    0001 1110 işaretli sistemde pozitif bir sayıdır. (Decimal olarak 29 sayısına eşittir.)

    Ancak negatif olan sayıların değerini ancak bir dönüşümle elde edebiliriz:

    1001 1101 işaretli sistemde negatif bir sayıdır. (Çünkü işaret biti 1)

    2lik sistemde ifade edilen negatif bir sayının 10’luk sistemde hangi negatif sayıya eşit olduğunu nasıl bulunur?

    Sayının en yüksek anlamlı biti (MSD) işaret bitidir. Bu bit 1 ise sayı negatifdir. Sayının kaça eşit olduğunu hesaplamak için ilk önce sayının 2’ye tümleyeni bulunur. Ve bu sayının hangi pozitif sayıya karşılık geldiğini hesap edilir. Elde etmek istenen sayı, bulunan pozitif sayı ile aynı değerdeki negatif sayı olacaktır.

    Örneğin 1001 1101 sayısının 10’luk sistemde hangi sayıya karşılık geldiği bulunmak istenirse:

    Sayının en soldaki biti 1 olduğuna göre bu sayı negatif bir sayı olacaktır. Hangi negatif sayı olduğunu bulmak için sayının 2’ye tümleyenini alınır.

    1001 1101 sayısının ikiye tümleyeni 0110 0011 sayısıdır.

    Bu sayının 10'luk sistemde hangi sayıya denk olduğu hesaplanırsa :
    (1 * 1 + 1 * 2 + 0 * 4 + 0 * 8 + 0 * 16 + 1 * 32 + 1 * 64 = 99)

    ilk yazılan sayının -99 olduğu anlaşılmış olur.

    10'luk sistemde ifade edilen negatif sayıların işaretli ikilik sistemde yazılması :

    Önce sayının aynı değerli fakat pozitif olanı ikilik sistemde ifade edilir : Daha sonra yazılan sayının ikiye tümleyenini alınarak, yazmak istenilen sayı elde edilir.

    Örnek : İkilik sistemde –17 yazmak istenirse;

    önce 17 yazılır. 0001 0001
    bu sayının 2'ye tümleyeni alınırsa 1110 1111 sayısı elde edilir.


    Sayı değeri aynı olan Negatif ve Pozitif sayılar birbirlerinin ikiye tümleyenleridir.
    İkilik sistemde gösterilmiş olsa da aynı sayının negatifiyle pozitifinin toplamı 0 değerini verecektir. (Deneyiniz!)

    Bir byte’lık (8 bitlik) bir alana yazabileceğimiz (işaret bitini dikkate almadan) en büyük sayı 255 (1111 1111) ve en küçük sayı ise 0’dır.(0000 0000). Peki işaret biti dikkate alındığında 1 byte’lık alana yazılabilecek en büyük ve en küçük sayılar ne olabilir?

    En büyük sayı kolayca hesaplanabilir. işaret biti 0 olacak (yani sayı pozitif olacak) ve sayı değerini en büyük hale getirmek için diğer bütün bit değerleri 1 olacak, bu sayı 0111 1111 sayısıdır. Bu sayıyı desimal sisteme dönüştürürsek 127 olduğunu görürüz. Peki ya en küçük negatif sayı kaçtır ve nasıl ifade edilir?

    0111 1111 sayısının ikiye tümleyenini alındığında –127 sayısını elde edilir.
    1000 0001 (127) Bu sayıdan hala 1 çıkartabilir.
    1000 0000 (-128) 1 byte alana yazılabilecek en küçük negatif sayıdır.

    Burada dikkat edilmesi gereken iki önemli nokta vardır :

    1 byte alana yazılabilecek en büyük sayı sınırı aşıldığında negatif bölgeye geçilir.
    0111 1111 (en büyük pozitif tamsayı)
    1 (1 toplarsak)
    1000 0000 (-128 yani en küçük tamsayı)
    yani 1 byte alana yazılabilecek en büyük tamsayıya 1 eklendiğinde 1 byte alana yazılabilecek en küçük tamsayıyı elde ederiz.
    1 byte alana yazılabilecek en küçük tamsayıdan 1 çıkardığımızda da 1 byte alana yazılabilecek en büyük tamsayıyı elde ederiz.

    Yukarıda anlattıklarımıza göre -1 sayısının işaretli ikilik sayı sisteminde 8 bitlik bir alanda aşağıdaki şekilde ifade edilecektir.

    -1 = 1111 1111

    Yani işaretli ikilik sayı sisteminde tüm bitleri 1 olan sayı -1'dir. İleride bu sayıyla çok işimiz olacak!

    16’lık sayı sistemi (hexadecimal numbering system) ve 8’lik sayı sistemi (octal system)


    Bilgisayarların tamamen 2’lik sistemde çalıştığını söylemiştik, ama yukarıda görüldüğü gibi 2’lik sistemde sayıların ifade edilmesi hem çok uzun hem de zahmetli. Bu yüzden, yazım ve algılama kolaylığı sağlamak için 16’lık ve 8’lik sayı sistemleri de kullanılmaktadır.
    16'lık ve 8’lik sayı sistemlerinde sayılar daha yoğun olarak kodlanıp kullanabilir.

    Başta da söz edildiği gibi 10 luk sistemde 10, 2’lik sistemde ise 2 sembol bulunmaktadır. Bu durumda 16’lık sayı sisteminde de 16 sembol bulunur.

    ilk 10 sembol 10'luk sistemde kullanılan sembollerle tamamen aynıdır :
    1, 2, 3, 4, 5, 6, 7, 8, 9,

    Daha sonraki semboller

    A = 10
    B = 11
    C = 12
    D = 13
    E = 14
    F = 15

    16’lık sayı sisteminde yazılmış bir sayıyı 10’luk sisteme çevirmek için, en sağdan başlayarak basamak değerleri 16’nın artan kuvvetleriyle çarpılır :

    01AF = (15 * 1) + (10 * 16) + (1 * 256) + (0 * 4096) = 431

    10’luk sistemde yazılmış bir sayıyı 16’lık sisteme çevirmek için 10 luk sistemden 2’lik sisteme yapılan dönüşümlerdekine benzer şekilde sayı sürekli 16 ya bölünerek, kalanlar soldan sağa doğru yazılır.

    Pratikte 16 lık sayı sistemlerinin getirdiği önemli bir avantaj vardır. Bu avantaj 16 lık sayı sistemi ile 2’lik sayı sistemi arasındaki dönüşümlerin kolay bir şekilde yapılmasıdır.

    16’lık sistemdeki her digit 2’lik sistemdeki 4 bit (1 Nibble) alan ile ifade edilebilir :

    0001

    1

    0010

    2

    0011

    3

    0100

    4

    0101

    5

    0110

    6

    0111

    7

    1000

    8

    1001

    9

    1010

    A

    1011

    B

    1100

    C

    1101

    D

    1110

    E

    1111

    F



    Örnek : 2ADFH sayısının (en sondaki H sayının hexadecimal olarak gösterildiğini anlatır yani sayıya ilişkin bir sembol değildir) 16'lık sistemde ifadesi :

    2
    =
    0010
    A
    =
    1010
    D
    =
    1101
    F
    =
    1111


    Bu durumda 2ADFH = 0010 1010 1101 1111

    2’lik sistemden 16’lık sisteme yapılacak dönüşümler de benzer şekilde yapılabilir :
    Önce sayıları sağdan başlayarak dörder dörder ayırırız (en son dört eksik kalırsa sıfır ile tamamlarız.) Sonra her bir dörtlük grup için doğrudan 16'lık sayı sistemindeki karşılığını yazarız.

    1010 1110 1011 0001 = AEB1H
    0010 1101 0011 1110 = 2D3EH

    soru : 16'lık sayı sisteminde 2 byte'lık bir alanda yazılmış olan 81AC H sayısı pozitif mi negatif midir?
    cevap : Sayının yüksek anlamlı biti 1 olduğu için, işaretli sayı sistemlerinde sayı negatif olarak değerlendirilecektir. (1001 0001 1010 1100)

    16 bitlik bir alanda ve işaretli sayı sisteminde -1 sayısını nasıl ifade edebiliriz :
    Cevap : FFFF

    8’lik sayı sistemi (octal numbering system)

    Daha az kullanılan bir sayı sistemidir.

    8 adet sembol vardır. (0 1 2 3 4 5 6 7)
    8’lik sayı sisteminin her bir digiti 2’lik sistemde 3 bit ile ifade edilir.

    001 1
    010 2
    011 3
    100 4
    101 5
    110 6
    111 7

    8'lik sayı sisteminin de kullanılma nedeni, 2'lik sayı sistemine göre daha yogun bir ifade tarzı olması, ve ikilik sayı sistemiyle, 8'lik sayı sistemi arasında yapılacak dönüşümlerin çok kolay bir biçimde yapılabilmesidir.

    GERÇEK SAYILARIN BELLEKTE TUTULMASI


    Sistemlerin çoğu gerçek sayıları IEEE 754 standardına göre tutarlar. (Institute of Electrical and Electronics Engineers) Bu standarda göre gerçek sayılar için iki ayrı format belirlenmiştir:

    single precision format (tek hassasiyetli gerçek sayı formatı)

    Bu formatta gerçek sayı 32 bit (8 byte) ile ifade edilir.
    32 bit üç ayrı kısma ayrılmıştır.

    1. İşaret biti (sign bit) (1 bit)
    Aşağıda S harfi ile gösterilmiştir.
    İşaret biti 1 ise sayı negatif, işaret biti 0 ise sayı pozitiftir.

    2. Üstel kısım (exponent) (8 bit)
    Aşağıda E harfleriyle gösterilmiştir.

    3. Ondalık kısım (fraction) (23 bit)
    Aşağıda F harfleriyle gösterilmiştir.

    S EEEEEEEE FFFFFFFFFFFFFFFFFFFFFFF
    31 30-----------23 22-------------------------------------0

    Aşağıdaki formüle göre sayının değeri hesaplanabilir :

    V sayının değeri olmak üzere:

    E = 255 ise ve F 0 dışı bir değer ise V = NaN (Not a number) bir gerçek sayı olarak kabul edilmez. Örnek :

    0 11111111 00001000000100000000000 = Sayı değil
    1 11111111 00010101010001001010101 = Sayı değil


    E = 255 ise ve F = 0 ise ve S = 1 ise V = -sonsuz
    E = 255 ise ve F = 0 ise ve S = 1 ise V = +sonsuz

    0 < E < 255 ise

    V = (-1)S * 2(E -127) * (1.F)

    Önce sayının fraction kısmının başına 1. eklenir. Daha sonra bu sayı 2(E-127) ile çarpılarak noktanın yeri ayarlanır. Noktadan sonraki kısım 2'nin artan negatif kuvvetleriyle çarpılarak elde edilecektir. Örnekler :

    0 10000000 00000000000000000000000 = +1 * 2 (128 - 127) * 1.0
    = 2 * 1.0
    = 10.00
    = 2



    0 10000001 10100000000000000000000 = +1 * 2 (129 - 127) * 1.101
    = 22 * 1.101
    = 110.100000
    = 6.5


    1 10000001 10100000000000000000000 = -1 * 2 (129 - 127) * 1.101
    = -22 * 1.101
    = 110.100000
    = -6.5

    0 00000001 00000000000000000000000 = +1 * 2 (1 - 127) * 1.0
    = 2-126

    E = 0 ve F sıfır dışı bir değer ise

    V = (-1)S * 2(-126) * (0.F)

    Örnekler :

    0 00000000 10000000000000000000000 = +1 * 2-126 * 0.1
    =

    0 00000000 00000000000000000000001 = +1 * 2-126 0. 00000000000000000000001
    = 2-149 (en küçük pozitif değer)

    E = 0 ve F = 0 ve S = 1 ise V = -0

    E = 0 ve F = 0 ve S = 0 ise V = 0


    double precision format (çift hassasiyetli gerçek sayı formatı)

    Bu formatta gerçek sayı 64 bit (8 byte) ile ifade edilir.
    64 bit üç ayrı kısıma ayrılmıştır.

    1. İşaret biti (sign bit) (1 bit)
    Aşağıda S harfi ile gösterilmiştir.
    İşaret biti 1 ise sayı negatif, işaret biti 0 ise sayı pozitiftir.

    2. Üstel kısım (exponent) (11 bit)
    Aşağıda E harfleriyle gösterilmiştir.

    3. Ondalık kısım (fraction) (52 bit)
    Aşağıda F harfleriyle gösterilmiştir.

    S EEEEEEEEEEE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    63 62------------------52 51-----------------------------------------------------------------------------0

    Aşağıdaki formüle göre sayının değeri hesaplanabilir :

    Aşağıdaki formüle göre sayının değeri hesaplanabilir :

    V sayının değeri olmak üzere:

    E = 2047 ise ve F 0 dışı bir değer ise V = NaN (Not a number) bir gerçek sayı olarak kabul edilmez.

    E = 2047 ise ve F = 0 ise ve S = 1 ise V = -sonsuz
    E = 2047 ise ve F = 0 ise ve S = 1 ise V = +sonsuz

    0 < E < 2047 ise

    V = (-1)S * 2(E -1023) * (1.F)

    Önce sayının fraction kısmının başına 1. eklenir. Daha sonra bu sayı 2(E-1023) ile çarpılarak noktanın yeri ayarlanır. Noktadan sonraki kısım 2'nin artan negatif kuvvetleriyle çarpılarak elde edilecektir.

    E = 0 ve F sıfır dışı bir değer ise

    V = (-1)S * 2(-126) * (0.F)

    E = 0 ve F = 0 ve S = 1 ise V = -0

    E = 0 ve F = 0 ve S = 0 ise V = 0

  2. #2
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    GENEL KAVRAMLAR

    ATOM KAVRAMI VE ATOM TÜRLERİ


    Bir programlama dilinde yazılmış programı en küçük parçalara bölmeye çalışalım. Öyle bir noktaya geleceğiz ki, artık bu parçaları daha da bölmeye çalıştığımızda anlamsız parçalar oluşacak. İşte bir programlama dilinde anlam taşıyan en küçük birime atom (token) denir.
    Atomlar daha fazla parçaya bölünemezler.
    Yazdığımız kaynak kod (program) derleyici tarafından ilk önce atomlarına ayrılır. (Tokenizing). Atom yalnızca C diline ilişkin bir kavram değildir. Tüm programlama dilleri için atom kavramı söz konusudur, ama farklı programlama dillerinin atomları birbirlerinden farklı olabilir.
    Atomları aşağıdaki gibi gruplara ayırabiliriz :
    1. Anahtar Sözcükler (keywords, reserved words)

    Bu atomlar dil için belli bir anlam taşırlar. Değişken olarak kullanılmaları yasaklanmıştır. Yani programcı bu anahtar sözcükleri kendi tanımlayacağı değişkenlere isim olarak veremez.
    Standard ANSI C dilinde 32 tane anahtar sözcük bulunmaktadır.(Derleyici yazan firmalar kendi yazdıkları derleyiciler için ilave anahtar sözcükler tanımlayabilmektedir.)


    auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while


    Bazı programlama dillerinde anahtar sözcüklerin küçük ya da büyük harf olması fark etmemektedir. Ama C’de bütün anahtar sözcükler küçük harf olarak tanımlanmıştır. C büyük harf küçük harf duyarlığı olan bir dildir. (case sensitive) bir dildir. Ama diğer programlama dillerinin çoğunda büyük - küçük harf duyarlığı yoktur. (case insensitive)
    Örneğin, programcı olarak biz kullanacağımız bir değişkene “register” ismini vermeyiz. Çünkü bu bir anahtar sözcüktür. (C dili tarafından rezerve edilmiştir) Ama buna karşın biz istediğimiz bir değişkene REGISTER, Register, RegisTER vs. gibi isimler verebiliriz, çünkü bunlar artık anahtar sözcük sayılmazlar. Anahtar sözcük olan yalnızca tamamen küçük harf ile yazılan "register" dir.
    2. İsimlendirilenler (identifiers)

    Değişkenlere, fonksiyonlara, makrolara, yapı ve birliklere vs. programlama dili tarafından belirlenmiş kurallara uyulmak şartıyla, istediğimiz gibi isim verebiliriz. Bu atomlar genellikle bellekte bir yer belirtirler.
    C dilinde değişkenlerin isimlendirilmesine ilişkin kurallar vardır. Bunu ileride detaylı olarak göreceğiz.
    3. Operatörler (Operators)

    Operatörler önceden tanımlanmış birtakım işlemleri yapan atomlardır.
    Örneğin +, -, *, / , >=, <= birer operatördür.
    Programlama dillerinde kullanılan operatör sembolleri birbirinden farklı olabileceği gibi, operatör tanımlamaları da birbirinden farklı olabilir. Örneğin birçok programlama dilinde üs alma operatörü tanımlanmışken C dilinde böyle bir operatör yoktur. Üs alma işlemi operatör ile değil bir fonksiyon yardımıyla yapılabilir.
    C dilinde bazı operatörler iki karakterden oluşmaktadır bu iki karakter bitişik yazılmalıdır aralarına space karakteri koyarsak operatör anlamını yitirir.
    4. Sabitler (Constants)

    Doğrudan işleme sokulan değişken bilgi içermeyen atomlardır.
    Örneğin SAYAC = SON + 10 gibi bir ifadede 10 sabiti doğrudan SON değişkeni ile toplanmaktadır.
    5. Stringler (String literals)

    İki tırnak içindeki ifadelere string denir. Stringler programlama dillerinin çoğunda tek bir atom olarak alınırlar, daha fazla parçaya bölünemezler.
    “STRİNGLER DE BİRER ATOMDUR” ifadesi bir stringdir.
    6. Ayıraçlar ya da noktalama işaretleri (Separators, Punctuators, Delimiters)

    Yukarıda sayılan atom sınıflarının dışında kalan tüm atomları bu gruba sokabiliriz. Genellikle diğer atomları birbirinden ayırma amacıyla kullanıldıkları için ayıraç olarak isimlendirilirler.
    örnek bir C programının atomlarına ayrılması:


    Aşağıda 1 den kullanıcının klavyeden girdiği bir tamsayıya kadar olan tamsayıları toplayan ve sonucu ekrana yazdıran bir C programı görülüyor. Bu kaynak kodu atomlarına ayıralım. Amacımız söz konusu programı açıklamak değil, atomlar hakkında gerçek bir programdan örnek vermek.


    #include <stdio.h>

    main()
    {
    int number, k, total = 0;

    printf("lütfen bir sayı giriniz\n");
    scanf("%d", &number);
    for(k = 1; k<= number; ++k)
    total += k;
    printf("toplam = %d\n", toplam);
    return 0;
    }

    # include < stdio.h > main ( ) { int number , k , total = 0 ;
    printf ( "lütfen bir sayı giriniz\n" ) ; scanf ( "%d" , & number ) ;
    for ( k = 1 ; k <= ; ++ k ) total += k ;
    printf ( "toplam = %d\n" , toplam ) ; }

    programda yer alan atomlardan

    anahtar sözcükler
    include int for return

    isimlendirilenler (identifiers / variables)
    main n k toplam printf scanf

    operatörler
    = <= ++ +=

    sabitler
    0 1 0

    stringler
    ("lütfen bir sayı giriniz\n" ) "%d" "toplam = %d\n"

    ayıraçlar noktalama işaretleri
    < > ( ) , ; { }

    NESNE (OBJECT)


    Bellekte yer kaplayan ve içeriklerine erişilebilen alanlara nesne denir. Bir ifadenin nesne olabilmesi için bellekte bir yer belirtmesi gerekir. Programlama dillerinde nesnelere isimlerini kullanarak erişebiliriz.
    a = b + k; örneğinde a, b ve k birer nesnedir. Bu ifadede a nesnesine b ve k nesneleriine ait değerlerin toplamı atanmaktadır.
    sonuc = 100; sonuc isimli nesneye 100 sabit değeri atanmaktadır.

    nesnelerin bazı özelliklerinden söz edilebilir :

    İsimleri (name) :


    Nesneyi temsil eden karakterlerdir. Nesnelere isimleri programcı tarafından verilir. Her dil için nesne isimlendirmede bazı kurallar söz konusudur.
    VERGI = 20000; (Burada VERGI bir nesne ismidir.)
    Nesne ile Değişken kavramları birbirine tam olarak eşdeğer değildir. Her değişken bir nesnedir ama her nesne bir değişken değildir. Değişkenler, programcının isimlendirdiği nesnelerdir. Peki programcının isimlendirmediği de nesneler var mıdır? Evet, göstericiler konusunda da göreceğimiz gibi, değişken olmayan nesneler de vardır, nesne kavramı değişken kavramını kapsamaktadır.

    Değerleri (value) :


    Nesnelerin içlerinde tuttuklerı bilgilerdir. Başka bir deyişle nesneler için bellekte ayrılan yerklerdeki 1 ve 0 ların yorumlanış biçimi ilgili nesnenin değeridir. Bu değerler programlama dillerinin kurallarına göre , istenildikleri zaman programcı tarafından değiştirilebilirler. C dilinde bazı nesneler ise bir kez değer verildikten sonra bir daha değiştirilemezler.

    Türleri (Type) :


    Nesnenin türü derleyiciye o nesnenin nasıl yorumlanacağı hakkında bilgi verir. Yine bir nesnenin türü onun bellekteki uzunluğu hakkında da bilgi verir. Her türün bellekte ne kadar uzunlukta bir yer kapladığı programlama dillerinde önceden belirtilmiştir. Bir nesnenin türü, ayrıca o nesne üzerinde hangi işlemlerin yapılabileciği bilgisini de verir.
    Tür nesnenin ayrılmaz bir özelliğidir, türsüz bir nesne kavramı söz konusu değildir.
    Türleri ikiye ayırabiliriz :
    1. Önceden tanımlanmış veri türleri (default types)

    Bu türler programlama dilinin tasarımında var olan veri türleridir. Örneğin C dilinde önceden tanımlanmış 11 ayrı veri türü vardır.
    2. Programcı tarafından tanımlanan veri türleri (user defined types)

    Programlama dillerinin çoğunda programcının tür tanımlamasına izin vermektedir. Örneğin C dilinde yapılar, birlikler, bit alanları, C++ dilinde de sınıflar programcı tarafından tanımlanan veri türleridir.
    Programlama dillerindeki tür tanımlamaları birbirlerinden farklı olabilir. Örneğin bazı programlama dillerinde Boolean isimli (Mantıksal Doğru ya da Yanlış değerlerini alan) bir türdür tanımlanmıştır. Ama C dilinde böyle bir tür doğrudan tanımlanmamıştır.

    Faaliyet alanları (scope / visibility) :

    Nesnenin, dilin derleyicisi ya da yorumlayıcısı tarafından tanınabildiği program alanıdır. (ileride detaylı inceleyeceğiz)

    Ömürleri (storage duration / lifespan) :

    Programın çalıştırılması sırasında nesnenin varlığını sürdürdüğü zaman parçasıdır. (İleride detaylı inceleyeceğiz)

    Bağlantıları (linkage)

    Nesnelerin programı oluşturan diğer modüllerde tanınabilme özelliğidir. (İleride detaylı inceleyeceğiz)

    İFADE (Expression)


    Değişken, operatör ve sabitlerin kombinasyonlarına ifade denir.

    a + b / 2
    c * 2, d = h + 34
    var1

    geçerli ifadelerdir.

    DEYİM (statement)


    Derleyicinin, bilgisayara bir iş yaptıracak şekilde kod üretmesine (yani icra edilebilecek bir kod üretmesine) yol açan ifadelere deyim denir.
    Örneğin C dilinde
    ; ile sonlandırılmış ifadelere deyim diyoruz.
    result = number1 * number2

    bir ifadedir. Ancak

    result = number1 * number2;

    bir deyimdir. Bu deyim derleyicinin, number1 ve number2 değişkenlerin değerlerinin çarpılarak, elde edilen değerin result değişkenine atanmasını sağlayacak şekilde kod üretmesine neden olacaktır.
    Deyimleri İleride detaylı olarak inceleyeceğiz.

    SOL TARAF DEĞERİ (Left Value)


    Nesne gösteren ifadelere denir. Bir ifadenin sol taraf değeri olabilmesi için mutlaka bir nesne göstermesi gerekir. Bir ifadenin Sol taraf değeri olarak isimlendirilmesinin nedeni o ifadenin atama operatörünün sol tarafına getirilebilmesidir.
    Örneğin a ve b nesneleri tek başına sol taraf değerleridir. Çünkü bu ifadeler atama operatörünün sol tarafına getirilebilirler.
    Örneğin a = 17, ya da b = c * 2 denilebilir.
    Ama a + b bir sol taraf değeri değildir. Çünkü a + b = 25 denilemez.
    Değişkenler her zaman sol taraf değeridirler.
    sabitler sol taraf değeri olamazlar.
    SAĞ TARAF DEĞERİ (Rigth Value)

    Daha az kullanılan bir terimdir. Nesne göstermeyen ifadeler sağ taraf değeri olarak isimlendirilirler. Tipik olarak, atama operatörünün sol tarafında bulunamayan yalnızca sağ tarafında bulunabilen ifadelerdir.
    Sabitler her zaman sağ taraf değeri oluştururlar.
    (Bir ifade sol taraf değeri değilse sağ taraf değeridir. Sağ taraf değeri ise sol taraf değeri değildir.Her ikisi birden olamaz. Yani atama operatörünün sağ tarafına gelebilen her ifade sağ taraf değeri olarak isimlendirilmez.) Sağ taraf değeri, genellikle bir ifadenin nesne göstermediğini vurgulamak için kullanılır.



    2. VERİ TÜRLERİ


    Nesne (Object) kavramını incelediğimiz zaman, nesnelerin en önemli özelliklerinden birinin nesnenin türü olduğunu belirtmiştik. Tür (type) nesnenin olmazsa olmaz bir özelliğidir ve türü olmayan bir nesneden söz etmek mümkün değildir. Derleyiciler nesnelerle ve verilerle ilgili kod üretirken, tür bilgisinden faydalanırlar. Tür bilgisinden, söz konusu veriyi bellekte ne şekilde tutacaklarını, verinin değerini ne şekilde yorumlayacaklarını, veriyi hangi işlemlere tabi tutabileceklerini öğrenirler.

    Programlama dilleri açısından baktığımız zaman türleri iki ayrı gruba ayırabiliriz.

    1. Önceden tanımlanmış veri türleri (Doğal veri türleri)
    (Basic tpes, default types, built-in types, primitive types)

    Programlama dilinin tasarımından kaynaklanan ve dilin kurallarına göre varlığı garanti altına alınmış olan türlerdir. Her programlama dili programcının doğrudan kullanabileceği, çeşitli özelliklere sahip veri türleri tanımlar. C dilinde de önceden tanımlanmış 11 adet veri türü vardır.

    2. Programcının tanımlanmış olduğu veri türleri (user defined types)

    Programlama dillerinin çoğu, önceden tanımlanmış veri türlerine ek olarak, programcının da yeni türler tanımlanmasına izin vermektedir. Programcının tanımlayacağı bir nesne için önceden tanımlanmış veri türleri yetersiz kalıyorsa, programcı kendi veri türünü yaratabilir. C dilinde de programcı yeni bir veri türünü derleyiciye tanıtabilir ve tanıttığı veri türünden nesneler tanımlayabilir.

    Farklı programlama dillerindeki önceden tanımlanan veri türleri birbirlerinden farklı olabilir. Daha önce öğrenmiş olduğunuz bir programlama dilindeki türlerin aynısını C dilinde bulamayabilirsiniz.

    C dilininin önceden tanımlanmış 11 veri türü vardır. Bu veri türlerinden 8 tanesi tamsayı türünden verileri tutmak için, kalan 3 tanesi ise gerçek sayı türünden verileri tutmak için tasarlanmıştır. Biz bu türlere sırasıyla "Tamsayı veri türleri" (integer types) ve "gerçek sayı veri türleri" (floating types) diyeceğiz.

    tamsayı veri türleri (integer types)


    C dilinin toplam 4 ayrı tamsayı veri türü vardır ancak her birinin kendi içinde işaretli ve işaretsiz biçimi olduğundan, toplam tamsayı türü 8 kabul edilir.

    İşaretli (signed) tamsayı türlerinde pozitif ve negatif tam sayı değerleri tutulabilirken, işaretsiz (unsigned) veri türlerinde negatif tamsayı değerleri tutulamaz.

    Bu türleri sırasıyla inceleyelim:
    işaretli ve işaretsiz char veri türü :

    Şüphesiz char sözcüğü ingilizce character sözcüğünden kısaltılmıştır ve türkçe "karakter" anlamına gelmektedir. Ancak bu türün ismini, bundan sonraki derste C dilinin bir anahtar sözcüğü olduğunu öğreneceğimiz char sözcüğü ile özdeşleştirip, "char türü" (çar diye okuyunuz) diye söyleyeceğiz. İşaretli char türünden bir nesnenin bir byte'lık bir alanda tutulması C standartlarınca garanti altına alınmıştır.

    1 byte'lık bir alanı işaretli olarak kullandığımızda yazabileceğimiz değerlerin -128 / 127 değerleri arasında değişebileceğini sayı sistemleri dersimizden hatırlayalım.
    işaretsiz char veri türünün işaretli olandan farkı 1 byte'lık alanın işaretsiz olarak, yani yalnızca 0 ve pozitif sayıların ifadesi için kullanılmasıdır. Bu durumda işaretsiz char türünde 0 - 255 arasındaki tamsayı değerleri tutulabilir.
    işaretli ve işaretsiz short int veri türü (işaretli kısa tamsayı türü - işaretsiz kısa tamsayı türü) :

    Yine bundan sonraki derste öğreneceğimiz gibi, short ve int sözcükleri C dilinin anahtar sözcüklerinden olduğu için bu türün ismini genellikle short int, ya da kısaca short türü olarak telaffuz edeceğiz.
    işaretli ve işaretsiz short veri türünden bir nesne tanımlandığı zaman, nesnenin bellekte kaç byte yer kaplayacağı sistemden sisteme değişebilir. Sistemlerin çoğunda, short int veri türünden yaratılan nesne bellekte 2 byte'lık bir alan kaplayacaktır. işaretli short int veri türünden bir nesne -32768 - +32767 aralığındaki tamsayı değerlerini tutabilirken, işaretsiz short türü söz konusu olduğıundan tutulabilecek değerler 0 - +65535 aralığında olabilir.
    işaretli int (signed int) türü ve işaretsiz int (unsigned int) türü :

    işaretli ve işaretsiz int veri türünden bir nesne tanımlandığı zaman, nesnenin bellekte kaç byte yer kaplayacağı sistemden sisteme değişebilir. Çoğunlukla 16 bitlik sistemlerde, int veri , 32 bitlik sistemlerde ise int veri türü 4 byte yer kaplamaktadır.
    16 bitlik sistem, 32 bitlik sistem ne anlama geliyor.
    16 bitlik sistem demekle işlemcinin yazmaç (register) uzunluğunun 16 bit oldugunu anlatıyoruz.

    int veri türünün 2 byte uzunluğunda olduğu sistemlerde bu veri türünün sayı sınırları, işaretli int türü için -32768 - +32767, işaretsiz int veri türü için 0 - +65535 arasında olacaktır.
    işaretli ve işaretsiz long int veri türü (işaretli uzun tamsayı türü - işaretsiz uzun tamsayı türü)

    Bu türün ismini genellikle long int, ya da kısaca long türü olarak telaffuz edeceğiz.
    işaretli ve işaretsiz long int veri türünden biriyle tanımlanan bir nesnenin bellekte kaç byte yer kaplayacağı sistemden sisteme değişebilir. Sistemlerin çoğunda, long int veri türünden yaratılan nesne bellekte 4 byte'lık bir alan kaplayacaktır. İşaretli long int veri türünden bir nesne -2147483648 - +2147483647 aralığındaki tamsayı değerlerini tutabilirken, işaretsiz long int türü söz konusu olduğıundan tutulabilecek değerler 0 - +4.294.967.296 aralığında olur.
    GERÇEK SAYI TÜRLERİ

    C dilinde gerçek sayı değerlerini tutabilmek için 3 ayrı veri türü tanımlanmıştır. Bunlar sırasıyla, float, double ve long double veri türleridir. Gerçek sayı veri türlerinin hepsi işaretlidir. Yani gerçek sayı veri türleri içinde hem pozitif hem de negatif değerler tutulabilir. Gerçek sayıların bellekte tutulması sistemden sisteme değişebilen özellikler içerebilir. Ancak sistemlerin çoğunda IEEE 754 sayılı standarda uyukmaktadır.

    Sistemlerin hemen hemen hepsinde float veri türünden bir nesne tanımlandığı zaman bellekte 4 byte yer kaplayacaktır. 4 byte'lık yani 32 bitlik alana özel bir kodlama yapılarak gerçek sayı değeri tutulur. IEEE 754 sayılı standartta 4 byte'lık gerçek sayı formatı "single precision " (tek hassasiyet) olarak isimlendirilmiştir. Bu standartta 32 bitlik alan 3 bölüme ayrılmıştır.

    1 bitlik alan (sign bit): gerçek sayının işaret bilgisini yani pzoitif mi negatif mi olduğu bilgisini tutar.
    8 bitlik alan (exponential part) :
    23 bitlik alan (fraction part) : sayının ondalık kısmını tutar.

    Sistemlerin hemen hemen hepsinde double veri türünden bir nesne tanımlandığı zaman bellekte 8 byte yer kaplayacaktır. Gerçek sayıların bellekte tutulması sistemden sisteme değişebilen özellikler içerebilir. Ancak sistemlerin çoğunda IEEE 754 sayılı standarda uyulmaktadır.

    long double veri türünden bir nesne tanımlandığı zaman bellekte 10 byte yer kaplayacaktır.

    C dilinin doğal veri türlerine ilişkin bilgileri aşağıda bir tablo şeklinde veriyoruz:



    C DİLİNİN ÖNCEDEN TANIMLANMIŞ
    (DEFAULT ) VERİ TÜRLERİ


    TAMSAYI TÜRLERİ


    (INTEGER TYPES)



    TÜR İSMİ


    UZUNLUK(byte)


    (DOS / UNIX)


    SINIR DEĞERLERİ


    signed char


    1


    -128


    127


    unsigned char


    1


    0


    255


    signed short int


    2


    -32.768


    32.767


    unsigned short int


    2


    0


    65.535


    signed int


    2


    4


    -32.768


    -2.147.483.648


    32.767


    2.147.483.647


    unsigned int


    2


    4


    0


    0


    65.535


    4.294.967.296


    long int


    4


    -2.147.483.648


    2.147.483.647


    unsigned long int


    4


    0


    4.294.967.296




    GERÇEK SAYI TÜRLERİ


    (FLOATING TYPES)



    TÜR İSMİ


    UZUNLUK


    (byte)


    SINIR DEĞERLERİ




    en küçük pozitif değer


    en büyük pozitif değer


    float


    4


    1.17 x 10-38


    (6 basamak hassasiyet)


    3.40 x 1038


    double


    8


    2.22 x 10-308


    (15 basamak hassasiyet)


    1.17 x 10-38


    (15 basamak hassasiyet)


    long double


    10


    taşınabilir değil



    Yukarıda verilen tablo sistemlerin çoğu için geçerli de olsa ANSI C standartlarına göre yalnızca aşağıdaki özellikler garanti altına alınmıştır:

    char türü 1 byte uzunluğunda olmak zorundadır.
    short veri türünün uzunluğu int türünün uzunluğuna eşit ya da int türü uzunluğundan küçük olmalıdır. Yani

    short <= int

    long veri türünün uzunluğu int türüne eşit ya da int türünden büyük olmak zorundadır. Yani

    long >= int

    Derleyiciler genel olarak derlemeyi yapacakları sistemin özelliklerine göre int türünün uzunluğunu işlemcinin bir kelimesi kadar alırlar. 16 bitlik bir işlemci için yazılan tipik bir uygulamada

    char türü 1 byte
    int türü 2 byte (işlemcinin bir kelimesi kadar)
    short türü 2 byte (short = int)
    long türü 4 byte (long > int)

    alınabilir.

    Yine 32 bitlik bir işlemci için yazılan tipik bir uygulamada

    char türü 1 byte
    int türü 4 byte (işlemcinin bir kelimesi kadar)
    short türü 2 byte (short < int)
    long türü 4 byte (long = int)

    alınabilir.

    C dilinin en çok kullanılan veri türleri tamsaylar için int türü iken gerçek sayılar için double veri türüdür. Peki hangi durumlarda hangi veri türünü kullanmak gerekir. Bu sorunun cevabı olarak hazır bir reçete vermek pek mümkün değil, zira kullanacağımız bir nesne için tür seçerken bir çok faktör söz konusu olabilir, ama genel olarak şu bilgileri verebiliriz :

    Gerçek sayılarla yapılan işlemler tam sayılarla yapılan işlemlere göre çok daha fazla yavaştır. Bunun nedeni şüphesiz gerçek sayıların özel bir şekilde belirli bir byte alanına kodlanmasıdır. Tamsayıların kullanılmasının yeterli olduğu durumlarda bir gerçek sayı türünün kullanılması , çalışan programın hızının belirli ölçüde yavaşlatılması anlamına gelecektir. Bir tamsayı türünün yeterli olması durumunda gerçek sayı türünün kullanılması programın okunabilirliğininin de azalmasına neden olacaktır.



    3. BİLDİRİM VE TANIMLAMA

    Progamlama dillerinin çoğunda nesneler kullanılmadan önceye derleyiciye tanıtılırlar.

    Nesnelerin kullanılmalarından önce, özellikleri hakkında derleyiciye bilgi verme işlemlerine bildirim (declaration) denir. Bildirim işlemi yoluyla, derleyiciler nesnelerin hangi özelliklere sahip olduklarını anlarlar ve böylece bu nesneler için bellekte uygun bir yer tahsisatı yapabilirler. Yaratılacak nesne hakkında derleyiciye verilecek en önemli bilgi şüphesiz nesneye ilişkin tür (type) bilgisidir.

    C dilinde eğer yapılan bir bildirim işlemi, derleyicinin bellekte bir yer ayırmasına neden oluyorsa bu işleme tanımlama (definition) denir. Tanımlama nesne yaratan bir bildirimdir.

    Her tanımlama işlem aynı zamanda bir bildirim işlemidir ama her bildirim işlemi bir tanımlama olmayabilir. Başka bir deyişle, tanımlama nesne yaratan bir bildirim işlemidir.

    C dilinde bir değişkeni bildirimini yapmadan önce kullanmak derleme işleminde hata (error) oluşumuna yol açar.

    Bir değişkenin derleyiciye tanıtılması değişkenin türünün ve isminin derleyiciye bildirilmesidir ki, derleyici bu bilgiye dayanarak değişken için bellekte ne kadar yer ayıracağını, değişkenin için ayrılan byte'lardaki 1 ve 0 ların nasıl yorumlanacağı bilgisini elde eder.

    C Dilinde Bildirim İşleminin Genel Biçimi
    C programlama Dili'nde bildirim işlemi aşağıdaki şekilde yapılmaktadır :

    <tür> <nesne ismi> <;>

    Burada noktalı virgül karakterine sonlandırıcı karakter diyoruz. Noktalı virgül ayıraç türünden bir atomdur ve C'de bütün ifadeler noktalı virgül ile birbirlerinden ayrılırlar.

    a = x + 1; b = x + 2;

    ifadelerinde bulunan noktalı virgüller bunların ayrı birer ifade olduklarını gösterirler. Eğer bir tek noktalı virgül olsaydı derleyici iki ifadeyi tek bir ifade gibi yorumlayacaktı.

    a = x + 1 b = x + 2;

    Yukarıdaki ifade tek bir ifade gibi yorumlanır ve derleyici buna bir anlam veremez.

    Tür belirten anahtar sözcükler, C dilinin önceden tanımlanmış veri türlerine ilişkin anahtar sözcüklerdir. Bu sözcükleri bildirim sentaksında kullanarak, daha önce öğrenmiş olduğumuz 11 temel veri türünden hangisinden değişken tanımlamak istediğimizi derleyiciye bildirmiş oluyoruz. C dilinin önceden tanımlanmış veri türlerine ilişkin, bilidirim işleminde kullanılabilecek anahtar sözcükler şunlardır :

    signed, unsigned, char, short, int, long, float, double

    Bu sözcüklerin hepsi anahtar sözcük olduğundan küçük harf ile yazılmalıdır, C dilinin büyük harf küçük har duyarlı (case sensitive) bir dil olduğunu hatırlayalım. C dilinin tüm anahtar sözcükleri küçük harf ile tanımlanmıştır.

    Tür belirten anahtar sözcükler aşağıdaki tabloda listelenen seçeneklerden biri olmalıdır. Köşeli parantez içerisindeki ifadeler kullanılması zorunlu olmayan, yani seçime bağlı olan anahtar sözcükleri göstermektedir. Aynı satırdaki tür belirten anahtar sözcükler tamamen aynı anlamda kullanılabilmektedir.

    1
    char
    [signed ] char


    2
    unsigned char



    3
    short
    [signed] short
    short [int]
    [signed] short [int]
    4
    unsigned short



    5
    int
    [signed] int
    signed

    6
    unsigned int
    unsigned


    7
    long
    [signed] long
    long [int]
    [signed] long [int]
    8
    unsigned long
    unsigned long [int]


    9
    float



    10
    double



    11
    long double




    Yukarıdaki tablodan da görüldüğü gibi, belirli türleri birden fazla şekilde ifade etmek mümkündür.
    char a;
    int a;
    long a;
    signed char a;
    signed int a;
    long int a;

    signed a;
    signed long a;


    signed long int a;

    Yukarıda aynı kolon üzerindeki bildirimlerin hepsi aynı türden nesne yaratır.

    Bildirim işleminde nesne ismi olarak, C dilinin isimlendirme kurallarına uygun olarak seçilen herhangi bir isim kullanılabilir.

    C dilinde isimlendirilenler (identifiers) kavramı 6 grubu içerir. Değişkenler (variable) bunlardan yalnızca bir tanesidir. Fonksiyonlar (functions), etiketler (labels), makrolar (macros), yapı ve birlik isimleri (structure and union tags), enum sabitleri (enum constants) isimlerini programcılardan alırlar.
    C Dilinin İsimlendirme Kuralları

    İsimlendirmede yalnızca 63 karakter kullanılabilir.
    Bunlar: İngiliz alfabesinde yer alan 26 karakter, (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z) rakam karakterleri (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) ve alttire (underscore) karakteridir. (_)

    İsimlendirmelerde yukarıda belirtilen karakterlerin dışında başka bir karakterin kullanılması derleme zamanında hata oluşumuna yol açar. (ömeğin boşluk karakterinin kullanılması Türkçe karakterlerin kullanılması, +, -, /, *, & ya da $ karakterinin kullanılması gibi).

    Değişken isimleri rakam karakteriyle başlayamaz. Rakam karakteri dışında, yukarıda geçerli herhangi bir karakterle başlayabilir.

    C'nin anahtar sözcükleri isimlendirme amacı ile kullanılamaz.

    İsimler boşluk içeremeyeceği için uygulamalarda genellikle boşluk hissi vermek için alttire (underscore) karakteri kullanılır.

    genel_katsayi_farki, square_total, number_of_cards gibi.

    Başka bir teknik de isimlendirmede her sözcüğün ilk harfini Büyük, diğer harfleri küçük yazmaktır.

    GenelKatsayiFarki, SquareTotal, NumberOfCards gibi.

    C dilinde yapılan isimlendirmelerde, isimlerin maksimum uzunluğu tanımlanmamıştır. Bu derleyicilere göre değişebilir. Ancak bir çok derleyicide 32 sayısı kullanılmaktadır. Eğer verilen isim 32 karakterden daha fazla karakter içeriyorsa, derleyici bu ismi budar, yani yalnızca ilk 32 karakterini algılar.

    C dili büyük harf küçük harf duyarlığı olan bir dil olduğu için (case sensitive) isimlendirmelerde de büyük harf ve küçük harfler farklı karakterler olarak ele alınacaktır :

    var, Var, VAr, VAR, vAR, vaR değişkelerinin hepsi ayrı değişkenler olarak ele alınacaktır.

    Bu noktaların hepsi C dilinin sentaksı açısından, hata oluşumunu engellemek için zorunlu durumları belirtmek için anlatılmıştır.

    İsimlendirme yazılan programların okunabilirliği açısından da çok önemlidir. Kullanılan isimlerin legal olmalarının dışında, anlamlı olmalarına, kodu okuyacak kişiye bir fikir verecek şekilde seçilmelerine de dikkat edilmelidir.

    Bildirim işlemi noktalı virgül ile sonlandırılmalıdır.
    Bildirim Örnekleri

    int x;
    unsigned long int var;
    double MFCS;
    unsigned _result;
    signed short total;

    Tür belirten anahtar sözcüklerin yazılmasından sonra aynı türe ilişkin birden fazla nesnenin bildirimi, isimleri arasına virgül koyularak yapılabilir. Bildirim deyimi yine noktalı virgül ile sonlandırılmalıdır.

    unsigned char ch1, ch2, ch3, ch4;
    float FL1, Fl2;
    unsigned total, subtotal;
    int _vergi_katsayisi, vergi_matrahi;

    Farklı türlere ilişkin bildirimler virgüllerle birbirinden ayrılamaz.

    long x, int y; /* error */

    signed ve unsigned sözcükleri tür belirten anahtar sözcük(ler) olmadan yalnız başlarına kullanılabilirler. Bu durumda int türden bir değişkenin bildiriminin yapıldığı kabul edilir:

    signed x, y;

    ile

    signed int x, y;

    tamamen aynı anlamdadır. Yine

    unsigned u;

    ile

    unsigned int u;

    tamamen aynı anlamdadır. Ancak bu tür bir bildirimi tavsiye etmiyoruz, standartlar komitesi ileride bu özelliğin dilin kurallarından kaldırılabileceğini bildirmiştir. (deprecated feature).

    Bildirim işleminde, tür belirten anahtar sözcük birden fazla ise bunların yazım sırası önemli değildir, ama okunabilirlik açısından önce işaret belirten anahtar sözcüğün sonra tip belirten anahtar sözcüğün kullanılması gelenek haline gelmiştir. Örneğin :

    signed long int x;
    signed int long x;
    long signed int x;
    long int signed x;
    int long signed x;
    int signed long x;

    hepsi geçerli bildirimlerdir. Ama yukarıdaki bildirimde, seçimlik olan anahtar sözcükler özellikle kullanılmak isteniyorsa 1. yazım biçimi okunabilirlik açısından tercih edilmelidir.

    Bildirimlerin Kaynak Kod İçinde Yapılış Yerleri
    C dilinde genel olarak 3 yerde bildirim yapılabilir :

    1. Blokların içinde
    2. Tüm blokların dışında.
    3. Fonksiyon parametre değişkeni olarak fonksiyon parantezlerinin içerisinde

    Fonksiyon parametre parantezleri içerisinde yapılan bildirimler, başka bir sentaks kuralına uyarlar, bu bildirimler fonksiyonlar konusuna gelindiğinde detaylı olarak incelenecektir.

    C dilinde eğer bildirim blokların içinde yapılacaksa, bildirim işlemi blokların ilk işlemi olmak zorundadır. Başka bir deyişle bildirimlerden önce başka bir ifade bulunmamalı ya da bildirimden önce bir fonksiyon çağırılmamalıdır. (Aksi halde derleme zamanı sırasında hata oluşur.)

    Bildirimin mutlaka ana bloğun başında yapılması gibi bir zorunluluk yoktur. Eğer içiçe bloklar varsa içteki herhangi bir bloğun başında da (o bloğun ilk işlemi olacak şekilde) bildirim yapılabilir. Örnekler :

    {
    int var1, var2;
    char ch1, ch2, ch3;

    var1 = 10;
    float f; /* error */
    }

    Yukarıdaki örnekte var1, var2, ch1, ch2, ch3 değişkenlerinin tanımlanma yerleri doğrudur. Ancak f değişkeni yanlış yerde bildirilmiştir. Çünkü bildirim işleminden önce başka bir işlem (deyim) yer almaktadır. Bu durum derleme aşamasında hata oluşumuna neden olur.

    Aynı program parçası şu şekilde yazılmış olsaydı bir hata söz konusu olmazdı :

    {
    int var1, var2;
    char ch1, ch2, ch3;

    var1 = 10;
    { float f; }
    }

    bu durumda artık f değişkeni de kendi bloğunun başında (ilk işlem olarak) tanımlanmıştır.

    İleride de göreceğimiz gibi C dilinde tek başına bir noktalı virgül, bir deyim oluşturur. C sentaksına göre oluşan bu deyim icra edilebilir bir deyimdir. Dolayısıyla aşağıdaki kod parçasında y değişkeninin tanımlaması derleme zamanında hata oluşturacaktır.

    {
    int x;;
    int y; /* hata! ikinci sonlandırıcı atom icra edilebilir bir deyim olarak ele alınıyor. */
    }

    Aynı şekilde boş bir blok da C dilinde bir deyim gibi ele alınır. Bu yazım tamamen noktalı virgülün (sonlandırıcının) yalnız kullanılmasına eşdeğerdir. Dolayısıyla aşağıdaki kod parçası da hatalıdır:

    {
    int x;
    { }
    int y; /* ERROR ! y değişkeninin bildirimi doğru yerde değil. */
    }

    Bir ya da birden fazla deyimin de blok içine alınması C dilinde bileşik deyim (compound statement) ismini alır ve bileşik deyimler de icra edilebilir deyim kategorisine girerler. Dolayısıyla aşağıdaki kod parçası da hatalıdır.

    {
    {int x;}
    int y; /* ERROR */
    }


    (C++ dilinde blok içinde bildirimi yapılan değişkenlerin, blokların ilk işlemleri olacak şekilde bildirilmeleri zorunlu değildir. Yani C++ da değişkenler blokların içinde herhangi bir yerde bildirilebilirler.)






    4. SABİTLER

    Veriler ya nesnelerin içerisinde ya da doğrudan sabit biçiminde bulunurlar. Sabitler nesne biçiminde olmayan, programcı tarafından doğrudan girilen verilerdir. Sabitlerin sayısal değerleri derleme zamanında tam olarak bilinmektedir. Örneğin :

    x = y + z;

    ifadesi bize a ve b içindeki sayıların toplanacağı ve c’ye aktarılacağını anlatır. Oysa

    d = x + 10;

    ifadesinde x değişkeni içinde saklanan değer ile 10 sayısı toplanmıştır. Burada 10 sayısı herhangi bir değişkenin içindeki değer değildir, doğrudan sayı biçiminde yazılmıştır. Nesnelerin türleri olduğu gibi sabitlerin de türleri vardır. Nesnelerin türleri daha önce gördüğümüz gibi bildirim yapılırken belirlenir. Sabitlerin türlerini ise derleyici, belirli kurallar dahilinde sabitlerin yazılış biçimlerinden tespit eder. Sabitlerin türlerini bilmek zorundayız, çünkü C dilinde sabitler, değişkenler ve operatörler bir araya getirilerek (kombine edilerek) ifadeler (expressions) oluşturulur. Daha sonra detaylı göreceğimiz gibi C dilinde ifadelerin de bir türü vardır ve ifadelerin türleri, içerdikleri sabit ve değişkenlerin türlerinden elde edilir. O halde sabit türlerini detaylı olarak inceleyelim :
    Tamsayı Sabitleri (integer constants)

    İşaretli Tamsayı Sabitleri (signed int) :

    Bunlar tipik olarak int türden değişkenlerine atanan ve tamsayı biçiminde olan sabitlerdir, yazılırken herhangi bir ek almazlar. C’de int türü sınırları içinde olan her tamsayı birer tamsayı sabit (ya da int türden sabit ) olarak ele alınır.

    -25
    30000
    25789
    -320
    0

    sayılarının hepsi işaretli tamsayı (signed int) sabiti olarak ele alınırlar, çünkü int türü sayı sınırları içinde bulunuyorlar ve sonlarında herhangi bir ek bulunmuyor.

    int türü sistem bağımlıdır ve int sabitleri de sistemden sisteme değişebilir.


    sistem


    uzunluk


    sınır değerler

    DOS, WINDOWS 3.1
    2 byte
    - 32768, + 32767
    UNIX WINDOWS 95
    4 byte
    -2147483648,
    +2147483647



    Örneğin 425000 Dos’ta int sabiti değildir ama UNIX’te int sabittir.
    Uzun Tamsayı Sabitleri(Long integer constants)
    Uzun Tamsayı Sabitleri

    İşaretli Uzun Tamsayı Sabitleri (signed long)

    long türden sabitler iki türlü ifade edilirler :

    1. long int türünün sayı sınırları içinde bulunan bir sayının sonuna L ya da l yazarak.
    Bu durumda derleyiciler ilgili sayı int sınırları içinde olsa da long sabit olarak ele alır.

    22345l long sabittir. çünkü sonunda l eki var.
    0l, -23465L, 325l long sabitlerdir.

    long sabit kullanımında algılanması daha kolay olduğu için L soneki tercih edilmelidir. l soneki 1 rakamıyla görünüm açısından çok benzediği için karışıklığa neden olabilir.

    2. int türün sayı sınırlarını aşan fakat long int türü sayı sınırları içinde kalan her tamsayı doğrudan long int türden sabit olarak ele alınır. Bu durum doğal olarak, DOS gibi int ve long türlerinin birbirinden farklı olduğu sistemlerde anlamlıdır.

    Örneğin DOS’da
    325000
    -33333
    1278902

    long türden sabitlerdir. Oysa 32 bitlik sistemlerde long türünün uzunluğuyla int türün uzunluğu aynı (4 byte) olduğu için bu sayılar int sabiti olarak ele alınacaktır. Bu sistemlerde yukarıdaki sayıları long sabit olarak ele almak istersek sonlarına l ya da L eklememiz gerekmektedir.
    Karakter Sabitleri (char)

    char sabitleri tipik olarak char türden nesnelere atanan sabitlerdir. (Böyle bir zorunluluk yok.) char türden sabitler C dilinde dört ayrı biçimde bulunabilirler.

    1. İstenilen bir karakter tek tırnak (single quote) içerisinde kullanılırsa char türden sabit olarak ele alınır. Örnek :

    'a'
    'J'
    'Ç'
    ':'
    '8'
    '<'

    Yukarıdaki gösterimlerin herbiri birer char türden sabitidir.

    C'de tek tırnak içerisinde belirtilen char sabitleri, aslında o karakterin karakter setindeki (örneğin ASCII tablosundaki) sıra numarasını gösteren bir tamsayıdır.

    {
    char ch;
    ch = 'a';
    ...
    }

    Bu örnekte aslında ch isimli char türden değişkene a karakterinin ASCII tablosundaki sıra numarası olan 97 sayısı aktarılmaktadır. Tek tırnak içindeki karakter sabitlerini görünce aslında onların küçük birer tamsayı olduğunu bilmeliyiz. Çünkü bellekte karakter diye birşey yoktur herşey ikilik sistemde 1 ve 0 lardan oluşan sayılardır. Yukarıdaki örnekte istersek ch değişkenine aşağıdaki gibi bir atama yapabiliriz:

    ch = 'a' + 3;

    Bu durumda ch değişkenine sayısal olarak 100 değeri atanacaktır. Bu sayıya da ASCII tablosundaki 'd' karakteri karşılık gelir.

    2. Önceden tanımlanmış ters bölü karakter sabitleri (escape sequences)
    Yukarıda tanımlanan yöntemde ekrana basılamayan yani ekranda görüntü oluşturmayan (non printable) karakterleri ifade edemeyiz. Örneğin çan karakteri (çan sesi) ya da ters boşluk (backspace) karakteri ekrana basılamaz. Tek tırnak içindeki ters bölü (back slash) karakterinden sonra yer alan bazı karakterler çok kullanılan ancak basılamayan bazı karakterlerin yerlerini tutarlar. Bunların listesi aşağıda verilmiştir:

    Önceceden Tanımlanmış Ters Bölü Karakter Sabitleri (Escape Sequences)




    Tanım
    ASCII No
    '\0'
    '\x0'
    '\0'
    NULL karakter
    0
    '\a'
    '\x7'
    '\07'
    çan sesi (alert)
    7
    '\b'
    '\x8'
    '\010'
    geri boşluk (back space)
    8
    '\t'
    '\x9'
    '\011'
    tab karakteri (tab)
    9
    '\n'
    '\xA'
    '\012'
    aşağı satır (new line)
    10
    '\v'
    '\xB'
    '\013'
    düşey tab (vertical tab)
    11
    '\f'
    '\xC'
    '\014'
    sayfa ileri (form feed)
    12
    '\r'
    '\xD'
    '\015'
    satır başı (carriage return)
    13
    '\"'
    '\x22'
    '\042'
    çift tırnak (double quote)
    34
    '\\'
    '\x5C'
    '\134'
    ters bölü (back slash)
    92


    kullanılışlarına bir örnek :

    {
    char ch;

    ch = '\a';
    }

    3. 16'lık (hexadecimal) sayı sisteminde tanımlanmış karakter sabitleri
    Tek tırnak içinde tersbölü ve x karakterlerinden sonra bir hex sayı verilirse bu ASCII tablosundaki o sayısal değerin gösterdiği sıra numarasındaki karaktere işaret eden bir karakter sabitidir.

    '\x41' /* 41H numaralı ASCII karakteridir. */
    '\xff' /* FFH numaralı '2' karakter sabitidir. */
    '\x1C' /* 1C numaralı ASCII karakter sabitidir. */

    Küçük "x" yerine büyük harfle "X" yazmak C'nin ilk klasik versiyonunda kabul ediliyordu şimdi artık geçerli değildir. Örnek :

    {
    char harf;

    harf = '\x41';
    }

    Yukarıdaki örnekte harf isimli char türden değişkene 41H ASCII sıra no.lu karakter atanmıştır. Bu da desimal sistemdeki 65 sayısına eşittir. 65 sıra nolu ASCII karakteri 'A' karakteridir. Dolayısıyla harf isimli değişkene 'A' atanmıştır.

    4. 8'lik (octal) sayı sistemde tanımlanmış karakter sabitleri
    Tek tırnak içinde tersbölü karakterinden sonra bir oktal sayı yazılırsa bu kullanılan karakter setindeki o sayısal değerin gösterdiği sıra numarasındaki karaktere işaret eden bir karakter sabitidir. Tek tırnak içindeki ters bölü karakterini izleyen sayı üç basamaktan uzun olmamalıdır. Sekizlik sayıların yazımında olduğu gibi sayının başında sıfır olma zorunluluğu yoktur. Bu şekilde yazılabilecek en büyük karakter sabiti '\377' dir.:

    '\012' /* 10 numaralı ASCII karakteri, Tam sayı değeri 10 */
    '\16' /* 14 numaralı ASCII karakteri. Tam sayı değeri 14 */
    '\123' /* 83 numaralı ASCII karakteri. Tam sayı değeri 83 */

    Program içinde kullanımına bir örnek:

    {
    char a, b;

    a = '\xbc' ; /* hex sistemde tanımlanmış char sabit */
    b = '\012'; /* oktal sistemde tanımlanmış bir char sabit */
    }

    7 numaralı ASCII karakteri olan çan karakterini sabit olarak 3 biçimde de yazabiliriz.

    '\x7' /* hex gösterimli karakter sabiti */
    '\07' /* oktal gösterimli karakter sabiti */
    '\a' /* önceden belirlenmiş ters bölü karakter sabiti */

    Burada tercih edilecek biçim son biçim olmalıdır.Hem taşınabilir bir biçimdir hem de okunabilirliği daha iyidir. Başka karakter setlerinde çan sesi karakteri 7 sıra numaralı karakter olmayabilir ama önceden belirlenmiş ters bölü karakter sabiti şeklinde ifade edersek hangi sistem olursa olsun çan sesi karakterini verecektir. Ayrıca kodu okuyan kişi çan sesi karakterinin 7 numaralı ASCII karakteri olduğunu bilmeyebilir ama C programcısı olarak '\a' nın çan sesi karakteri olduğunu bilecektir.

    Karakter sabitleri konusunu kapatmadan önce karakter setleri konusunda da biraz bilgi verelim:

    Günümüzde kullanılan en popüler karakter seti ASCII karakter setidir. ASCII (American Standard Code for Information Interchange) sözcüklerinin başharflerinden oluşan bir kısaltmadır. ASCII setinin orjinal versiyonunda karakterler 7 bitlik bir alanda kodlanmıştır. Bazı bilgisayarlar ise 8 bit alana genişletilmiş ASCII seti kullanırlar ki bu sette 128 yerine 256 karakter temsil edilebilmektedir. Farklı bilgisayarlar farklı karakter setleri kullanabilmektedir. Örnek olarak IBM mainframe'leri daha eski bir set olan EBCDIC seti kullanırlar. Unicode ismi verilen daha geliştirilmiş bir karakter seti vardır ki karakterler 2 byte alanda temsil edildikleri için bu sette 65.536 farklı karakter yer alabilmektedir. Gelecekte bir çok makinanın bu karakter setini destekleyecek biçimde tasarlanacağı düşünülmektedir.
    İşaretsiz türlere ilişkin sabitler

    İşaretsiz türlere ilişkin sabitler onların işaretli biçimlerinin sonuna u ya da U getirilmesiyle elde edilirler.

    -15000 (signed) int sabit
    15000U (unsigned) int sabit.
    1200L (signed) long sabit
    1200Lu (unsigned) long sabit.

    Sonek olarak kullanılan l, L, u ve U harflerinin sırası önemli değildir.

    123ul
    123UL
    123Lu
    123lu

    Yukarıdaki hepsi geçerli birer uzun tamsayı (unsigned long int) sabittir.
    Tamsayı sabitlerinin 16'lık ve 8'lik sistemlerde gösterilmesi

    C'de tamsayı sabitleri (char, int, long) 10'luk sistemin yanısıra 16'lık ve 8'lik sistemlerde de yazılabilirler. Bu sayı sistemleriyle yazılmış tamsayı sabit türleri için yukarıda verilen kurallar aynen geçerlidir. Çünkü bir sayıyı 16'lık ya da 8'lik sistemde yazmakla onun yalnızca görünümünü değiştirmiş oluruz. Sabit türleri gösterim biçimiyle değil nicelikle ilişkilidir. C dilinde ikilik sayı sisteminde sabitlerin yazılması söz konusu değildir.

    16'lık sistemde gösterim 0Xbbb.. biçimindedir. (b karakterleri basamakları gösteriyor, 9'dan büyük basamak değerleri için A, B, C, D, E, F karakterleri ya da a, b, c, d, e, f karakterleri kullanılabilir.

    8'lik sistemde ise 0bbb.. biçimindedir. (nadir olarak kullanılır). Örnekler:

    0x12 sayısı hex gösterimli bir tamsayı (int) sabit.
    0X12L sayısı hex gösterimli bir uzun tamsayı (long) sabit.
    0x1C205470 hex gösterimli bir uzun tamsayı (long) sabit. Çünkü (DOS'da) tamsayı sayı sınırını aşmaktadır.
    0x1934ul hex gösterimli işaretsiz uzun tamsayı (unsigned long) sabittir.
    01234 oktal gösterimli tamsayı (int) sabit
    0567L oktal gösterimli uzun tamsayı (long) sabit
    0777U oktal gösterimli işaretsiz tamsayı (unsigned int) sabit
    0452Lu oktal gösterimli (unsigned long) sabit.

    Sabitler yukarıda gösterildiği gibi her üç sayı sisteminde de yazılabilir, hatta bir ifade içinde kullanılan sabitler farklı sayı sistemlerinde de yazılmış olabilirler, bu derleme zamanında error oluşturacak bir neden olmayıp tamamen legaldir.

    ...
    int x;
    x = 0x1AC2 + 345 + 01234;
    Gerçek Sayı Sabitleri (floating Constants)

    1. float türden sabitler
    Nokta içeren ve sonuna f ya da F getirilmiş sabitler float türden sabitler olarak ele alınırlar. Örneğin:

    1.31F
    10.F
    -2.456f

    float türden sabitlerdir.

    Not : Klasik C'de, yani C dilinin standartlaştırılmasından önceki dönemde float türden bir sabit elde etmek için, sayının sonuna f eki alması yeterliydi yani nokta içermesi gerekmiyordu ama standartlarda yapılan değişiklikle artık float sabitlerin sonuna ek gelse de mutlaka nokta içermeleri gerekiyor. Yani derleyiciler 3f şeklinde bir yazımı derleme zamanında bir hata (error) mesajıyla bildiriyorlar.

    2. double türden sabitler
    Sonuna f ya da F eki almamış nokta içeren sabitler ile float duyarlığını aşmış sabitler double sabitler olarak değerlendirilirler. Örneğin :

    -24.5 double türden sabittir.

    3. long double türden sabitler
    long double türden sabitler noktalı ya da üstel biçimdeki sayıların sonuna l ya da L getirilerek elde edilirler :

    1.34L
    10.2L

    long double türden sabitlerdir.
    Gerçek Sayı Sabitlerinin Üstel Biçimde Gösterilmesi

    Gerçek sayı sabitleri üstel biçimde de ifade edilebilirler, bunun için sayının sonuna e ya da E eki getirilerek bir tamsayı yazılır. Bu, sayının 10x gibi bir çarpanla çarpıldığını gösterir.

    2.3e+04f
    1.74e-6F
    8.e+9f

    burada e 10'un kuveti anlamına gelmektedir:

    1.34E-2f ile 0.0134
    -1.2E+2F ile 120.f
    aynı sabitlerdir.


  3. #3
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    FONKSİYONLAR


    C'de alt programlara fonksiyon denir. Fonksiyon sözcüğü burada matematiksel anlamıyla değil diğer programlama dillerinde kullanılan, "alt program", "prosedür", "subroutine" sözcüklerinin karşılığı olarak kullanılmaktadır.

    Fonksiyonlar C dilinin temel yapı taşlarıdır. Çalıştırılabilen bir C programı en az bir C fonksiyonundan oluşur. Bir C programının oluşturulmasında fonksiyon sayısında bir kısıtlama yoktur.

    Fonksiyonların onları çağıran fonksiyonlardan aldıkları girdileri ve yine onları çağıran fonksiyonlara gönderdikleri çıktıları vardır. Fonksiyonların girdilerine aktüel parametreler (actual parameters) ya da argumanlar (arguments) diyoruz. Fonksiyonların çıktılarına geri dönüş değeri (return value) diyoruz.

    Bir fonksiyon iki farklı amaçla kullanılabilir :

    1. Fonksiyon, icrası süresince belli amaçları gerçekleştirir. (Belli işlemleri yapar)
    2. Fonksiyon icrası sonunda üreteceği bir değeri kendisini çağıran fonksiyona gönderebilir.
    Fonksiyonların Tanımlanması ve Çağırılması

    Bir fonksiyonun ne iş yapacağının ve bu işi nasıl yapacağının C dilinin sentaks kurallarına uygun olarak anlatılmasına o fonksiyonun tanımlanması (definition) denir. Fonksiyon tanımlamaları aşağıda inceleneceği gibi birtakım sentaks kurallarına tabidir.
    Bir fonksiyonun çağırılması ise o fonksiyonun yapacağı işi icraya davet edilmesi anlamına gelir. Fonksiyon çağırma ifadesi karşılığında derleyici, programın akışını ilgili fonksiyonun kodunun bulunduğu bölgeye aktaracak şekilde bir kod üretir. Programın akışı fonksiyonun kodu içinde akıp bu kodu bitirdiğinde, yani fonksiyon icra edildiğinde, programın akışı yine fonksiyonun çağırıldığı noktaya geri dönecektir. Fonksiyon çağırmaya ilişkin sentaks da yine aşağıda açıklanacaktır.
    Fonksiyonların Geri Dönüş Değerleri (return values)

    Bir fonksiyonun yürütülmesi sonunda onu çağıran fonksiyona dönüşünde gönderdiği değere, fonksiyonun geri dönüş değeri (return value) denmektedir. Her fonksiyon bir geri dönüş değeri üretmek zorunda değildir. Fonksiyonların geri dönüş değerleri farklı amaçlar için kullanılabilir;


    1. Bazı fonksiyonlar tek bir değer elde etmek amacıyla tasarlanmışlardır. Elde ettikleri değeri de kendilerini çağıran fonksiyonlara geri dönüş değeri olarak iletirler. Örneğin:

    y = pow(2, 3);

    pow fonksiyonu standart bir C fonksiyonudur. Birinci parametresiyle belirtilen sayının ikinci parametresiyle belirtilen kuvvetini hesaplayarak, hesapladığı sayıyı geri dönüş değeri olarak kendisini çağıran fonksiyona iletir. Yukarıdaki örnekte 2 sayısının 3. kuvveti bu fonksiyon yardımıyla hesaplanarak bulunan değer y değişkenine atanmıştır.

    2. Bazı fonksiyonların geri dönüş değerleri fonksiyonun yürütülmesi sırasında yapılan işlemlerin başarısı hakkında bilgi verir. Yani bu tür fonksiyonların geri dönüş değerleri test amacıyla kullanılmaktadır. Geri dönüş değerleri yapılması istenen işlemin başarılı olup olmaması durumunu açıklar. Örneğin :

    p = malloc(200);

    ifadesiyle bellekte 200 byte uzunluğunda bir blok tahsis etmek isteyen programcı bu işlemin başarılı bir biçimde yerine getirilip getirilmediğini de test etmek zorundadır. Hemen arkasından p değişkeninin aldığı değeri kontrol edecek ve işlemin başarısı hakkında bir karara varacaktır. Dolayısıyla malloc fonksiyonunun geri dönüş değeri, fonksiyonun yapması gereken işin başarılı bir şekilde sonuçlanıp sonuçlanmadığını göstermektedir.

    3. Bazı fonksiyonlar kendilerine gönderilen argumanları belirli bir kritere göre test ederler. Ürettikleri geri dönüş değerleri ise test sonucunu belirtir. Örneğin:

    if (isalpha(ch)) {
    ...
    }

    Burada isalpha fonksiyonu arguman olarak gönderilen karakterin bir harf karakteri olup olmadığını test eder. Eğer harf karakteriyse, isalpha fonksiyonu 0 dışı bir değere geri dönecek, eğer harf karakteri değilse 0 değerine geri dönecektir. Çağıran fonksiyonda da geri dönüş değerine göre farklı işlemler yapılabilecektir.

    4. Bazı fonksiyonlar hem belli bir amacı gerçekleştirirler hem de buna ek olarak amaçlarını tamamlayan bir geri dönüş değeri üretirler. Örneğin :

    x = printf("Merhaba Dünya\n");

    Burada printf fonksiyonu ekrana Merhaba Dünya yazısını yazmak için kullanılmıştır. Ancak ekrana yazdığı karakter sayısını da geri dönüş değeri olarak vermektedir.
    Bir yazı içersinde bulunan belirli bir karakteri silecek bir fonksiyon tasarladığımızı düşünelim. Fonksiyon işini bitirdikten sonra yazıdan kaç karakter silmiş olduğunu geri dönüş değeri ile çağırıldığı yere bildirilebilir.

    5. Bazen geri dönüş değerlerine ihtiyaç duyulmaz. Örneğin yalnızca ekranı silme amacıyla tasarlanmış olan bir fonksiyonun geri dönüş değerine sahip olması gereksizdir.

    clrscr();

    clrscr fonksiyonu yalnızca ekranı siler, böyle bir fonksiyonun geri dönüş değerine ihtiyacı yoktur.

    Fonksiyonların geri dönüş değerlerinin de türleri söz konusudur. Fonksiyonların geri dönüş değerleri herhangi bir türden olabilir. Geri dönüş değerlerinin türleri fonksiyonların tanımlanması sırasında belirtilir.
    Fonksiyonların Tanımlanması

    Kendi yazdığımız fonksiyonlar için tanımlama (definition) terimini kullanıyoruz. C'de fonksiyon tanımlama işleminin genel biçimi şöyledir:

    [Geri dönüş değerinin türü] <fonksiyon ismi> ([parametreler])
    {
    ...
    ...
    }

    Yukarıdaki gösterimde açısal parantez içinde belirtilen ifadeler zorunlu olarak bulunması gerekenleri köşeli parantez içinde belirtilen ifadeler ise bulunması zorunlu olmayan, isteğe bağlı (optional) ifadeleri göstermektedir. Tanımlanan fonksiyonlar en az bir blok içerirler. Bu bloğa fonksiyonun ana bloğu denir. Ana blok içinde istenildiği kadar içiçe blok yaratılabilir. Aşağıdaki fonksiyon tanımlamasından fonk1 fonksiyonunun parametre almadığını ve geri dönüş değerinin de double türden olduğunu anlıyoruz.

    double fonk1()
    {
    ...
    ... Fonksiyonun ana bloğu
    ...
    }



    void Anahtar Sözcüğü
    Bir fonksiyonun parametre değişkeni ya da geri dönüş değeri olmak zorunda değildir. Bir fonksiyonun parametre değişkeni olmadığı iki şekilde belirtilebilir:

    1. Fonksiyon parametre parantezinin içi boş bırakılır, yani buraya hiçbirşey yazılmaz.
    2. Fonksiyon parametre parantezinin içine void anahtar sözcüğü yazılır.

    fonk() fonk(void)
    { {
    ... ...
    } }

    Yukarıdaki tanımlamalar C'de aynı anlama gelmiyor. Fonksiyon prototipleri konusunu öğrenirken bu iki tanımlama arasındaki farkı da öğrenmiş olacağız. Şimdilik bu iki tanımlamanın aynı anlama geldiğini ve fonksiyonun parametre almadığını belirttiklerini varsayacağız.

    Geri dönüş değerine ihtiyaç duyulmadığı durumlarda da geri dönüş değerinin türü yerine void anahtar sözcüğü yerleştirilir. Örneğin:

    void sample(void)
    {
    ...
    }

    Yukarıda tanımlanan sample fonksiyonu parametre almamakta ve bir geri dönüş değeri de üretmemektedir.

    Fonksiyon tanımlarken geri dönüş değeri yazılmayabilir. Bu durum geri dönüş türünün olmadığı anlamına gelmez. Eğer geri dönüş değeri yazılmazsa, C derleyicileri tanımlanan fonksiyonun int türden bir geri dönüş değerine sahip olduğunu varsayarlar. Örneğin :

    sample2()
    {
    ...
    }

    Tanımlanan sample2 fonksiyonunun parametresi yoktur ama int türden bir geri dönüş değeri vardır.

    C dilinde fonksiyon içinde fonksiyon tanımlanamaz!

    Örneğin aşağıdaki durum error oluşturur, çünkü sample2 fonksiyonu sample1 fonksiyonunun içinde tanımlanmıştır:

    double sample1()
    {
    ...
    int sample2() /* error */
    {
    ...
    }
    ...
    }

    tanımlamanın aşağıdaki şekilde yapılması gerekirdi :

    double sample1()
    {
    ...
    }

    int sample2()
    {
    ...
    }
    Fonksiyonların Çağırılması (function calls)

    C dilinde fonksiyon çağırma operatörü olarak () kullanılmaktadır. Bir fonksiyon çağırıldığı zaman programın akışı fonksiyonu icra etmek üzere bellekte fonksiyonun kodunun bulunduğu bölgeye atlar, fonksiyonun icra edilme işlemi bittikten sonra da akış tekrar çağıran fonksiyonun kalınan yerinden devam eder.

    Bir fonksiyonun geri dönüş değeri varsa, fonksiyon çağırma ifadesi geri dönüş değerini üretir.
    Geri dönüş değeri bir değişkene atanabileceği gibi doğrudan aritmetik işlemlerde de kullanılabilir. Örneğin:

    sonuc = hesapla();

    Burada hesapla fonksiyonunun çağırılma ifadesiyle üretilen geri dönüş değeri sonuc değişkenine atanmaktadır. Bir başka deyişle bir fonksiyon çağırma ifadesinin ürettiği değer, ilgili fonksiyonun ürettiği (eğer üretiyorsa) geri dönüş değeridir. Yukarıdaki örnekte önce hesapla() fonksiyonu çağırılacak daha sonra fonksiyonun icra edilmesiyle oluşan geri dönüş değeri sonuc değişkenine atanacaktır.

    Fonksiyonların geri dönüş değerleri nesne değildir yani sol taraf değeri (L value) değildir. Yani C dilinde aşağıdaki gibi bir atama her zaman hata verecektir:

    hesapla() = 5; /* hata (L value required) */

    Fonksiyonların geri dönüş değerleri sağ taraf değeri (R value) dir.

    sonuc = hesapla1() + hesapla2() + x + 10;

    gibi bir ifade geçerlidir. çağırılmış olan hesapla1 ve hesapla2 fonksiyonları icra edilerek üretilen geri dönüş değerleri ile x değişkeni içindeki değer ve 10 sabiti toplanacaktır. İfadeden elde edilen değer sonuç değişkenine atanacaktır.

    Fonksiyonlar ancak tanımlanmış fonskiyonların içerisinden çağırılabilirler. Blokların dışından fonksiyon çağırılamaz.

    Çağıran fonksiyon ile çağırılan fonksiyonun her ikisi de aynı amaç kod içerisinde bulunmak zorunda değildir. Çağıran fonksiyon ile çağırılan fonksiyon farklı amaç kodlar içerisinde de bulunabilir. Çünkü derleme işlemi sırasında bir fonksiyonun çağırıldığını gören derleyici, amaç kod içerisine (yani .obj içine) çağırılan fonksiyonun adını ve çağırılış biçimini yazmaktadır. Çağıran fonksiyon ile çağırılan fonksiyon arasında bağlantı kurma işlemi, bağlama aşamasında, bağlayıcı program (linker) tarafından yapılır.

    Bu nedenle tanımlanan bir fonksiyon içerisinde, var olmayan bir fonksiyon çağırılsa bile derleme aşamasında bir hata oluşmaz. Hata bağlama aşamasında oluşur. Çünkü bağlayıcı çağırılan fonksiyonu bulamayacaktır.

    Bütün C programları çalışmaya main fonksiyonundan başlar. Programın başladığı nokta olma dışında main fonksiyonunun diğer fonksiyonlardan başka hiçbir farkı yoktur. main fonksiyonun icrası bitince program da sonlanır. Bir C programının çalışabilmesi için mutlaka bir main fonksiyonuna sahip olması gerekir. Eğer main fonksiyonu yoksa hata bağlama (linking) aşamasında bağlayıcı program tarafından bildirilecektir.

    Standart C Fonksiyonları

    Standard C fonksiyonları, C dilinin standarlaştırılmasından sonra, her derleyicide bulunması zorunlu hale getirilmiş fonksiyonlardır. Yani derleyicileri yazanlar mutlaka standard C fonksiyonlarını kendi derleyicilerinde tanımlamak zorundadırlar. Bu durum C dilinin taşınabilirliğini (portability) artıran ana faktörlerden biridir.

    Bir fonksiyonun derleyiciyi yazanlar tarafından tanımlanmış ve derleyici paketine eklenmiş olması, o fonksiyonun standart C fonksiyonu olduğu anlamına gelmez. Derleyiciyi yazanlar programcının işini kolaylaştırmak için çok çeşitli fonksiyonları yazarak derleyici paketlerine eklerler. Ama bu tür fonksiyonların kullanılması durumunda, oluşturulan kaynak kodun başka bir derleyicide derlenebilmesi yönünde bir garanti yoktur, yani artık kaynak kodun taşınabilirliği azalır. Örneğin printf fonksiyonu standart bir C fonksiyonudur. Yani printf fonksiyonu her derleyici paketinde aynı isimle bulunmak zorundadır.

    Standart C fonksiyonları özel kütüphanelerin içerisinde bulunurlar. Başlık dosyaları içinde, yani uzantısı .h biçiminde olan dosyaların içinde standart C fonksiyonlarının prototipleri bulunmaktadır. Fonksiyon prototipleri konusu ileride detaylı olarak incelenecektir.

    Kütüphaneler (libraries) derlenmiş dosyalardan oluşur. DOS'da kütüphane dosyalarının uzantısı .lib, UNIX'de ise .a (archive) biçimindedir. WINDOWS altında uzantısı .dll biçiminde olan dinamik kütüphaneler de bulunmaktadır.

    Derleyicileri yazanlar tarafından kaynak kodu yazılmış standart C fonksiyonları önce derlenerek .obj haline getirilirler ve daha sonra aynı gruptaki diğer fonksiyonların .obj halleriyle birlikte kütüphane dosyalarının içine yerleştirilirler. Standart C fonksiyonları bağlama aşamasında, bağlayıcı (linker) tarafından çalışabilir (.exe) kod içerisine yazılırlar. Entegre çalışan derleyicilerde bağlayıcılar amaç kod içerisinde bulamadıkları fonksiyonları, yerleri önceden belirlenmiş kütüphaneler içinde ararlar. Oysa komut satırlı uyarlamalarında (command line version) bağlayıcıların hangi kütüphanelere bakacağı komut satırında belirtilir.

    Standart fonksiyonlarını kullanmak programların taşınabilirliğini artırdığı gibi proje geliştirme süresini de kısaltır. Bu yüzden iyi bir C programcısının C dilinin standart fonksiyonlarını çok iyi tanıması ve bu fonksiyonları yetkin bir şekilde kullanabilmesi gerekmektedir.
    Fonksiyonların Geri Dönüş Değerlerinin Oluşturulması

    C dilinde fonksiyonların geri dönüş değerleri return anahtar sözcüğü ile oluşturulur. return anahtar sözcüğünün bir başka işlevi de içinde bulunduğu fonksiyonu sonlandırmasıdır.

    #include <stdio.h>

    int sample(void)
    {
    int x = 10; int y = 20;

    return x * y;
    }

    int main()
    {
    int c;

    c = sample();
    printf("c = %d\n", c);

    return 0;
    }

    Yukarıdaki örnekteki sample fonksiyonunda return anahtar sözcüğünün yanında yer alan x * y ifadesi sample fonksiyonunu sonlandırmakta ve sample fonksiyonunun geri dönüş değerini oluşturmaktadır. Fonksiyonun geri dönüş değeri, main fonksiyonu içinde c değişkenine atanmış ve daha sonra standart C fonksiyonu olan printf ile c fonksiyonunun değeri ekrana yazdırılmıştır. fonksiyonun geri dönüş değerini başka bir değişkene atamadan aşağıdaki ifade ile de doğrudan ekrana yazdırabilirdik :

    printf("%d\n", sample());

    Aynı örnekte main fonksiyonu içinde de bir return ifadesinin yer aldığı görülmektedir. main de bir fonksiyondur ve main fonksiyonunun da bir geri dönüş değeri olabilir. main fonksiyonun geri dönüş değeri programın icrası bittikten sonra işletim sistemine bildirilmektedir. main fonksiyonunun başına bir geri dönüş değer türü yazılmazsa derleyiciler main fonksiyonunun geri dönüş değerinin int türden olduğunu varsayarlar. Özellikle yeni derleyiciler, tanımlamalarında bir geri dönüş değeri üretecekleri belirtilen fonksiyonlarının, return anahtar sözcüğüyle geri dönüş değeri üretmemelerini bir uyarı (warning) mesajı ile bildirirler. Borland derleyicilerinde bu uyarı mesajı genellikle "warning : function should return a value..." şeklindedir. Bu uyarı mesajını kesmek için iki yol vardır:

    1. main fonksiyonu da yukarıdaki örnekte olduğu gibi int türden bir geri dönüş değeri üretir. Geleneksel olarak bu değer 0 ise programın problemsiz bir şekilde sonlandırıldığı anlamına gelir.

    2. main fonksiyonunun başına void anahtar sözcüğü yazılarak bu fonksiyonun bir geri dönüş değeri üretmeyeceği derleyiciye bildirilir. Bu durumda derleyici geri dönüş değeri beklemediği için bir uyarı mesajı göndermez.

    return anahtar sözcüğünün kullanılması zorunlu değildir. Bir fonksiyon içinde return anahtar sözcüğü kullanılmamışsa fonksiyonun icrası, fonksiyonun ana bloğunun sonuna gelindiğinde otomatik olarak biter. Tabi bu tür bir fonksiyon anlamlı bir şekilde bir geri dönüş değeri üretemeyecektir. Bir geri dönüş değerine sahip olacak şekilde tanımlanmış fakat return ile geri dönüş değeri oluşturulmamış fonksiyonlar rastgele bir değer döndürürler.

    return anahtar sözcüğünden sonra parantez kullanılabilir ama parantez kullanımı zorunlu değildir. Okunabilirlik açısından özellikle uzun return ifadelerinde parantez kullanımı tavsiye edilmektedir.

    return (a * b - c * d);

    return 5; /* return ifadesinin değişken içermesi bir zorunluluk değildir. Bir fonksiyon sabit bir değerle de geri dönebilir. */

    return sample();

    Bu örnekte return anahtar sözcüğünden sonra bir fonksiyon çağırma ifadesi yer almaktadır. Bu durumda önce çağırılan fonksiyon icra edilir, ve geri dönüş değeri elde edilir, daha sonra elde edilen geri dönüş değeri tanımlanması yapılan fonksiyonun da geri dönüş değeri yapılmaktadır.

    Geri dönüş değeri olmayan fonksiyonlarda return anahtar sözcüğü yanında bir ifade olmaksızın tek başına da kullanılabilir :

    return;

    Bu durumda return içinde yer aldığı fonksiyonu geri dönüş değerini oluşturmadan sonlandırır.

    C dilinde fonksiyonlar yalnızca bir geri dönüş değeri üretebilirler. Bu da fonksiyonların kendilerini çağıran fonksiyonlara ancak bir tane değeri geri gönderebilmeleri anlamına gelmektedir. Ancak, fonksiyonların birden fazla değeri ya da bilgiyi kendilerini çağıran fonksiyonlara iletmeleri gerekiyorsa, C dilinde bunu sağlayacak başka mekanizmalar vardır ve bu mekanizmalar ileride detaylı olarak incelenecektir.

    Fonksiyonların ürettiği geri dönüş değerlerinin kullanılması yönünde bir zorunluluk yoktur. Örneğin fonk() fonksiyonu int türden bir değeri geri dönen bir fonksiyon olsun:

    a = fonk();

    yukarıdaki ifadese fonk fonksiyonunun geri dönüş değeri a değişkenine atanmaktadır. Dolayısıyla biz bu fonksiyonu bir kez çağırmamıza karşın artık geri dönüş değerini a değişkeninede tuttuğumuz için, bu geri dönüş değerine fonksiyonu tekrar çağırmadan istediğimiz zaman ulaşabiliriz. Ancak:

    fonk();

    şeklinde bir fonksiyon çağırma ifadesinde artık geri dönüş değeri bir değişkende saklanmamakatdır. Bu duruma geri dönüş değerinin kullanılmaması denir. (discarded return value).
    Örneğin standart bir C fonksiyonu olan printf fonksiyonun da bir geri dönüş değeri vardır (printf fonksiyonu ekrana bastırılan toplam karakter sayısına geri döner) ama bu geri dönüş değeri nadiren kullanılır.

    Fonksiyon Parametre Değişkenlerinin Tanımlanması

    Bir fonksiyonun parametreleri ya da parametre değişkenleri fonksiyonların kendilerini çağıran fonksiyonlardan aldıkları girdileri tutan değişkenleridir. Bir fonksiyonun parametre sayısı ve bu parametrelerin türleri gibi bilgiler, fonksiyonların tanımlanması sıarsında derleyicilere bildirilirler.

    C dilinde fonksiyonların tanımlanmasında kullanılan 2 temel biçim vardır. Bu biçimler birbirlerinden fonksiyon parametrelerinin derleyicilere tanıtılma şekli ile ayrılırlar. Bu biçimlerden birincisi eski biçim (old style) ikincisi ise yeni biçim (new style) olarak adlandırılır.

    Artık eski biçim hemen hemen hiç kullanılmamaktadır, ama C standartlarına göre halen geçerliliğini korumaktadır. Kullanılması tavsiye edilen kesinlikle yeni biçimdir ancak eski kodların ya da eski kaynak kitapların incelenmesi durumunda bunların anlaşılabilmesi için eski biçimin de öğrenilmesi gerekmektedir.

    Eski Biçim (old style)

    Bu biçimde fonksiyonun parametre değişkenlerinin yalnızca ismi fonksiyon parantezleri içinde yazılır. (Eğer parametre değişkenleri birden fazla ise aralarına virgül koyulur. Daha sonra alt satıra geçilerek bu değişkenlerin bildirimi yapılır. Bu bildirimler daha önce öğrendiğimiz, C dilinin bildirim kurallarına uygun olarak yapılır. Örnek :

    double alan(x, y)
    int x, y;
    {
    return x * y;
    }

    Yukarıda tanımlanan alan fonksiyonunun iki parametre değişkeni vardır ve bu parametre değişkenlerinin isimleri x ve y'dir. Her iki parametre değişkeni de int türdendir.

    int sample (a, b, c)
    int a;
    double b;
    long c;
    {
    ...
    }

    Bu örnekte ise sample fonksiyonu üç parametre almaktadır. Parametre değişkenlerinin isimleri a, b ve c'dir. İsmi a olan parametre değişkeni int türden, b olanı double türden ve ismi c olanı ise long türdendir.

    Eski biçimin dezavantajı gereksiz yere uzun oluşudur. Çünkü fonksiyon parantezelerinin içinde parametre değişkenlerinin isimi yer almakta sonra tekrar bu isimler alt satırlarda yeniden kullanılarak bildirim yapılmaktadır.

    Yeni Biçim (new style)

    Yeni biçim eski biçime göre hem daha kısadır hem de okunabilmesi eski biçime göre çok daha kolaydır.

    Yeni biçimde fonksiyon parametre değişkenlerinin bildirimi fonksiyon parantezelerinin içinde yalnızca bir kez yapılmaktadır. Bu biçimde fonksiyonun parantezlerinin içine parametre değişkenin türü ve yanına da ismi yazılır. Eğer birden fazla fonksiyon parametre değişkeni varsa bunlar virgüllerle ayrılır ancak her defasında tür bilgisi yeniden yazılır . Örnek :

    int sample (int x, int y)
    {
    ...
    }

    int fonk(double x, int y)
    {
    ...
    }

    Bu biçimde dikkat edilmesi gereken önemli nokta, fonksiyon parametre değişkenleri aynı türden olsalar bile her defasında tür bilgisinin tekrar yazılması zorunluluğudur. Örneğin :

    int sample (double x, y) /* error */
    {
    ...
    }

    bildirimi hatalıdır. Doğru tanımlamanın aşağıdaki şekilde olması gerekir:

    int sample (double x, double y)
    {
    ...
    }

    Klavyeden Karakter Alan C Fonskiyonları

    Sistemlerin hemen hemen hepsinde klavyeden karakter alan 3 ayrı C fonksiyonu bulunur. Bu fonksiyonların biri tam olarak standarttır ama diğer ikisi sistemlerin hemen hemen hepsinde bulunmasına karşın tam olarak standart değildir.

    getchar Fonksiyonu

    int getchar(void);

    getchar standart bir C fonksiyonudur. Geri dönüş değeri klavyeden alınan karakterin ASCII tablosundaki sıra numarasını gösteren int türden bir sayıdır. getchar fonskiyonu klavyeden karakter almak için enter tuşuna ihtiyaç duyar.

    Aşağıda yazılan programda önce klavyeden bir karakter alınmış daha sonra alınan karakter ve karakterin sayısal karşılıkları ekrana yazdırılmıştır. (getchar fonksiyonunun geri dönüş değeri klavyede basılan tuşa ilişkin karakterin sistemde kullanılan karakter seti tablosundaki sıra numarasıdır.)

    #include <stdio.h>

    int main()
    {
    char ch;

    ch = getchar();
    printf("\nKarakter olarak ch = %c\nASCII numarası ch = %d\n", ch, ch);
    return 0;
    }

    getchar derleyicilerin çoğunda stdio.h dosyasında bir makro olarak tanımlanmıştır. Makrolar konusunu daha ileride inceleyeceğiz.
    getch Fonksiyonu

    int getch(void);

    getchar fonksiyonu gibi bu fonksiyonda klavyede basılan tuşun kullanılan karakter setindeki sıra numarasıyla geri döner. getchar fonksiyonundan iki farkı vardır.
    1. Basılan tuş ekranda görünmez.
    2. Enter tuşuna ihtiyaç duymaz.

    Yukarıda verilen programda getchar yerine getch yazarak programı çalıştırırsanız farkı daha iyi görebilirsiniz.

    Tam olarak standardize edilmemiştir ama neredeyse bütün sistemlerde bulunur. getch fonksiyonu özellikle tuş bekleme ya da onaylama amacıyla kullanılmaktadır:

    ....
    printf("devam için herhangi bir tuşa basınız...\n");
    getch();

    Burada basılacak tuşun programcı açısından bir önemi olmadığı için fonksiyonun geri dönüş değeri kullanılmamıştır.

    getche Fonksiyonu

    int getche(void);

    getche İngilizce get char echo sözcüklerinden gelmektedir. getche fonksiyonu da basılan tuşun karakter setindeki sıra numarasıyla geri döner ve enter tuşuna gereksinim duymaz. Ama basılan tuşa ilişkin karakter ekranda görünür.


    getchar
    enter tuşuna ihtiyaç duyar
    alınan karakter ekranda görünür.
    getch
    enter tuşuna ihtiyaç duymaz
    alınan karakter ekranda görünmez
    getche
    enter tuşuna ihtiyaç duymaz
    alınan karakter ekranda görünür.

    Ekrana Bir Karakterin Görüntüsünü Yazan C Fonksiyonları

    C dilinde ekrana karakter yazamakta iki fonksiyonun kullanıldığı görülür :


    putchar Fonksiyonu

    int putchar(int ch);

    putchar standart bir C fonksiyonudur. Bütün sistemlerde bulunması zorunludur. Parametresi olan karakteri ekranda imlecin bulunduğu yere yazar. Örneğin:

    #include <stdio.h>

    main()
    {
    char ch;

    ch = getchar();
    putchar (ch);
    return 0;
    }

    Burada putchar fonksiyonunun yaptığı işi printf fonksiyonuna da yaptırabilirdik;

    printf("%c", ch);

    putchar(ch) ile tamamen aynı işleve sahiptir.

    putchar fonksiyonu ile '\n' karakterini yazdırdığımızda printf fonksiyonunda olduğu gibi imleç sonraki satırın başına geçer. putchar fonksiyonu ekrana yazılan karakterin ASCII karşılığı ile geri dönmektedir.

    putchar fonksiyonu derleyicilerin çoğunda stdio.h dosyası içinde bir makro olarak tanımlanmıştır. Makrolar konusunu ileriki derslerde detaylı olarak öğreneceğiz.

    putch Fonksiyonu

    int putch(int ch);

    putch standart bir C fonksiyonu değildir. Dolayısıyla sistemlerin hepsinde bulunmayabilir. Bu fonksiyonun putchar fonksiyonundan tek farkı '\n' karakterinin yazdırılması sırasında ortaya çıkar. putch, '\n" karakterine karşılık yalnızca LF(line feed) (ASCII 10) karakterini yazmaktadır. Bu durum imlecin bulunduğu kolonu değiştirmeksizin aşağı satıra geçmesine yol açar.

    printf Fonksiyonu

    Değişkenlerin içerisindeki değerler aslında bellekte ikilik sistemde tutulmaktadır. Bir değişkenin içerisindeki değerin ekrana, kaçlık sistemde ve nasıl yazdırılacağı programcının isteğine bağlıdır. Değişkenlerin içerisindeki değerlerin ekrana yazdırılmasında printf fonksiyonu kullanılır. printf standart bir C fonksiyonudur.

    printf aslında çok ayrıntılı özelliklere sahip bir fonksiyondur. Burada yalnızca temel özellikleri görsel bir biçimde açıklanacaktır. printf iki tırnak içerisindeki karakterleri ekrana yazar. Ancak iki tırnak içinde gördüğü % karakterlerini ekrana yazmaz. printf fonksiyonu % karakterlerini yanındaki karakter ile birlikte format karakteri olarak yorumlar. Format karakterleri iki tırnaktan sonra yazılan parametrelerle birebir eşleştirilir. Örnek:

    int x, y;

    x = 125;
    y = 200;
    printf("x = %d\ny = %d\n", x, y);



    printf fonksiyonunun yukarıdaki şekilde çağırılmasıyla x ve y değişkeni içindeki değerler ekrana onluk sistemde yazdırılacaktır.

    Format karakterleri yerine eşlenen değişkenlerin içerisindeki değerler ekrana yazılır. Format karakterleri sayıların ekrana nasıl yazılacağını belirtmekte kullanılır.

    format karakteri
    Anlamı


    %d
    int türünü desimal sistemde yazar.
    %ld
    long türünü desimal sistemde yazar
    %x
    unsigned int türünü hexadecimal sistemde yazar.
    %X
    unsigned int türünü hexadecimal sistemde yazar.(semboller büyük harfle)
    %lx
    unsigned long türünü hexadecimal sistemde yazar.
    %u
    unsigned int türünü decimal sistemde yazar.
    %o
    unsigned int türünü oktal sistemde yazar.
    %f
    float ve double türlerini desimal sistemde yazar.
    %lf
    double türünü desimal sistemde yazar.
    %e
    gerçek sayıları üstel biçimde yazar.
    %c
    char veya int türünü karakter görüntüsü olarak yazdırır.
    %s
    string olarak yazdırır.
    %lf
    long double türünü desimal sistemde yazdırır.

    Yukarıdaki tabloda görüldüğü gibi double türü hem %f format karakteri hem de %lf format karakteri ile yazdırılabilmektedir. Ama %lf (okunabiliriliği artırdığı için) daha çok tercih edilmektedir.

    Yukarıdaki tabloya göre unsigned int türünden bir sayıyı aşağıdaki şekillerde yazdırabiliriz :

    unsigned int u;

    printf("%u", u); /* u sayısını 10'luk sistemde yazar */
    printf("%o, u); /* u sayısını 8'lik sistemde yazar */
    printf("%x, u); /* u sayısını 16'lık sistemde yazar */


    short bir sayıyı yazarken d o u ya da x karakterlerinden önce h karakterini kullanıyoruz :

    short int sh;

    printf("%hd", sh); /* 10'luk sistemde yazar */

    unsigned short int unsh;

    printf("%hu", unsh); /* 10'luk sistemde yazar */
    printf("%ho", unsh); /* 8'lik sistemde yazar */
    printf("%hx", unsh); /* 16'lık sistemde yazar */

    long bir sayıyı yazarken d o u ya da x karakterlerinden önce l karakterini kullanıyoruz :

    long int lo;

    printf("%ld", lo); /* 10'luk sistemde yazar */

    unsigned long int unlo;

    printf("%lu", unlo); /* 10'luk sistemde yazar */
    printf("%lo", unlo); /* 8'lik sistemde yazar */
    printf("%lx", unlo); /* 16'lık sistemde yazar */

    Yukarıdaki bilgilerde unsigned bir tamsayıyı printf fonksiyonuyla 8'lik ya da 16'lık sistemde yazdırabileceğimizi gördük. Peki signed bir tamsayıyı 8'lik ya da 16'lık sistemde yazdıramaz mıyız? Yazdırırsak ne olur? Sözkonusu signed tamsayı pozitif olduğu sürece bir sorun olmaz. Sayının işaret biri 0 olduğu için sayının nicel büyüklüğünü etkilemez. Yani doğru sayı ekrana yazar, ama sayı negatifse işaret biti 1 demektir. Bu durumda ekrana yazılacak sayının işaret biti de nicel büyüklüğün bir parçası olarak değerlendirilerek yazılır. Yani yazılan değer doğru olmayacaktır.

    % karakterinin yanında önceden belirlenmiş bir format karakteri yoksa , % karakterinin yanındaki karakter ekrana yazılır.

    %% (%) karakterini yaz anlamına gelir.

    scanf Fonksiyonu
    scanf fonksiyonu klavyeden her türlü bilginin girişine olanak tanıyan standart bir C fonksiyonudur. scanf fonksiyonu da printf fonksiyonu gibi aslında çok detaylı, geniş kullanım özellikleri olan bir fonksiyondur. Ancak biz bu noktada scanf fonksiyonunu yüzeysel bir şekilde tanıyacağız.

    scanf fonksiyonunun da birinci parametresi bir stringdir. Ancak bu string klavyeden alınacak bilgilere ilişkin format karakterlerini içerir. Bu format karakterleri önceden belirlenmiştir ve % karakterinin yanında yer alırlar. scanf fonksiyonunun kullandığı format karakterlerinin printf fonksiyonunda kullanılanlar ile aynı olduğunu söyleyebiliriz. Yalnızca gerçek sayılara ilişkin format karakterlerinde önemli bir farklılık vardır. printf fonksiyonu %f formatı ile hem float hem de double türden verileri ekrana yazabilirken scanf fonksiyonu %f format karakterini yalnızca float türden veriler için kullanır. double tür için scanf fonksiyonunun kullandığı format karakterleri %lf şeklindedir. scanf fonksiyonunun format kısmında format karakterlerinden başka bir şey olmamalıdır. printf fonksiyonu çift tırnak içindeki format karakterleri dışındaki karakterleri ekrana yazıyordu, ancak scanf fonksiyonu format karakterleri dışında string içine yazılan karakterleri ekrana basmaz, bu karakterler tamamen başka anlama gelecektir. Bu nedenle fonksiyonun nasıl çalıştığını öğrenmeden bu bölgeye format karakterlerinden başka bir şey koymayınız. Buraya konulacak bir boşluk bile farklı anlama gelmektedir.

    int x, y;

    scanf(“%d%d”, &x, &y);

    Yukarıdaki örnekte x ve y sayıları için desimal sistemde klavyeden giriş yapılmaktadır. Giriş arasına istenildiği kadar boşluk karakteri konulabilir. Yani ilk sayıyı girdikten sonra ikinci sayıyı SPACE, TAB ya da ENTER tuşuna bastıktan sonra girebilirsiniz. Örneğin:

    5 60

    biçiminde bir giriş geçerli olacağı gibi;

    5
    60

    biçiminde bir giriş de geçerlidir. scanf fonksiyonuna gönderilecek diğer argumanlar & operatörü ile kullanılmaktadır. & bir gösterici operatörüdür. Bu operatörü göstericiler konusunda öğreneceğiz.

    scanf fonksiyonunun yalnızca giriş için kullanılır, ekrana yazmak için printf fonksiyonunu kullanmamız gerekir :

    int number;

    printf(“bir sayi giriniz : “);
    scanf(“%d”, &number);

    C++ dilinde bir fonksiyon tanımlanmasında fonksiyonun geri dönüş değerinin türü olarak void anahtar sözcüğü yazılmamışsa, fonksiyon return anahtar sözcüğü kullanılarak mutlaka bir geri dönüş değeri üretmelidir. Fonksiyonun geri dönüş değeri üretmemesi durumunda derleme zamanında hata oluşacaktır. Yani C dilinde olduğu gibi rasgele bir değer üretilmesi söz konusu değildir. Yine C++ dilinde geri dönüş değeri üretecek bir fonksiyonun tanımlanması içinde return anahtar sözcüğü yalın olarak kullanılamaz. return anahtar sözcüğünün yanında mutlaka bir ifade yer almalıdır.



    2. NESNELERİN FAALİYET ALANLARI VE ÖMÜRLERİ

    Daha önce C dilinde nesnelerin 3 ayrı özelliğini görmüştük. Bunlar nesnelerin isimleri, değerleri ve türleriydi. Nesnelerin C dili açısından çok önem taşıyan iki özellikleri daha söz konusudur. Bunlar tanınabilirlik alanları (scope) ve ömürleridir (storage duration). Bu dersimizde bu iki kavramı detaylı olarak inceleyeceğiz.

    Tanınabilirlik Alanı (scope / visibility)
    Tanınabilirlik alanı bir nesnenin ömrünü sürdürdüğü ve tanınabildiği program aralığıdır. Burada program aralığı demekle kaynak kodu kastediyoruz. Dolayısıyla tanınabilirlik alanı doğrudan kaynak kod ile ilgili bir kavramdır, dolayısıyla derleme zamanına ilişkindir. C dilinde derleyici, bildirimleri yapılan değişkenlere kaynak kodun ancak belirli bölümlerinde ulaşılabilir. Yani bir değişkeni tanımlıyor olmamız o değişkene kodun istediğimiz bir yerinde ulaşabilmemizi sağlamaz. Tanınabilirlik alanlarını 2 ayrı grupta toplayabiliriz :

    Blok tanınabilirlik alanı. (Block scope): Bir değişkenin tanımlandıktan sonra, derleyici tarafından, yalnızca belirli bir blok içinde tanınabilmesidir.

    Dosya tanınabilirlik alanı (File scope) : Bir değişkenin tanımlandıktan sonra tüm kaynak dosya içinde, yani tanımlanan tüm fonksiyonların hepsinin içerisinde tanınabilmesidir.

    Değişkenleri de tanınabilirlik alanlarına göre ikiye ayırabiliriz :

    Yerel değişkenler (local variables)
    Global değişkenler (global variables)

    C dili için çok önemli olan bu değişken tiplerini şimdi detaylı olarak inceleyeceğiz :

    Yerel Değişkenler (local variables)
    Blokların içlerinde tanımlanan değişkenlere yerel değişkenler denir. Hatırlanacağı gibi C dilinde blokların içlerinde tanımlanan değişkenlerin tanımlama işlemleri blok içinde ilk işlem olarak yapılmalıydı. (C++ dilinde böyle bir zorunluluk yoktur) Yerel değişkenler blok içlerinde tanımlanan değişkenlerdir, iç içe blokların söz konusu olması durumunda hangi blok içerisinde tanımlanırlarsa tanımlansınlar bunları yerel değişken olarak adlandıracağız. Yani yerel değişken olmaları için en dış blok içinde tanımlanmaları gerekmiyor.

    Yerel değişkenlerin tanınabilirlik alanı blok tanınabilirlik alanıdır. Yani yerel değişkenlere yalnızca tanımlandıkları blok içinde ulaşılabilir. Tanımlandıkları bloğun daha dışında bir blok içinde bu değişkenlere ulaşamayız. Örnek :

    main ()
    {
    float x;
    ...
    ...
    {
    int y;
    ...
    x değişkeninin tanınabilirlik alanı

    ...
    y değişkeninin tanınabilirlik alanı

    {
    int z;
    z değişkeninin tanınabilirlik alanı

    ...
    ...
    }
    }
    }

    Yukarıdaki örnekte tanımlanan değişkenlerden hepsi yerel değişkenlerdir. Çünkü x y z değişkenleri blokların içlerinde tanımlanmışlardır. Bu değişkenlere yalnızca tanımlanmış oldukları blok içinde ulaşabiliriz. Tanımlandıkları blok dışında bunlara ulaşmaya çalışmak derleme aşamasında error ile neticelenecektir.

    Dikkat etmemiz gereken bir nokta da şudur : Yukarıdaki örnekte bu değişkenlerin hepsi yerel değişkenler oldukları için blok tanınabilirlik alanı kuralına uyarlar, ancak bu tanınabilirlik alanlarının tamamen aynı olmasını gerektirmez. Yukarıdaki şekilden de görüldüğü gibi x değişkeni en geniş tanınabilirlik alanına sahipken y değişkeni daha küçük ve z değişkeni de en küçük tanınabilirlik alanına sahiptir.

    Yukarıdaki örneği genişletelim :

    main ()
    {
    float x = 2.5;

    printf(“x = %f\n”, x); /* LEGAL BU ALANDA x’E ULAŞILABİLİR */
    printf(“y = %d\n”, y); /* ERROR BU ALANDA y’YE ULAŞILAMAZ. */
    printf(“z = %ld\n”, z); /* ERROR BU ALANDA z’YE ULAŞILAMAZ. */
    {
    int y = 1;

    printf(“x = %f\n”, x); /* LEGAL BU ALANDA x’E ULAŞILABİLİR */
    printf(“y = %d\n”, y); /* LEGAL BU ALANDA y’E ULAŞILABİLİR */
    printf(“z = %ld\n”, z); /* ERROR BU ALANDA z’YE ULAŞILAMAZ. */
    ...
    ...

    {
    long z = 5;

    printf(“x = %f\n”, x); /* LEGAL BU ALANDA x’E ULAŞILABİLİR */
    printf(“y = %d\n”, y); /* LEGAL BU ALANDA x’E ULAŞILABİLİR */
    printf(“z = %ld\n”, z); /* LEGAL BU ALANDA x’E ULAŞILABİLİR */
    }
    }
    }

    C dilinde aynı isimli birden fazla değişken tanımlanabilmektedir. Genel kural şudur: iki değişkenin scopları (tanınabilirlik alanları aynı ise) aynı isimi taşıyamazlar, aynı isim altında tanımlanmaları derleme zamanında hata oluşturur. İki değişkenin scoplarının aynı olup olmadıkları nasıl tespit edilecek? Standartlar bu durumu şöyle açıklamaktadır : İki değişkenin scopları aynı kapanan küme parantezinde sonlanıyorsa, bu değişkenlerin scopları aynı demektir.

    {
    float a;
    int b;
    double a; /* error */
    {
    int c;
    ...
    }
    }

    Yukarıdaki program parçasının derlenmesi derleme aşamasında error ile neticelenir. Çünkü her iki a değişkeninin de tanınabilirlik alanı (scopları) aynıdır. (scopları aynı küme paranteziyle sonlanmaktadır.)

    Bu durum error oluşturmasaydı, yukarıdaki örnekte derleyici hangi a değişkeninin yazdırılmak istendiğini nasıl anlayacaktı. Zira bu durum error ile engellenmeseydi printf fonksiyonunun çağırıldığı yerde her iki a değişkeni de tanınabilir olacaktı.

    C dilinde farklı tanınabilirlik alanlarına sahip birden fazla aynı isimli değişken tanımlanabilir. Çünkü derleyiciler için artık bu değişkenlerin aynı isimli olması önemli değildir. Bunlar bellekte farklı yerlerde tutulurlar.

    {
    int x = 100;

    printf(“%d\n”, x);
    {
    int x = 200;

    printf(“%d\n”, x);
    {
    int x = 300;

    printf(“%d\n”, x);
    }
    }
    }

    Yukarıdaki program parçasında bir hata bulunmamaktadır. Çünkü her üç x değişkeninin de tanınabilirlik alanları birbirlerinden farklıdır. Peki yukarıdaki örnekte içerideki bloklarda x ismini kullandığımızda derleyici hangi x değişkenini kast ettiğimizi nasıl anlayacak? Belirli bir kaynak kod noktasında, aynı isimli birden fazla değişkenin faaliyet alanı (scope) içindeysek, değişken ismini kullandığımızda derleyici hangi değişkene ulaşacaktır? Aynı isimli değişkenlere ulaşma konusundaki kural şudur : C dilinde daha dar faaliyet alanına sahip değişken diğer aynı isimli değişkenleri maskeler.

    Konunun daha iyi anlaşılması için bir kaç örnek daha verelim :

    {
    int a;
    char ch;
    long b;
    double a, f; /* hata aynı tanınabilirlik alanında ve aynı blok seviyesinde aynı isimli iki değişken tanımlanmış */
    }


    {
    int var1;
    char var2;
    {
    int var1;
    char var2;
    }
    }

    Yukarıdaki kodda herhangi bir hata bulunmamaktadır. var1 ve var2 değişkenlerinin ismi ikinci kez içteki blokta tanımlama işleminde kullanılmıştır ama artık bu tanınabilirlik alanında farklılık yaratan ayrı bir bloktur, dolayısıyla bir hata söz konusu değildir.




    void sample1(void)
    {
    int k;
    ...
    }

    void sample2(void)
    {
    int k;
    ...
    }

    void sample3(void)
    {
    int k;
    ...
    }

    Yukarıdaki kodda da bir hata söz konusu değildir. Zira her üç fonksiyonunda da k isimli bir değişken tanımlanmış olsa da bunların tanınabilirlik alanları tamamen birbirinden farklıdır.
    Global Değişkenler (global variables)

    C dilinde tüm blokların dışında da değişkenlerin tanımlanabileceğini söylemiştik. İşte bütün blokların dışında tanımlanan değişkenler global değişkenler olarak isimlendirilirler.

    Bütün blokların dışı kavramını daha iyi anlamak için bir örnek verelim :

    #include <stdio.h>

    /* bu bölge tüm blokların dışı burada global bir değişken tanımlanabilir. */

    int sample1()
    {
    ...
    }
    /* bu bölge tüm blokların dışı burada global bir değişken tanımlanabilir. */

    int sample2()
    {
    ...
    }
    /* bu bölge tüm blokların dışı burada global bir değişken tanımlanabilir. */

    int sample 3()
    {
    ...
    }
    /* bu bölge tüm blokların dışı burada global bir değişken tanımlanabilir. */

    main()
    {
    ...
    }
    /* bu bölge tüm blokların dışı burada global bir değişken tanımlanabilir. */

    Yorum satırlarının bulunduğu yerler global değişkenlerin tanımlanabileceği yerleri göstermektedir. Bu bölgeler hiçbir fonksiyon içinde değidir. Global değişkenler dosya tanınabilirlik alanı kuralına uyarlar. Yani global değişkenler programın her yerinde ve bütün fonksiyonların içinde tanınabilirler. Burada bir noktayı gözden kaçırmamak gerekir, bir değişken yerel de olsa global de olsa tanımlaması yapılmadan önce bu değişkene ulaşılamaz. Derleme işleminin bir yönü vardır ve bu yön kaynak kod içerisinde yukarıdan aşağıya doğrudur. Bunu şu şekilde de ifade edebiliriz : Global değişkenler tanımlandıkları noktadan sonra kaynak kod içerisinde her yerde tanınabilirler.

    #include <stdio.h>

    int y; /* y bir global değişken tanımlandıktan sonra her yerde tanınabilir */

    void sample(void)
    {
    y = 10; /*başka bir fonksiyondan da y global değişkenine ulaşılabilir */
    }

    void main()
    {
    y = 20;

    printf(“y = %d\n”, y); /* y = 20 */
    sample();
    printf(“y = %d\n”, y); /* y = 10 */
    }

    Yukarıdaki örnekte y değişkeni tüm blokların dışında tanımlandığı için (ya da hiçbir fonksiyonun içinde tanımlanmadığı için) global değişkendir. y değişkeninin tanınabilirlik alanı dosya tanınabilirlik alanıdır. yani y değişkeni tanımlandıktan sonra tüm fonksiyonların içinde tanınabilir. Yukarıdaki programın çalışması main fonksiyonundan başlayacaktır. y global değişkenine önce 20 değeri atanmakta ve daha sonra bu değer printf fonksiyonuyla ekrana yazdırılmaktadır. Daha sonra sample fonksiyonu çağırılmıştır. sample fonksiyonu çağırılınca kodun akışı sample fonksiyonuna geçer. sample fonksiyonu içinde de y global değişkeni tanınabilir. sample fonksiyonunda global y değişkenine 10 değeri atanmakta ve daha sonra bu değer yine printf fonksiyonuyla ekrana yazdırılmaktadır.

    Peki bir global değişkenle aynı isimli yerel bir değişken olabilir mi? Kesinlikle olabilir! İki değişkenin tanınabilirlik alanları aynı olmadığı için bu durum bir hataya neden olmaz. Aynı isimli hem global hem de yerel bir değişkene ulaşılabilecek bir noktada, ulaşılan yerel değişken olacaktır, çünkü daha önce de söylediğimiz gibi aynı tanınabilirlik alanında birden fazla aynı isimli değişken olması durumunda o alan içinde en dar tanınabilirlik alanına sahip olanına erişilebilir. Aşağıdaki kodu çok dikkatli inceleyelim :

    int g= 20; /* g global bir değişken */

    void sample(void)
    {
    g = 100; /* global g değişkenine atama yapılıyor. */
    printf(“global g = %d\n”, g); /* global g yazdırılıyor. */
    }

    int main()
    {
    int g; /* g yerel değişken */

    g = 200; /* yerel olan g değişkenine atama yapılıyor */
    printf(“yerel g = %d\n”, g); /* yerel g yazdırılıyor. */
    sample();
    printf(“yerel g = %d\n”, g); /* yerel g yazdırılıyor. */
    return 0;
    }

    Fonksiyonların kendileri de bütün blokların başlarında tanımlandıklarına göre global nesnelerdir. Gerçekten de fonksiyonlar kaynak kodun heryerinden çağırılabilirler. Aynı tanınabilirlik alanına ilişkin aynı isimli birden fazla değişken olamayacaağına göre , aynı isme sahip birden fazla fonksiyon da olamaz. (C++ dilinde aynı isimli fakat farklı parametrik yapıya sahip fonksiyonlar tanımlamak mümkündür.)

    Programcıların çoğu global değişkenleri mümkün olduğu kadar az kullanmak ister. Çünkü global değişkenleri kullanan fonksiyonlar başka projelerde kullanılamazlar. Kullanıldıkları projelerde de aynı global değişkenlerin tanımlanmış olması gerekecektir. Dolayısıyla global değişkenlere dayanılarak yazılan fonksiyonların yeniden kullanılabilirliği azalmaktadır.
    Parametre Değişkenleri (formal parameters)

    Parametre değişkenleri, fonksiyon parametreleri olarak kullanılan değişkenlerdir. Parametre değişkenleri de blok tanınabilirlik alanı kuralına uyarlar. Yani parametresi oldukları fonksiyonun her yerinde tanınabilirler. Fonksiyon parametre değişkeninin scope'u fonksiyonun ana bloğunun kapanmasıyla sonlanacaktır. Yani fonksiyon parametre değişkeninin tanınabilirlik alanı fonksiyonun ana bloğudur.

    function(int a, double b)
    {

    / *a ve b bu fonksiyonun heryerinde tanınır. */

    }

    Başka bir örnek :

    sample (int a, int b)
    {
    int a; /* hata aynı tanınırlık alanı içinde ve aynı seviyede aynı isimli değişken */
    ...
    {
    int b; /* hata değil tanınabilirlik alanları farklı */
    ...
    }
    }

    Bu örnekte fonksiyonun ana bloğunun başında tanımlanmış olan a, “aynı tanınabilirlik alanında aynı blok seviyesinde aynı isimli birden fazla değişken olamaz “ kuralına göre geçersizdir. Biri parametre değişkeni biri yerel değişken olmasına karşın, her iki a değişkeni aynı tanınabilirlik alanına sahiptir.

    Nesnelerin Ömürleri (storage duration / lifespan)

    Ömür, nesnelerin faaliyet gösterdiği zaman aralığını anlatmak için kullanılan bir kavramdır. Bir kaynak kod içinde tanımlanmış nesnelerin hepsi program çalışmaya başladığında aynı zamanda yaratılmazlar. Ömürleri bakımından nesneleri iki gruba ayırabiliriz :

    1. Statik ömürlü nesneler (static objects)
    2. Dinamik ömürlü nesneler (dynamic / automatic objects)
    Statik Ömürlü Nesneler (static duration – static storage class)

    Statik ömürlü nesneler, programın çalışmaya başlamasıyla yaratılırlar, programın çalışması bitene kadar varlıklarını sürdürürler, yani bellekte yer kaplarlar. Statik nesneler genellikle amaç kod (.obj) içerisine yazılırlar.
    C dilinde statik ömürlü 3 nesne grubu vardır :


  4. #4
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb


    global değişkenler
    stringler(iki tırnak içerisndeki ifadeler)
    statik yerel değişkenler

    stringler ve statik yerel değişkenleri daha sonra inceleyeceğiz.

    statik ömürlü nesneler olan global değişkenlerin ömürleri konusunda şunları söyleyebiliriz : Global değişkenler programın çalışması süresince yaşayan, yani programın çalışması süresince bellekte yer işgal eden değişkenlerdir.

    Dinamik Ömürlü Nesneler

    Dinamik ömürlü nesneler programın çalışmasının belli bir zamanında yaratılan ve belli süre faaliyet gösterdikten sonra yok olan (ömürlerini tamamlayan) nesnelerdir. Bu tür nesnelerin ömürleri programın toplam çalışma süresinden kısadır.

    C dilinde dinamik ömürlü üç nense grubu vardır :

    yerel değişkenler
    parametre değişkenleri
    dinamik bellek fonksiyonları ile tahsisat yapılarak yaratılmış nesneler

    dinamik bellek fonskiyonları ile yaratılmış nesneleri daha sonra inceleyeceğiz.

    Yerel değişkenler ve parametre değişkenleri dinamik ömürlü nesnelerdir. Tanımlandıkları bloğun çalışması başladığında yaratılırlar, bloğun çalışması bitince yok olurlar (ömürleri sona erer.) Tanınabilirlik alanları kendi bloklarının uzunluğu kadar olan yerel değişkenlerin ömürleri, program akışışının bu bloğa gelmesiyle başlar ve bloğun çalışma süresi bitince de sona erer . Örnek :

    {
    double x; /* programın akışı bu noktaya geldiğinde bellekte x için bir yer ayrılıyor. ve blok içinde a yaşamaya devam ediyor.*/
    ...
    } /* programın akışı bloğun sonuna geldiğinde a değişkeninin de
    ömrü sona eriyor */


    Fonksiyonların parametre değişkenleri de benzer biçimde fonksiyon çağırıldığında yaratılırlar, fonksiyon icrası boyunca yaşarlar, fonksiyonun icrası bitince yok olurlar.

    Statik değişkenlerle dinamik değişkenler arasında ilk değer verme(initialization) açısından da fark bulunmaktadır. Statik olan global değişkenlere de yerel değişkenlerde olduğu gibi ilk değer verilebilir.

    İlk değer verilmemiş ya da bir atama yapılmamış bir yerel değişkenin içinde rasgele bir değer bulunur. Bu değer o an bellekte o değişken için ayrılmış yerde bulunan rasgele bir sayıdır.(garbage value). Oysa ilk değer verilmemiş, ya da bir atama yapılmamış global değişkenler içinde her zaman 0 değeri vardır. Yani bu değişkenler 0 değeriyle başlatılırlar.

    Aşağıdaki kısa programı derleyerek çalıştırınız :

    #include <stdio.h>

    int globx;

    int main()
    {
    int localy;

    printf("globx = %d\n", globx);
    printf("localy = %d\n", localy);

    return 0;
    }

    Yerel değişkenler ile global değişkenler arasındaki başka bir fark da, global değişkenlere ancak sabit ifadeleriyle ilk değer verilebilmesidir. Global değişkenlere ilk değer verme işleminde kullanılan ifadede (initializer), değişkenler ya da fonksiyon çağırma ifadeleri kullanılamazlar, ifade yalnızca sabitlerden oluşmak zorundadır. (C++ dilinde böyle bir zorunluluk bulunmamaktadır.)
    Ancak yerel değişkenlere ilk değer verilme işleminde böyle bir kıstlama bulunmamaktadır. Örnek :

    #include <stdio.h>

    int x = 5; /* legal */
    int y = x + 5; /* error! ilk değer verme işleminde sabit ifadesi kullanılmamış */
    int z = funk(); /* error! ilk değer verme işleminde funksiyon çağırma ifadesi var */

    int main()
    {
    int a = b;
    int k = b - 2; /* legal, k yerel değişken olduğu için ilk değer verme ifadesi değişken içerebilir */
    int l = funk() /* legal, k yerel değişken olduğu için ilk değer verme ifadesinde fonksiyon çağırma i fadesi bulunabilir. */
    ...
    }

    int funk()
    {
    ...
    }

    Argumanların Parametre Değişkenlerine Kopyalanması

    C dilinde bir fonksiyonun parametre değişkenlerinin tanımlandıkları fonksiyon içinde her yerde tanınabildiklerini söylemiştik. Peki bir fonksiyonun parametre değişkenleri ne işe yararlar?
    Bir fonksiyonun parametre değişkenleri, o fonksiyonun çağırılma ifadesiyle kendisine gönderilen argumanları tutacak olan yerel değişkenlerdir. Örnek :

    fonk(int a)
    {
    .....
    }

    main()
    {
    int x;
    ....
    fonk (x);
    }

    Yukarıdaki örnekte fonk fonksiyonu x argumanı ile çağırıldığında, programın akışı fonk fonksiyonunun kodunun bulunduğu yere sıçrar, fonk fonksiyonundaki a isimli parametre değişkeni için bellekte bir yer ayrılır ve a parametre değişkenine x değişkeninin değeri atanır. Yani

    a = x;

    işleminin otomatik olarak yapıldığını söyleyebiliriz.

    Başka bir örnek verelim :

    #include <stdio.h>

    main()
    {
    int x = 100, y = 200, z;

    z = add(x, y);
    printf("%d\n", z);
    return 0;
    }

    int add(int a, int b)
    {
    return a + b;
    }

    add fonksiyonu çağırıldığında programın akışı bu fonksiyona geçmeden önce, x ve y değişkenlerinin içinde bulunan değerler add fonksiyonunun parametre değişkenleri olan a ve b'ye kopyalanırlar.


    2. OPERATÖRLER

    Operatörler nesneler veya sabitler üzerinde önceden tanımlanmış birtakım işlemleri yapan atomlardır. Operatörler mikroişlemcinin bir işlem yapmasına neden olurlar ve bu işlem sonunda da bir değer üretilmesini sağlarlar. Programlama dillerinde tanımlanmış olan her bir operatör en az bir makine komutuna karşılık gelmektedir.

    Benzer işlemleri yapmalarına karşılık programlama dillerinde operatör atomları birbirlerinden farklılık gösterebilir.

    C programlama dilinde her ifade en az bir operatör içerir. (Sabitler, nesneler ve operatörlerin kombinezonlarına ifade denir.)

    c = a * b / 2 + 3 /* 4 operatör vardır ifadedeki sırasıyla =, *, /, + */
    ++x * y-- /* 3 operaör vardır, ifadedeki sırasıyla ++, *, -- */
    a >= b /* 1 operatör vardır. >= */

    Her programlama dilinde operatörlerin birbirlerine göre önceliği söz konusudur. (Eğer öncelik kavramı söz konusu olmasaydı, operatörlerin neden olacağı işlemlerin sonuçları makinadan makinaya, derleyiciden derleyiciye farklı olurdu.)

    C' de toplam 45 operatör vardır, ve bu operatörler 15 ayrı öncelik seviyesinde yer alır. (bakınız operatör öncelik tablosu) . Bir öncelik seviyesinde eğer birden fazla operatör varsa bu operatörlerin soldan sağa mı sağdan sola mı öncelikle dikkate alınacağı da tanımlanmalıdır. Buna öncelik yönü diyebiliriz. (associativity)

    Bir sembol, birden fazla operatör olarak kullanılılabilmektedir. Örneğin * operatörü hem çarpma hem de içerik alma operatörü (göstericilerle ilgili bir operatör) olarak kullanılır. Yine bir C dilinde &(ampersand) hem bitsel ve hem de göstericilere ilişkin adres operatörü olarak kullanılmaktadır. Operatör öncelik listesinde gördüğümüz operatörlerin çoğunu bu ders tanıyacağız ama bir kısmı ileriki derslerimizde yer alacak. (örneğin gösterici operatörleri)

    Operatör Terminolojisi

    Hem operatörler konusunun daha iyi anlaşılması hem de ortak bir dil oluşturmak amacıyla operatörler hakkında kullanabileceğimiz terimleri inceleyelim.

    Operand
    Operatörlerin değer üzerinde işlem yaptıkları nesne veya sabitlere denir. C'de operatörleri aldıkları operand sayısına göre 3 gruba ayırabiliriz.

    1. Tek Operand Alan Operatörler (unary operators)
    Örneğin ++ ve -- operatörleri unary operatörlerdir. (C dilinde unary operatörler operatöröncelik tablosunun 2. seviyesinde bulunurlar.)

    2. İki Operand Alan Operatörler. (binary operators)
    Aritmetik işlem operatörü olan + ve / operatörlerini örnek olarak verebiliriz.

    3. Üç Operand Alan Operatör (ternary operator)
    Çoğul kullanmıyoruz çünkü C'de 3 opererand olan tek bir operatör vardır. (koşul operatörü - conditional operator)


    Operatörleri operandının ya operandlarının neresinde bulunduklarına göre de gruplayabiliriz:

    1. Sonek Konumundaki Operatörler (postfix operators)
    Bu tip operatörler operandlarının arkasına getirilirler. Örneğin sonek ++ operatörü (x++)

    2. Önek Konumundaki Operatörler (prefix operators)
    Bu tip operatörler operandlarının önüne getirilirler.
    Örneğin önek ++ operatörü (++x)

    C Dilinin Operatör Öncelik Tablosu

    Seviye
    Operatör
    Tanım

    Öncelik Yönü

    (associativity)

    1
    ( )
    öncelik kazandırma ve fonksiyon çağırma
    soldan sağa

    [ ]
    index operatörü (subscript)


    .
    yapı elemanına ulaşım (structure access)


    ->
    yapı elemanına gösterici ile ulaşım

    2
    +
    işaret operatörü (unary)
    sağdan sola

    -
    işaret operatörü (unary)


    ++
    1 artırma (increment)


    --
    1 eksiltme (decrement)


    ~
    bitsel değil (bitwise not)


    !
    mantıksal değil (logical not)


    *
    içerik operatörü (indirection)


    &
    adres operatörü (address of)


    sizeof
    sizeof operatörü


    (tür)
    tür dönüştürme (type cast operator)

    3
    *
    çarpma (multiplication)
    soldan sağa

    /
    bölme (division)


    %
    modulus (bölümden kalan)

    4
    +
    toplama (addition)
    soldan sağa

    -
    çıkarma (suntraction)

    5
    <<
    bitsel sola kaydırma (bitwise shift left)
    soldan sağa

    >>
    bitsel saga kaydırma (bitwise shift right)

    6
    <
    küçüktür (less than)
    soldan sağa

    >
    büyüktür (greater than)


    <=
    küçük eşittir (less than or equal)


    >=
    büyük eşittir (greater than or equal)

    7
    ==
    eşittir (equal)
    soldan sağa

    !=
    eşit değildir (not equal to)

    8
    &
    bitsel VE (bitwise AND)
    soldan sağa
    9
    ^
    bitsel EXOR (bitwise EXOR)
    soldan sağa
    10
    |
    bitsel VEYA (bitwise OR)
    soldan sağa
    11
    &&
    mantıksal VE (logical AND)
    soldan sağa
    12
    ||
    mantıksal VEYA (logical OR)
    soldan sağa
    13
    ?:
    koşul operatörü (conditional operator)
    sağdan sola
    14
    =
    atama (assignement)
    sağdan sola

    +=
    işlemli atama (assignment addition)


    -=
    işlemli atama (assignment subtraction)


    *=
    işlemli atama (assignment multiplication)


    /=
    işlemli atama (assignment division)


    %=
    işlemli atama (assignment modulus)


    <<=
    işlemli atama (assignment shift left)


    >>=
    işlemli atama (assignment shift right)


    &=
    işlemli atama (assignment bitwise AND)


    |=
    işlemli atama (assignment bitwise OR)


    ^=
    işlemli atama (assignment bitwise EXOR)

    15
    ,
    virgül operatörü (comma)




    3. Araek Konumundaki Operatörler (infix operators)
    Bu tip operatörler operandlarının aralarına getirilirler.
    Örneğin aritmetiksel toplama operatörü (x + y)

    Son olarak operatörleri yaptıkları işlere göre sınıflayalım :

    1. Aritmetik operatörler (arithmetic operators)
    2. İlişkisel operatörler (relational operators)
    3. Mantıksal operatörler (logical operators)
    4. Gösterici operatörleri (pointer operators)
    5. Bitsel işlem yapan operatörler (bitwise operators)
    6. Özel amaçlı operatörler (special purpose operators)

    İlk 3 grup programlama dillerinin hepsinde vardır. Bitsel işlem yapan operatörler ve gösterici operatörleri yüksek seviyeli programla dillerinde genellikle bulunmazlar. Programlama dillerinden çoğu, kendi uygulama alanlarında kolaylık sağlayacak birtakım özel amaçlı operatörlere de sahip olabilir.

    Operatörlerin Değer Üretmeleri
    Operatörlerin en önemli özellikleri bir değer üretmeleridir. Operatörlerin ana işlevi bir değer üretmeleridir. Programcı, bir ifade içinde operatörlerin ürettiği değeri kullanır ya da kullanmaz. (discard edebiliriz.) Operatörlerin ürettiği değeri nasıl kullanabiliriz:

    Üretilen değeri başka bir değişkene aktararak kullanabiliriz.

    x = y + z;

    Yukarıdaki örnekte y + z ifadesinin değer, yani + operatörünün ürettiği değer x değişkenine aktarılmaktadır.

    Üretilen değeri başka bir fonksiyona arguman olarak gönderebiliriz.

    func(y + z);

    Yukarıdaki örnekte func fonksiyonuna arguman olarak y + z ifadesinin değeri gönderilmektedir. (Yani + operatörünün ürettiği değer)

    Üretilen değeri return anahtar sözcüğü ile fonksiyonların geri dönüş değerlerinin belirlenmesinde kullanabiliriz.

    int sample(void)
    {
    ...
    return (y + z)
    }

    yukarıda sample fonksiyonunun geri dönüş değeri y + z ifadesinin değeridir. (Yani + operatörünün ürettiği değer)

    Operatörlerin elde ettiği değerin hiç kullanılmaması C sentaksı açısından bir hataya neden olmaz. Ancak böyle durumlarda derleyiciler bir uyarı mesajı vererek programcıyı uyarırlar. Örneğin :

    int x = 20;
    int y = 10;
    ...
    x + y;

    Yukarıdaki ifadede + operatörü bir değer üretir. (+ operatörünün ürettiği değer operandlarının toplamı değeri, yani 20 değeridir.) Ancak bu değer hiçbir şekilde kullanılmamıştır. Derleyici böyle bir işlemin büyük ihtimalle yanlışlıkla yapıldığını düşünecek ve bir uyarı mesajı verecektir. Borland derleyicilerinde verilen uyarı mesajı şu şekildedir :

    warning : "code has no effect!"

    Daha önce belirttiğimiz gibi C dilinde ifadelerin türleri ve değerleri söz konusudur. Bir ifadenin değerini derleyici şu şekilde tespit eder : İfade içindeki operatörler öncelik sıralarına göre değer üretirler, ve ürettikleri değerler, ifade içindeki önceliği daha az olan operatörlere operand olarak aktarılır. Bu işlemin sonunda tek bir değer elde edilir ki bu da ifadenin değeridir.

    int x = 10;
    int y = 3;

    printf("%d\n", x % y / 2 + 7 && !x || y);

    Yukarıdaki kod parçasında printf fonksiyonu çağırımıyla x % y / 2 + 7 && !x || y ifadesinin değeri yazdırılmaktadır. Yazdırılacak değer nedir? ifade içindeki operatörler sırayla değer üretecek ve üretilen değerler öncelik sırasına göre, değer üretecek operatörlere operand olacaktır. En son kalan değer ise ifadenin değeri, yani ekrana yazdırılan değer olacaktır.

    Operatörlerin Yan Etkileri (side effect of operators)
    C dilinde operatörlerin ana işlevleri, bir değer üretmeleridir. Ancak bazı operatörler operandı olan nesnelerin değerlerini değiştirirler. Yani bu nesnelerin bellekteki yerlerine yeni bir değer yazılmasına neden olurlar. Bir operatörün kendilerine operand olan nesnenin değerlerini değiştirmesine operatörün yan etkisi diyeceğiz. Yan etki, bellekte yapılan eğer değişikliği olarak tanımlanmaktadır. Örneğin atama operatörünün, ++ ve -- operatörlerinin yan etkileri söz konusudur.

    Operatörler Üzerindeki Kısıtlamalar
    Programlama dilinin tasarımında, bazı operatörlerin kullanılmalarıyla ilgili birtakım kısıtlamalar söz konusu olabilir. Örneğin ++ operatörünün kullanımında, operandın nesne gösteren bir ifade olması gibi bir kısıtlama söz konusudur. Eğer operand olan ifade bir nesne göstermiyorsa, yani sol taraf değeri değilse, derleme zamanında hata oluşacaktır.

    Kısıtlama operatörün operand ya da operandlarının türleriyle de ilgili olabilir. Örneğin % operatörünün operandlarının tamsayı türlerinden birine ait olması gerekmektedir. % operatörünün operandları gerçek sayı türlerinden birine ait olamaz. Operandın gerçek sayı türlerinden birinden olması durumunda derleme zamanında hata oluşacaktır.

    Operatörlerin Detaylı Olarak İncelenmesi
    Aritmetik Operatörler
    Aritmetik operatörler dört işlemlerle ilgili işlem yapan operatörlerdir.

    + ve - Operatörleri. (toplama ve çıkarma operatörleri)
    İki operand alan araek operatörlerdir. (binary infix) Diğer bütün programlama dillerinde oldukları gibi operandlarının toplamını ve farkını almak için kullanırlar. Yani ürettikleri değer, operandlarının toplamı ya da farkı değerleridir. Operandları üzerinde herhangi bir kısıtlama yoktur. Öncelik tablosundan da görüleceği gibi genel öncelik tablosunun 4. sırasında yer alırlar ve öncelik yönleri soldan sağadır (left associative). Yan etkileri yoktur, yani operandlarının bellekte sahip oldukları değerleri değiştirmezler.

    Toplama ve çıkarma operatörleri olan + ve – operatörlerini tek operand alan + ve – operatörleriyle karıştırmamak gerekir.

    İşaret Operatörü Olan – ve + Operatörleri
    Bu operatörler unary prefix (tek operand alan önek konumundaki) operatörlerdir. - operatörü operandı olan ifadenin değerinin ters işaretlisini üretir. Unary prefix – operatörünün ürettiği bir nesne değil bir değerdir. Aşağıdaki ifade matematiksel olarak doğru olmasına karşın C dili açısından hatalıdır, derleme zamanında hata (error) oluşumuna neden olur:

    int x;
    -x = 5;

    x bir nesne olmasına karşı –x ifadesi bir nesne değil, x nesnesinin değerinin ters işaretlisi olan değerdir. Dolayısıyla bir sol taraf değeri değildir ve bu ifadeye bir atama yapılamaz.

    + operatörü yalnızca sentaks açısından C diline ilave edilmiş bir operatördür. Unary prefix bir operand olarak derleyici tarafından ele alınır. Operandı olan ifade üzerinde herhangi bir etkisi olmaz.

    Unary prefix olan – ve + operatörleri operatör öncelik tablosunun 2. seviyesinde bulunurlar ve öncelik yönleri sağdan sola doğrudur. Aşağıdaki ifadeyi ele alalım :

    int x = 5;
    int y = -2;

    -x - -y;

    Yukarıdaki ifadede 3 operatör bulunmaktadır. Sırasıyla unary prefix – operatörü, binary infix çıkarma operatörü ve yine unary prefix - operatörü. İfadenin değerinin hesaplanmasında operatör önceliklerine göre hareket edilecektir. unary prefix olan – operatörü 2. öncelik seviyesinde olduğu için binary infix olan çıkarma operatörüne göre daha yüksek önceliklidir, ve kendi içinde öncelik yönü sağdan soladır. Önce –y ifadesi değer üretir. Üretilen değer 2 olacaktır. daha sonra –x ifadesi değer üretir. Üretilen değer –5 olacaktır. üretilen bu değerler çıkarma operatörüne operand olurlar ve ana ifadenin değeri –5 – 2 yani –7 değeri olarak hesaplanır.

    * ve / Operatörleri
    İki operand alan araek operatörlerdir. (binary infix) * operatörü operandları çarpmak / ise operandları bölmek amacıyla kullanılır. Yani üretilen değer operandlarının çarpım ya da bölüm değerleridir. Operandları herhangi bir türden olabilir. Öncelik tablosunda görülebileceği gibi, genel öncelik tablosunun 3. sırasında bulunurlar. Öncelik yönleri soldan sağadır. Her iki operatörün de bir yan etkisi yoktur.

    / operatörünün kullanımında dikkatli olmak gerekir. Eğer her iki operand da tamsayı türlerindense operatörün bulunduğu ifadenin türü de aynı tamsayı türünden olacaktır. (Bu konuya ileride detaylı değineceğiz.)

    C programlama dilinde * sembolü aynı zamanda bir gösterici operatörü olarak da kullanılır. Ama aynı sembol kullanılmasına karşın bu iki operatör hiçbir zaman birbirine karışmaz çünkü aritmetik çarpma operatörü olarak kullanıldığında binary infix durumundadır, ama gösterici operatörü olarak kullanıldığında unary prefix dir.

    % (modulus) Operatörü
    İki operand alan araek bir operaötördür. (binary infix) Operatörlerin her ikisinin de türü de tamsayı türlerinden (char, short, int, long) biri olmak zorundadır. (Değilse bu durum derleme zamanında error ile neticelenir.) Ürettiği değer sol tarafındaki operandın sağ tarafındaki operanda bölümünden kalanıdır. Operatörün yan etkisi yoktur. Örneğin:

    c = 15 % 4; /* burada c'ye 3 değeri atanacaktır */

    int c = 13 - 3 * 4 + 8 / 3 - 5 % 2;

    Burada c'ye 2 değeri atanacaktır. Çünkü işlem şu şekilde yapılmaktadır:

    c = 13 - (3 * 4) + (8 / 3) - (5 % 2)
    c = 13 - 12 + 2 - 1;
    c = 2;

    Artırma ++ ve Eksiltme -- Operatörleri (increment and decrement operators)
    Artırma (++) ve eksiltme (--) operatörleri C dilinde en çok kullanılan operatörlerdendir. Tek operand alırlar. Önek ya da sonek durumunda bulunabilirler. ++ operatörü operandı olan değişkenin içeriğini 1 artırmak –- operatörü de operandı olan değişkenin içeriğini 1 eksiltmek için kullanılır. Dolayısıyla yan etkileri söz konusudur. Operandları olan nesnenin bellekteki değerini değiştirirler. Bu iki operatör de diğer aritmetik operatörlerden daha yüksek önceliğe sahiptir. Operatör öncelik tablosunun 2. seviyesinde bulunurlar ve öncelik yönleri sağdan soladır. (right associative)

    Yalın olarak kullanıldıklarında (başka hiçbir operatör olmaksızın) önek ya da sonek durumları arasında hiçbir fark yoktur. ++ bir artır ve –- bir eksilt anlamına gelir.

    ++c ve c++ ifadeleri tamamen birbirine denk olup c = c + 1 anlamına gelirler.
    --c ve c— ifadeleri tamamen birbirine denk olup c = c – 1 anlamına gelirler.

    Diğer operatörlerle birlikte kullanıldıklarında (örneğin atama operatörleriyle) önek ve sonek biçimleri arasında farklılık vardır :

    Önek durumunda kullanıldığında operatörün ürettiği değer artırma/eksiltme yapıldıktan sonraki değerdir. Yani operandın artırılmış ya da azaltılmış değeridir. Sonek durumunda ise operatörün ürettiği değer artırma / eksiltme yapılmadan önceki değerdir. Yani operandı olan nesnenin artırılmamış ya da azaltılmamış değeridir. Operandın değeri ifadenin tümü değerlendirildikten sonra artırılacak ya da eksiltilecektir.

    x = 10;
    y = ++x;

    Bu durumda:

    ++x &THORN; 11
    ve y = 11 değeri atanır..

    x = 10;
    y = x++;

    y = 10;
    ++x &THORN; 11

    Aşağıdaki programı inceleyelim:

    int main()
    {
    int a, b;
    a = 10;
    b = ++a;
    printf(“a = %d b = %d\n”, a, b);
    a = 10;
    b = a++;
    printf(“a = %d b = %d\n”, a, b);

    return 0;
    }

    Yukarıdaki birinci printf ifadesi ekrana 11 11 yazdırırken ikinci printf ifadesi 11 10 yazdırır.

    Bir örnek :

    x = 10;
    y = 5;
    z = x++ % 4 * --y;

    --y &THORN;4
    10 % 4 &THORN; 2
    2 * 4 &THORN; 8
    z = 8
    x++ &THORN;11

    Bir örnek daha:

    c = 20;
    d = 10;
    a = b = d + c--;

    İşlem sırası:

    10 + 20 &THORN; 30
    b = 30
    a = 30
    c-- &THORN; 19

    İfadenin sonunda değişkenler:
    a = 30, b = 30, c = 19, d = 10 değerlerini alırlar.

    Fonksiyon çağırma ifadelerinde bir değişken postfix ++ ya da – operatörü ile kullanılmışsa, ilk önce fonksiyon artırılmamış veya eksiltilmemiş değişken ile çağırılır, fonksiyonun çalışması bitince değişken 1 artıtılır veya eksiltilir.

    #include <stdio.h>

    int func(int x)
    {
    printf(“%d\n”, x);
    }

    int main()
    {
    int a;
    a = 10;
    func(a++);
    printf(“%d\n”, a);

    return 0;
    }

    ++ ve -- Operatörleriyle İlgili Şüpheli Kodlar (undefined behaviours)

    ++ ve – operatörlerinin bilinçsiz bazı kullanımları derleyiciler arasında yorum farklılığına yol açarak taşınabilirliği bozarlar. Böyle kodlardan sakınmak gerekir.

    3 tane + (+++) ya da 3 tane – (---) karakteri boşluk olmaksızın yan yana getirilmemelidir.

    x = a+++b; /* şüpheli kod */

    Burada derleyiciler x = a++ + b;işlemlerini yapabileceği gibi x = a+ ++b; işlemlerini de yapabilirler.

    Bir ifadede bir değişken ++ ya da – operatörleriyle kullanılmışsa, o değişken o ifadede
    bir kez daha yer almamalıdır. Örneğin aşağıdaki ifadelerin hepsi şüpheli kodlardır :
    int x = 20, y;

    y = ++x + ++x; /* şüpheli kod */
    y = ++x + x /* şüpheli kod */
    a = ++a; /* şüpheli kod */

    Bir fonksiyon çağırılırken parametrelerden birinde bir değişken ++ ya da – ile kullanılmışsa budeğişken diğer parametrelerde gözükmemelidir.
    Argumanlarım patrametre değişkenlerine kopyalanma sırası standart bir biçimde belirlenmemiştir. Bu kopyalama işlemi bazı sistemlerde soldan sağa bazı sistemlerde ise sağdan soladır. örnek :

    int a = 10;

    fonk (a, a++); /* şüpheli kod fonk fonksyinuna 10, 10 değerleri mi 11, 10 değerleri mi
    önderiliyor? */

    void fonk(x, y)
    {
    ...
    }

    Karşılaştırma Operatörleri (ilişkisel operatörler)
    C programlama dilinde toplam 6 tane karşılaştırma operatörü vardır:

    < küçüktür (less than)
    > büyüktür (greater than)
    <= küçük eşit (less than or equal)
    >= büyük eşit (greater than or equal)
    == eşittir. (equal)
    != eşit değildir. (not equal)

    Bu operatörlerin hepsi iki operand alan araek operatörlerdir. (binary infix)

    Operatör öncelik tablosuna bakıldığında, karşılaştırma operatörlerinin aritmetik operatörlerden daha sonra geldiği yani daha düşük öncelikli oldukları görülür. Karşılaştırma operatörleri kendi aralarında iki öncelik grubu biçiminde bulunurlar.

    Karşılaştırma operatörleri bir önerme oluştururlar ve sonuç ya doğru ya da yanlış olur. Karşılaştırma operatörlerinden elde edilen değer , önerme doğru ise 1 önerme yanlış ise 0 değeridir ve int türdendir. Bu operatörlerin ürettiği değerler de tıpkı aritmetik operatörlerin ürettiği değerler gibi kullanılabilirler. örnekler :

    #include <stdio.h>

    int main()
    {
    int x = 5;

    ...
    y = (x > 5) + 1;
    printf("%d\n", y);
    funk(y >= x);

    return 0;
    }

    Yukarıdaki örnekte ekrana 1 değeri yazdırılacaktır. Daha sonra da funk fonksiyonu 0 değeriyle çağırılacaktır.

    Bazı programlama dillerinde x = (a < b) + 1; gibi bir işlem hata ile sonuçlanır. Çünkü örneğin Pascal dilinde a < b ifadesinden elde edilen değer True ya da False dir. (yani BOOL ya da BOOLEAN türündendir.) Ama C doğal bir dil olduğu için karşılaştırma operatörlerinin ürettikleri değeri Boolean türü ile kısıtlamamıştır. C’de mantıksal veri türü yerine int türü kullanılır. Mantıksal bir veri türünün tamsayı türüyle aynı olması C’ye esneklik ve doğallık kazandırmıştır. Başka bir örnek :

    x = y == z;

    Yukarıdaki deyim C dili için son derece doğal ve okunabilir bir deyimdir. Bu deyimin icrasıyla x değişkenine ya 1 ya da 0 değeri atanacaktır.

    Karşılaştırma operatörünün kullanılmasında bazı durumlara dikkat etmemiz gerekmektedir. Örneğin

    int x = 12;

    5 > x > 9

    Yukarıdaki ifade matematiksel açıdan doğru değildir. Çünkü 12 değeri 5 ve 9 değerlerinin arasında değildir. Ancak ifade C açısından doğru olarak değerlendirilir. Çünkü aynı seviyede olan iki operatöre (> ve >) ilişkin öncelik yönü soldan sağadır. Önce soldaki > operatörü değer üretecek ve ürettiği değer olan 0 sağdaki > operatörüne operand olacaktır. Bu durumda 0 > 9 ifadesi elde edilecek ve bu ifadeden de 0 yani yanlış değeri elde edilecektir.


    Mantıksal Operatörler (logical operators)
    Bu operatörler operandları üzerinde mantıksal işlem yaparlar. Operandlarını Doğru (true) ya da Yanlış(false) olarak yorumladıktan sonra işleme sokarlar. C’de dilinde öncelikleri farklı seviyede olan (tabloya bakınız) üç mantıksal operatör vardır:

    ! değil operatörü (not)
    && Ve operatörü (and)
    || veya operatörü (or)

    Tablodan da görüleceği gibi && ya da || operatörlerin aynı ifade içinde birden fazla kullanılması durumunda öncelik yönü soldan sağadır, 2. seviyede bulunan ! operatörü için ise öncelik yönü sağdan soladır.

    C’de mantıksal veri türü yoktur. Mantıksal veri türü olmadığı için bunun yerine int türü kullanılır ve mantıksal doğru olarak 1 mantıksal yanlış olarak da 0 değeri kullanılır.

    C dilinde herhangi bir ifade mantıksal operatörlerin operandı olabilir. Bu durumda söz konusu ifade mantıksal olarak yorumlanır. Bunun için ifadenin sayısal değeri hesaplanır. Hesaplanan sayısal değer 0 dışı bir değer ise doğru (1), 0 ise yanlış (0) olarak yorumlanır. Örneğin:

    25 Doğru (Çünkü 0 dışı bir değer)
    -12 Doğru (Çünkü 0 dışı bir değer)
    0 Yanlış (Çünkü 0)

    int x = 5;
    x * x – 25

    İfadesi mantıksal bir operatörün operandı olduğu zaman yanlış olarak yorumlanacaktır. Çünkü sayısal değeri 0’a eşittir.

    ! Operatörü
    Değil operatörü tek operand alır ve her zaman önek durumundadır. (unary prefix) ! operatörü operandının mantıksal değerini değiştirir. Yani Doğru değerini yanlış değerini, yanlış değerini de Doğru değerine dönüştürür:

    x
    !x
    Doğru (0 dışı değer)
    Yanlış (0)
    Yanlış (0)
    Doğru (1)


    Örnekler :

    a = !25; /* a değişkenine 0 değeri atanır */
    b = 10 * 3 < 7 + !2

    İşlem sırası:

    !2 = 0
    10 * 3 = 30
    7 + 0 = 7
    30 < 7 = 0
    b = 0 (atama operatörü en düşük öncelikli operatördür)

    y = 5;
    x = !++y < 5 != 8;

    İşlem sırası:

    ++y &THORN; 6
    !6 &THORN; 0 /* ++ ve ! operatörleri aynı öncelik seviyesindedir ve öncelik yönü sağdan soladır. */
    0 < 5 &THORN; 1
    1 != 8 &THORN; 1
    x = 1

    && (ve / and) Operatörü
    Bu operatör ilişkisel operatörlerin hepsinden düşük, || (veya / or) operatöründen yüksek önceliklidir. Operandlarının ikisi de Doğru ise Doğru (1), operandlardan bir tanersi yanlış ise yanlış (0) değerini üretir.

    x
    y
    x&&y
    yanlış
    yanlış
    yanlış
    yanlış
    Doğru
    yanlış
    Doğru
    yanlış
    yanlış
    Doğru
    Doğru
    Doğru


    x = 3 < 5 && 7;
    3 < 5 &THORN; 1
    7 &THORN; 1
    1 && 1 &THORN; 1
    x = 1

    && operatörünün önce sol tarafındaki işlemler öncelik sırasına göre tam olarak yapılır. Eğer bu işlemlerde elde edilen sayısal değer 0 ise && operatörünün sağ tarafındaki işlemler hiç yapılmadan Yanlış (0) sayısal değeri üretilir. Örneğin :

    x = 20;
    b = !x == 4 && sqrt(24);
    !20 &THORN; 0
    0 == 4 &THORN; 0

    Sol taraf 0 değeri alacağından operatörün sağ tarafı hiç icra edilmeyecek dolayısıyla da sqrt fonksiyonu çağırılmayacaktır. Dolayısıyla b değişkenine 0 değeri atanacaktır.

    Uygulamalarda mantıksal operatörler çoğunlukla ilişkisel (karşılaştırma) operatörleriyle birlikte kullanılırlar :

    scanf(“%d”, &x);
    y = x >= 5 && x <= 25;

    Bu durumda y değişkenine ya 1 ya da 0 değeri atanacaktır. Eğer x 5’den büyük ya da eşit ve 25’den küçük ya da eşit ise y değişkenine 1 değeri bunun dışındaki durumlarda y değişkenine 0 değeri atanacaktır.
    ch = ‘c’
    z = ch >= ‘a’ && ch <= ‘z’

    Yukarıdaki örnekte ch değişkeninin küçük harf olup olmaması durumuna göre z değişkenine 1 ya da 0 atanacaktır.

    || (veya / and) Operatörü

    Önceliği en düşük olan mantıksal operatördür. İki operandından biri Doğru ise Doğru değerini üretir. İki operandı da yanlış ise yanlış değerini üretir.

    x
    y
    x||y

    yanlış
    yanlış
    yanlış
    yanlış
    Doğru
    Doğru
    Doğru
    yanlış
    Doğru
    Doğru
    Doğru
    Doğru


    a = 3 || 5 /* a = 1 */
    x = 0 || -12 /* x = 1 */
    sayi = 0 || !5 /* sayi = 0 */

    || operatörünün önce sol tarafındaki işlemler öncelik sırasına göre tam olarak yapılır. Eğer bu işlemlerden elde edilen sayısal değer 1 ise sağ tarafındaki işlemler yapılmaz. Örnekler:

    a = 20;
    b = 10;
    y = a + b >= 20 || a – b <= 10;

    a + b &THORN; 30
    30 >= 20 &THORN; 1

    Artık sağ taraftaki işlemlerin yapılmasına gerek kalmadan y değişkenine 1 değeri atanır.

    Aşağıdaki ifadede ch değişkeninin içinde bulunan karakterin (Türkçe karakterler de dahil olmak üzere) küçük harf olup olmadığı bulunur:

    ch = ‘ç’;

    a = ch >= ‘a’ && ch <= ‘z’ || ch == ‘ı’ || ch == ‘ü’ || ch == ‘ğ’ || ch == ‘ç’ || ch ==’ö’ || ch == ‘ş’;

    Son olarak şunu da ilave edelim, mantıksal operatörler bir değer üretebilmek için operandlarını önce 1 ya da 0 (yani DOĞRU ya da YANLIŞ) olarak yorumlarlar, ama yan etkileri yoktur. Yani operandlarının nesne olması durumunda bu neslerin bellekteki değerlerini 1 ya da 0 olarak değiştirmezler.

    Özel Amaçlı Operatörler

    Bu bölümde özel amaçlı operatörlerden atama, işlemli atama, öncelik ve virgül operatörlerini inceleyeceğiz.

    Atama Operatörü (assignment operator)
    Atama operatörü C dilinde öncelik tablosunun en alttan ikinci seviyesinde bulunur ve yalnızca virgül operatöründen daha yüksek önceliklidir. Her operatör bir değer üretir ve atama operatörü de yaptığı atama işleminin yanısıra bir değer üretir. Atama operatörünün ürettiği değer sağ taraf değerinin kendisidir. Örneğin:

    if (a = 20) {
    ...
    }

    Burada sırasıyla şunlar olur :

    1. 20 sayısı x değişkenine atanır.
    2. Bu işlemden 20 sayısı üretilir.
    3. 20 sıfır dışı bir sayı olduğu için if deyimi doğru olarak değerlendirilir.

    Atama operatörünün ürettiği değer nesne değildir. dolayısıyla;

    (b = c) = a; /* ifadesi C’de geçerli bir deyim değildir. */

    b = c atamasından elde edilen değer c nesnesinin kendisi değil c nesnesinin sayısal değeridir.

    Operatör tablosundan da göreceğimiz gibi atama operatörü kendi arasında sağdan sola önceliklidir. (right associative) Bu yüzden :

    var1 = var2 = var3 = 0; deyimi C’de geçerlidir. Bu deyimde önce var3 değişkenine 0 değeri atanacak ve üretilen 0 değeri var2 değişkenine atanacak ve yine üretilen 0 değeri son olarak var1 değişkenine atanacaktır.

    İşlemli Atama Operatörleri
    Bir işlemin operandı ile işlem sonucunda üretilen değerin atanacağı nesne aynı ise işlemli atama operatörleri kullanılır.

    <nesne1> = <nesne1> işlem <operand2> ile
    <nesne1> işlem= <operand2> aynı anlamdadır.

    İşlemli atama operatörleri atama operatörüyle sağdan sola eşit öncelike sahiptir. (operatör öncelik tablosuna bakınız)

    İşlemli atama operatörleri hem okunabilirlik hem da daha kısa yazım için tercih edilirler. Aşağıdaki ifadeler eşdeğerdir:

    deger1 += 5; deger1 = deger1 + 5;
    sonuc *= yuzde; sonuc = sonuc * yuzde;
    x %= 5 x = x % 5;

    katsayi = katsayi * (a * b + c * d) ifadesi de yine
    katsayi *= a * b + c * d; şeklinde yazılabilir.

    var1 = 3;
    var2 = 5;
    var1 += var2 *= 3; ifadesinde var2 = var2 * 3 işlemiyle önce var2 değişkenine 15 değeri atanacak ve işlem sonucunda 15 değeri üretilecek var1 = var1 + 15 işlemiyle var1 değişkenine 18 değeri atanacaktır.

    Özellikle += ve -= operatörlerinin yanlış yazılması, tespit edilmesi zor hatalara neden olabilir.

    x += 5;

    ifadesi x degişkeninin eski değerini 5 artırıyorken bu deyim yanlışlıkla aşağıdaki gibi yazıldığı takdirde

    x =+ 5;

    x değişkenine 5 değeri atanacaktır. Çünku burada iki ayrı operatör söz konusudur. (atama operatörü olan = ve işaret operatörü olan +)

    yukarıdaki örneklerden de görüldüğü gibi atama grubu operatörlerinin yan etkileri vardır. Yan etkileri operatörün sol operandının bellekteki değerinin değiştirilmesi (operatörün sağ tarafındaki operandı olan ifadenin değerinin sol tarafındaki nesneye aktarılması) şeklinde kendini gösterir.

    Virgül (,) Operatörü
    İki ayrı ifadeyi tek bir ifade olarak birleştiren virgül operatörü C’nin en düşük öncelikli operatörüdür.
    ifade1;
    ifade2;

    ile

    ifade1, ifade2;

    aynı işleve sahiptir.

    Virgül operatörünün önce sol tarafındaki ifade sonra sağ tarafındaki ifade tam olarak yapılır. Bu operatörün ürettiği değer sağ tarafındaki ifadenin ürettiği değerdir. Virgülün sol tarafındaki ifadenin ürettiği değerin virgül operatörünün ürettiği değere bir etkisi yoktur. Örneğin :

    x = (y *= 5, z = 100);

    ifadesinde x değişkenine 100 değeri atanacaktır.

    Aşağıdaki örnekte if değeri yanlış olarak değerlendirilecektir :

    if (a =10, b = 0) {
    ...
    }

    Virgül operatörleri ile birden fazla ifade tek bir ifade olarak birleştirilebilir. Örneğin :

    if (x == 20) {
    a1 = 20;
    a2 = 30;
    a3 = 40;
    }

    yerine

    if (x == 20)
    a1 = 20, a2 = 30, a3 = 40;

    yazılabilir.

    Öncelik Operatörü ( )
    Öncelik operatörü bir ifadenin önceliğini yükseltmek amacıyla kullanılmaktadır.

    x = (y + z) * t;

    Öncelik operatörünü C nin en yüksek öncelikli operatörler grubundadır. Öncelik operatörü de kendi arasında soldan sağa öncelik kuralına uyar. Örneğin:

    a = (x + 2) / ((y + 3) * (z + 2) – 1);

    ifadesinde işlem sırası şöyledir :

    i1 : x + 2
    i2 : y + 3
    i3 : z + 2
    i4 : i2 * i3
    i5 : i4 – 1
    i6 : i1 / i5
    i7 : a = i6


    3. if DEYİMİ

    C dilinde program akışını kontrol etmeye yönelik en önemli deyim if deyimidir. if deyiminin genel biçimi aşağıda verilmiştir:

    if (ifade)
    deyim1;
    else
    deyim2;

    deyim1 ve deyim2 yalın deyim (simple statement) olabileceği gibi, bileşik bir deyim (compound statement) ya da başka bir kontrol deyimi de (control statement) olabilir.

    if parantezi içindeki ifadeye kontrol ifadesi (control expression) denir.

    if deyiminin icrası aşağıdaki gibi yapılır:

    Derleyici önce kontrol ifadesinin sayısal değerini hesaplar. Hesapladığı sayısal değeri mantıksal doğru ya da yanlış olarak yorumlar. İfadenin hesaplanan değeri 0 ise yanlış, 0 dışında bir değer ise doğru olarak yorumlanır. (Örneğin kontrol ifadesinin hesaplanan değerinin –5 olduğunu düşünelim, bu durumda kontrol ifadesi doğru olarak değerlendirilecektir). Eğer ifadenin sonucu doğru ise else anahtar sözcüğüne kadar olan kısım, eğer ifadenin sonucu yanlış ise else anahtar sözcüğünden sonraki kısım icra edilir. Örnek :

    {
    int var = 3;

    if (var * 5 < 50)
    deyim1;
    else
    deyim2;
    deyim3;
    }

    1. adım : if parantezi içindeki ifadenin sayısal değeri hesaplanacak :
    15 < 50 sonuç 1 (doğru)

    2. adım : deyim1 icra edilecek.

    3. adım : daha sonra else kısmı atlanarak deyim3 icra edilecek.

    if parantezi içerisindeki ifadeler karmaşık yapıda da olabilir :

    ...
    char ch = ‘Q’;

    if (ch >= ‘a’ && ch <= ‘z’)
    printf(“%c\n”, ch);
    else
    printf(“küçük harf değil\n”);
    ch = ‘A’;

    Yukarıdaki if deyimi ile ch karakterinin küçük harf olup olmadığı test edilmektedir. ch karakterinin küçük harf olması durumunda bu karakter ekrana basılacak, aksi durumda ekrana “küçük harf değil” mesajı yazılacaktır. Her iki durumdada programın akışı

    ch = ‘A’;

    atama deyimiyle devam edecektir.

    if deyiminin doğru ve / veya yanlış kısmı birden fazla deyimden oluşuyorsa bloklama yapılmalıdır :

    if (b * b – 4 * a * c < 0) {
    deyim1;
    deyim2;
    deyim3;
    }
    else {
    deyim4;
    deyim5;
    deyim6;
    }

    Yukarıdaki örnekte kontrol ifadesinin doğru olması durumunda, deyim1, deyim2, deyim3 icra edilecektir. Kontrol ifadesinin yanlış olması durumunda ise deyim4, deyim5, deyim6 icra edilecektir.

    Bir if deyiminin else kısmı olmayabilir:

    if (result < 0) {
    clrscr();
    printf(“sonuç 0’dan küçük\n”);
    }
    ++x;
    ...

    Bir if deyimi yalnızca else kısmına sahip olamaz. Bu durumda if deyiminin doğru kısmına boş deyim ya boş bileşik deyim yerleştirilmelidir.

    if (ifade)
    ;
    else
    deyim1;

    ya da

    if (ifade)
    { }
    else
    deyim1;

    Ama daha iyi teknik koşul ifadesini değiştirmektir:

    if (!ifade)
    deyim1;

    if parantezindeki ifade değişken içermek zorunda değildir, sabit ifadesi de olabilir:

    if (10)
    deyim1
    ...
    if (-1)
    deyim2

    Yukarıdaki kontrol ifadelerinin değeri her zaman doğru olacaktır. (0 dışı değer)

    if ( 0)
    deyim1;


  5. #5
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb


    Yukarıdaki kontrol ifadesi ise her zaman yanlış olacağından if deyiminin doğru kısmı hiçbir zaman icra edilmeyecektir.


    if (x) {
    deyim1;
    deyim2;
    ....
    }

    Bu if deyiminde ise x değişkeninin değerinin 0 dışı bir değer olup olmamasına göre deyim1 ve deyim2 icra edilecektir.

    Yukarıdaki yapıyla aşağıdaki yapı eşdeğerdir:

    if (x != 0) {
    deyim1;
    deyim2;
    ....
    }

    Aşağıdaki örneği inceleyelim :

    if (!x) {
    deyim1;
    deyim2;
    ....
    }

    Bu if deyiminde ise ancak x değişkeninin değerinin 0 olması durumunda deyim1 ve deyim2 icra edilecektir.

    Yine yukarıdaki yapıyla aşağıdaki yapı eşdeğerdir:

    if (x == 0) {
    deyim1;
    deyim2;
    ....
    }


    if ((ch = getchar()) == ‘h’)
    deyim1;
    else
    deyim2;

    Bu if deyiminin kontrol ifadesinde ise klavyeden bir değer alınmakta alınan değer ch değişkenine atanmakta ve daha sonra da ch değişkeni değerinin ‘h’ karakteri olup olmadığı test edilmektedir. Eğer klavyeden alınan değer 'h' ise deyim1 değil ise deyim2 yapılacaktır.
    kontrol ifadesi içindeki parantezler atama işlemine öncelik kazandırmak amacıyla kullanılmaktadır. Parantez kullanılmasaydı eşitlik karşılaştırma operatörünün (==) önceliği atama operatöründen daha yüksek olduğu için önce karşılaştırma işlemi yapılacak daha sonra üretilen 0 ya da 1 değeri ch değişkenine atanacaktı.

    Yukarıdaki örnekte de görüldüğü gibi bir if deyiminin koşul ifadesi içinde atama operatörünün bir değer üretmesi fikrinden sıklıkla faydalanılır.

    Fonksiyonların geri dönüş değerleri de sıklıkla if deyiminin kontrol ifadesi olarak kullanılır.

    if (isupper(ch) != 0)
    deyim1;
    else
    deyim2;

    isupper parametresinin büyük harf olup olmadığını kontrol eden bir standart C fonksiyonudur. Yukarıdaki if deyiminde ch karakterinin büyük harf olup olmamasına göre deyim1 ya da deyim2 icra edilecektir.

    Yukarıdaki koşul ifadesi yerine C programcıları genellikle aşağıdaki ifadeyi tercih ederler :

    if (isupper(ch))
    deyim1;
    else
    deyim2;


    if deyiminin doğru ya da yanlış kısmı başka bir if deyimi de olabilir :

    if (ifade1)
    if (ifade2) {
    deyim1;
    deyim2;
    deyim3;
    }
    deyim4;

    Bu örnekte ikinci if deyimi birinci if deyiminin doğru kısmını oluşturmaktadır. Birinci ve ikinci if deyimlerinin yanlış kısımları yoktur.

    İç içe if deyimlerinde son if anahtar sözcüğünden sonra gelen else anahtar sözcüğü en içteki if deyimine ait olacaktır:

    if (ifade1)
    if (ifade2)
    deyim1;
    else
    deyim2;


    Yukarıdaki örnekte yazım tarzı olarak else kısmının birinci if deyimine ait olması gerektiği gibi bir görüntü verilmiş olsa da else kısmı ikinci if deyimine aittir. else anahtar sözcüğü bu gibi durumlarda kendisine yakın olan if deyimine ait olacaktır. (dangling else) Eğer else anahtar sözcüğünün birinci if deyimine ait olması isteniyorsa, birinci if deyiminin doğru kısmı blok içine alınmalıdır.


    if (ifade1) {
    if (ifade2)
    deyim1;
    }
    else
    deyim2;

    Yukarıdaki örnekte else kısmı birinci if deyimine aittir.


    if (ifade1) {
    if (ifade2)
    deyim1;
    else {
    deyim2;
    deyim3;
    }
    deyim4;
    }
    else
    deyim5;

    Birinci if deyiminin doğru kısmı birden fazla deyimden oluştuğu için (bu deyimlerden birisi de yine başka bir if deyimidir) bloklama yapılmıştır. deyim5 birinci if deyiminin yanlış kısmını oluşturmaktadır.

    Ayrık karşılaştırma ve else if merdivenleri :

    Aşağıdaki if deyimlerini inceleyelim :


    if (m == 1)
    printf(“Ocak\n”);
    if (m == 2)
    printf(Şubat\n”);
    if (m == 3)
    printf(“Mart\n”);
    .....
    if (m == 12)
    printf(“Aralık\n”);

    Yukarıdaki örnekte verilen yapıda olduğu gibi, eğer bir karşılaştırmanın doğru olarak yorumlanması durumunda yapılan diğer karşılaştırmaların doğru olması söz konusu değilse bu tür karşılaştırmalara ayrık karşılaştırma denir. Ayrık karşılaştırmalarda ayrı ayrı if deyimlerinin kullanılması kötü tekniktir. Yukarıdaki örnekte m değişkeninin değerinin 1 olduğunu düşünelim. Bu durumda ekrana Ocak yazdırılacak fakat daha sonra yer alan if deyimleriyle m değişkeninin sırasıyla 2, 3, .... 12’ye eşit olup olmadığı test edilecektir. Ama x değişkeni 1 değerine sahip olduğundan bütün diğer if deyimleri içindeki kontrol ifadelerinin yanlış olarak değerlendirileceği bellidir.

    Ayrık karşılaştırmalarda else if merdivenleri uygulamaları çok kullanılır :

    if (ifade1)
    deyim1;
    else
    if (ifade2)
    deyim2;
    else
    if (ifade3)
    deyim3;
    else
    if (ifade4)
    deyim4;
    else
    deyim5;

    Bu yapıda artık herhangi bir if deyimi içerisindeki bir kontrol ifadesi doğru olarak değerlendirilirse programın akışı hiçbir zaman başka bir if deyimine gelmeyecektir. Bu yapıya else if merdiveni (cascaded if / else if ladder) denir. else if merdivenlerinin yukarıdaki biçimde yazılışı özellikle uzun else if merdivenlerinde okunabilirliği bozduğu için aşağıdaki yazım tarzı okunabilirlik açısından tercih edilmelidir.


    if (ifade1)
    deyim1;
    else if (ifade2)
    deyim2;
    else if (ifade3)
    deyim3;
    else if (ifade4)
    deyim4;
    else deyim5;


    if Deyiminin Kullanılmasına İlişkin Sık Yapılan Hatalar
    if parantezinin sonuna yanlışlıkla ; yerleştirilmesi:

    ...
    if (x > 5);
    printf("doğru!\n");
    ...

    Yukarıdaki örnekte x > 5 ifadesi doğru da yanlış da olsa printf fonksiyonu çağırılacaktır. Zira printf çağırımı if deyiminin dışındadır. if deyiminin doğru kısmını bir boş deyim (Null statement) oluşturmaktadır. Aynı nedenden dolayı aşağıdaki kod derleme zamanında error oluşumuna neden olacaktır :

    ...
    if (x > 5);
    printf("doğru!\n");
    else
    printf("yanlış\n");
    ...

    if anahtar sözcüğü olmayan bir else anahtar sözcüğü:

    Tabi ki bir if deyiminin doğru ya da yanlış kısmını bir ; (boş deyim - null statement) oluşturabilir. Bu durumda okunabilirlik açısından bu boş deyim bir tab içeriden yazılmalıdır:

    ...
    if (funk())
    ;
    else
    x = 5;

    if parantezi içerisinde karşılaştırma operatörü (==) yerine yanlışlıkla atama operatörünün (=) kullanılması

    ...
    if (x == 5)
    printf("eşit\n");
    ...

    Yukarıdaki if deyiminde x değişkeninin değeri eğer 5'e eşitse printf fonksiyonu çağırılacaktır. Operatörler konusunda da değindiğimiz gibi karşılatırma operatörünün yan etkisi yoktur, yani yukarıdaki if parantezi içerisinde x değişkeninin değeri yalnızca 5 sabiti ile karşılaştırılmakta, değişkenin değeri değiştirilmemektedir. Oysa karşılaştırma operatörünün yerine yanlışlıkla atama operatörü kullanılırsa:

    ...
    if (x = 5)
    printf("eşit\n");
    ...

    Atama operatörü atama operatörünün sağ tarafındaki ifadenin değerini üreteceğinden if parantezi içindeki ifadenin değeri 5 olarak hesaplanacak ve 5 de 0 dışı bir değer olduğundan printf fonksiyonu x değişkeninin değeri ne olursa olsun, çağırılacaktır. Tabi, atama operatörü yan etkisi olan bir operatör olduğundan x değişkeni de if deyiminin icrası sırasında 5 değerini alacaktır.

    C derleyicilerinin çoğu if parantezi içindeki ifade yalın bir atma ifadesi ise, durumu şüpheyle karşılayarak, bir uyarı mesajı verirler. Örneğin Borland derleyicilerinde tipik bir uyarı mesajı aşağıdaki gibidir :

    warning : possibly incorrect assignment! (muhtemelen yanlış atama!)

    Oysa if parantezi içerisinde atama operatörü bilinçli olarak da kullanılabilir. Bilinçli kullanımda, uyarı mesajının kaldırılması için ifade aşağıdaki gibi düzenlenebilir :

    ...
    if ((x = funk()) != 0)
    m = 20;
    ...

    Yukarıdaki örnekte atama operatörünün ürettiği değer açık olarak bir karşılaştırma operatörüne operand yapıldığı için derleyiciler bu durumda bir uyarı mesajı vermezler.


    if deyiminin doğru ya da yanlış kısmının birden fazla basit deyimden oluşması durumunda if deyiminin doğru ya da yanlış kısmı bileşik deyim haline getirilmelidir.


    ...
    if ( x == 10)
    m = 12;
    k = 15;
    ...

    Yukarıdaki if deyiminde sadece

    m = 12;

    deyimi if deyiminin doğru kısmını oluşturmaktadır.

    k = 15;

    deyimi if deyimi dışındadır. Bu durum, genellikle programcının if deyiminin doğru ya da yanlış kısmını önce basit bir deyimle oluşturmasından sonra, doğru ya da yanlış kısma ikinci bir basit deyimi eklerken, bloklama yapmayı unutması yüzünden oluşur!

    Kodun yazılış biçiminden de if deyiminin doğru kısmının yanlışlıkla bloklanmadığı anlaşılıyor! : Doğrusu aşağıdaki gibi olmalıydı :

    ...
    if ( x == 10) {
    m = 12;
    k = 15;
    }
    ...

    Aşağıdaki if deyimi ise yine if anahtar sözcüğü ile eşlenmeyen bir else anahtar sözcüğü kullanıldığı için derleme zamanında error oluşumuna neden olacaktır :

    ...
    if ( x == 10)
    m = 12;
    k = 15;
    else
    y = 20;
    ...


    Bu tür yanlışlıklardan sakınmak için bazı C programcıları if deyiminin doğru ya da yanlış kısmı basit deyimden oluşsa da, bu basit deyimi bileşik deyim olarak yazarlar :

    if ( x > 10) {
    y = 12;
    }
    else {
    k = 5;
    }

    Yukarıdaki örnekte if deyiminin doğru ya da yanlış kısmına başka bir basit deyimin ilave edilmesi durumunda bir yanlışlık ya da error oluşmayacaktır. Ancak biz yukarıdaki gibi bir kodu stil açısından beğenmiyoruz. Gereksiz bloklamadan kaçınmalıyız.

    Bazen de if deyiminin yanlış kısmı unutulur :

    ...
    if (x == 10)
    printf("x 10'a eşit!\n"")
    printf("x 10'a eşit değil!\n);
    ...

    if parantezi içerisindeki ifadenin yanlış olması durumunda bir yanlışlık söz konusu değil ama ifade doğru ise ekrana ne yazılacak?

    x 10'a eşit!
    x 10'a eşit değil!

    Tehlikeli bir bug da if parantezi içerisindeki ifadenin bir fonksiyon çağırma ifadesi olması durumunda yanlışlıkla fonksiyon çağırma operatörünün unutulmasıdır!

    ...
    if (funk())
    m = 12;
    ...

    yerine

    ...

    if (funk)
    m = 12;
    ...
    yazılırsa, if deyiminin her zaman doğru kısmı icra edilir. Zira C dilinde bir fonksiyon ismi, o fonksiyonun kodunun bellekteki yerine eşdeğer bir adres bilgisi olarak ele alınır. (Göstericiler konusunda göreceğiz.) Bu adres bilgisi de her zaman 0 dışı bir değer olacağından, koşul ifadesi her zaman doğru olarak değerlendirilecektir.

    if Deyiminin Kullanıldığı Örnek Uygulamalar

    islower Fonksiyonu

    islower standart bir C fonksiyonudur. Parametresi olan karakter, küçük harf karakterlerinden biri ise Doğru (sıfır dışı bir değer), değilse Yanlış (sıfır) değerine geri döner. Bu fonksiyonu aşağıdaki şekilde yazabiliriz :

    #include <stdio.h>

    int _islower (int ch)
    {
    if (ch >= ‘a’ && ch <= ‘z’)
    return ch:
    return 0;
    }

    main()
    {
    char ch;

    ch = getchar();
    if (_islower(ch))
    printf(“küçük harf\n”);
    else
    printf(“küçük harf değil\n”);
    }

    Yukarıda yazılan _islower fonksiyonunda önce parametre değişkeninin küçük harf olup olmadığı test edilmektedir. Parametre değişkeni eğer küçük harf ise if deyiminin Doğru kısmı yapılacaktır. Doğru kısmının yapılması ile fonksiyon ch’nın kendi değerine geri dönecektir. (Bu da 0 dışı bir değerdir. Pekala 1 gibi sabit bir değer ile de geri dönebilirdik ama ch parametre değişkeninin değeri ile geri dönmemiz fonksiyonun kullanılmasında bazı avantajlar getirebilecektir. Fonksiyonun yazımında dikkat edilmesi gereken bir nokta da şudur : if deyiminin Yanlış kısmı yoktur. (Yani else anahtar sözcüğü kullanılmamıştır. Test fonksiyonlarında bu durum çok sık görülür. Çünkü if deyiminin Doğru kısmında return anahtar sözcüğü ile fonksiyon yalnızca bir geri dönüş değeri üretmekle kalmamakta aynı zamanda sonlanmaktadır. Bu durumda else anahtar sözcüğüne gerek kalmaz. (Çünkü Doğru kısmının yapılmasından sonra Yanlış kısmının da yapılması mümkün değildir.) Bu tür durumlarda else anahtar sözcüğünün kullanılması kötü teknik olarak değerlendirilir.

    isalpha Fonksiyonu

    isalpha fonksiyonu da standart bir C fonksiyonudur. Parametresi olan karakter , eğer alfabetik karakterse (yani büyük ya da küçük harf ise) Doğru (sıfır dışı bir değere), alfabetik bir karakter değilse Yanlış (sıfır değerine) geri döner.

    #include <stdio.h>

    int isalpha (char ch)
    {
    if (ch >= ‘a’ && ch <= ‘z’ || ch >= ‘A’ && ch <= ‘Z’)
    return ch:
    return 0;
    }

    main()
    {
    char ch;

    ch = getchar();
    if (isalpha(ch))
    printf(“alfabetik karakter\n”);
    else
    printf(“alfabetik karakter değil\n”);

    tolower Fonksiyonu

    tolower standart bir C fonksiyonudur. Parametresi olan karakter, eğer büyük harf ise, onun küçük harf karşılığıyla geri döner. tolower küçük harf olmayan karakterlere hiç dokunmaz, onları değiştirmeden geri dönüş değeri olarak verir :

    #include <stdio.h>

    int tolower (int ch)
    {
    if (ch >= ‘A’ && ch <= ‘Z’)
    return ch – ‘A' + ‘a’;
    return ch;
    }

    main()
    {
    char ch;

    ch = getchar();
    printf(“%c\n”, tolower(ch));
    }

    isdigit Fonksiyonu

    isdigit standart bir C fonksiyonudur. Parametresi olan karakter, eğer bir rakam karakteri ise 0 dışı bir değer ile, rakam karakteri değilse 0 değeri ile geri döner.

    int _digit (char ch)
    {
    if (ch >= ‘0’ && ch <= ‘9’)
    return ch:
    return 0;
    }

    yukarıdaki örneklerde görüldüğü gibi, fonksiyonların parametreleri ve geri dönüş değerleri char türden olsa bile, int biçiminde gösterilir.

    Karakter Test Fonksiyonları

    Karakter test fonksiyonları karakterler hakkında bilgi edinmemizi sağlayan fonksiyonlardır. Bu fonksiyonların hepsi ctype.h başlık dosyası içinde makro olarak bulunurlar. Bu nedenle karakter test fonksiyonları kullanılmadan önce kaynak koda mutlaka ctype.h dosyası dahil edilmelidir. Karakter test fonksiyonları ASCII karakter setinin ilk yarısı için geçerlidir, yani türkçe karakterler için kullanılması durumunda geri dönüş değerleri güvenilir değildir.

    C dilinindeki standart karakter test fonksiyonları:

    fonksiyon
    geri dönüş değeri
    isalpha
    alfabetik karakterse Doğru değilse Yanlış
    isupper
    Büyük harf ise Doğru değilse Yanlış
    islower
    Küçük harf ise Doğru değilse yanlış
    isdigit
    sayısal bir karakterse Doğru değilse Yanlış
    isxdigit
    hex sayıları gösteren bir karakterse Doğru değilse Yanlış
    isalnum
    alfabetik ya da nümerik bir karakterse Doğru değilse Yanlış
    isspace
    Boşluk karakterlerinden biriyse(space, carriage return, new line, vertical tab, form feed) Doğru değilse Yanlış
    ispunct
    Noktalama karakterlerinden biriyse (kontrol karakterleri, alfanümerik karakterler ve boşluk karakterlerinin dışındaki karakterler) Doğru değilse Yanlış
    isprint
    Ekranda görülebilen (print edilebilen) bir karakterse (space karakteri dahil) Doğru, değilse Yanlış.
    isgraph
    Ekranda görülebilen bir karakterse (space dahil değil) Doğru değilse yanlış
    iscntrl
    Kontrol karakteri ya da silme karakteri ise (İlk 32 karakter ya da 127 numaralı karakter) Doğru değilse Yanlış
    isascii
    ASCII tablosunun standart kısmı olan ilk 128 karakterden biriyse Doğru değilse Yanlış


    Uygulamalar


    Kendisine gönderilen 0 ie 15 arasındaki bir sayının hexadesimal sembol karşılığı karakter ile geri dönen get_hex_char fonksiyonunun yazılması :

    int get_hex_char(int number)
    {
    if (number >= 0 && number <= 9)
    return ('0' + number);
    if (number >= 10 && number <= 15)
    return ('A' + number - 10);
    return -1;
    }

    test kodu :

    main()
    {
    int number;

    printf("0 ile 15 arasında bir sayı giriniz : ");
    scanf("%d", &number);
    printf("hexadesimal digit karşılığı = %c\n", get_hex_char(number));
    return 0;
    }

    Kendisine gönderilen hex digit olan bir karakterin desimal sistemdeki değerine geri dönen get_hex_value(char digit) fonksiyonunun yazılması.

    #include <stdio.h>
    #include <ctype.h>

    int get_hex_value(char digit)
    {
    digit = toupper(digit);

    if (digit >= '0' && digit <= '9')
    return digit - '0';
    if (digit >= 'A' && digit <= 'F')
    return digit - 'A' + 10;
    }

    test kodu :

    main()
    {
    char hex;

    printf("hex digit gösteren bir karakter giriniz: ");
    hex = getchar();
    printf("girmiş oldugunuz hex basamağın desimal değeri %d\n", get_hex_value(hex)); return 0;
    }


    Kendisine göderilen karakter küçük harf ise büyük harfe dönüştüren, büyük harf ise küçük harfe dönüştüren, eğer harf karakteri değilse karakterin kendisiyle geri dönen change_case isimli bir fonksiyonun tasarlanması :

    #include <stdio.h>
    #include <conio.h>


    int change_case(int ch)
    {
    if (ch >= 'A' && ch <= 'Z')
    return ch - 'A' + 'a';
    if (ch >= 'a' && ch <= 'z')
    return ch - 'a' + 'A';
    return ch;
    }

    test kodu :

    main()
    {
    int kar;

    printf("bir karakter giriniz : ");
    kar = getchar();
    kar = change_case(kar);
    putchar(kar);
    getch();
    return 0;
    }


    change_case fonksiyonunu standart C fonksiyonlarını kullanarak aşağıdaki şekilde de tanımlayabilirdik :

    int change_case(int ch)
    {
    if (isupper(ch))
    return tolower(ch);
    return toupper(ch);
    }

    İkinci dereceden bir denklemin çözüm kümesini bulan bir program :

    #include <stdio.h>
    #include <math.h>


    main()
    {
    double a, b, c;
    double delta, kokdelta;

    printf("denklemin katsayılarını giriniz : ");
    scanf("%lf%lf%lf", &a, &b, &c);

    delta = b * b - 4 * a * c;
    if (delta < 0) {
    printf("denkleminizin gerçek kökü yok!\n");
    return 0;
    }
    if (delta == 0) {
    printf("denkleminizin tek gerçek kökü var\n");
    printf("kok = %lf\n", -b / (2 * a));
    return 0;
    }
    kokdelta = sqrt(delta);
    printf("denkleminizin 2 gerçek kökü var : ");
    printf("kök 1 = %lf\n", (-b + kokdelta) / (2 * a));
    printf("kök 1 = %lf\n", (-b - kokdelta) / (2 * a));
    return 0;
    }


    2. FONKSİYON PROTOTİPLERİ

    C programlama dilinde, bir fonksiyonun çağırılması durumunda derleyiciler fonksiyonun geri dönüş değerinin türünü bilmek zorundadır. C derleyicileri fonksiyonların geri dönüş değerlerini CPU yazmaçlarından (registers) alırlar ve aslında geri dönüş değeri türü, değerin hangi yazmaçtan alınacağını gösterir.

    Eğer çağırılan fonksiyonun tanımlaması, fonksiyon çağırma ifadesinden daha önce yer alıyorsa, derleyici derleme işlemi sırasında fonksiyon çağırma ifadesine gelmeden önce, çağırılan fonksiyonun geri dönüş değeri türü hakkında zaten bilgi sahibi olacaktır. Çünkü derleme işlemi yukarıdan aşağı doğru yapılır.

    # include <stdio.h>

    float calculate(float x, float y)
    {
    return x * y / (x + y);
    }

    int main()
    {
    float a, b, c;

    c = calculate(a, b);
    printf(“%f\n”, c );
    return 0;
    }

    Yukarıdaki örnekte calculate fonksiyonu kendisini çağıran mainfonksiyonundan daha önce tanımlanmıştır. Dolayısıyla çağırma ifadesine gelmeden önce derleyici, calculatefonksiyonunun geri dönüş değeri türünü zaten bilecektir.

    Eğer çağırılan fonksiyonun tanımlaması çağıran fonksiyondan daha sonra yapılmışsa, derleyici fonskiyon çağırma ifadesine geldiğinde, söz konusu fonksiyonun geri dönüş değerinin türünü belirleyemez. Bu problemli bir durumdur.

    # include <stdio.h>

    int main()
    {
    float a, b, c;
    c = calculate(a, b);
    printf(“%f\n”, c );
    return 0;
    }

    float calculate (float x, float y)
    {
    return x * y / (x + y);
    }

    Yukarıda calculate fonksiyonu main içinde çağırılmıştır. Fakat calculate fonksiyonunun tanımlaması kaynak kod içinde main’den daha sonra yer almaktadır. Derleme akışı içerisinde calculate fonksiyonuna ilişkin çağırma ifadesine gelindiğinde, derleyici bu fonksiyonun geri dönüş değerini bilmemektedir.

    C derleyicileri derleme işlemi sırasında bir fonksiyon çağırma ifadesi gördüklerinde, eğer fonksiyonun geri dönüş değeri türü hakkında henüz sahibi değillerse, söz konusu geri dönüş değerinin int türden olduğunu kabul ederler.
    Yukarıdaki örnekte derleyici calculate fonksiyonunun geri dönüş değerinin int türden olduğunu varsayacak ve buna göre kod üretecektir. Daha sonra derleme akışı fonksiyonun tanımlama kısmına geldiğinde ise artık iş işten geçmiş olacaktır. Hedef kod oluşumunu engelleyen bu durumu derleyiciler bir hata mesajı ile bildirirler.

    Bu hata mesajı Microsoft derleyicilerinde : 'calculate': redefinition
    Borland derleyicilerinde ise : Type mismatch in redeclaration of 'calculate'

    Çağırılan fonksiyonu çağıran fonksiyonun üstünde tanımlamak her zaman mümkün değildir. Büyük bir programda yüzlerce fonksiyon tanımlanabilir ve tanımlanan her fonksiyonun birbirini çağırması söz konusu olabilir. Bu durumda çağırılacak fonksiyonu çağıran fonksiyonun üzerinde tanımlanması çok zor olacaktır. Kaldı ki, C dilinde iki fonksiyon birbirini de çağırabilir. Bu tür bir fonksiyon tasarımında artık çağırılan fonksiyonun daha önce tanımlanması mümkün olamayacaktır :

    double func1(void)
    {
    ...
    func2();
    ...
    }

    double func2(void)
    {
    ...
    func1();
    ...
    }

    Ayrıca standart C fonksiyonları da ancak bağlama aşamasına gelindiğinde bağlayıcı (linker) tarafından kütüphanelerden alınarak çalışabilen kod (.exe) içine yerleştirilirler. İşte bu gibi durumlarda derleyiciye çağırılan fonksiyonun geri dönüş tür bilgisi fonksiyon prototipleriyle verilir. Çağırılana kadar tanımlaması yapılmamış fonksiyonlar hakkında derleyicilerin bilgilendirilmesi işlemi fonksiyon prototip bildirimleri ile yapılır.

    Fonksiyon Prototip Bildirimlerinin Genel Biçimi


    [geri dönüş değeri türü] <fonksiyon ismi> ([tür1], [tür2].....);

    Örneğin calculate fonksiyonu için prototip aşağıdaki biçimde yazılabilir:

    float calculate(float, float);

    Derleyici böyle bir prototip bildiriminden calculate fonksiyonunun geri dönüş değerinin türünün float olduğunu anlayacaktır.

    Birkaç örnek daha verelim:

    int multiply (int, int);
    double pow (double, double);
    void clrscr(void);

    Tıpkı fonksiyon tanımlamalarında olduğu gibi, fonksiyon prototip bildirimlerinde de, fonksiyonun geri dönüş değeri belirtilmemişse, derleyici bildirimin int türden bir geri dönüş değeri için yapıldığını anlayacaktır.

    func(double);

    Yukarıdaki prototip bildirimi örneğinde, derleyici func fonksiyonunun geri dönüş değerinin int türden olduğu bilgisini alır, fonksiyonun geri dönüş değerinin olmadığı bilgisini değil. Eğer tanımlanacak fonksiyon geri dönüş değeri üretmeyecekse, void anahtar sözcüğü kullanılmalıdır:

    void func(double);

    Fonksiyon protipleri yalnızca derleyiciyi bildirme amacıyla kullanılır bir bildirimdir (declaration) bir tanımlama (definition) işlemi değildir, dolayısıyla yapılan bildirim sonucunda bellekte bir yer ayrılmaz.

    Fonksiyon prototip bildirimlerinde parametre değişkenlerinin türlerinden sonra parametre isimleri de yazılabilir. Prototiplerdeki parametre isimlerinin faaliyet alanları yalnızca parametre parantezi ile sınırlıdır. Buraya yazılan parametre değişkenleri isimleri yalnızca okunabilirlik açısından faydalıdır. Buradaki değişken isimlerinin fonksiyonun gerçek tanımlamasında kullanılacak formal parametre değişkenlerinin isimleriyle aynı olması zorunlulugu yoktur.
    Yukarıdaki prototiplerini parametre isimleriyle tekrar yazalım.

    float calculate(float a, float b);
    int multiply(int number1, int number2);
    double pow(double base, double exp);

    Fonksiyon prototip bildirimlerinde parametre değişkenlerine isim verilmesi, bildirimleri okuyan kişilerin fonksiyonların tanımlarını görmeden, değişkenler için kullanılan isimleri sayesinde, fonksiyonların yaptığı iş konusunda daha fazla bilgi sahibi olmalarına yardımcı olur.

    Aynı türden geri dönüş değerine sahip fonksiyonların bildirimi virgüllerle ayrılarak yazılabilir, ama bu genel olarak pek tercih edilen bir durum değildir :

    double func1(int), func2(int, int), func3(float);

    Yukarıdaki bildirimde func1, func2 ve func3 fonksiyonlarının hepsi double türden geri dönüş değerine sahip fonksiyonlardır.

    Fonksiyon prototip bildirimleri değişken tanımlamalarıyla da birleştirilebilir. Bu da tercih edilen bir durum değildir.

    long func1(int), long func2(void), x, y;

    Yukarıdaki deyim ile func1 ve func2 fonksiyonlarının prototip bildirimi yapılmışken, x ve y değişkenleri tanımlanmıştır.

    (C++ dilinde eğer çağrılan fonksiyon çağıran fonksiyondan daha önce tanımlanmamışsa, fonksiyonun geri dönüş değeri int türden kabul edilmez. Bu durumda fonksiyon prototip bildiriminin yapılması zorunludur. Prototip bildiriminin yapılmaması durumunda derleme zamanında hata (error) oluşacaktır.)

    Fonksiyon Prototiplerinin Bildirim Yerleri

    Fonksiyon prototiplerinin bildirimi programın herhangi bir yerinde yapılabilir. Prototipler bildirimleri global düzeyde yapılmışsa (yani tüm blokların dışında yapılmışsa) bildirildikleri yerden dosya sonuna kadar olan alan içinde geçerliliklerini sürdürürler. Önemli olan nokta söz konusu fonksiyon çağırılmadan bildiriminin yapımış olmasıdır.

    Ancak uygulamalarda çok az raslanmasına karşılık, fonksiyon prototipleri yerel düzeyde de yapılabilir. Bu durumda prototip bildirimi ile , yalnızca bildirimin yapılmış olduğu bloğa bilgi verilmiş olur. Başka bir deyişle prototip bildirimi de, değişken tanımlamaları gibi faaliyet alanı kuralına uyar.
    Geleneksel olarak fonksiyon prototip bildirimleri programın en yukarısında ya da programcının tanımladığı başlık dosyalarının birinin içinde yapılır. Başlık dosyaları (header files) ileride detaylı olarak ele alınacaktır.

    Standart C Fonksiyonlarının Prototipleri

    Standart C fonksiyonlarının prototipleri standart başlık dosyaları içine yerleştirilmiştir. Programcı, uygulamalarda standart bir C fonksiyonunun prototipini kendi yazmaz, bu prototip bildiriminin bulunduğu başlık dosyasını #include önişlemci komutuyla (ileride detaylı göreceğiz) koda dahil eder.

    Standart C fonksiyonlarının prototip bildirimlerinin yapılmaması durumda hata ortaya çıkmaz.
    Eğer geri dönüş değeri türü int değilse ve geri dönüş değeri kullanılırsa, programda yanlışlık söz konusu olacaktır.

    Derleme zamanında hata oluşumu söz konusu değildir, çünkü derleyici fonksiyonun geri dönüş değerinin türünü int türden kabul edecek fakat daha sonra kaynak kod içinde fonksiyonun tanımlamasını göremediğinden hata oluşmayacaktır. Ancak programın çalışma zamanı sırasında fonksiyonun geri dönüş değeri int türden bir değer olarak alınacağından yanlışlık söz konusu olacak ve program doğru çalışmayacaktır.

    Standart C fonksiyonlarının prototipleri sonu .h uzantılı olan (header) başlık dosyalar içindedir. Önişlemci komutuyla ilgili başlık dosyasının kaynak koda ilave edilmesiyle, aslında standart C fonksiyonunun da prototip bildirimi yapılmış olmaktadır. Zira önişlemci modülünün çıktısı olan kaynak program artık derleyiciye verildiğinde, ekleme yapılmış bu dosyada fonksiyonun prototip bildirimi de bulunmaktadır. Şüphesiz, başlık dosyayı kaynak koda ilave etmek yerine standart C fonksiyonunun prototipini kendimiz de kaynak koda yazabiliriz, bu durumda da bir yanlışlık söz konusu olmayacaktır. Örnek :

    Standart bir C fonksiyonu olan pow fonksiyonunun prototipini iki şekilde kaynak koda ekleyebiliriz:

    1. Fonksiyon prototipinin bulunduğu başlık dosyasını kaynak koda bir önişlemci komutuyla dahil ederek.

    #include <math.h>

    2. Fonksiyonun prototipini kendimiz yazarak.

    double pow(double taban, double us);

    Ancak tercih edilecek yöntem başlık dosyasını kaynak koda dahil etmek olmalıdır. Çünkü:

    1. Programcı tarafından fonksiyonun prototipi yanlış yazılabilir.

    2. Başlık dosyalarının kaynak koda dahil edilmesinin nedeni yalnızca fonksiyon prototip bildirimi değildir. Başlık dosyalarında daha başka bilgiler de vardır. (Makrolar, sembolik sabitler, tür tanımlamaları, yapı bildirimleri vs.)

    Fonksiyon Prototip Bildirimi İle Arguman-Parametre Uyumu Kontrolü

    Fonksiyon prototiplerinin ana amacı yukarıda da belirtildiği gibi, derleyiciye fonksiyonun geri dönüş değeri türü hakkında bilgi vermektir. Ancak fonksiyon prototip bildirimlerinde fonksiyon parametrelerinin türleri belirtilmişse, derleyici prototip bildirimindeki parametre değişkeni sayısını fonksiyon çağırma ifadesindeki fonksiyona gönderilen arguman sayısı ile karşılaştırır. Örneğin:

    float calculate(float, float);

    biçiminde bir prototip yazıldığında eğer calculate fonskiyonu eksik ya da fazla parametre ile çağırılırsa derleme hatası oluşacaktır.
    x = calculate(5.8); /* hata eksik parametre ile çağırılmış */
    y = calculate(4.6, 7.9, 8.0) /* hata fazla parametre ile çağırılmış */

    Fonksiyon prototip bildiriminde parantezin içi boş bırakılırsa (buraya hiçbir parametre tür bilgisi yazılmazsa) bu durumun özel bir anlamı vardır. Bu durumda derleyiciden parametre kontrolu yapması istenmemiş olur. (Derleyici artık fonksiyon çağırma ifadesindeki arguman sayısıyla fonksiyonun formal parametrelerinin sayısının eşitliğini kontrol etmez.) Parametre kontrolu yapılmasının istenmemesi uygulamalarda sık görülen bir durum değildir. Parametre değişkenlerinin sayısı ile fonksiyona gönderilen arguman sayısının uyumu kontrolü, C diline standartlaşırma çalışmaları sırasında eklenmiştir. Klasik C diye adlandırdığımız, C dilinin standartlaştırılmasından önceki dönemde böyle bir kontrol söz konusu değildi ve prototip bildirimlerinde fonksiyon parantezlerinin içi boş bırakılırdı. Geriye doğru uyumun korunması ve eskiden yazılmış kaynak kodların da desteklenmesi amacıyla, fonksiyon prototip bildirimlerinde fonksiyon parantezlerinin içinin boş bırakılması durumu halen geçerli bir işlem olarak bırakılmıştır.

    float calculate(); /* derleyiciden parametre kontrolü yapmaması isteniyor */

    Yukarıdaki bildirimden calculate fonksiyonunun parametre almadığı anlamı çıkmaz. Yukarıdaki bildirimden sonra eğer calculate fonksiyonu aşağıdaki ifadelerle çağırılırsa derleme zamanı hatası oluşmayacaktır:

    x = calculate (5.8); /* derleme zamanında hata oluşmaz */
    y = calculate (4.6, 7.9, 8.0) /* derleme zamanında hata oluşmaz */

    Eğer fonksiyonun gerçekten parametre değişkeni yoksa, ve derleyicinin fonksiyonun çağırılması durumunda arguman parametre değişkeni kontrolu yapması isteniyorsa, prototip bildiriminde fonksiyon parantezi içerisine void anahtar sözcüğü yazılmalıdır.

    float sample(void);

    Burada sample fonksiyonunun parametre almadığı bildirilmiştir. Eğer fonksiyon

    x = sample(20); /* derleme hatası */

    İfadesiyle çağırılırsa derleme hatası oluşur.

    (C++ dilinde fonksiyon prototip bildiriminde, fonksiyon parametre parantezinin içinin boş bırakılması, fonksiyonun parametre değişkeni olmadığını göstermektedir. Başka bir deyişle, prototip bildirimi sırasında parantezlerin içini boş bırakmak, derleyiciden arguman sayısı kontrolu yapmasını istememek anlamına gelmez. C++'da arguman sayısı ile parametre değişlenlerinin sayısının uyumu daima derleyici tarafından kontrol edilir. Dolayısıyla derleyici, prototip bildiriminde, parametre parantezi içine bir şey yazılmadığını gördükten sonra, fonksiyonun tanımlanma ifadesinde parametre değişken(ler)inin varlığını görürse, bu durumu bir error mesajı ile bildirir.)

    Fonksiyon prototip bildiriminin yapılmış olması o fonksiyonun tanımlamasını ya da çağırılmasını zorunlu kılmaz.
    Prototip bildirimi yapılan bir fonksiyonu tanımlamamak hata oluşturmaz.

    Bir fonksiyonun prototip bildirimi birden fazla yapılabilir.. Bu durumda hata oluşturmaz. Ama yapılan bildirimler birbirleriyle çelişmemelidir.

    Kaynak dosya içinde aynı fonksiyona ilişkin prototip bildirimlerinin farklı yerlerde ve aşağıdaki biçimlerde yapıldığını düşünelim :

    int sample (int, int);
    sample (int, int);
    int sample(int x, int y);
    sample(int number1, int number2);

    Yukarıdaki bildirimlerinin hiçbirinde bir çelişki söz konusu değildir. Fonksiyon parametre değişkenlerinin isimleri için daha sonraki bildirimlerde farklı isimler kullanılması bir çelişki yaratmayacaktır. Çünkü bu isimlerin faaliyet alanı (name scope) yalnızca bildirimin yapıldığı parantezin içidir. Ancak aşağıdaki farklı bildirimler derleme zamanında error oluşturacaktır :

    double func(int x, double y);
    double func(int x, float y); /* error! bildirimler arasında çelişki var */

    long sample(double x);
    sample (double x); /* error! bildirimler arasında çelişki var. */


    Fonksiyon prototiplerinde parametre değişkenlerinin türlerinin de belirtilmesi, argumanların parametre değişkenlerime aktarılmasında tür dönüşümüne olanak sağlamaktadır. Bu durum tür dönüşümleri konusunda ele alınacaktır.


    3. KOŞUL OPERATÖRÜ

    Koşul operatörü (conditional operator) C dilinin 3 operand alan tek operatörüdür. (ternary operator) Koşul operatörünün 3 operandı, ifade tanımına uygun herhangi bir ifade olabilir. Koşul operatörünün genel sentaksı aşağıdaki gibidir:

    ifade1 ? ifade2 : ifade3

    Koşul operatörü yukarıdaki biçimden de görüldüğü gibi birbirinden ayrılmış iki atomdan oluşmaktadır. ? ve : atomları operatörün 3 operandını birbirinden ayırır.

    Derleyici bir koşul operatörü ile karşılaştığını ? atomundan anlar ve ? atomunun solundaki ifadenin (ifade1) sayısal değerini hesaplar. Eğer ifade1’in değeri 0 dışı bir sayısal değerse, bu durum koşul operatörü tarafından doğru olarak değerlendirilir, ve bu durumda yalnızca ifade2’nin sayısal değeri hesaplanır.

    Eğer ifade1’in değeri 0 ise bu durum koşul operatörü tarafından yanlış olarak değerlendirilir ve bu durumda yalnızca ifade3’ün sayısal değeri hesaplanır.

    Diğer operatörlerde olduğu gibi koşul operatörü de bir değer üretir. Koşul operatörünün ürettiği değer ifade1 doğru ise (0 dışı bir değer ise) ifade2’nin değeri, ifade1 yanlış ise ifade3’ün değeridir. Örnek:

    m = x > 3 ? y + 5 : y – 5;

    Burada önce x > 3 ifadesinin sayısal değeri hesaplanacaktır. Bu ifade 0 dışı bir değerse (yani doğru ise) koşul operatörü y + 5 değerini üretecektir. x > 3 ifadesinin değeri 0 ise (yani yanlış ise) koşul operatörü y – 5 değerini üretecektir. Bu durumda m değişkenine x > 3 ifadesinin doğru ya da yanlış olmasına göre y + 5 ya da y – 5 değeri atanacaktır.

    Aynı işlem if deyimi ile de yapılabilir :

    if (x > 3)
    m = y + 5;
    else
    m = y – 5;

    Koşul operatörü Operatör Öncelik Tablosunun 13. öncelik seviyesindedir. Bu seviye atama operatörünün hemen üstüdür. Aşağıdaki ifadeyi ele alalım:

    x > 3 ? y + 5 : y – 5 = m

    Koşul operatörünün önceliği atama operatöründen daha yüksek olduğu için, önce koşul operatörü ele alınır. x > 3 ifadesinin DOĞRU olduğunu ve operatörün y + 5 değerini ürettiğini düşünelim. Toplam ifadenin değerlendirilmesinde kalan ifade

    y + 5 = m

    olacak ve bu da derleme zamanı hatasına yol açacaktır. Çünkü y + 5 ifadesi sol taraf değeri değildir, nesne göstermez. (Lvalue required).

    Koşul operatörünün birinci kısmını (ifade1) parantez içine almak gerekmez. Ancak, okunabilirlik açısından genellikle parantez içine alınması tercih edilir.

    (x >= y + 3) ? a * a : b

    Koşul operatörünün üçüncü operandı (ifade3) konusunda dikkatli olmak gerekir. Örneğin :

    m = a > b ? 20 : 50 + 5

    a > b ifadesinin doğru olup olmamasına göre koşul operatörü 20 ya da 55 değerini üretecek ve son olarak da m değişkenine koşul operatörünün ürettiği değer atanacaktır. Ancak m değişkenine a > b ? 20 : 50 ifadesinin değerinin 5 fazlası atanmak isteniyorsa bu durumda ifade aşağıdaki gibi düzenlenmelidir:

    m = (a > b ? 20 : 50) + 5

    Koşul operatörünün 3 operandı da bir fonksiyon çağırma ifadesi olabilir, ama çağırılan fonksiyonların geri dönüş değeri üreten fonksiyonlar olması (void olmayan) gerekmektedir. Üç operanddan biri geri dönüş değeri void olan bir fonksiyona ilişkin fonksiyon çağırma ifadesi olursa koşul operatörü değer üretmeyeceğinden bu durum derleme zamanında hata oluşmasına neden olacaktır.

    Aşağıdaki kod parçasını inceleyelim:

    #include <stdio.h>

    int func1(void);
    int func2(void);
    int func3(void);

    int main()
    {
    int m;

    m = func1() ? func2() : func3();
    return 0;
    }

    Yukarıda koşul operatörünün kullanıldığı ifadede m değişkenine, func1 fonksiyonunun geri dönüş değerinin 0 dışı bir değer olması durumunda func2 fonksiyonunun geri dönüş değeri, aksi halde func3 fonksiyonunun geri dönüş değeri atanacaktır.

    Koşul operatörünün ürettiği bir nesne değil bir değerdir. Koşul operatörünün ürettiği değer nesne göstermediği için bu değere bir atama yapılamaz. Aşağıdaki if deyimini inceleyelim:

    if (x > y)
    a = 5;
    else
    b = 5;

    Yukarıdaki if deyimine x > y ifadesinin doğru olması durumunda a değişkenine, yanlış olması durumunda ise b değişkenine 5 değeri atanıyor. Aynı işi koşul operatörü kullanarak yapmak istenip de aşağıdaki ifade oluşturulursa:

    (x > y) ? a : b = 5;

    bu durum derleme zamanı hatasına yol açacaktır. Çünkü koşul operatörünün ürettiği a ya da b değişkenlerinin değeridir, nesnenin kendisi değildir. Böyle bir atama sol tarafın nesne gösteren bir ifade olmamasından dolayı derleme zamanında hata oluşturur.

    Aynı nedenden dolayı aşağıdaki ifadenin değerlendirilmeside derleme zamanında hata oluşmasına neden olacaktır:

    (x > 5 ? y : z)++;

    Parantez içindeki ifade değerlendirildiğinde elde edilen y ya da z nesneleri değil bunların değerleridir. Yani postfix ++ operatörünün operandı nesne değildir. Bu durumda error oluşacaktır. (L value required!)


  6. #6
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    Koşul Operatörünün Kullanıldığı Durumlar

    Koşul operatörü her zaman if deyimine bir alternatif olarak kullanılmamalıdır. Koşul operatörünün kullanılmasının tavsiye edildiği tipik durumlar vardır ve bu durumlarda genel fikir, koşul operatörünün ürettiği değerden aynı ifade içinde faydalanmak, bu değeri bir yere aktarmaktır.

    1. Bir önermenin doğru ya da aynlış olmasına göre farklı iki değerden birinin aynı değişkene aktarılması durumunda:

    p = (x == 5) ? 10 : 20;

    m = (a >= b + 5) ? a + b : a – b;

    Yukarıdaki deyimleri if else yapısıyla da kurabilirdik:
    if (x == 5)
    p = 10;
    else
    p = 20;

    if (a >= b + 5)
    m = a + b;
    else
    m = a – b;

    2. Fonksiyonlarda return ifadesini oluştururken:

    return (x > y ? 10 : 20);

    Bu örnekte x > y ifadesinin doğru olup olmamasına göre fonksiyonun geri dönüş değeri 10 ya da 20 olacaktır. Yukaridaki ifade yerine aşağıdaki if yapısı da kullanılabilirdi :

    if (x > y)
    return 10;
    else
    return 20;

    3. Fonksiyon çağırma ifadelerinde arguman olarak:

    func(a == b ? x : y);

    Bu ifadede func fonksiyonu a değişkeninin b degişkenine eşit olup olmaması durumuna
    göre x ya da y argumanlarıyla çağırılacaktır. if deyimiyle aşağıdaki şekilde karşılanabilir:

    if (a == b)
    func(x);
    else
    func(y);

    4. if deyiminde koşul ifadesi olarak da koşul operatörünün kullanıldığı görülür:

    if ( y == (x > 5 ? 10 : 20))

    Yukaridaki ifadede x > 5 ifadesinin doğru olup olmamasına göre if parantezi içinde y
    değişkeninin 10 ya da 20’ye eşitliği test edilecektir.

    Yukarıdaki durumlarda koşul operatörünün if deyimine tercih edilmesi iyi tekniktir. Bu durumlarda koşul operatörü daha okunabilir bir yapı oluşturmaktadır. (C’ye yeni başlayanlar için if yapısı daha iyi okunabilir ya da algılanabilir, ancak bir C programcisi için koşul operatörünün kullanılması daha okunabilir bir yapı oluşturur.)

    Koşul operatörünün bilinçsizce kullanılmaması gerekir. Eğer koşul operatörünün ürettiği değerden doğrudan faydalanılmayacaksa koşul operatörü yerine if kontrol deyimi tercih edilmelidir. Örneğin:

    x > y ? a++ : b++;

    Deyiminde koşul operatörünün ürettiği değerden faydalanılmamaktadır. Burada aşağıdaki if yapısı tercih edilmelidir:

    if (x > y)
    a++;
    else
    b++;

    Başka bir örnek:

    x == y ? printf(“eşit\n”) : printf(“eşit değil\n”);

    Bu örnekte printf fonksiyonunun bir geri dönüş değeri üretmesinden faydalanılarak koşul operatörü kullanılmıştır. Koşul operatörü x == y ifadesinin doğru olup olmamasına göre, 2. veya 3. ifade olan printf fonksiyonu çağırma ifadelerinden birinin değerini (geri dönüş değerini) üretecektir. (Bu da aslında ekrana yazılan karakter sayısıdır.) Ama ifade içinde koşul operatörünün ürettiği değerin kullanılması söz konusu değildir. Burada da if kontrol deyimi tercih edilmelidir :

    if (x == y)
    printf(“eşit\n”);
    else
    printf(“eşit değil\n”);

    Koşul operatörünün ikinci ve üçüncü operandlarının türleri farklı ise tür dönüştürme kurallari diğer operatörlerde olduğu gibi devreye girecektir:

    long a;
    int b;

    m = (x == y) ? b : a;

    Bu örnekte a nesnesi long türden b nesnesi ise int türdendir. x == y karşılaştırma ifadesi yanlış olsa bile tür dönüşümü gerçekleşecek ve int türden olan b long türe dönüştürülecektir.

    Bazı durumlarda koşul operatörünün de if kontrol değiminin de kullanılması gerekmeyebilir.

    if (x > 5)
    m = 1;
    else
    m = 0;

    Yukarıdaki if deyimi koşul operatörü ile aşağıdaki şekilde oluşturulabilirdi :

    m = (x > 5) ? 1 : 0;

    Ancak koşul operatörünün üreteceği değerlerin 1 veya 0 olabileceği durumlarda doğrudan karşılaştırma operatörünü kullanmak daha iyi teknik olarak değerlendirilmelidir.

    m = x > 5;

    Başka bir örnek :

    return x == y ? 1 : 0;

    yerine

    return x == y;

    yazabilirdik.

    Aşağıda tanımlanan is_leap fonksiyonunu inceleyelim, fonksiyonun geri dönüş değerinin üretilmesinde koşul operatörü kullanılmayıp doğrudan mantıksal operatörlerin 0 ya da 1 değeri üretmeleri fikrinden faydalanılmıştır:

    #define BOOL int

    BOOL is_leap(int year)
    {
    return !(year % 4) && year % 100 || !(year % 400);
    }

    Koşul operatörünün öncelik yönü sağdan soladır. (right associative). Bir ifade içinde birden fazla koşul operatörü varsa önce en sağdaki değerlendirilecektir.

    Aşağıdaki kod parçasını inceleyelim :

    int x = 1;
    int y = 1;

    m = x < 5 ? y == 0 ? 4 : 6 : 8;
    printf("m = %d\n", m);

    Yukarıdaki kod parçasında printf fonksiyonu çağırımında m değişkeninin değeri 6 olarak yazdırılacaktır. İfade aşağıdaki gibi ele alınacaktır :

    m = x < 5 ? (y == 0 ? 4 : 6) : 8;


    2. DÖNGÜ DEYİMLERİ

    Bir program parçasının yinelemeli olarak çalıştırılmasını sağlayan kontrol deyimlerine döngü denir. C dilinde 3 ayrı döngü deyimi vardır:

    Kontrolün başta yapıldığı while döngüleri
    Kontrolün sonda yapıldığı while döngüleri (do while döngüleri)
    for döngüleri

    Bu döngü deyimlerinden en fazla kullanılanı for deyimidir. for deyimi yalnızca C dilini değil, tüm programlama dillerinin en güçlü döngü yapısıdır. Aslında while ya da do while döngüleri olmasa da bu döngüler kullanılarak yazılan kodlar, for döngüsüyle yazılabilir. Ancak okunabilirlik açısından while ve do while döngülerinin tercih edildiği durumlar vardır.

    Kontrolun Basta Yapıldığı while Döngüleri

    Genel biçimi:

    while (ifade)
    deyim;

    while anahtar sözcüğünü izleyen parantez içerisindeki ifadeye kontrol ifadesi (control statement) denir. while parantezini izleyen ilk deyime döngü gövdesi denir. Döngü gövdesi basit bir deyim olabileceği gibi, bloklanmış birden fazla deyimden de (bileşik deyim) oluşabilir.

    while deyiminin icrası şu şekilde olur: Kontrol ifadesinin sayısal değeri hesaplanır. Kontrol ifadesinin sayısal değeri 0 dışı bir değerse, mantıksal olarak doğru kabul edilir ve döngü gövdesindeki deyim ya da deyimler çalıştırılır. Kontrol ifadesinin sayısal değeri 0 ise mantıksal olarak yanlış kabul edilir programın akışı döngünün dışındaki ilk deyimle devam eder. Yani while döngüsü, kontrol ifadesinin sayısal değeri 0 dışı bir değer olduğu sürece, döngü gövdesini oluşturan deyim(ler)in icrası ile devam eder.

    Daha önce belirttiğimiz gibi C dilinde yalın bir deyimin olduğu yere bileşik bir deyim de yerleştirilebilir. Bu durumda while döngüsü aşağıdaki gibi de oluşturulabilir :

    while (ifade) {
    ifade1;
    ifade2;
    ifade3;
    ......
    }

    Bu durumda kontrol ifadesinin sayısal değeri 0 dışı bir değer olduğu (doğru) sürece blok parantezleri arasında kalan tüm deyimler icra edilecektir. Örnek:

    #include <stdio.h>

    int main()
    {
    int i = 0;

    while (i < 10) {
    printf ("%d\n", i)
    ++i;
    }
    return 0;
    }

    Başka bir örnek:

    #include <stdio.h>
    #include <ctype.h>

    int main()
    {
    char ch;

    while (ch = getch(), toupper(ch) != 'Q')
    putchar(ch);
    return 0;
    }

    Başka bir örnek daha:

    int main()
    {
    char ch;

    while (ch = getch(), isupper (ch))
    putchar(ch);
    return 0;
    }

    while parantezinin içindeki ifade yani koşul ifadesi, ifade tanımına uygun herhangi bir ifade olabilir.

    while (1) {
    ....
    }

    Yukarıdaki while deyiminde kontrol ifadesi olarak bir sabit olan 1 sayısı kullanılmıştır. 1 değeri 0 dışı bir değer olduğundan ve kontrol ifadesi bir değişkene bağlı olarak değişemeyeceğinden, böyle bir döngüden çıkmak mümkün olmayacaktır. Bu tür döngülere sonsuz döngüler (infinite loops) denir. Sonsuz döngüler bir yanlışlık sonucu oluşturulabildiği gibi, bilinçli olarak da oluşturulabilir. Sonsuz döngülerden bazı yöntemlerle çıkılabilir.

    break Anahtar Sözcüğü

    break anahtar sözcüğü ile bir döngü sonlandırılabilir. Kullanımı

    break;

    şeklindedir. Programın akışı break anahtar sözcüğünü gördüğünde, döngü kırılarak döngünün akışı döngü gövdesi dışındaki ilk deyim ile devam eder. Yani koşulsuz olarak döngüden çıkılır.

    int main (void)
    {
    char ch;

    while (1) {
    ch = getch();
    if (ch == 'q')
    break;
    putchar(ch);
    }
    printf("döngüden çıkıldı!..\n");
    return 0;
    }

    Yukarıdaki programda bir sonsuz döngü oluşturulmuştur. Döngü içerisinde, döngünün her bir iterasyonunda ch değişkenine klavyeden bir değer alınmaktadır. Eğer klavyeden alınan karakter 'q' ise break anahtar sözcüğüyle programın akışı while döngü gövdesi dışındaki ilk deyimle devam edecektir.

    İçiçe Döngüler

    Bir döngünün gövdesini başka bir kontrol deyimi oluşturabilir. Döngü gövdesini oluşturan kontrol deyimi bir if deyimi olabileceği gibi başka bir döngü deyimi de olabilir. (while, do while, for deyimleri)

    int main()
    {
    int i = 0;
    int k = 0;

    while (i < 10) {
    while (k < 10) {
    printf("%d %d", i, k);
    ++k;
    }
    }
    ++i;
    return 0;
    }

    İçiçe döngülerde içerideki döngüde break deyimi kullanıldığında yalnızca içerideki döngüden çıkılır, her iki döngüden birden çıkmak için goto deyimi kullanılmalıdır. (ileride göreceğiz)

    while döngüsü bir bütün olarak tek deyim içinde ele alınır. Örnek:

    while (1)
    while (1) {
    .....
    .....
    .....,
    }

    Burada ikinci while döngüsü tek bir kontrol deyimi olarak ele alınacağı için, bloklamaya gerek yoktur.

    while döngüsünün yanlışlıkla boş deyim ile kapatılması çok sık yapılan bir hatadır.

    int main()
    {
    int i = 10;

    while (--i > 0); /* burada bir boş değim var */
    printf("%d\n", i);
    return 0;
    }

    Döngü while parantezi içerisindeki ifadenin değeri 0 olana kadar devam eder ve boş değim döngü gövdesi olarak icra edilir. Döngüden çıkıldığında ekrana 0 basılır.

    Sonlandırıcı ; while parantezinden sonra konulursa herhangi bir sentaks hatası oluşmaz. Derleyici while döngüsünün gövdesinin yalnızca bir boş deyimden oluştuğu sonucunu çıkartır. Eğer bir yanlışlık sonucu değil de bilinçli olarak while döngüsünün gövdesinde boş deyim (null statement) bulunması isteniyorsa, okunabilirlik açısından, bu boş deyim while parantezinden hemen sonra değil, alt satırda ve bir tab içeriden yazılmalıdır.

    while Döngüsü İçerisinde Postfix ++ ya da -- Operatörünün Kullanılması

    Bir postfix artırım ya da eksiltme işlemi yapıldığında önce döngüye devam edilip edilmeyeceği kararı verilir, sonra artırım ya da eksiltim uygulanır. Örnek :

    int main()
    {
    int i = 0;

    while (i++ < 100)
    printf("%d\n", i);
    printf("%d\n", i); /* ekrana 101 değerini basar. */
    return 0;
    }

    Başka bir örnek:

    ...
    int i = 10;

    while (i-- > 0)
    printf("%d\n", i);
    printf("%d\n", i);

    n bir pozitif tam sayı olmak üzere while döngüsü kullanılarak n defa dönen bir while döngüsü oluşturmak için

    while (n-- > 0)

    ya da

    while (n--)

    kullanılabilir. Aşağıdaki içiçe döngü yapısı bir gecikme sağlamak için kullanılmıştır.

    int main()
    {
    int i = 0;
    long j;

    while (i++ < 10) {
    printf("%d\n", i);
    j = 1000000L;
    while (j--> 0 )
    ;
    return 0;
    }

    Bazen döngüler bilinçli bir şekilde boş deyimle kapatılmak istenebilir. Bu durumda boş deyim normal bir deyim gibi tablama kuralına uygun olarak yerleştirilmelidir.

    Kontrolün Sonda Yapıldığı while Döngüleri

    Genel biçim;

    1. do
    ifade 1;
    while (ifade 2);

    2. do {
    ifade 1;
    ifade 2;
    } while (ifade);

    do while döngüsünde kontrol ifadesi sondadır. while parantezinden sonra sonlandırıcı ";" bulunmalıdır. Yani buradakı sonlandırıcı yanlışlık sonucu koyulmamıştır, deyime ilişkin sentaksın bir parçasıdır. Döngü gövdesindeki deyim(ler) en az bir kere icra edilecektir. Örnek :

    int main()
    {
    int i = 0;
    do {
    ++i;
    printf("%d\n", i);
    } while (i < 10);
    return 0;
    }

    Başka bir örnek:

    int main()
    {
    char ch;

    do {
    printf ("(e)vet / (h)ayıt?\n");
    ch = getch();
    } while (ch != 'e' && ch != 'h');
    printf("ok...\n");
    return 0;
    }

    Uygulama

    1'den 100'e kadar sayıları her satırda beş tane olacak biçimde ekrana yazan bir C programının yazılması:

    #include <stdio.h>

    int main()
    {
    int i = 0;

    do {
    printf("%d ", ++i);
    if (i % 5 == 0)
    printf("\n");
    } while (i < 100);
    return 0;
    }

    Başka bir çözüm:

    #include <stdio.h>

    void main()
    {
    int i = 0;

    while (i < 100) {
    printf ("%d", i);
    if (i % 5 == 4)
    printf("\n");
    ++i;
    return 0;
    }

    Uygulama

    Bir tamsayının basamak sayısını bulan program.

    #include <stdio.h>

    int main()
    {
    int digits = 0;
    int number;

    printf("bir tamsayi girin: ");
    scanf("%d", &number);

    do {
    n /= 10;
    digits++;
    } while (n > 0);
    printf("the number has %d digit(s).\n", digits);
    return 0;
    }

    Aynı programı while döngüsü kullanarak yazalım.

    ...
    while (n > 0) {
    n /= 10;
    digits++;
    }
    ...

    do while döngüsü yerine while döngüsü kullanıldığında, girilen sayının 0 olması durumunda döngü gövdesindeki deyimler hiç icra edilmeyecekti. Bu durumda ekranda:

    The number has 0 digit(s)

    yazısı çıkacaktı.


    for Döngüleri


    for döngüleri yalnızca C dilinin değil, belki de tüm programlama dillerinin en güçlü döngü yapılarıdır. for döngülerinin genel biçimi şu şekildedir:

    for (ifade1; ifade2; ifade3)
    deyim1;

    for (ifade1; ifade2; ifade3) {
    deyim1;
    deyim2;
    ...
    }

    Derleyici for anahtar sözcüğünden sonra bir parantez açılmasını ve parantez içerisinde iki noktalı virgül bulunmasını bekler. Bu iki noktalı virgül for parantezini üç kısıma ayırır. Bu kısımlar yukarıda ifade1 ifade2 ve ifade 3 olarak gösterilmiştir.

    for parantezi içinde mutlaka 2 noktalı virgül bulunmalıdır. for parantezi içinin boş bırakılması, ya da for parantezi içerisinde 1, 3 ya da daha fazla noktalı virgülün bulunması derleme zamanında hata oluşmasına yol açacaktır.

    for parantezinin kapanmasından sonra gelen ilk deyim döngü gövdesini (loop body) oluşturur. Döngü gövdesi basit bir deyimden oluşabileceği gibi, bileşik deyimden de yani blok içine alınmış birden fazla deyimden de oluşabilir.

    for parantezi içerisindeki her üç kısımın da ayrı ayrı işlevleri vardır.

    for parantezinin 2. kısımını oluşturan ifadeye kontrol ifadesi denir. (control expression). Tıpkı while parantezi içindeki ifade gibi, döngünün devamı konusunda bu ifade söz sahibidir. Bu ifadenin değeri 0 dışı bir değer ise, yani mantıksal olarak doğru ise, döngü devam eder. Döngü gövdesindeki deyim(ler) icra edilir. Kontrol ifadesinin değeri 0 ise programın akışı for döngüsünün dışındaki ilk deyimle devam edecektir.

    Programın akışı for deyimine gelince, for parantezinin 1. kısmı 1 kez icra edilir ve genellikle döngü değişkenine ilk değer verme amacıyla kullanılır. (Böyle bir zorunluluk yoktur).

    for döngüsünün 3. kısım döngü gövdesindeki deyim ya da deyimler icra edildikten sonra, dönüşte çalıştırılır. Ve çoğunlukla döngü değişkeninin artırılması ya da azaltılması amacıyla kullanılır. (Böyle bir zorunluluk yok.)

    for (ilk değer; koşul; işlem) {
    ...
    ...
    ...
    }

    int main()
    {
    int i;

    for (i = 0; i < 2; ++i)
    printf(“%d\n”, i);
    printf(“son değer = %d\n”, i);
    return 0;
    }

    Yukarıdaki programı inceleyelim:

    Programın akışı for deyimine gelince, önce for parantezi içindeki 1. ifade icra ediliyor. Yani i değişkenine 0 değeri atanıyor.

    Şimdi programın akışı for parantezinin 2. kısmına yani kontrol ifadesine geliyor ve i < 2 koşulu sorgulanıyor. Kontrol ifadesinin değeri 0 dışı bir değer olduğu için, ifade mantıksal olarak doğru kabul ediliyor ve programın akışı döngü gövdesine geçiyor. Döngü gövdesi bloklanmadığı için, döngü gövdesinde tek bir deyim var. (basit deyim). Bu deyim icra ediliyor. Yani ekrana i değişkeninin değeri yazılarak imleç alt satıra geçiriliyor.

    Programın akışı bu kez for parantezinin 3. kısımına geliyor ve buradaki ifade bir deyimmiş gibi icra edeiliyor, yani i değişkeninin değeri 1 artırılıyor. i değişkeninin değeri 1 oluyor.

    2. ifade yeniden değerlendiriliyor ve i < 2 ifadesi doğro olduğu için bir kez daha döngü gövdesi içra ediliyor.

    Programın akışı yine for parantezinin 3. kısımına geliyor ve buradaki ifade bir deyimmiş gibi icra edeiliyor, yani i değişkeninin değeri 1 artırılıyor. i değişkeninin değeri 2 oluyor.

    Programın akışı yine for parantezinin 2. kısmına geliyor ve buradaki kontrol ifadesi tekrar sorgulanıyor. i < 2 ifadesi bu kez yanlış olduğu için programın akışı döngü gövdesine girmiyor ve programın akışı döngü gövdesi dışındaki ilk deyimle devam ediyor.
    Yani ekrana :

    son değer = 2

    yazılıyor.

    Uygulama

    1’den 100’e kadar olan sayıların toplamını bulan program :

    int main()
    {
    int i;
    int total = 0;

    for (i = 0; i < 100; ++i)
    total += i;
    printf(“Toplam = %d”, total);
    return 0;
    }

    Döngü değişkeninin tamsayı türlerinden birinden olması gibi bir zorunluluk yoktur. Döngü değişkeni gerçek sayı türlerinden de olabilir.

    int main()
    {
    double i;

    for (i = 0; i < 6.28; i = i + 0.01)
    printf(“lf\n”, i);
    return 0;
    }

    Daha önce de söylendiği gibi for parantezi içindeki her 3 kısım da ifade tanımına uygun ifadeler içerebilir, yani bir döngü değişkenine ilk değer verilmesi, döngü değişkenine bağlı bir koşul ifadesi olması, döngü değişkeninin de azaltılması ya da artırılması bir zorunluluk değildir.

    int main()
    {
    char ch;

    for (ch = getch(); ch != ‘p’ ; ch = getch())
    putchar(ch);
    return 0;
    }

    Başka bir örnek:

    int main()
    {
    for (printf(“1. ifade\n"); printf(“2. ifade\n”), getch() != ‘q’; printf(“3. ifade\n));
    }

    Virgül operatörü ile birleştirilmiş değimlerin soldan sağa doğru sırayla icra edileceğini ve toplam ifadenin üreteceği değerin en sağdaki ifadenin değeri olacağını hatırlayın.

    for döngüsünün 1. kısmı hiç olmayabilir. Örneğin döngü dışında, programın akışı for deyiminin icrasına gelmeden önce, döngü değişkenine ilk değer verilmiş olabilir.

    ...
    i = 0;
    for (; i < 100; ++i)
    printf(“%d\n”, i);

    for döngüsünün 3. kısmı da olmayabilir. Döngü değişkeninin artırılması ya da eksiltilmesi for parantezi içi yerine döngü gövdesi içerisinde gerçekleştirilebilir.

    1. ve 3. kısmı olmayan (yalnızca 2. kısma sahip) bir for döngüsü örneği:

    ...
    i = 0;

    for (; i < 100; ) {
    printf(“%d\n”, i);
    ++i;
    }
    ...

    1.ve 3. kısmı olmayan for döngüleri tamamen while döngüleriyle eşdeğerdir. C’de for döngüleriyle while döngüleriyle yapabildiğimiz herşeyi yapabiliriz. O zaman şöyle bir soru aklımıza gelebilir: Madem for döngüleri while döngülerini tamamen kapsıyor, o zaman while döngülerine ne gerek var? while döngülerinin bazı durumlarda kullanılması for döngülerine göre çok daha okunabilir bir yapı yaratmaktadır.

    for parantezinin 2. kısmı da hiç olmayabilir. Bu durumda kontrol ifadesi olmayacağı için döngü bir koşula bağlı olmaksızın sürekli dönecektir. Yani sonsuz döngü oluşturulacaktır. Ancak iki adet noktalı virgül yine parantez içinde mutlaka bulunmak zorundadır.

    for parantezinin hiçbir kısmı olmayabilir. Ancak yine iki noktalı virgül bulunmak zorundadır:

    ...
    i = 0;
    for (; {
    printf(“%d\n”, i);
    ++i;
    if (i == 100)
    break;
    }
    ...

    for (; ile while (1) eşdeğerdir. İkisi de sonsuz döngü belirtir.

    Sonsuz döngü oluşturmak için for (; biçimi while (1)'e göre daha çok tercih edilir. Bunun nedeni eski derleyicilerde while(1) ifadesi kullanıldığında dögünün her dönüşünde kontrol ifadesinin tekrar test edilmesidir. Ama yeni derleyicilerde böyle bir kontrol söz konusu değildir. Ama bazı programcılar while (1) ifadesini tercih ederler. (Tabi burada kontrol ifadesi 1 yerine 0 dışı herhangi bir değer de olabilirdi ama geleneksel olarak 1 ifadesi kullanılmaktadır.)

    Sonsuz Döngülerden Çıkış

    1. break anahtar sözcüğü ile.
    Bu durumda programın akışı döngü gövdesi dışındaki ilk deyime yönlenecektir. (eğer iç içe döngü varsa break anahtar sözcüğü ile yalnızca içteki döngüden çıkılacaktır.)

    2. return anahtar sözcüğü ile
    bu durumda fonksiyonun (main de olabilir) icrası sona erecektir.
    3. goto anahtar sözcüğüyle.
    İçiçe birden fazla döngü varsa en içteki döngü içinden en dıştaki döngünün dışına kadar çıkabiliriz. (goto anahtar sözcüğünün çok az sayıdaki faydalı kullanımından biri budur.)
    4. exit fonksiyonu ile. (ileride göreceğiz)

    continue Anahtar Sözcüğü

    continue anahtar sözcüğü de tıpkı break anahtar sözcüğü gibi bir döngü içerisinde kullanılabilir. Programın akışı continue anahtar sözcüğüne geldiğinde sanki döngü yinelemesi bitmiş gibi yeni bir yinelemeye geçilir. Eğer for döngüsü içerisinde kullanılıyorsa yeni bir yinelemeye geçmeden önce döngünün 3. kısmı yapılır. Örnek :

    #include <stdio.h>

    int main()
    {
    int i, k;
    char ch;

    for (i = 0; i < 100; ++i) {
    if (i % 3 == 0)
    continue;
    printf("%d\n", i);
    }
    return 0;
    }

    break anahtar sözcüğü bir döngüyü sonlandırmak için, continue anahtar sözcüğü de bir döngünün o anda içinde bulunulan yinelemesini sonlandırmak için kullanılır.

    continue anahtar sözcüğü özellikle, döngü içerisinde uzun if deyimlerini varsa, okunabilirliği artırmak amacıyla kullanılır.

    for (i = 0; i < n; ++i) {
    ch = getch();
    if (!isspace(ch)) {
    ...
    ...
    ...
    }
    }

    Yukarıdaki kod parçasında döngü içinde, klavyeden getch fonksiyonu ile değer atanan ch değişkeni boşluk karakteri değilse, bir takım deyimlerin icrası istenmiş. Yukarıdaki durum continue anahtar sözcüğüyle daha okunabilir hale getirilebilir :

    for (i = 0; i < n; ++i) {
    ch = getch();
    if (isspace(ch))
    continue;
    ...
    ...
    ...
    }

    n kere dönen for deyimi kalıpları

    for (i = 0; i < n; ++i)

    for (i = 1; i <= n; ++i)

    for (i = n - 1; i >= 0; --i)

    for (i = n; i > 0; --i)

    Bir döngüden çıkmak için döngü değişkeni ile oynamak kötü bir tekniktir. Bu programları okunabilirlikten uzaklaştırır. Bunun yerine break anahtar sözcüğü ile döngülerden çıkılmalıdır.

    Uygulama

    Basamaklarının küpleri toplamı kendisine eşit olan 3 basamaklı sayıları bulan program.

    #include <stdio.h>
    #include <conio.h>

    int main()
    {
    int i, j, k;
    int number = 100;

    clrscr();
    for (i = 1; i <= 9; ++i)
    for (j = 0; j <= 9; ++j)
    for (k = 0; k <= 9; ++k) {
    if (i * i * i + j * j * j + k * k * k == number)
    printf("%d sayısı şartları saglıyor\n", number);
    number++;
    }
    return 0;
    }

    Başka bir çözüm:

    #include <stdio.h>
    #include <conio.h>

    int main()
    {
    int i, b1, b2, b3;

    clrscr();
    for (i = 100; i <= 999; ++i) {
    b1 = i / 100;
    b2 = i % 100 / 10;
    b3 = i % 10;
    if (b1 * b1 * b1 + b2 * b2 * b2 + b3 * b3 * b3 == i)
    printf("%d sayısı şartları sağlıyor\n", i);
    }
    return 0;
    }

    Uygulama

    Kendisine gönderilen iki tamsayının obeb ve okek değerlerini hesaplayan fonksiyonlar.

    #include <stdio.h>
    #include <conio.h>

    int obeb(int number1, int number2);
    int okek(int number1, int number2);

    int main()
    {
    int x, y;
    int n = 20;

    clrscr();
    while (n-- > 0) {
    printf("iki tamsayı giriniz : ");
    scanf("%d%d", &x, &y);
    printf("obeb = %d\n", obeb(x, y));
    printf("okek = %d\n", okek(x, y));
    }
    getch();
    return 0;
    }

    int obeb(int number1, int number2)
    {
    int i;
    int min = (number1 < number2) ? number1 : number2;

    for (i = min; i >= 1; --i)
    if (number1 % i == 0 && number2 % i == 0)
    return i;
    }

    int okek(int number1, int number2)
    {
    int i;
    int max = (number1 > number2) ? number1 : number2;

    for (i = max; i <= number1 * number2; i += max)
    if (i % number1 == 0 && i % number2 == 0)
    return i;
    }

    Uygulama

    Kendisine gönderilen int türden argumanın faktöriyel değerini hesaplayan fonksiyon.

    long fact(int number)
    {
    int i;
    int result = 1;

    if (number == 0 || number == 1)
    return 1;
    for (i = 2; i <= number; ++i)
    result *= i;
    return result;
    }

    Uygulama

    Birinci parametre değişkeninde tutulan tamsayının ikinci parametre değişkeninde tutulan tamsayı kuvvetini hesaplayan fonksiyon.

    long power(int base, int exp)
    {
    long result = 1;
    int k;

    for (k = 1; k <= exp; ++k)
    result *= base;
    return result;
    }

    ya da

    long power(int base, int exp)
    {
    long result = 1;

    while (exp-- > 0)
    result *= base;
    }

    Uygulama

    Kendisine gönderilen sayının asal sayı olup olmadığını test eden isprime fonksiyonu.

    int isprime(long number)
    {
    int k;

    if (number == 0 || number == 1)
    return 0;
    if (number % 2 == 0)
    return number == 2;
    if (number % 3 == 0)
    return number == 3;
    if (number % 5 == 0)
    return number == 5;

    for (k = 7; k * k <= number; k += 2)
    if (number % k == 0)
    return 0;
    return 1;
    }

    Uygulama

    Bölenlerinin toplamına eşit olan sayılara mükemmel tamsayı (perfect integer) sayı denir. Örneğin 28 sayısı bir mükemmel tamsayıdır.

    1 + 2 + 4 + 7 + 14 = 28

    Kendisine gönderilen bir argumanın mükemmel tamsayı olup olmadığını test eden is_perfect fonksiyonu.

    #include <stdio.h>

    int is_perfect(int number);

    int main()
    {
    int k;

    for (k = 1000; k <= 9999; ++k)
    if (isperfect(k)) {
    printf("%d perfect\n");
    return 0;
    }
    return 0;
    }

    int is_perfect(int number)
    {
    int i;
    int total = 1;

    for (i = 2; i < = number / 2; ++i)
    if (number % i == 0)
    total += i;
    return number == total;
    }

    Uygulama

    int türden bir sayıyı çarpanlarına ayıran ve çarpanları küçükten büyüğe ekrana yazdıran display_factors fonksiyonunu.

    #include <stdio.h>
    #include <conio.h>

    void factors(int number);

    /* test kodu : 1111 ile 1200 arasındaki sayıları çarpanlara ayırıyor */

    int main()
    {
    int k;

    for (k = 1111; k < 1200; ++k) {
    printf("%d sayısının çarpanları = ", k);
    factors(k);
    putchar('\n');
    getch();
    }
    return 0;
    }

    void factors(int number)
    {
    int temp = number;
    int k;

    for (k = 2; k <= number / 2; ++k)
    while (temp % k == 0) {
    printf("%d ", k);
    temp /= k;
    }
    }

    Uygulama

    Klavyeden alınan cümleyi ekrana yazan ve cümle "." karakteriyle sonlanınca yazılan toplam kelime sayısını ve ortalama kelime uzunlugunu bulan program.

    #include <stdio.h>
    #include <conio.h>
    #include <ctype.h>

    int main()
    {
    int ch;
    int wordlength = 0, total = 0, wordcount = 0;

    clrscr();

    while ((ch = getch()) != '.') {
    putchar(ch);
    if (isspace(ch)) {
    total += wordlength;
    if (wordlength)
    wordcount++;
    wordlength = 0;
    }
    else
    wordlength++;
    }
    wordcount++;
    total += wordlength;
    printf("\n\ntoplam kelime sayısı = %d\n", wordcount);
    printf("ortalama uzunluk = %f\n", (double) total / wordcount);
    return 0;
    }

    Uygulama

    Klavyeden sürekli karakter alınmasını sağlayan, alınan karakterleri ekranda gösteren, ancak arka arkaya "xyz" karakterleri girildiğinde sonlanan bir program.

    #include <stdio.h>
    #include <conio.h>

    main()
    {
    char ch;
    int total = 0;

    while (total < 3) {
    ch = getch();
    putchar(ch);
    if (ch == 'ç' && total == 0)
    total++;
    else if (ch == 'ı' && total == 1)
    total++;
    else if (ch == 'k' && total == 2)
    total++;
    else total = 0;
    }
    return 0;
    }

    Çalışma Soruları

    Prototipi aşağıda verilen isprimex fonksiyonunu tanımlayınız.

    int isprimex(long number);

    isprimex fonksiyonuna gönderilen argumanın asal olup olmadığı test edilecek, eğer sayı asal ise bu kez sayının basamak değerleri toplanarak elde edilen sayının asal olup olmadığı test edilecektir. Bu işlem sonuçta tek basamaklı bir sayı kalana kadar devam edecektir. Eğer en son elde edilen tek basamaklı sayı dahil tüm sayılar asal ise isprimex fonksiyonu 0 dışı bir değere geri dönecektir. Eğer herhangi bir kademede asal olmayan bir sayı elde edilirse fonksiyon 0 değerine geri dönecektir.

    Yazdığınız fonksiyonu aşağıdaki main fonksiyonu ile test edebilirsiniz :

    #include <stdio.h>
    #include <conio.h>

    int isprimex(long number);

    int main()
    {
    long k;

    clrscr();

    for (k = 19000; k <= 20000; ++k)
    if (isprimex(k))
    printf("%ld \n", k);
    return 0;
    }


  7. #7
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    TÜR DÖNÜŞÜMLERİ

    Bilgisayarların aritmetik işlemleri gerçekleştirmesinde bir takım kısıtlamalar söz konusudur.

    Bilgisayarların aritmetik bir işlemi gerçekleştirmesi için (genellikle) işleme sokulan operandların uzunluklarının aynı olması (yani bit sayılarının aynı olması) ve aynı şekilde belleğe yerleştirilmiş olmaları gerekmektedir. Örnek vermek gerekirse, bilgisayar 16 bit uzunluğunda iki tam sayıyı doğrudan toplayabilir ama 16 bit uzunluğunda bir tam sayı ile 32 bit uzunluğundaki bir tamsayıyı ya da 32 bit uzunluğunda bir tamsayı ile 32 bit uzunluğunda bir gerçek sayıyı doğrudan toplayamaz.

    C programlama dili, değişik türlerin aynı ifade içerisinde bulunmalarına izin verir. Yani tek bir ifadede bir tamsayı türünden değişken, bir float sabit ya da char türden bir değişken birlikte yer alabilir. Bu durumda C derleyicisi bunları herhangi bir işleme sokmadan önce bilgisayar donanımının ifadeyi değerlendirebilmesi için uygun tür dönüşümlerini yapar.

    Örneğin 16 bitlik bir int sayıyla 32 bitlik bir int sayıyı topladığımızda, derleyici önce 16 bitlik int sayıyı 32 bit uzunluğunda bir int sayıya dönüştürecek ve ondan sonra toplama işlemini gerçekleştirecektir. Yine 16 bitlik bir int sayıyla 64 bitlik bir double sayıyı çarpmak istediğimizde derleyici önce int sayıyı 64 bitlik bir double sayıya dönüştürecektir. Bu tür dönüşümü daha komplekstir çünkü int ve double sayılar bellekte farklı şekillerde tutulurlar.

    Bu tür dönüşümleri programcının kontrolü dışında otomatik olarak gerçekleştirilirler. Bu tür dönüşümlerine otomatik tür dönüşümleri (implicit type conversions) diyeceğiz. C dili programcıya herhangi bir değişkenin ya da sabitin türünü, bir operatör kullanarak değiştirme olanağı da verir. Programcı tarafından yapılan bu tip tür dönüşümlerine bilinçli tür dönüşümleri (explicit type conversions/type casts) diyeceğiz.

    Önce otomatik tür dönüşümlerini inceleyeceğiz. Otomatik tür dönüşümleri ne yazık ki karmaşık yapıdadır ve iyi öğrenilmemesi durumunda programlarda hatalar kaçınılmazdır. Zira C dilinde 11 ana tür vardır. Herhangi bir hataya düşmemek için bunların her türlü ikili kombinezonu için nasıl bir otomatik tür dönüşümü yapılacağını çok iyi bilmemiz gerekir.

    Aşağıda belirtilen 4 durumda mutlaka otomatik bir tür dönüşümü yapılacaktır:

    1. Aritmetik ya da mantıksal bir ifadedenin operandları aynı türden değilse:
    ...
    int x, y;
    float fl;

    if (fl * ix / iy) {
    ...
    }

    Bu durumda yapılan tür dönüşümlerine genel aritmetik tür dönüşümleri diyeceğiz.

    2. Atama operatörü kullanıldığında atama operatörünün sağ tarafıyla sol tarafı aynı türden değilse:

    double toplam;
    long sayi1, sayi2;

    toplam = sayi1 + sayi2;

    Bu durumda yapılan tür dönüşümlerine atama tür dönüşümleri diyeceğiz.

    3. Bir fonksiyon çağırılması sırasında kullanılan bir argumanın türü ile fonksiyonun ilgili parame değişkeni aynı türden değilse:

    double sonuc;
    int sayi1, sayi2;
    ...
    sonuc = pow(sayi1, sayi2)

    double pow (double taban, double us)
    {
    ...
    }


    4. Bir return ifadesinin türü ile ilgili fonksiyonun geri dönüş değerinin türü arasında farklılık varsa:

    double funktion (int para1, int para2)
    {
    ...
    return (para1 + para2)
    }

    3. ve 4. durumlar da bir atama işlemi olarak düşünülebilir. Zira fonksiyon çağırma ifadesindeki argumanlar parametre değişkenlerine kopyalanarak (atanarak) geçirilir. Yine return ifadesi de aslında geçici bölgeye yapılan bir atama işlemidir.

    Otomatik Tür Dönüşümleri

    Otomatik tür dönüşümleri iki operand alan operatörlerin bulunduğu ifadelerde operandların türlerinin farklı olması durumunda uygulanır. Ve otomatik tür dönüşümü sonucunda farklı iki tür olması durumu ortadan kaldırılarak operandların her ikisinin de türlerinin aynı olması sağlanır. örneğin

    int i;
    double d, result;

    result = i + d;

    Bu ifadenin sağ tarafında yer alan i ve d değişkenlerinin türleri farklıdır. (biri int diğeri double türden). Bu durumda i ve d nesnelerinin türleri otomatik olarak aynı yapılır. Peki int tür mü double türe dönüştürülecek yoksa double tür mü int türe dönüştürülecek? Eğer double tür int türe dönüştürülse bilgi kaybı söz konusu olurdu (Çünkü bu durumda gerçek sayının en az virgülden sonraki kısmı kaybedilir).

    C'de otomatik tür dönüşümleri mümkünse bilgi kaybı olmayacak şekilde yapılır.

    Bu durumda bilgi kaybını engellemek için genel olarak küçük tür büyük türe dönüştürülür. (istisnaları var) Bu duruma terfi (promotion) diyeceğiz.

    Kuralları detaylı olarak öğrenmek için oluşabilecek durumları iki ana durum altında inceleyelim:

    1. Operandlardan birinin türü gerçek sayı türlerinden biriyse.

    Operandlardan birinin long double diğerinin farklı bir tür olması durumunda diğer operand long double türüne çevrilir.

    Operandlardan birinin double diğerinin farklı bir tür olması durumunda diğer operand double türüne çevrilir.

    Operandlardan birinin float diğerinin farklı bir tür olması durumunda diğer operand float türüne çevrilir.

    2. Operandlardan hiçbiri gerçek sayı türlerinden değilse:

    Eğer ifade içindeki operandlardan herhangi biri char, unsigned char, short ya da unsigned short türden ise aşağıdaki algoritma uygulanmadan önce bu türler int türe dönüştürülür. Bu duruma “tam sayiya terfi” (integral promotion) denir.

    Daha sonra aşağıdaki kurallar uygulanır:

    Operandlardan birinin unsigned long diğerinin farklı bir tür olması durumunda (long, unsigned int, int) diğer operand unsigned long türüne çevrilir.

    Operandlardan birinin long türden diğerinin farklı bir türden olması durumunda (unsigned int, int) diğer operand long türüne çevrilir.

    Operandlardan biri unsigned int türden diğerinin farklı bir türden olması durumunda (int) ikinci operand unsigned int türüne çevrilir.

    İstisnalar:

    Eğer operandlardan biri long int diğeri unsigned int türünden ise ve kullanılan sistemde bu türlerin uzunlukları aynı ise (UNIX ve Win 32 sistemlerinde olduğu gibi) her iki tür de unsigned long int türüne dönüştürülür.

    Eğer operandlardan biri int diğeri unsigned short türünden ise ve kullanılan sistemde bu türlerin uzunlukları aynı ise (DOS'da olduğu gibi) her iki tür de unsigned int türüne dönüştürülür.

    Otomatik tür dönüşümleri ile ilgili kuralları aşağıdaki tabloda özetleyelim:
    operandlardan herhangi biri
    yapılacak dönüşüm
    char,short,unsigned char,unsigned short
    ilgili operand int türe dönüştürülecek.


    operand 1
    operand 2
    yapılacak dönüşüm
    long double
    double, float, unsigned long, long, int, unsigned int
    2. operand long double türüne çevrilecek.
    double
    float, unsigned long, long, int, unsigned int
    2. operand double türüne çevrilecek.
    float
    unsigned long, long, int, unsigned int
    2. operand float türüne çevrilecek.
    unsigned long
    long, int, unsigned int
    2. operand unsigned long türüne çevrilecek
    long
    int, unsigned int
    2. operand long türüne dönüştürülecek.
    (istisnai duruma dikkat!)
    unsigned int
    int
    2. operand unsigned int türüne dönüştürülecek.


    Fonksiyon çağırma ifadeleri de, operatörlerle birlikte başka ifadeleri oluşturuyorsa, otomatik tür dönüşümlerine neden olabilir. Zira geri dönüş değerine sahip olan fonksiyonlar için fonksiyonun çağırılma ifadesi, fonksiyonun geri dönüş değerini temsil etmektedir. Örneğin:

    int i = 5;
    ...
    pow(2, 3) + i

    ifadesinde pow fonksiyonunun geri dönüş değeri double türden olduğu için, int türden olan i değişkeni de, işlemin yapılabilmesi için double türüne çevrilerek işleme sokulacaktır.

    Atama Tür Dönüşümleri

    Bu tür dönüşümlerin çok basit bir kuralı vardır: Atama operatörünün sağ tarafındaki tür, atama operatörünün sol tarafındaki türe otomatik olarak dönüştürülür.

    Küçük türlerin büyük türlere dönüştürülmesinde bilgi kaybı söz konusu değildir. Örneğin :

    double leftx;
    int righty = 5;

    leftx = righty;

    yukarıdaki örnekte righty değişkeninin türü int’tir. Önce double türe otomatik dönüşüm yapılacak ve double hale getirilen 5 sabiti (5.) leftx değişkenine atanacaktır.

    Aşağıda 16 bitlik sistemler için bazı örnekler verilmektedir :
    TÜR
    desimal
    hex
    dönüştürülecektür
    hex
    desimal
    int
    138
    0x008A
    long int
    0x0000008A
    138L
    char
    ‘d’ (100)
    0x64
    int
    0x0064
    100
    int
    -56
    0xFFC8
    long int
    0xFFFFFFFC8
    -56L
    char
    ‘\x95’ (-07)
    0x95
    int
    0xFF95
    -107
    unsigned int
    45678
    0xB26E
    long int
    0XFFFFB26EL
    -19858L
    char
    ‘0’ (48)
    0x30
    long int
    0x00000030L
    30L


    Negatif olan bir tamsayı küçük türden büyük türe dönüştürüldüğünde sayının yüksek anlamlı bitleri negatifliğin korunması amacıyla 1 ile beslenmektedir. (Hex olarak F ile)

    Derleyici tarafından yapılan otomatik, atama tür dönüşümlerinde, büyük türün küçük türe dönüştürülmesi durumunda bilgi kaybı söz konusu olabilir.

    Aşağıdaki basit kuralları verebiliriz:

    Eğer atama operatörünün her iki tarafı da tam sayı türlerinden ise (char, short, int, long), atama operatörünün sağ tarafının daha büyük bir türden olması durumunda bilgi kaybı olabilir. Bilgi kaybı ancak, atama operatörünün sağ tarafındaki değerin, sol taraftaki türün sınırları içinde olmaması durumunda söz konusu olacaktır. Bilgi kaybı yüksek anlamlı byte’ların kaybolması şeklinde ortaya çıkar. Örnek:

    long m = 0x12345678;
    int y;

    y = m;
    printf (“m = %x\n”, m);

    Yukarıdaki örnekte int türden olan y değişkenine long türden bir değişkenin değeri atanmıştır. (16 bitlik bir sistemde örneğin DOS altında çalıştığımızı düşünüyoruz.). DOS altında int türü için sayı sınırları –32768 +32767 değerleridir. Bu sayılarda iki byte’lık bir alan için işaretli olarak yazılabilecek en büyük ve en küçük sayılardır. Hex gösterimde her bir hex digit 4 bite ve her iki hex digit 1 byte alana tekabül edecektir. Dolayısıyla 0x12345678 sayısı 8 hex digit yani 4 byte uzunluğunda bir sayıdır. Oysa atamanın yapılacağı taraf int türdendir ve bu tür max. 4 hex digit (2 byte) uzunlukta olabilmektedir. Bu durumda m değişkenine ilişkin değerin yüksek anlamlı 2 byte’ı yani (4 hex digiti) kaybedilecektir. Atama işleminden sonra, printf fonksiyonuyla y değişkeninin değeri yazdırıldığında (hex gösterimli) ekrana 5678 değerinin yazdırıldığı görülecektir.

    Atama operatörünün sağ tarafı gerçek sayı türlerinden bir türden ise (float, double, long double) ve sol tarafı ise tam sayı türlerinden birine ait ise, bu herşeyden önce “undefined behaviour” (şüpheli kod) durumudur ve bu durumun oluştuğu kodlardan kesinlikle kaçınmak gerekir. Ama derleyicilerin hemen hemen hepsi bu durumda aşağıdaki şekilde tür dönüşümü yaparlar:

    Sağ taraftaki gerçek sayı türündeki değer nokta içeriyorsa, önce kesir kısmı kaybedilir. Kesir kısmı kaybedildikten sonra kalan değer eğer sol taraf türünün sınırları içinde kalıyorsa daha fazla bir bilgi kaybı olmaz, fakat sol taraf türünün sınırları aşılıyorsa ilave bir bilgi kaybı daha olur ve yüksek anlamlı byte’lar kaybedilir.

    Örnek:

    double y = 234.12;
    int x;

    x = y;
    printf(“x = %d\n”, x ); /* x değişkenine 234 değeri atanacaktır */

    y = 32768.1;
    x = y;
    printf(“x = %d\n”, x ); /* x değişkenine -32768 değeri atanacaktır */

    Tam Sayıya Terfi (integral promotion)

    Daha önce de açıklandığı gibi “integral promotion” bir ifade içinde bulunan char, unsigned char, short, unsigned short türlerinin, ifadenin derleyici tarafından değerlendirilmesinden önce, otomatik olarak int türe dönüştürülmeleri anlamına gelir.

    Peki dönüşüm signed int türüne mi unsigned int türüne mi yapılacak, bunu nasıl bileceğiz?

    Genel kural şudur: Tür dönüşümüne uğrayacak türün bütün değerleri int türü ile ifade edilebiliyorsa int, edilemiyorsa unsigned int türüne dönüşüm yapılır.

    Örneğin unsigned short ve int türlerinin aynı uzunlukta olduğu DOS işletim sisteminde unsigned short türü, tamsayıya terfi ettirilirken unsigned int türüne dönüştürülür.

    Eğer tam sayıya terfi ettirilecek değer, signed char, unsigned char ya da short (signed short) türlerinden ise dönüşüm signed int türüne yapılacaktır.

    Bilgi kaybı ile ile ilgili şu hususu da göz ardı etmemeliyiz. Bazı durumlarda bilgi kaybı tür dönüşümü yapıldığı için değil yapılmadığı için oluşur. Sınır değer taşmaları buna iyi bir örnek olabilir.

    Örnek: (DOS altında çalıştığımızı düşünelim)

    long x = 1000 * 2000;

    Yukarıdaki kod ilk bakışta normal gibi görünüyor. Zira çarpma işleminin sonucu olan 2000000 değeri DOS altında long türü sayı sınırları içinde kalmaktadır. Oysa bilgi kaybı atama işleminden önce gerçekleşir. 1000 ve 2000 int türden sabitlerdir, işleme sokulduklarında çarpma operatörünün de ürettiği değer int türden olacaktır. Bu durumda 2 byte uzunlukta olan int türü 2000000 sayısını tutamayacağı için yüksek anlamlı byte kaybedilecektir. 2000000 hex sistemde 0x1E8480 olarak gösterilebilir. Yüksek anlamlı byte kaybedilince işlem sonucu 0x8480 olarak bulunur. 0x8480 negatif bir sayıdır. İkiye tümleyenini alırsak

    0x8480 1000 0100 1000 0000
    ikiye tümleyeni 0111 1011 1000 0000 (0x7B80 = 31616)

    Görüldüğü gibi işlem sonucu üretilecek değer –31616 dir. Bu durumda x değişkeninin türü long da olsa, atanacak değer –31616 olacaktır.

    Fonksiyon Çağırmalarında Tür Dönüşümü

    Daha önce söylendiği gibi bir fonksiyona gönderilecek argumanlarla, bu argumanları tutacak fonksiyonun parametre değişkenleri arasında tür farkı varsa otomatik tür dönüşümü gerçekleşecek ve argumanların türü, parametre değişkenlerinin türlerine dönüştürülecektir. Ancak bu tür dönüşümünün gerçekleşmesi için, derleyicinin fonksiyon çağırma ifadesine gelmeden önce fonksiyonun parametre değişkenlerinin türleri hakkında bilgi sahibi olması gerekecektir. Derleyici bu bilgiyi iki ayrı şekilde elde edebilir:

    1. Çağrılan fonksiyon çağıran fonksiyondan daha önce tanımlanmışsa, derleyici fonksiyonun tanımlamasından parametre değişkenlerinin türünü belirler.
    2. Fonksiyonun prototip bildirimi yapılmışsa derleyici parametre değişkenlerinin türü hakkında önceden bilgi sahibi olur.

    Örnek:

    #include <stdio.h>

    double func(double x, double y)
    {
    ...
    }

    int main()
    {
    int a, b;
    ...
    funk(a, b);
    return 0;
    }

    Yukarıdaki örnekte main fonksiyonu içinde çağırılan func fonksiyonuna arguman olarak int türden olan a ve b değişkenlerinin değerleri gönderilmiştir. Fonksiyon tanımlaması çağırma ifadesinden önce yer aldığı için int türden olan a ve b değişkenlerinin değerleri, double türe dönüştürülerek func fonksiyonunun parametre değişkenleri olan x ve y değişkenlerine aktarılırlar. func fonksiyonunun main fonksiyonundan daha sonra tanımlanması durumunda otomatik tür dönüşümünün yapılabilmesi için prototip bildirimi ile derleyiciye parametre değişkenlerinin türleri hakkında bilgi verilmesi gerekir.

    #include <stdio.h>

    double funk(double x, double y);

    int main()
    {
    int a, b;
    ...
    funk(a, b);
    return 0;
    }

    double funk(double x, double y)
    {
    ...
    }


    Peki çağırılan fonksiyon çağıran fonksiyondan daha sonra tanımlanmışsa, ve fonksiyon prototipi bildirimi yapılmamışsa (tabi bu durumda derleme zamanında error oluşmaması için fonksiyonun int türden bir geri dönüş değerine sahip olması gerekecektir) tür dönüşümü gerçekleşebilecek mi?
    Bu durumda fonksiyonun parametre değişkenlerinin türleri hakkında derleyici bilgi sahibi olamayacağı için, fonksiyona gönderilen argumanları, default arguman dönüşümü denilen dönüşüme tabi tutar. Default arguman dönüşümü şu şekilde olur:

    char ya da short türünden olan argumanlar tamsayıya terfi ettirilir. (integral promotion). float türünden olan argumanlar double türüne dönüştürülür. Bunun dışındaki türlerden olan argumanların tür dönüştürülmesine tabi tutulmaz.


    2. switch DEYİMİ


    switch deyimi ile bir ifadenin farklı değerleri için farklı işler yapmak mümkün hale gelir. Özellikle else if yapılarına bir alternatif olarak kullanılır.

    Genel biçimi:

    switch (ifade) {
    case ifade_1:
    case ifade_2:
    case ifade_3:
    .......
    case ifade_n:
    default:
    }

    switch, case ve default anahtar sözcüklerdir.

    switch Deyiminin Çalışması

    Derleyici switch parantezi içerisindeki ifadenin sayısal değerini hesaplar. Bu sayısal değere eşit bir case ifadesi olup olmadığını araştırır. Bulursa programın akışı o case ifadesine geçirilir. Artık program buradan akarak ilerler. Eğer switch parantezi içindeki ifadenin sayısal değeri hiçbir case ifadesine eşit değilse akış default anahtar sözcüğünün bulunduğu kısıma geçirilir. default anahtar sözcüğünün bulunması zorunlu değildir. Uygun bir case ifadesi yoksa ve default anahtar sözcüğü de yoksa programın akışı switch deyiminin içine girmez ve switch deyimi dışındaki ilk deyim ile devam eder.

    Örnek:

    #include <stdio.h>

    int main()
    {
    int a;

    scanf(“%d”, &a);

    switch (a) {
    case 1: printf(“bir\n”);
    case 2: printf(“iki\n”);
    case 3: printf(“üç\ n”);
    case 4: printf(“dört\n”);
    default: printf(“hiçbiri\n”);
    }
    return 0;
    }

    Burada unutulmaması gereken noktayı tekrar edelim: Uygun bir case ifadesi bulunduğunda programın akışı buraya geçirilir. Yalnızca bulunan case ifadesini izleyen deyim ya da deyimlerin icra edilmesi söz konusu değildir.Uygun case ifadesini izleyen deyimlerin icrasından sonra programın akışı daha aşağıda bulunan case ifadlerini izleyen deyimlerin icra edilmesiyle devam eder.

    Yukarıdaki örnekte scanf fonksiyonu ile a değişkenine 1 atandığını varsayalım. Bu durumda program çıktısı şu şekilde oluşacaktır :
    bir
    iki
    üç
    dört
    hiçbiri

    Eğer uygun case ifadesi bulunduğunda yalnızca bu ifadeye ilişkin deyim(ler)in icra edilmesini istersek break anahtar sözcüğünden faydalanırız. break anahtar sözcüğü döngülerden olduğu gibi switch deyimlerinden de çıkmamıza olanak verir.

    #include <stdio.h>,

    int main()
    {
    int a;

    scanf(“%d”, &a);

    switch (a) {
    case 1: printf(“bir\n”); break;
    case 2: printf(“iki\n”); break;
    case 3: printf(“üç\ n”); break;
    case 4: printf(“dört\n”); break;
    default : printf(“hiçbiri\n”); break;
    }
    return 0;
    }

    Uygulamalarda da switch deyiminde çoğunlukla her case ifadesi için bir break anahtar sözcüğünün kullanıldığını görürüz. (Tabi böyle bir zorunluluk yok!)

    case ifadelerini izleyen ":" atomundan sonraki deyimler istenildiği kadar uzun olabilir. case ifadelerinin sıralı olması ya da default anahtar sözcüğünün en sonda olması gibi bir zorunluluk yoktur. default herhangi bir yere yerleştirilebilir. default ifadesi nerede olursa olsun, programın akışı ancak uygun bir case ifadesi bulunamazsa default içine girecektir.

    Yukarıda da anlatıldığı gibi switch parantezi içerisindeki ifadenin sayısal değerine eşit bir case ifadesi bulunana kadar derleyici derleme yönünde (yani yukarıdan aşağıya doğru) tüm case ifadelerini sırasıyla kontrol eder. Bu yüzden en yüksek olasılığa ilişkin (biliniyorsa) case ifadesinin en başa (diğerlerinden önce) yerleştirilmesi iyi tekniktir.

    switch kontrol deyimi belli ölçülerde else if (else if ladder - cascaded if) yapılarıyla karşılanabilir. Yani switch deyimi olmasaydı yapmak istediklerimizi else if deyimi ile yapabilirdik. Ancak bazı durumlarda else if yapısı yerine switch deyimi kullanmak okunabilirliği artırmaktadır. Örneğin aşağıdaki iki kod işlevsel olarak birbirinin aynıdır:

    if ( a == 1) { switch (a) {
    ifade_1; case 1:
    ifade_2; ifade_1;
    } ifade_2;
    else if (a == 2) { case 2:
    ifade_3; ifade_3;
    ifade_4; ifade_4;
    } case 4:
    else if (a == 4) { ifade_5;
    ifade_5; default:
    } ifade_6;
    else { ifade_7;
    ifade_6; }
    ifade_7;
    }

    örnek :

    Bir tarih bilgisini (ay, gün, yıl) yazı ile gg – yazı – yyyy (12 - Ağustos - 2002 gibi) biçiminde ekrana yazan, prototipi void display_date(int day, int month, int year) biçiminde olan fonksiyon. .

    #include <stdio.h>
    #include <conio.h>

    void display_date(int day, int month, int year);

    int main()
    {
    int d, m, y;
    int k;

    for (k = 0; k < 10; ++k) {
    scanf(“%d%d%d”, &d, &m, &y);
    display_date(d, m, y);
    putchar('\n');
    }
    getch();
    return 0;
    }

    void display_date(int day, int month, int year)
    {
    printf(“%d – “, 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;
    case 9: printf(“Eylul”); break;
    case 10: printf(“Ekim”); break;
    case 11: printf(“Kasim”); break;
    case 12: printf(“Aralik”); break;
    }
    printf(“ %d\n”, year);
    }

    Birden fazla case ifadesi için aynı işlemlerin yapılması şöyle sağlanabilir.

    case 1:
    case 2:
    case 3:
    ifade1;
    ifade2;
    ifade3;
    break;
    case 4:

    Bunu yapmanın daha kısa bir yolu yoktur. Bazı programcılar bu yapıyı şu şekilde de yazarlar:
    case 1: case 2: case 3: case 4: case 5:
    ifade1:
    ifade2;


    Her switch deyimini else if yapısıyla karşılayabiliriz, ama her else if yapısını switch deyimiyle karşılayamayız. switch değiminin parantezi içindeki ifadenin türü tamsayı olmak zarundadır. case ifadeleri de tamsayı sabit ifadesi olmak zarundadır. switch deyimi, tamsayı türünden bir ifadenin değerinin değişik tamsayı değerlerine eşitliğinin test edilmesi ve eşitlik durumunda farklı işlerin yapılması için kullanılır. Oysa else if yapısında her türlü karşılaştırma söz konusu olabilir.

    Örnek :

    if (x > 20)
    m = 5;
    else if (x > 30 && x < 55)
    m = 3;
    else if (x > 70 && x < 90)
    m = 7;
    else
    m = 2;

    Yukarıdaki else if yapısını switch deyimiyle karşılayamayız.

    Şöyle bir soru aklımıza gelebilir : Madem her switch yapısını else if yapısıyla karşılayabiliyoruz, o zaman switch deyimine ne gerek var? Yani switch deyiminin varlığı C diline ne kazandırıyor?

    switch deyimi bazı durumlarda else if yapısına göre çok daha okunabilir bir yapı oluşturmaktadır, yani switch deyiminin kullanılması herşeyden önce kodun daha kolay okunabilmesi ve anlamlandırılması açısından bir avantaj sağlayacaktır.

    case ifadelerinin tam sayı türünden (integral types) sabit ifadesi olması gerekmektedir. Bilindiği gibi sabit ifadeleri derleme aşamasında derleyici tarafından net sayısal değerlere dönüştürülebilir.

    case 1 + 3: /* legal */

    mümkün çünkü 1 + 3 sabit ifadesi ama ,

    case x + 5: /* error */

    çünkü sabit ifadesi değil. Derleyici derleme aşamasında sayısal bir değer hesaplayamaz.

    case ‘a’ : /* geçerli, bir sabit ifadesidir. */

    case 3.5 : /* gerçek sayı (double) türünden olduğu için geçerli değildir. */

    case ifadelerini izleyen deyimlerin 15 - 20 satırdan uzun olması okunabilirliği zayıflatır. Bu durumda yapılacak işlemlerin fonksiyon çağırma biçimine dönüştürülmesi doğru bir tekniktir.

    switch (x) {
    case ADDREC:
    addrec();
    break;
    case DELREC:
    delrec();
    break;
    case FINDREC:
    findrec();
    break;
    }

    gibi. (Bu örnekte case ifadelerindeki ADDREC, DELREC vs. daha önce tanımlanmış sembolik sabitlerdir.)

    Sembolik sabitler derleme işleminden önce önişlemci tarafından değiştirilecekleri için, case ifadelerinde yer alabilirler :

    #define TRUE 1
    #define FALSE 0
    #define UNDEFINED 2
    ...

    case TRUE:
    case FALSE:
    case UNDEFINED:

    Yukarıdaki ifadeler, sembolik sabitlerin kullanıldığı geçerli birer case ifadesidir.

    char türünün de bir tamsayı türü olduğunu hatırlatarak case ifadelerinin doğal olarak karakter sabitleri de içerebileceğini belirtelim.

    char ch;

    ch = getch();

    switch (ch) {
    case ‘E’ : deyim1; break;
    case ‘H’ : deyim2; break;
    default : deyim3;
    }

    Bir switch deyiminde aynı sayısal değere sahip birden fazla case ifadesi olmaz. Bu durum derleme zamanında hata oluşturur.

    switch deyimi başka bir switch deyiminin, do while ya da for deyiminin gövdesini oluşturabilir. Bu durumda dışarıdaki döngünün bloklanmasına gerek olmayacaktır, çünkü switch deyimi gövdeyi oluşturan tek bir deyim olarak ele alınacaktır :

    ...
    for ((ch = getch()) != ESC)
    switch (rand() % 7 + 1) {
    case 1: printf("Pazartesi"); break;
    case 2: printf("Salı"); break;
    case 3: printf("Çarşamba"); break;
    case 4: printf("Perşembe"); break;
    case 5: printf("Cuma"); break;
    case 6: printf("Cumartesi"); break;
    case 7: printf("Pazar");
    }

    Yukarıdaki kod parçasında switch deyimi dıştaki for döngüsünün gövdesini oluşturmaktadır, ve switch deyimi tek bir deyim (kontrol deyimi) olarak ele alınacağından, dıştaki for döngüsünün bloklanmasına gerek yoktur (tabi bloklama bir hataya neden olmayacaktır.) Ancak case ifadeleri içinde yer alan break anahtar sözcüğüyle yalnızca switch deyiminden çıkılır, for döngüsünün dışına çıkmak için case ifadesi içinde goto anahtar sözcüğü kullanılmalıdır.

    Uygulama

    Kendisine gönderilen bir tarihi ingilizce (15th Aug. 2000 ) formatında ekrana yazan fonksiyonun.

    void displaydate(int day, int month, int year);

    #include <stdio.h>
    #include <conio.h>

    void dispdate(int day, int month, int year);

    int main()
    {
    int day, month, year;
    int n = 20;

    clrscr();
    while (n-- > 0) {
    printf("gun ay yil olarak bir tarih giriniz : ");
    scanf("%d%d%d", &day, &month, &year);
    dispdate(day, month, year);
    putchar('\n');
    }
    return 0;
    }

    void dispdate(int day, int month, int year)
    {
    printf("%2d", day);

    switch (day) {
    case 1 :
    case 21:
    case 31: printf("st "); break;
    case 2 :
    case 22: printf("nd "); break;
    case 3 :
    case 23: printf("rd "); break;
    default : printf("th ");
    }

    switch (month) {
    case 1 : printf("Jan "); break;
    case 2 : printf("Feb "); break;
    case 3 : printf("Mar "); break;
    case 4 : printf("Apr "); break;
    case 5 : printf("May "); break;
    case 6 : printf("Jun "); break;
    case 7 : printf("Jul "); break;
    case 8 : printf("Aug "); break;
    case 9 : printf("Sep "); break;
    case 10: printf("Oct "); break;
    case 11: printf("Nov "); break;
    case 12: printf("Dec ");
    }
    printf("%d", year);
    }

    Uygulama

    Kendisine gönderilen bir tarihin (gün, ay, yıl ) o yılın kaçıncı günü olduğunu bulan fonksiyon.

    #include <stdio.h>
    #include <conio.h>

    int isleap(int year);
    int dayofyear(int day, int month, int year);

    int main()
    {
    int n = 20;
    int day, month, year;

    clrscr();
    while (n-- > 0) {
    printf("gun ay yil olarak bir tarih giriniz : ");
    scanf("%d%d%d", &day, &month, &year);
    printf("%d yilinin %d. gunudur.\n", year, dayofyear(day, month, year));
    }
    return 0;
    }

    int isleap(int year)
    {
    return (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
    }

    int dayofyear(int day, int month, int year)
    {
    int yearday = day;

    switch (month - 1) {
    case 11: yearday += 30;
    case 10: yearday += 31;
    case 9 : yearday += 30;
    case 8 : yearday += 31;
    case 7 : yearday += 31;
    case 6 : yearday += 30;
    case 5 : yearday += 31;
    case 4 : yearday += 30;
    case 3 : yearday += 31;
    case 2 : yearday += isleap(year) ? 29 : 28;
    case 1 : yearday += 31;
    }
    return yearday;
    }

    Uygulama

    01.01.1900 – 31.12.2000 tarihleri arasında geçerli rasgele tarih üreterek ekrana yazan fonksiyon.

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <time.h>

    int isleap(int year);
    void randomdate(void);

    int main()
    {
    int n = 20;

    clrscr();
    srand(time(NULL));
    while (n-- > 0) {
    randomdate();
    putchar('\n');
    }
    return 0;
    }

    int isleap(int year)
    {
    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
    return year;
    return 0;
    }

    void randomdate(void)
    {
    int day, month, year;
    int monthdays;

    year = rand() % 101 + 1900;
    month = rand() % 12 + 1;

    switch (month) {
    case 4:
    case 6:
    case 9:
    case 11: monthdays = 30; break;
    case 2: monthdays = isleap(year) ? 29 : 28; break;
    default : monthdays = 31;
    }
    day = rand() % monthdays + 1;

    printf("%02d - %02d - %d", day, month, year);
    }

    Uygulama

    int make_char_eng(char ch); fonksiyonu. Kendisine gönderilen karakter türkçe'ye özel karakterlerden birisi ise (ç, ı, ğ, ü vs.) bu karakterlerin ingilizce benzerine aksi halde karakterin kendisine geri dönen fonksiyon:

    #include <stdio.h>
    #include <conio.h>

    int make_char_eng(char ch);

    int main()
    {
    int ch;

    while ((ch = getch()) != 'q')
    putchar(make_char_eng(ch));
    return 0;
    }

    int make_char_eng(char ch)
    {
    switch (ch) {
    case 'ç': return 'c';
    case 'ğ': return 'g';
    case 'ı' : return 'i';
    case 'ö': return 'o';
    case 'ş': return 's';
    case 'ü': return 'u';
    case 'Ç': return 'C';
    case 'Ğ': return 'G';
    case 'İ': return 'I';
    case 'Ö': return 'O';
    case 'Ş': return 'S';
    case 'Ü': return 'U';
    default : return ch;
    }
    }

    Çalışma Sorusu:

    İki basamaklı bir sayıyı yazı olarak ekrana basan num_text isimli fonksiyonu yazınız. Fonksiyonun prototipi :

    void num_text(int n);

    şeklindedir.

    Fonksiyonun parametre değişkeni yazılacak olan iki basamaklı sayıdır. Aşağıdaki programla test edebilirsiniz :

    #include <stdio.h>

    void num_text(int n);

    int main()
    {
    for (k = 10; k < 100; ++k) {
    num_text(k);
    putchar('\n');
    getch();
    }
    return 0;
    }


    3. ÖNİŞLEMCİ KAVRAMI

    Şimdiye kadar tek bir yapı halinde ele aldığımız C derleyicileri aslında, iki ayrı modülden oluşmaktadır:

    1. Önişlemci Modülü
    2. Derleme Modülü

    İsim benzerliği olsa da önişlemcinin bilgisayarın işlemcisi ya da başka bir donanımsal elemanıyla hiçbir ilgisi yoktur, önişlemci tamamen C derleyicisine ilişkin yazılımsal bir kavramdır.

    Önişlemci, kaynak program üzerinde birtakım düzenlemeler ve değişiklikler yapan bir ön programdır. Önişlemci programının bir girdisi bir de çıktısı vardır. Önişlemcinin girdisi kaynak programın kendisidir. Önişlemcinin çıktısı ise derleme modülünün girdisini oluşturur. Yani kaynak program ilk aşamada önişlemci tarafından ele alınır. Önişlemci modülü kaynak programda çeşitli düzenlemeler ve değişiklikler yapar, daha sonra değiştirilmiş ve düzenlenmiş olan bu kaynak program derleme modülü tarafından amaç koda dönüştürülir.

    önişlemci


    derleyici

    kaynak kod

    amaç program







    C programlama dilinde # ile başlayan bütün satırlar önişlemciye verilen komutlardır.
    # karakterinin sağında bulunan sözcükler ki bunlara önişlemci komutları denir (preprocessor directives), önişlemciye ne yapması gerektiğini anlatır. Örneğin:

    #include
    #define
    #if
    #ifdef
    #ifndef

    hepsi birer önişlemci komutudur.

    Önişlemci komutları derleyici açısından anahtar sözcük değildir # karakteriyle birlikte anlam kazanırlar, yani istersek include isimli bir değişken tanımlayabiliriz ama bunun okunabilirlik açısından iyi bir fikir olmadığını söyleyebiliriz. Önişlemci komutlarını belirten sözcükler ancak sol taraflarındaki # karakteriyle kullanıldıkları zaman özel anlam kazanırlar ki bunlara önişlemci anahtar sözcükleri de diyebiliriz.

    Önişlemci amaç kod oluşturmaya yönelik hiçbir iş yapmaz. Önişlemci kaynak programı # içeren satırlardan arındırır. Derleme modülüne girecek programda artık # içeren satırlar yer almayacaktır.

    Bir çok önişlemci komutu olmakla birlikte şimdilik yalnızca #include komutu ve #define önişlemci komutlarını inceleyeceğiz. Geriye kalan önişlemci komutlarını da ileride detaylı olarak inceleyeceğiz.

    #include önişlemci komutu

    genel kullanım biçimi:

    #include <dosya ismi>

    ya da

    #include "dosya ismi"

    #include, ilgili kaynak dosyanın derleme işlemine dahil edileceğini anlatan bir önişlemci komutudur. Bu komut ile önişlemci belirtilen dosyayı diskten okuyarak komutun yazılı olduğu yere yerleştirir. (metin editörlerindeki copy – paste işlemi gibi)

    #include komutundaki dosya ismi iki biçimde belirtilebilir:

    1. Açısal parantezlerle:

    #include <stdio.h>
    #include <time.h>

    2. Çift tırnak içerisinde:

    #include "stdio.h"
    #include "deneme.c"

    Dosya ismi eğer açısal parantezler içinde verilmişse, sözkonusu dosya önişlemci tarafından yalnızca önceden belirlenmiş bir dizin içerisinde aranır. Çalıştığımız derleyiciye ve sistemin kurulumuna bağlı olarak, önceden belirlenmiş bu dizin farklı olabilir. Örneğin:

    \TC\INCLUDE
    \BORLAND\INCLUDE
    \C600\INCLUDE

    gibi. Benzer biçimde UNIX sistemleri için bu dizin, örneğin:

    /USR/INCLUDE

    biçiminde olabilir. Genellikle standart başlık dosyaları önişlemci tarafından belirlenen dizinde olduğundan, açısal parantezler ile kaynak koda dahil edilirler.

    Dosya ismi iki tırnak içine yazıldığında önişlemci ilgili dosyayı önce çalışılan dizinde (current directory), burada bulamazsa bu kez de sistem ile belirlenen dizinde arayacaktır. Örneğin:

    C:\SAMPLE

    dizininde çalışıyor olalım.

    #include "strfunc.h" komutu ile önilemci strfunc.h dosyasını önce C:\SAMPLE dizininde arar. Eğer burada bulamazsa bu kez sistem ile belirlenen dizinde arar. Programcıların kendilerinin oluşturdukları başlık dosyaları genellikle sisteme ait dizinde olmadıkları için iki tırnak içinde kaynak koda dahil edilirler.

    #include ile koda dahil edilmek istenen dosya ismi dosya yolu da (path) de içerebilir:

    #include <sys\stat.h>
    #include "c:\headers\myheader.h"
    ....
    gibi.

    Bu durumda açısal parantez ya da iki tırnak gösterimleri arasında bir fark yoktur. Her iki gösterimde de önişlemci ilgili dosyayı yalnızca yolun belirttiği dizinde arar.

    #include komutu ile yalnızca baslık dosyalarının koda dahil edilmesi gibi bir zorunluluk yoktur. Herhangi bir kaynak dosya da bu yolla kaynak koda dahil edilebilir.


  8. #8
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb





    prog.c

    #include "topla.c"

    int main()
    {
    toplam = a + b;
    printf("%d", toplam);
    return 0;
    }

    topla.c

    int a = 10;
    int b = 20;
    int toplam;

    Yukarıdaki örnekte #include "topla.c" komutu ile önişlemci komutunun bulunduğu yere topla.c dosyasının içeriğini yerleştirecek böylece prog.c dosyası derlendiğinde herhangi bir hata oluşmayacaktır.

    Çünkü artık derleme işlemine girecek dosya:

    int a = 10;
    int b = 20;
    int toplam;

    int main()
    {
    toplam = a + b;
    printf("%d", toplam);
    return 0;
    }

    şeklinde olacaktır.

    #include komutu kaynak programın herhangi bir yerinde bulunabilir. Fakat standart başlık dosyaları gibi, içerisinde çeşitli bildirimlerin bulunduğu dosyalar için en iyi yer kuşkusuz programın en tepesidir.

    #include komutu içiçe geçmiş (nested) bir biçimde de bulunabilir. Örneğin çok sayıda dosyayı kaynak koda dahil etmek için şöyle bir yöntem izleyebiliriz.

    ana.c

    #include "project.h"

    int main()
    {
    ...
    }
    project.h

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <time.h>


    ana.c dosyası içerisine yalnızca project.h dahil edilmiştir. Önişlemci bu dosyayı kaynak koda dahil ettikten sonra yoluna bu dosyadan devam edecektir.

    #define Önişlemci Komutu

    #define önişlemci komutu text editörlerindeki bul ve değiştir özelliği (find & replace) gibi çalışır. Kaynak kod içerisindeki bir yazıyı başka bir yazı ile değiştirmek için kullanılır.

    Önişlemci define anahtar sözcüğünden sonra boşlukları atarak ilk boşluksuz yazı kümesini elde eder. (Buna STR1 diyelim.) Daha sonra boşlukları atarak satır sonuna kadar olan tüm boşlukların kümesini elde eder. (Buna da STR2 diyelim) Kaynak kod içerisinde STR1 yazısı yerine STR2 yazısını yerleştirir. (STR2 yazısı yalnızca boşluktan ibaret de olabilir. Bu durumda STR1 yazısı yerine boşluk yerleştirilir, yani STR1 yazısı silinmiş olur.) Örnekler :

    #define SIZE 100

    önişlemci komutuyla, önişlemci kaynak kod içerisinde gördüğü tüm SIZE sözcükleri yerine 100 yerleştirecektir. Derleme modülüne girecek kaynak programda artık SIZE sözcüğü hiç yer almayacaktır.

    #define kullanılarak bir yazının bir sayısal değerle yer değiştirmesinde kullanılan yazıya "sembolik sabit" (symbolic constants) denir. Sembolik sabitler nesne değildir. Derleme modülüne giren kaynak kodda sembolik sabitler yerine bunların yerini sayısal ifadeler almış olur.

    Sembolik sabitler basit makrolar (simple makro) olarak da isimlendirirler ama biz "sembolik sabit" demeyi yeğliyoruz.

    Sembolik sabitlerin kullanılmasında dikkatli olunmalıdır. Önişlemci modülünün herhangi bir şekilde aritmetik işlem yapmadığı yalnızca metinsel bir yer değiştirme yaptığı unutulmamalıdır:

    #define MAX 10 + 20

    int main()
    {
    int result;

    result = MAX * 2;
    printf("%d\n", result);
    return 0;
    }

    Yukarıdaki örnekte result değişkenine 50 değeri atanır. Ancak ön işlemci komutunu

    #define MAX (10 + 20)

    şeklinde yazsaydık, result değişkenine 60 değeri atanmış olurdu.

    Bir sembolik sabit başka bir sembolik sabit tanımlamasında kullanılabilir. Örneğin:

    #define MAX 100
    #define MIN (MAX - 50)

    Önişlemci " " içerisindeki yazılarda değişiklik yapmaz. (stringlerin bölünemeyen atomlar olduğunu hatırlayalım.) Yer değiştirme işlemi büyük küçük harf duyarlığı ile yapılır. Ancak sembolik sabitler geleneksel olarak büyük harf ile isimlendirilirler. Bunun nedeni, kodu okuyan kişinin değişkenlerle sembolik sabitleri ayırt edebilmesine yardımcı olmaktır. Bilindiği gibi geleneksel olarak değişken isimlendirmesinde C dilinde ağırlıklı olarak küçük harfler kullanılmaktadır.

    #define komutu ile ancak değişkenler ve anahtar sözcükler yer değiştirilebilir. Sabitler ve operatörler yer değiştiremez.

    Aşağıdaki #define önişlemci komutları geçerli değildir:

    #define + -
    #define 100 200

    Sembolik sabitler C dilinin değişken isimlendirme kurallarına uygun olarak isimlendirilir:

    #define BÜYÜK 10

    tanımlamasında hata oluşur. (Hata tanımlama satırında değil sembolik sabitin kod içinde kullanıldığı yerlerde oluşur. Tanımlanan sembolik sabit kod içinde kullanılmazsa hata da oluşmayacaktır.)

    Önişlemci #include komudu ile kaynak koda dahil edilen dosyanın içerisindeki önişlemci komutlarını da çalıştırır. İçinde semboli sabit tanımlamaları yapılmış bir dosya #include komudu ile kaynak koda dahil edildiğinde, bu sembolik sabitler de kaynak kod içinde tanımlanmış gibi geçerli olur.

    #define sembolik sabit tanımlamalarında stringler de kullanılabilir :

    #define HATA_MESAJI "DOSYA AÇILAMIYOR \n"

    ...
    printf(HATA_MESAJI);
    ...

    Sembolik sabit tanımlamasında kullanılacak string uzunsa okunabilirlik açısından birden fazla satıra yerleştirilebilir. Bu durumda son satır dışında diğer satırların sonuna "\" karakteri yerleştirilmelidir.

    Önişlemci değiştirme işlemini yaptıktan sonra #define komutlarını koddan çıkartır.

    Okunabilirlik açısından tüm sembolik sabit tanımlamaları alt alta getirecek şekilde yazılmalıdır. Seçilen sembolik sabit isimleri kodu okuyan kişiye bunların ne amaçla kullanıldığı hakkında fikir vermelidir.

    C'nin başlık dosyalarında bazı sembolik sabitler tanımlanmıştır. Örneğin stdio.h içerisinde

    #define NULL 0

    biçiminde bir satır vardır. Yani stdio.h dosyası koda dahil edilirse NULL sembolik sabiti 0 yerine kullanılabilir. math.h içinde de pek çok matematiksel sabit tanımlanmıştır.

    Bir sembolik sabitin tanımlanmış olması kaynak kod içerisinde değiştirilebilecek bir bilginin olmasını zorunlu hale getirmez. Tanımlanmış bir sembolik sabitin kaynak kod içerisinde kullanılmaması hataya yol açmaz.

    #define Önişlemci Komutu Neden Kullanılır

    1. Okunabilirliği ve algınalabilirliği artırır. Bir takım sabitlere onların ne amaçla kullanıldığını anlatan yazılar karşılık getirilirse programa bakan kişiler daha iyi anlamlandırır.

    #define PERSONEL_SAYISI 750

    int main()
    {
    if (x == PERSONEL_SAYISI)
    ...
    return 0;
    }

    2. Bir sabitin program içerisinde pekçok yerde kullanıldığı durumlarda değiştirme işlemi tek yerden yapılabilir. Böylece söz konusu program sembolik sabite baglı olarak yazılıp, daha sonra sembolik sabitin değiştirilmesiyle farklı parametrik değerler için çalıştırılabilir.

    3. Sayısal sabitlerin kullanılmasında tutarsızlıkları ve yazım yanlışlarını engeller.

    Örneğin matematiksel hesaplamalar yapan bir kodda sıksık pi sayısını kullandığımızı düşünelim. pi sayısı yerine

    #define PI 3.14159

    sembolik sabitini kullanabiliriz. Her defasında pi sayısının sabit olarak koda girersek, her defasında aynı değeri yazamayabiliriz. Örneğin bazen 3.14159 bazen 3.14156 girebileceğimiz gibi yanlışlıkla 3.15159 da girebiliriz. Sembolik sabit kullanımı bu tür hataları ortadan kaldırır.

    4. Sembolik sabitler kullanılarak C sintaksında küçük değişiklikler yapılabilir.

    #define FOREVER for(;
    #define BEGIN {
    #define END }

    Böyle bir sembolik sabit kullanımını kesinlikle tavsiye etmiyoruz. Okunabilirliği artırması değil azaltması söz konusudur. Bir C programcısı için en okunabilir kod C'nin kendi sentaksının kullanıldığı koddur.

    #define komutu kaynak kodun herhangi bir yerinde kullanılabilir. Ancak tanımlandığı yerden kaynak kodun sonuna kadar olan bölge içerisinde etki gösterir.

    Ancak en iyi tanımlama yeri kaynak kodun tepesidir. Geleneksel olarak sembolik sabitler #include satırlarından (bir satır boşluk verildikten sonra) hemen sonra yer alırlar.

    Sembolik Sabitlerin Kullanılmasında Çok Yapılan Hatalar

    1. Sembolik sabit tanımlamasında gereksiz yere = karakterini kullanmak.

    #define N = 100 /* yanlış */

    Bu durumda önişlemci N gördüğü yere = 100 yapıştıracaktır. Örneğin int a[N] gibi bir dizi tanımlanmışsa önişlemci bu tanımlamayı a[N = 100] yapar ki bu da derleme aşamasında hata (error) ile neticelenir.

    2. Sembolik sabit tanımlama satırını ; ile sonlandırmak.

    #define N 100; /* yanlış */

    Bu durumda önişlemci N gördüğü yere 100; yerleştirir. int a[N]; tanımlaması int a[100;] haline gelir. Derleme aşamasında hata oluşur. (Borland derleyicileri hata mesajı :Array bounds missing ])Bu tür hatalarda derleyici sembolik sabit kaç yerde kullanılmışsa o kadar error verecektir.

    3. Sembolik sabitlerle yer değiştirecek ifadelerin operatör içermesi durumunda bu ifadeleri paranteze almadan yerleştirmek.

    #define SIZE 5 + 10
    ...

    i = SIZE * 5 /* 5 + 10 * 5 = 55 (75 değil) */
    ...


    2. goto DEYİMİ

    Diğer programlama dillerinde olduğu gibi C dilinde de programın akışı, bir koşula bağlı olmaksızın kaynak kod içinde başka bir noktaya yönlendirilebilir. Bu C dilinde goto deyimi ile yapılmaktadır.

    goto deyiminin genel sentaksı aşağıdaki şekildedir.

    <goto label;>
    ....
    <label:>
    <statement;>

    goto C dilinin 32 anahtar sözcüğünden biridir. (anahtar sözcüklerin küçük harfle yazıldıklarını tekrar hatırlatalım.) Label (etiket) programcının verdiği bir isimdir. Şüphesiz C dilinin isimlendirme kurallarına uygun olarak seçilmelidir. Programın akışı bu etiketin yerleştirilmiş olduğu yere yönlendirilecektir. Etiket, goto anahtar sözcüğünün kullanıldığı fonksiyon içinde, en az bir değimden önce olaçak şekilde, herhangi bir yere yerleştirilebilir. Etiket isminden sonra gelen : atomu sentaksı tamamlar. Etiketin goto anahtar sözcüğünden daha sonraki bir kaynak kod noktasına yerleştirilmesi zorunluluğu yoktur, goto anahtar sözcüğünden önce de tanımlanmış olabilir. Örneklerle açıklayalım:

    int main()
    {
    ...
    goto GIT;
    ...
    ...
    GIT:
    printf("goto deyimi ile buraya gelindi);
    return 0;
    }


    int main()
    {
    GIT:
    printf("goto deyimi ile gelinecek nokta);
    ...
    goto GIT;
    ...
    return 0;
    }

    goto etiketleri bir fonksiyon içerisinde, bir deyimden önce herhangi bir yere yerleştirilebilir. Yani aynı fonksiyon içinde bulunmak kaydıyla goto anahtar sözcüğünün yukarısına ya da aşağısına etiketi yerleştirebiliriz. Bu da aslında değişik bir faaliyet alanı tipidir. C dili standartlarında bu faaliyet alanı türüne "fonksiyon faaliyet alanı" denmiştir.

    Yapısal programlama tekniğinde goto deyiminin kullanılması tavsiye edilmez. Çünkü goto deyiminin kullanılması birtakım dezavantajlar doğurur:

    1. goto deyimi programların okunabilirliğini bozar. Kodu takip eden kişi goto deyimiyle karşılaştığında fonksiyonun içinde etiketi arayacak, ve programı bu noktadan takip etmeye koyulacaktır.

    2. goto deyimlerinin kullanıldığı bir programda bir değişiklik yapılması ya da programın, yapılacak eklemelerle, geliştirilmeye çalışılması daha zor olacaktır. Programın herhangi bir yerinde bir değişiklik yapılması durumunda, eğer program içerisinde başka yerlerden kodun akışı değişikliğin yapıldığı yere goto deyimleri ile atlatılmış ise, bu noktalarda da bir değişiklik yapılması gerekebilerecektir.

    Bu olumsuzluklara karşın, bazı durumlarda goto deyiminin kullanılması programın okunabilirliğini bozmak bir yana, diğer alternatiflere göre daha okunabilir bir yapının oluşmasına yardımcı olacaktır:

    İçiçe birden fazla döngü varsa, ve içteki döngülerden birindeyken, yalnızca bu döngüden değil, bütün döngülerden birden çıkılmak isteniyorsa goto deyimi kullanılmalıdır.

    Aşağıdaki kod parçasında içiçe 3 döngü bulunmaktadır. En içteki döngünün içinde func fonksiyonu çağırılarak fonksiyonun geri dönüş değeri test edilmekte, fonksiyon eğer 0 değerine geri dönüyorsa programın akışı goto deyimiyle tüm döngülerin dışına yönlendirilmektedir:

    int i, j, k;

    ...
    for (i = 0; i < 100; ++i) {
    ...
    for (j = 0; j < 100; ++j) {
    ...
    for (k = 0 {
    ...
    if (!func())
    goto BREAK;
    ...
    }
    }
    }
    BREAK:
    printf("döngü dışındaki ilk deyim\n");
    ...

    Oysa goto deyimini kullanmasaydık, ancak bir bayrak (flag) kullanarak, ve her döngünün çıkışında bayrak olarak kullanılan değişkenin değerinin değiştirilip değiştirilmediğini kontrol ederek bunu başarabilirdik:

    int i, j, k;

    int flag = 0;

    ...
    for (i = 0; i < 100; ++i) {
    ...
    for (j = 0; j < 100; ++j) {
    ...
    for (k = 0; k < 100; ++k) {
    ...
    if (!func()) {
    flag = 1;
    break;
    }
    ...
    }
    if (flag)
    break;
    }
    if (flag)
    break;
    }
    printf("döngü dışındaki ilk deyim\n");
    ...

    Yine aşağıdaki kod parçasında goto deyimiyle hem switch deyimğinden hem de switch deyiminin içinde bulunduğu for döngüsünden çıkılmaktadır.

    ...
    int main()
    {
    int option;

    for (; {
    option = get_option();
    switch (option) {
    case ADDREC :addrec();break;
    case LISTREC :listrec();break;
    case DELREC :delrec(); break;
    case SORTREC :sortrec(); break;
    case EXITPROG :goto EXIT;
    }
    }
    EXIT:
    return SUCCESS;
    }

    Yukarıdaki kod parçasında option değişkeninin değeri EXITPROG olduğunda programın akışı goto deyimiyle
    sonsuz döngünün dışına gönderilmiştir. goto deyimi yerine break anahtar sözcüğü kullanılsaydı, yalnızca switch deyiminden çıkılmış olunacaktı.


    3. RASTGELE SAYI ÜRETİMİ


    Rasgele sayı üretimi matematiğin önemli konularından biridir. Rasgele sayılar ya da daha doğru ifadeyle, rasgele izlenimi veren sayılar (sözde rasgele sayılar - pseudo random numbers) istatistik, ekonomi, matematik gibi pekçok alanda olduğu gibi programcılıkta da kullanılmaktadır.

    Rasgele sayılar bir rasgele sayı üreticisi (random number generator) tarafından üretilirler. Rasgele sayı üreticisi aslında matematiksel bir fonksiyondur. Söz konusu fonksiyon bir başlangıç değeri alarak bir değer üretir. Daha sonra ürettiği her değeri girdi olarak alır ve tekrar başka bir sayı üretir . Üreticinin ürettiği sayılar rasgele izlenimi vermektedir.

    C standartları rasgele tamsayı üreten bir fonskiyonun standart bir C fonksiyonu olarak tanımlanmasını zorunlu kılmıştır. Bu fonksiyonun prototip bildirimi aşağıdaki gibidir:

    int rand(void);

    C standartları rand fonksiyonunun rasgele sayı üretimi konusunda kullanacağı algoritma ya da teknik üzerinde bir koşul koymamıştır. Bu konu derleyiciyi yazanların seçimine bağlı (implementation dependent) bırakılmıştır. rand fonksiyonunun prototipi standart bir başlık dosyası olan stdlib.h içinde bildirilmiştir.

    Bu yüzden rand fonksiyonunun çağırılması durumunda bu başlık dosyası "include" önişlemci komuduyla kaynak koda dahil edilmelidir.

    #include <stdlib.h>

    rand fonksiyonu her çağırıldığında [0, RAND_MAX] aralığında rasgele bir tamsayı değerini geri döndürür. RAND_MAX stdlib.h başlık dosyası içinde tanımlanan bir sembolik sabittir, derleyicilerin çoğunda RAND_MAX sembolik sabiti 32767 olarak, yani 2 byte'lık signed int türünün maximum değeri olarak tenımlanmıştır.

    Aşağıdaki program parçasında 0 ile 32767 arasında 10 adet rasgele sayı üretilerek ekrana yazdırılmaktadır :

    #include <stdio.h>
    #include <stdlib.h>

    int main()
    {
    int k;

    for (k = 0; k < 10; ++k)
    printf("%d ", rand());
    return 0;
    }

    Yukarıdaki kaynak kodla oluşturulan programın her çalıştırılmasında ekrana aynı sayılar yazılacaktır. Örneğin yukarıdaki programı DOS altında Borland Turbo C 2.0 derleyicisi ile derleyip çalıştırdığımızda ekran çıktısı aşağıdaki gibi oldu:

    346 130 10982 1090 11656 7117 17595 6415 22948 31126

    Oluşturulan program her çalıştırıldığında neden hep aynı sayı zinciri elde ediliyor? rand fonksiyonu rasgele sayı üretmek için bir algoritma kullanıyor. Bu algoritma derleyiciden derleyiciye değişsede, rasgele sayı üretiminde kullanılan ana tema aynıdır. Bir başlangıç değeri ile işe başlanır. Buna tohum değer diyoruz. Bu değer üzerinde bazı işlemler yapılarak rasgele bir sayı elde edilir. Tohum değer üzerinde yapılan işlem bu kez elde edilen rasgele sayı üzerinde tekrarlanır...

    rand fonksiyonu rasgele sayı üretmek için bir başlangıç değeri kullanıyor. Rasgele sayı üreticilerinin (random number generator) kullandıkları başlangıç değerine tohum değeri (seed) denir. rand fonksiyonunu içeren programı her çalıştırdığımızda aynı tohum değerinden başlayacağı için aynı sayı zinciri elde edilecektir. İşte ikinci fonksiyon olan srand fonksiyonu, rasgele sayı üreticisinin tohum değerini değiştirmeye yarar:

    void srand (unsigned seed);

    srand fonksiyonuna gönderilen arguman rasgele sayı üreticisinin tohum değerini değiştir. srand() fonksiyonuna başka bir tohum değeri gönderdiğimizde fonksiyonun ürettiği rasgele sayı zinciri değişecektir. Yukarıdaki programa bir ilave yaparak yeniden çalıştırın.

    #include <stdio.h>
    #include <stdlib.h>

    int main()
    {
    int k;

    srand(100);
    for (k = 0; k < 10; ++k)
    printf("%d ", rand());
    return 0;
    }

    1862 11548 3973 4846 9095 16503 6335 13684 21357 21505

    Ancak bu kez oluşturduğumuz programı her çelıştırdığımızda yine yukarıdaki sayı zinciri elde edilecek. Zira rand fonksiyonunun kullanmakta olduğu önceden seçilmiş (default) tohum değerini kullanmasak da, rasgele sayı üretme mekanizması bu kez her defasında bizim srand fonksiyonuyla göndermiş olduğumuz tohum değerini kullanacak. Programı birkaç kere çalıştırıp aynı sayı zincirini ürettiğini test edin.

    Bir çok durumda programın her çalıştırılmasında aynı rasgele sayı zincirinin üretilmesi istenmez. Programın her çalışmasında farklı bir sayı zincirinin elde edilmesi için programın her çalışmasında srand fonksiyonu başka bir değerle, rasgele sayı üreticisinin tohum değerini set etmelidir. Bu amaçla çoğu zaman time fonksiyonundan faydalanılır:

    time fonksiyonu standart bir C fonksiyonudur, prototip bildirimi standart bir başlık dosyası olan time.h içindedir. time fonksiyonu, parametre değişkeni gösterici olan bir fonksiyon olduğundan ileride detaylı olarak ele alınacaktır. Şimdilik time fonksiyonunu işimizi görecek kadar inceleyeceğiz. time fonksiyonu 0 argumanıyla çağırıldığında 01.01.1970 tarihinden fonksiyonun çağırıldığı ana kadar geçen saniye sayısını geri döndürür. Fonksiyonun geri dönüş değeri derleyicilerin çoğunda long türden bir değerdir. İçinde rasgele sayı üretilecek programda, srand fonksiyonuna arguman olarak time fonksiyonunun geri dönüş değeri arguman olarak gönderilirse, program her çalıştığında rasgele sayı üreticisi başka bir tohum değeriyle ilk değerini alacaktır, böyle progarmın her çalıştırılmasında farklı sayı zinciri üretilecektir.

    srand(time(0));

    srand fonksiyonunun bu şekilde çağırımı derleyicilerin çoğunda randomize isimli bir makro olarak tanımlanmıştır. Yukarıdaki fonksiyon çağırımı yerinde bu makro da çağırılabilir. Makrolar konusunu ileride detaylı olarak inceşeyeceğiz :

    randomize();

    Yukarıdaki örnek programı her çalıştığında farklı sayı zinciri üretecek hale getirelim:

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    int main()
    {
    int k;

    srand(time(0));
    for (k = 0; k < 10; ++k)
    printf("%d ", rand());
    return 0;
    }

    Programlarda bazen belirli bir aralıkta rasgele sayı üretmek isteyebiliriz. Bu durumda mod operatörü kullanılabilir :

    rand() % 2 Yalnızca 0 ya da 1 değerini üretir.
    rand() % 6 0 - 5 aralığında rasgele bir değer üretir
    rand() % 6 + 1 1 - 6 aralığında rasgele bir değer üretir.
    rand() % 6 + 3 5 - 8 aralığında rasgele bir değer üretir.

    Karmaşık olasılık problemleri, olsılığa konu olayın bir bilgisayar programı ile simule edilemesi yoluyla çözülebilir. İyi bir rasgele sayı üreticisi kullanıldığı takdirde, olasılığa konu olay bir bilgisayar programı ile simule edilir ve olay bilgisayarın işlem yapma hızından faydanlanılarak yüksek sayılarda tekrar ettirilir. Şüphesiz hesap edilmek istenen olaya ilişkin olasılık değeri, yapılan tekrar sayısına ve rasgele sayı üreticisinin kalitesine bağlı olacaktır. Aşağıdaki kod yazı tura atılması olayında tura gelme olasılığını hesap etmektedir.

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <time.h>

    #define TIMES 100
    #define HEADS 1

    int main()
    {
    long heads_counter = 0;
    long k;

    for (k = 0; k < TIMES; ++k)
    if (rand() % 2 == HEADS)
    heads_counter++;
    printf("tura gelme olasılığı = %lf", (double) heads_counter / TIMES);
    getch();

    return 0;
    }

    Yukarıdaki programı TIMES sembolik sabitinin farklı değerleri için çalıştırdığımızda ekran çıktısı aşağıdaki şekilde oldu:
    #define TIMES 100
    tura gelme olasılığı = 0.480000
    #define TIMES 500
    tura gelme olasılığı = 0.496000
    #define TIMES 2500
    tura gelme olasılığı = 0.506800
    #define TIMES 10000
    tura gelme olasılığı = 0.503500
    #define TIMES 30000
    tura gelme olasılığı = 0.502933
    #define TIMES 100000
    tura gelme olasılığı = 0.501450
    #define TIMES 1000000
    tura gelme olasılığı = 0.500198
    #define TIMES 10000000
    tura gelme olasılığı = 0.500015
    #define TIMES 2000000
    tura gelme olasılığı = 0.500198

    Aşağıdaki main fonksiyonunda uzunlukları 3 - 8 harf arasında değişen ingiliz alfabesindeki harfler ile oluşturulmuş rasgele 10 kelime ekrana yazdırılmaktadır:

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <time.h>

    #define TIMES 20

    void write_word(void);

    int main()
    {
    int k;

    srand(time(0));
    for (k = 0; k < TIMES; ++k) {
    write_word();
    putchar('_);
    }
    getch();
    return 0;
    }

    void write_word(void)
    {
    int len = rand() % 6 + 3;

    while (len--)
    putchar('A' + rand() % 26);
    }

    Programın ekran çıktısı:

    USPFY
    KDUP
    BUQJB
    LMU
    PKPBKFA
    WQM
    NQHPTXL
    QCANSTEH
    XZU
    MNRI
    OXUTGISR
    XBNG
    BYOQ
    TFO
    MQUCSU
    OIHFPJ
    BLVD
    WDDEM
    OHMHJBZY
    ALIAQ


    4. DİZİLER

    Bellekte ardışıl bir biçimde bulunan ve aynı türden nesnelerin oluşturduğu veri yapısına dizi (array) denir. Dizilerin kullanılmasını gerektiren iki önemli özellikleri vardır:

    1. Dizi elemanları bellekte ardışıl (contiguous) olarak bulunurlar.
    2. Dizi elemanları aynı türden nesnelerdir.

    Diziler bileşik nesnelerdir. Yani bir dizinin tanımlanmasıyla birden fazla sayıda nesne birlikte tanımlanabilir. (Bileşik sözcüğü ingilizce "aggregate" sözcüğünün karşılığı olarak kullanılmıştır.)
    10 elemanlık bir dizi tanımlamak yerine, şüphesiz farklı isimlere sahip 10 ayrı nesne de tanımlanabilir. Ama 10 ayrı nesne tanımlandığında bu nesnelerin bellekte ardışıl olarak yerleşmeleri garanti altına alınmış bir özellik değildir. Oysa dizi tanımlamasında, dizinin elemanı olan bütün nesnelerin bellekte ardışıl olarak yer almaları garanti altına alınmış bir özelliktir.

    Dizilerde bir veri türü olduğuna göre dizilerin de kullanılmalarından önce tanımlanmaları gerekir.

    Dizilerin Tanımlanması

    Dizi tanımlamalarının genel biçimi:

    <tür> <dizi ismi> [<eleman sayısı>];

    Yukaridaki gösterimde köşeli parantez eleman sayısının seçimlik olduğunu değil, eleman sayısı bilgisinin köşeli parantez içine yazılması gerektiğini göstermektedir.

    tür : Dizi elemanlarının türünü gösteren anahtar sözcüktür.
    dizi ismi : İsimlendirme kurallarına uygun olarak verilecek herhangi bir isimdir.
    eleman sayısı : Dizinin kaç elemana sahip olduğunu gösterir.

    Örnek Dizi Bildirimleri:


    double a[20]; /* a, 20 elemanlı ve elemanları double türden olan bir dizidir*/
    float ave[10]; /* ave 10 elemanlı ve her elemanı float türden olan bir dizidir. */
    unsigned long total[100]; /* total 100 elemanlı ve her elemanı unsigned long türden olan bir dizidir */
    char path[80]; /* path 80 elemanlı ve her elemanı char türden olan bir dizidir. */

    Tanımlamada yer alan eleman sayısının mutlaka tamsayı türlerinden birinden sabit ifadesi olması zorunludur. (Sabit ifadesi [constant expression] tanımını hatırlayalım; değişken ve fonksiyon çağırımı içermeyen, yani yalnızca sabitlerden oluşan ifadelere, sabit ifadesi denir.)

    int dizi[x]; /* x dizisinin bildirimi derleme zamanında hata olusturur .*/
    int dizi[5.]; /* gerçek sayı türünden sabit ifadesi olduğu için derleme zamanında hata olusturur . */

    int sample[10 * 20] /* sample dizisinin bildirimi geçerlidir. Eleman sayısını gösteren ifade sabit ifadesidir. */

    Dizi bildirimlerinde eleman sayısı yerine sıklıkla sembolik sabitler kullanılır:

    #define MAXSIZE 100
    ...
    int dizi[MAXSIZE]; /* geçerli bir bildirimdir */
    ...

    Diğer değişken bildirimlerinde olduğu gibi, virgül ayıracıyla ayrılarak, birden fazla dizi tek bir tür belirten anahtar sözcükle tanımlanabilir.

    int x[100], y[50], z[10];

    x, y ve z elemanları int türden olan dizilerdir.

    Dizi tanımlamaları diğer değişken tanımlamaları ile kombine edilebilir.

    int a[10], b, c;

    a int türden 10 elemanlı bir dizi, b ve c int türden nesnelerdir.

    Dizi elemanlarının her biri ayrı birer nesnedir. Dizi elemanlarına index operatörüyle [] ulaşılabilir. Index operatörü bir gösterici operatörüdür. Göstericiler konusunda ayrıntılı bir şekilde ele alınacaktır.

    İndex operatörünün operandı dizi ismidir. (Aslında bu bir adres bilgisidir, çünkü dizi isimleri adres bilgisi belirtirler.) Köşeli parantez içinde dizinin kaçıncı indisli elemanına ulaşacağımızı gösteren bir tamsayı ifadesi olmalıdır.

    C dilinde dizilerin ilk elemanı sıfırıncı indisli elemandır.

    a[n] gibi bir dizinin ilk elemanı a[0] son elemanı ise a[n - 1] dur.

    Örnekler:

    dizi[20] /* a dizisinin 20. indisli yani 21. sıradaki elemanı. */
    ave[0] /* ave dizisinin 0. indisli yani birinci sıradaki elemanı */
    total[j] /* total dizisinin j indisli elemanı */

    Görüldüğü gibi bir dizinin n. elemanı ve bir dizinin n indisli elemanı terimleri dizinin farklı elemanlarına işaret eder. Bir dizinin n indisli elemanı o dizinin n + 1 . elemanıdır.
    Bir dizi tanımlaması ile karşılaşan derleyici, tanımlanan dizi için bellekte yer tahsis edecektir..Ayrılacak yer şüphesiz dizinin eleman sayısı * bir elemanın bellekte kapladığı yer kadar byte olacaktır. Örneğin:

    int a[5];

    gibi bir dizi tanımlaması yapıldığını düşünelim. DOS işletim sisteminde çalışıyorsak, derleyici a dizisi için bellekte 2 * 5 = 10 byte yer ayıracaktır. Bellekte bu dizinin yerleşimi aşağıdaki gibi olacaktır :

    ...
    ...
    -----------
    a[0]
    10 byte

    -----------
    a[1]
    -----------
    a[2]
    -----------
    a[3]
    -----------
    a[4]
    -----------
    ...



    Dizi kullanımının getirdiği en büyük avantaj döngü deyimleri kullanarak tüm dizi elemanlarını kolay bir şekilde erişilebilmesidir.

    Dizi indis ifadelerinde ++ ya da – operatörlerinin kullanıldığı sık görülür.

    int a[20];
    int k = 10;
    int i =5;

    a[k++] = 100; /* a dizisinin 10 indisli elemanına yani 11. elemanına 100 değeri atanıyor. */
    a[--i] = 200; /* a dizisinin 4 indisli elemanına yani 5. elemanına 200 değeri atanıyor. */

    İndex operatörünün kullanılmasıyla artık dizinin herhangi bir elemanı diğer değişkenler gibi kullanılabilir.

    Örnekler:

    a[0] = 1; /* a dizisinin ilk elemanına 0 değeri atanıyor. */
    printf(“%d\n”, sample[5]); /* sample dizisinin 6. elemanı yazdırılıyor. */
    ++val[3]; /* val dizisinin 4. elemanı 1 artırılıyor. */
    val[2] = sample[2]; /* val dizisinin 3. elemanına sample dizisinin 3. elemanı atanıyor. */

    Diziler üzerinde işlem yapmak için sıklıkla for ve while döngüleri kullanılır. Aşağıda SIZE elemanlı ve sample isimli bir dizi için for ve while döngülerinin kullanıldığı bazı kalıplar gösterilmektedir:

    for (i = 0; i < SIZE; ++i)
    sample[i] = 0;

    i = 0;
    while (i < SIZE) {
    sample[i] = 0;
    ++i;
    } /* sample dizisinin bütün elemanları sıfırlanıyor */


    for (i = 0; i < SIZE; i++) {
    scanf(“%d”, &sample[i]);
    ++i;
    }


    i = 0;
    while (i < SIZE) {
    scanf(“%d”, &sample[i]);
    ++i;
    } /* sample dizisinin elemanlarına scanf fonksiyonuyla klavyeden değer aliniyor.*/


    for (i = 0; i < SIZE; i++)
    total += sample[i];


    i = 0;
    while (i < SIZE) {
    total += sample[i];
    ++i;
    } /* sample dizisinin elemanları toplaniyor. */

    Bir dizi tanımlaması yapıldığı zaman derleyici tanımlaması yapılmış dizi için bellekte toplam dizi uzunluğu kadar yer ayırır. Örneğin :

    double a[10];

    gibi bir tanımlama yapıldığında dizi için bellekte ardışıl (contigious) toplam 80 byte’lık bir yer ayırılacaktır.

    Dizinin son elemanı a[9] olacaktır. Çok sık yapılan bir hata, dizinin son elemanı diye bellekte derleyici tarafından tahsis edilmemiş bir yere değer atamaktır.

    a[10] = 5.;

    deyimiyle bellekte rasgele 8 byte’lık bir alana (güvenli olmayan) bir yere 5. değeri yazılmaktadır.

    Dizilere ilk değer verilmesi (array initialization)

    Dizilere ilk değer verme işleminin genel şekli aşağıdaki gibidir :

    <tür> <dizi ismi>[[uzunluk]] = {d1, d2, d3........};

    Örnekler :

    double sample[5] = {1.3, 2.5, 3.5, 5.8, 6.0};
    char str[4] = {‘d’, ‘i’, ‘z’, ‘i’};
    unsigned[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    Dizilere yukarıdaki gibi ilk değer verildiğinde, verilen değerler dizinin ilk elemanından başlayarak dizi elemanlarına sırayla atanmış olur.

    Dizilerin tüm elemanlarına ilk değer verme zorunluluğu yoktur. Dizinin eleman sayısından daha az sayıda elemana ilk değer verilmesi durumunda kalan elemanlara otomatik olarak 0 değeri atanmış olur. Bu kural hem yerel hem de global diziler için geçerlidir.

    Bu durumda bütün dizi elemanlarına 0 değeri atanmak isteniyorsa bunun en kısa yolu :

    int a[20] = {0};

    yalnızca dizinin ilk elemanına 0 değeri vermektir. Bu durumda derleyici dizinin kalan elemanlarına otomatik olarak 0 değeri atayacaktır.

    Dizi elemanlarına ilk değer verilmesinde kullanılan ifadeler, sabit ifadeleri olmalıdır.

    int a[10] = {b, b + 1, b + 2};

    gibi bir ilk değer verme işlemi derleme zamanı hatası oluşturur.

    Bir diziye ilk değer verme işleminde dizi eleman sayısından daha fazla sayıda ilk değer vermek derleme zamanında hata oluşumuna neden olur :

    int b[5] = {1, 2, 3, 4, 5, 6}; /* error */

    yukarıdaki örnekte b dizisi 5 elemanlı olmasına karşın, ilk değer verme ifadesinde 6 değer kullanılmıştır.

    dizi elemanlarına ilk değer verme işleminde dizi uzunluğu belirtilmeyebilir, bu durumda derleyici dizi uzunluğunu verilen ilk değerleri sayarak kendi hesaplar ve dizinin o uzunlukta açıldığını kabul eder. Örneğin :

    int sample[] = {1, 2, 3, 4, 5};

    derleyici yukarıdaki ifadeyi gördüğünde sample dizisinin 5 elemanlı olduğunu kabul edecektir. Bu durumda yukarıdaki gibi bir bildirimle aşağıdaki gibi bir bildirim eşdeğer olacaktır:

    int sample[5] = {1, 2, 3, 4, 5};

    başka örnekler :

    char name[ ] = {‘N’, ‘e’, ‘c’, ‘a’, ‘t’, ‘i’ ‘ ‘, ‘E’, ‘r’, ‘g’, ‘i’, ‘n’, ‘\0’};
    unsigned short count[ ] = {1, 4, 5, 7, 8, 9, 12, 15, 13, 21};

    derleyici name dizisinin uzunluğunu 13, count dizisinin uzunluğunu ise 10 olarak varsayacaktır.

    yerel ve global diziler

    Bir dizi de, diğer nesneler gibi yerel ya da global olabilir. Yerel diziler blokların içlerinde tanımlanan dizilerdir. Global diziler ise tüm blokların dışında tanımlanırlar. Global bir dizinin tüm elemanları global nesnelerin özelliklerine sahip olacaktır. Yani dizi global ise dizi elemanı olan nesneler dosya faaliyet alanına (file scope) sahip olacak, ve ömür karakteri açısından da statik ömürlü (static storage duration) olacaklardır. Global bir dizi söz konusu olduğunda eğer dizi elemanlarına değer verilmemişse, dizi elemanları 0 değeriyle başlatılacaktır. Ama yerel diziler söz konusu olduğunda, dizi elemanı olan nesneler blok faaliyet alanına (block scope) sahip olacaklar, ömür açısından ise dinamik ömür karakterinde olacaklardır. (dynamic storage class) Değer atanmamış dizi elemanları içinde rasgele değerler (garbage values) bulunacaktır. Aşağıdaki örneği yazarak derleyicinizde yazarak deneyiniz :

    #include <stdio.h>

    int global[10];

    main()
    {
    int yerel[10], i;

    for (i = 0; i < 10; ++i)
    printf(“global[%d] = %d\n”, i, global[i]);

    for (i = 0; i < 10; ++i)
    printf(“yerel[%d] = %d\n”, i, yerel[i]);
    }

    dizilerin birbirine atanması

    int a[SIZE], b[SIZE];

    tanımlamasından sonra, a dizisi elemanlarına b dizisinin elemanları kopyalanmak istenirse

    a = b; /* hata */

    yukarıdaki gibi bir atama derleme zamanı hatası verecektir. Çünkü a ve b dizi isimleridir. C dilinde dizi isimleri nesne göstermezler. Dizi isimleri dizilerin bellekte yerleştirildikleri alanın başlangıcını gösteren ve dizinin türü ile aynı türden adres değerleridir. (Dolayısıyla sabit değerlerdir.)

    Dizileri ancak bir döngü kullanarak kopyalayabiliriz :

    for (i = 0; i < SIZE; ++i)
    a[i] = b[i];

    Yukarıdaki döngü deyimiyle b dizisinin her bir elemanının değeri a dizisinin ilgili indisli elemanına atanmıştır. Dizilerin kopyalanması için başka bir yöntem de bir standart C fonksiyonu olan memcpy (memory copy) fonksiyonunu kullanmaktır. Bu fonksiyon göstericiler konusundan sonra ele alınacaktır.

    dizilerle ilgili uygulama örnekleri:

    Dizilerin kullanılmasını teşvik eden temel nedenlerden biri aynı türden nesnelerin bir dizi altında tanımlanmasıyla, döngüler yardımıyla dizi elemanlarının çok kolay bir şekilde işleme tutulabilmesidir. Aşağıdaki örneklerde bu temalar işlenmiştir :

    Uygulama 1 : Bir dizi elemanlarının toplamının bulunması

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, 67, 89, 24, -2, -15, 40};
    int toplam = 0;
    int k;

    clrscr();
    for (k = 0; k < SIZE; ++k)
    toplam += dizi[k];
    printf("dizi elemanları toplamı = %d\n", toplam);

    return 0;
    }

    Uygulama 2 : bir dizi içindeki en küçük sayıyı bulan program :

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, 67, 89, 24, -2, -15, 40};
    int min, k;

    clrscr();
    min = dizi[0];
    for (k = 1; k < SIZE; ++k)
    if (min > dizi[k])
    min = dizi[k];
    printf("en küçük eleman = %d\n", min);

    return 0;
    }

    eğer en küçük elemanın kendisiyle birlikte, dizinin kaçıncı elemanı olduğu da bulunmak istenseydi :

    int indis;
    ...
    for (k = 1; k < SIZE; ++k)
    if (min > dizi[k]) {
    min = dizi[k];
    indis = k; /* en küçük eleman yenilendikçe indisi başka bir değişkende saklanıyor. */
    }

    Uygulama 3: Bir diziye klavyeden değer alarak daha sonra dizi elemanlarını ekrana yazdıran program :

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10

    main()
    {
    int dizi[SIZE];
    int k;

    clrscr();
    for (k = 0; k < SIZE; ++k)
    scanf("%d", &dizi[k]);
    for (k = 0; k < SIZE; ++k)
    printf("dizi[%d] = %d\n", k, dizi[k]);
    return 0;
    }

    Uygulama 4 : Bir dizi içerisindeki elemanları küçükten büyüğe doğru bubble sort algoritmasıyla sıraya dizen program:

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, -23, 29, 12, 90, 1, 20};
    int i, k, temp;

    clrscr();
    for (i = 0; i < SIZE - 1; ++k)
    for (k = 0; k < SIZE - 1; ++k)
    if (dizi[k] > dizi[k + 1]) {
    temp = dizi[k];
    dizi[k] = dizi[k + 1];
    dizi[k + 1] = temp;
    }
    for (i = 0; i < SIZE; ++i)
    printf("dizi[%d] = %d\n", k, dizi[k]);
    return 0;
    }

    Bu algoritmanın performansı basit bir düzenleme ile artırılabilir. Dıştaki döngünün her dönüşünde en büyük sayı en sona gidecektir. Bu durumda içteki döngünün her defasında SIZE – 1 kez dönmesine gerek yoktur. İçteki döngü SIZE – 1 – i kez dönebilir.
    ...
    for (i = 0; k < SIZE - 1; ++k)
    for (k = 0; k < SIZE - 1; ++k)
    ...

    Bubble sort algoritmasını sıraya dizme işlemi tamamlandığında dıştaki döngüyü sonlandıracak biçimde düzenleyebiliriz :

    #include <stdio.h>


  9. #9
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb

    #include <conio.h>

    #define SIZE 10
    #define UNSORTED 0
    #define SORTED 1

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, -23, 29, 12, 90, 1, 20};
    int i, k, temp, flag;

    for (i = 0; i < SIZE - 1; ++k) {
    flag = SORTED;
    for (k = 0; k < SIZE - 1; ++k)
    if (dizi[k] > dizi[k + 1]) {
    temp = dizi[k];
    dizi[k] = dizi[k + 1];
    dizi[k + 1] = temp;
    flag = UNSORTED;
    }
    if (flag == SORTED)
    break;
    }
    for (i = 0; i < SIZE; ++i)
    printf("dizi[%d] = %d\n", i, dizi[i]);
    return 0;
    }

    bu yeni düzenleme diğerine göre daha iyi de olsa performans açısından çok ciddi bir farklılığı yoktur.

    Aynı algoritmayı bir do while döngüsü kullanarak da yazabilirdik:

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10
    #define UNSORTED 0
    #define SORTED 1

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, -23, 29, 12, 90, 1, 20};
    int i, k, temp, flag;

    do {
    flag = SORTED;
    for (k = 0; k < SIZE - 1; ++k)
    if (dizi[k] > dizi[k + 1]) {
    temp = dizi[k];
    dizi[k] = dizi[k + 1];
    dizi[k + 1] = temp;
    flag = UNSORTED;
    }
    } while (flag == UNSORTED);

    for (i = 0; i < SIZE; ++i)
    printf("dizi[%d] = %d\n", i, dizi[i]);
    return 0;
    }


    Uygulama 5: Bir dizi içerisindeki elemanları küçükten büyüğe insertion sort algoritmasıyla sıraya dizen program:

    #include <stdio.h>
    #include <conio.h>

    #define SIZE 10

    main()
    {
    int dizi[SIZE] = {12, 25, -34, 45, -23, 29, 12, 90, 1, 20};
    int i, j, temp;

    clrscr();
    for (i = 1; i < SIZE; ++i) {
    temp = dizi[i];
    for (j = i; j > 0 && dizi[j - 1] > temp; --j)
    dizi[j] = dizi[j - 1];
    dizi[j] = temp;
    }
    for (i = 0; i < SIZE; ++i)
    printf("dizi[%d] = %d\n", i, dizi[i]);
    return 0;
    }

    sıralama yönünü küçükten büyüğe yapmak yerine büyükten küçüğe yapmak için içerideki döngüyü

    for (j = i; j > 0 && dizi[j - 1] < temp; --j)

    şeklinde değiştirmek yeterli olacaktır.


    Uygulama 6 : Bir dizi içerisindeki elemanları küçükten büyüğe selection sort algoritmasıyla sıraya dizen program:

    #include <stdio.h>

    #define SIZE 10

    int a[SIZE] = { 12, 23, 45, 78, -23, ,56, 78, 3, 9, -4};

    int main()
    {
    int k, l, max, indis;

    for (k = 0; k < SIZE; ++k) {
    max = a[k];
    indis = k;
    for (l = k + 1; l < SIZE; ++l)
    if (a[l] > max) {
    max = a[l];
    indis = l;
    }
    a[indis] = a[k];
    a[k] = max;
    }
    for (k = 0; k < SIZE; ++k)
    printf(“%d\n”, a[k]);
    return 0;
    }

    karakter dizileri

    Karakter dizileri char türden dizilerdir. Karakter dizilerinin bazı ilave özellikler dışında diğer dizi türlerinden bir farkı yoktur. char türden diziler daha çok içlerinde yazı tutmak için tanımlanırlar.

    char s[100];

    yukarıdaki tanımlamada s dizisi bütün elemanları char türden olan 100 elemanlı bir dizidir.
    char türden bir dizi içinde bir yazı tutmak demek, dizinin herbir elemanına sırayla yazının bir karakterini atamak anlamına gelecektir. (Bu arada char türden bir dizinin içinde bir yazı tutmanın zorunlu olmadığını, böyle bir dizinin pekala küçük tamsayıları tutmak amacıyla da kullanılabileceğini hatırlatalım)

    Yukarıda tanımlanan dizi içinde "Ali" yazısını tutmak isteyelim :

    s[0] = 'A';
    s[1] = 'l';
    s[2] = 'i';

    Şimdi şöyle bir problem ortaya çıkıyor. Dizi 100 karakterlik olmasına karşın biz dizi içinde 100 karakter uzunluğundan daha kısa olan yazılar da tutabiliriz. Peki biz yazıya tekrar ulaşmak istediğimizde bunu nasıl yapacağız? Yazının uzunluk bilgisini bilmiyoruz ki! Örneğin yazıyı ekrana yazdırmak istediğimizde, int türden dizilerde kullandığımız şekilde aşağıdaki gibi bir döngü kullanırsak :

    for (k = 0; k < 100; ++k)
    putchar(s[k]);

    bu döngü ile yalnızca ALi yazısı ekrana yazdırılmayacak dizinin diğer 97 elemanının da görüntüleri (rasgele değerler) ekrana yazdırılacak.

    Programlama dilleri bu duruma bir çözüm bulma konusunda iki temel yaklaşım benimserler.

    a. Dizinin 1. elemanına yazının uzunluk bilgisi yazılır. Böylece dizideki yazıya ulaşılmak istendiğinde önce dizinin 1. elemanından yazının uzunluk bilgisi çekilir. Böylece döngü deyiminin iterasyon sayısı değeri elde edilmiş olur. Döngü bu değere göre oluşturulur.

    s[0] = 3;
    s[0] = 'A';
    s[1] = 'l';
    s[2] = 'i';

    ....

    for (k = 1; k <= s[0]; ++k)
    putchar(s[k]);

    C dilinde bu yaklaşım kullanılmaz.

    b. Dizinin elemanlarına yazının karakterleri sırasıyla yerleştirilir. Yazı bittikten sonra dizinin sıradaki elemanına özel bir karakter yerleştirilir. Bu özel karakter doğal olarak normal şartlar altında bir yazının elemanı olamayacak bir karakter olarak seçilmelidir. Böylece char türden dizi içinde saklanmış yazıya tekrar ulaşılmak istendiğinde, kullanılacak döngü deyiminde, dizinin döngü değişkeni olan indisli elemanının seçilen özel karaktere eşit olmadığı sürece döngünün dönmesi şartı oluşturulur. C dilinin tasarımında bu yaklaşım tercih edilmiştir.

    C dilinde karakterler üzerinde işlemlerin hızlı ve etkin bir biçimde yapılabilmesi için “sonlandırıcı karakter” (NULL KARAKTER) kavramından faydalanılmaktadır.
    Sonlandırıcı karakter ASCII tablosunun (ya da sistemde kullanılan karakter setinin) sıfır numaralı (‘\x0’ ya da ‘\0’) karakteridir. Dolayısıyla sayısal değer olarak 0 sayısına eşittir. Görüntüsü yoktur. Bu karakter DOS ve UNIX sistemlerinde sonlandırıcı karakter olarak kullanılmaktadır. stdio.h içerisinde NULL sembolik sabiti 0 olarak tanımlandığı için sonlandırıcı karaktere NULL karakter de denir. NULL karakter ‘0’ karakteri ile karıştırılmamalıdır.

    ‘0’ karakterinin ASCII sıra numarası 48’dir. Dolayısıyla tamsayı değeri olarak 48 değerine sahiptir.
    ‘\0” karakterinin ASCII sıra numarası 0’dır. Dolayısıyla tamsayı değeri olarak 0 değerine sahiptir.

    ...
    printf(“%d\n”, ‘0’);
    printf(“%d\n”, ‘\0’);
    ...

    yukarıdaki ilk printf fonksiyonunun çağırılmasıyla ekrana 48 değeri yazdırılırken, ikinci printf fonksiyonunun çağırılmasıyla ekrana 0 değeri yazdırılmaktadır.

    char türden dizilere ilk değer verilmesi

    char türden dizilere ilk değer verme işlemi (initializing) aşağıdaki biçimlerde yapılabilir .

    Diğer türden dizilerde olduğu gibi virgüllerle ayrılan ilk değerler küme parantezi içinde yer alır :

    char name[12] = {‘N’, ‘e’, ‘c’, ‘a’, ‘t’, ‘i’, ‘ ‘, ‘E’, ‘r’, ‘g’, ‘i’,’n’};

    Diğer dizilerde olduğu gibi dizi eleman sayısından daha fazla sayıda elemana ilk değer vermek derleme zamanında hata oluşumuna neden olur.

    char name[5] = {‘N’, ‘e’, ‘c’, ‘a’, ‘t’, ‘i’, ‘ ‘, ‘E’, ‘r’, ‘g’, ‘i’,’n’}; /* error */

    Dizi eleman sayısı kadar elemana ya da dizi eleman sayısından daha az sayıda elemana ilk değer verilebilir. Daha az sayıda elemana ilk değer verilmesi durumunda ilk değer verilmemiş elemanlar diğer dizilerde olduğu gibi 0 değeriyle başlatılacaklardır. 0 değerinin NULL karakter olduğunu hatırlayalım :

    char name [10] = {‘A’, ‘l’, ‘i’};

    ...
    ...
    ‘A’
    name[0]
    ‘l’
    name[1]
    ‘i’
    name[2]
    ‘\0’
    name[3]
    ‘\0’
    name[4]
    ‘\0’
    name[5]
    ‘\0’
    name[6]
    ‘\0’
    name[7]
    ‘\0’
    name[8]
    ‘\0’
    name[9]
    ...
    ...


    Dizi elemanlarına ilk değer verilirken dizi boyutu belirtilmeyebilir. Bu durumda derleyici dizi boyutunu verilen ilk değerleri sayarak saptar ve diziyi bu boyutta açılmış varsayar.

    char name[ ] = {‘A’, ‘l’, ‘i’};

    yukarıdaki tanımlama deyimiyle derleyici name dizisinin 3 elemanlı olarak açıldığını varsayacaktır.

    Boyut bilgisi vermeden, char türden dizilere tek tek ilk değer verilmesi durumunda, ve boyut bilgisi verilerek dizinin toplam eleman sayısı kadar elemana tek tek ilk değer verilmesi durumunda, derleyici NULL karakteri dizinin sonuna otomatik olarak yerleştirmeyecektir. Bu durumda eğer sonlandırıcı karakter özelliğinden faydalanılmak isteniyorsa, NULL karakter de verilen diğer ilk değerler gibi programcı tarafından verilmelidir. Örnek :

    char name[ ] = {‘A’, ‘l’, ‘i’, ‘\0’};
    char isim[7] = {‘N’, ‘e’, ‘c’, ‘a’, ‘t’, ‘i’, ‘\0’};

    Bu şekilde ilk değer vermek zahmetli olduğundan, ilk değer vermede ikinci bir biçim oluşturulmuştur. Karakter dizilerine ilk değerler çift tırnak içinde de verilebilir :

    char name[ ] = “Ali”;

    bu biçimin diğerinden farkı derleyicinin sonuna otomatik olarak NULL karakteri yerleştirmesidir.

    ...
    ...
    ‘A’
    name[0]
    ‘l’
    name[1]
    ‘i’
    name[2]
    ‘\0’
    name[3]


    yukarıdaki örnekte derleyici diziyi 4 elemanlı olarak açılmış varsayacaktır. Dizi eleman sayısından daha fazla sayıda elemana ilk değer vermeye çalışmak, bu biçimin kullanılması durumunda da derleme zamanında hata oluşumuna neden olacaktır.

    char city[5] = “İstanbul”; /* error */

    Bu durumun bir istisnası vardır. Eger tam olarak dizi eleman sayısı kadar dizi elemanına çift tırnak içinde ilk değer verilirse bu durum ERROR oluşturmaz. Derleyici bu durumda NULL karakteri dizinin sonuna yerleştirmez. Bu durum C dili standartlarına yöneltilen eleştirilerden biridir. (C++ dilinde son kabul edilen standartlara göre bu durum da error oluşturmaktadır.)

    char name[3] =”Ali”; /* C'de hata değil, C++'da hata */

    ...
    ...
    ‘A’
    name[0]
    ‘l’
    name[1]
    ‘i’
    name[2]
    ...
    buraya bu durumda null karakter yerleştirilmiyor.



    '\0' karakteri karakter dizileri üzerinde yapılan işlemleri hızlandırmak için kullanılır. Örneğin int türden bir dizi kullanıldığında dizi elemanları üzerinde döngüleri kullanarak işlem yaparken dizi uzunluğunun mutlaka bilinmesi gerekmektedir. Ama char türden diziler söz konusu olduğunda artık dizi uzunluğunu bilmemiz gerekmez, çünkü yazının sonunda '\0' karakter bulunacağından, kontrol ifadelerinde bu durum test edilerek yazının sonuna gelinip gelinmediği anlaşılır. Ancak '\0' karakterin, karakter dizilerinde yazıların son elemanı olarak kullanılmasının dezavantajı da diziye fazladan bir karakter yani '\0' karakteri eklemek zorunluluğudur. Bu nedenle SIZE elemanlı bir diziye en fazla SIZE – 1 tane karakter girilmelidir.

    klavyeden karakter dizisi alan ve ekrana karakter dizisi yazan standart C fonksiyonları

    Daha önce gördüğümüz getchar, getch, ve getche fonksiyonları klavyeden tek bir karakter alıyorlardı. Yine putchar fonksiyonu ise tek bir karakteri ekrana yazıyordu. C dilinde birden fazla karakteri (bir stringi) klavyeden alan ya da birden fazla karakteri ekraana yazan standart fonksiyonlar bulunmaktadır.

    gets fonksiyonu

    gets fonksiyonu klavyeden karakter dizisi almakta kullanılan standart bir C fonksiyonudur. Kullanıcı karakterleri girdikten sonra enter tuşuna basmalıdır. Klavyeden girilecek karakterlerin yerleştirileceği dizinin ismini parametre olarak alır. daha önce de belirtildiği gibi dizi isimleri aslında bir adres bilgisi belirtmektedir. gets fonksiyonunun da parametresi aslında char türden bir adrestir. Ancak göstericilerle ilgili temel kavramları henüz öğrenmediğimiz için şimdilik gets fonksiyonunun char türden bir dizi içine klavyeden girilen karakterleri yerleştirdiğini kabul edeceğiz. Örneğin :

    char name[20];

    gets(name);

    ile klavyeden enter tuşuna basılana kadar girilmiş olan tüm karakterler name dizisi içine sırayla yerleştirilirler. Klavyeden Necati yazısının girildiğini kabul edelim.

    ...
    ...
    ‘N’
    name[0]
    ‘e’
    name[1]
    ‘c’
    name[2]
    ‘a’
    name[3]
    ‘t’
    name[4]
    ‘i’
    name[5]
    ‘\0’
    name[6]
    ...
    ...


    gets fonksiyonu klavyeden girilen karakterleri diziye yerleştirdikten sonra dizinin sonuna NULL karakter (sonlandırıcı karakter) diye isimlendirilen 0 numaralı ASCII karakterini (ya da kullanılan karakter setinin 0 numaralı karakterini) koyar.

    gets fonksiyonu dizi için hiç bir şekilde sınır kontrolu yapmaz. gets fonksiyonu ile dizi eleman sayısından fazla karakter girilirse, d,z, taşacağı için beklenmeyen sonuçlarla karşılaşılabilir. Bu tür durumlar göstericiler konusunda (gösterici hataları) detaylı olarak incelenecektir.
    gets fonksiyonu ‘\0’ karakterini dizinin sonuna eklediği için SIZE uzunluğunda bir dizi için gets fonksiyonuyla alınacak karakter sayısı en fazla SIZE – 1 olmalıdır. Çünkü sonlandırıcı karakterde diğer karakterler gibi bellekte bir yer kaplamaktadır. Örnek :

    char isim[12];

    gets(isim);

    ile klavyeden Necati Ergin isminin girildiğini düşünelim.

    ...
    ...
    ‘N’
    isim[0]
    ‘e’
    isim[1]
    ‘c’
    isim[2]
    ‘a’
    isim[3]
    ‘t’
    isim[4]
    ‘i’
    isim[5]
    ‘ ‘
    isim[6]
    ‘E’
    isim[7]
    ‘r’
    isim[8]
    ‘g’
    isim[9]
    ‘i’
    isim[10]
    ‘n’
    isim[11]
    ‘\0’
    taşma
    ...



    isim dizisinin tanımlanmasıyla derleyici bu dizi için bellekte 12 byte yer ayıracaktır. isim[0] ...isim[11]
    gets fonksiyonu bu durumda ‘\0’ karakterini derleyicinin dizi için tahsis etmediği bir bellek hücresine yazacaktır. Bu tür durumlara dizinin taşırılması (off bye one) denmektedir.
    taşma durumuyla ilgili olarak ortaya çıkacak hatalar derleme zamanına değil çalışma zamanına (run time) ilişkindir.

    puts fonksiyonu

    puts fonksiyonu bir karakter dizisinin içeriğini ekrana yazdırmak için kullanılır. İçeriği yazdırılacak olan karakter dizisinin ismini parametre olarak alır. puts fonksiyonu karakter dizisini ekrana yazdıktan sonra imleci sonraki satırın başına geçirir.

    char name[20];

    gets(name);
    puts(name);

    yukarıdaki örnekte gets fonksiyonu ile klavyeden alınan karakter dizisi puts fonksiyonu ile ekrana yazdırılmaktadır.

    karakter dizilerini ekrana yazdırmak için printf fonksiyonu da kullanılabilir.

    printf(“%s\n”, name);

    ile

    puts(name);

    aynı işi yapmaktadır. Ancak printf fonksiyonu dizi içeriğini ekrana yazdırdıktan sonra imleci alt satıra taşımaz.

    puts(name);

    deyimi yerine aşağıdaki kod parçasını da yazabilirdik.

    for (i = 0; name[i] != ‘\0’; ++i)
    putchar(s[i]);
    putchar(‘\n’);

    puts fonksiyonu ve %s format karakteriyle kullanıldığında printf fonksiyonu, sonlandırıcı karakter görene kadar bütün karakterleri ekrana yazar. Bu durumda, herhangi bir şekilde NULL karakter ezilirse her iki fonksiyon da ilk sonlandırıcı karakteri görene kadar yazma işlemine devam edecektir.

    Örneğin :

    char city[ ] = “Ankara”;

    city[6] = ‘!’;

    deyimi ile sonlandırıcı karakter ortadan kaldırılırsa (ezilirse) :

    puts(name);

    şeklinde puts fonksiyonu çağırıldığında ekrana Ankara!yazıldıktan sonra tesadüfen ilk NULL karakter görülene kadar ekrana yazmaya devam edilir. puts ve printf fonksiyonları karakter dizilerini yazarken yalnızca NULL karakteri dikkate alırlar. karakter dizilerinin uzunluklarıyla ilgilenmezler.

    sizeof operatörünün dizilerle kullanılması

    sizeof operatörü operand olarak bir dizi ismi aldığında byte olarak o dizinin toplam uzunluğunu değer olarak üretir.

    double sample[10];

    sizeof(sample)

    ifadesi 80 değerini üretecektir.

    sizeof operatörü operand olarak dizinin bir elemanını aldığında ürettiği değer, dizi hangi türden ise o türün kullanılan sistemdeki byte olarak uzunluğu olacaktır. yani yukarıdaki örnekte

    sizeof(sample[0]) ifadesi, 8 değerini üretecektir.

    sizeof operatörünü bir dizinin uzunluk bilgisi gerektiği yerde de kullanabiliriz :

    sizeof(sample) / sizeof(sample[0])

    ifadesi dizi uzunluğunu verecektir. Örnek :

    for (i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
    a[i] = 0;


    karakter dizileriyle ilgili bazı küçük uygulama örnekleri

    Uygulama 7: Karakter dizisi içerisindeki yazının uzunluğunu bulan program:

    #include <stdio.h>

    main()
    {
    int k = 0,
    char s[50];

    gets(s);
    while (s[k] != ‘\0’)
    ++k;
    printf(“uzunluk = %d\n”, k);
    }

    yukarıda while döngüsü yerine for döngüsü kullanılabilirdi :

    for (k = 0; s[k] != ‘\0’; ++k)
    ;


    Uygulama 8: karakter dizisini tersten yazdıran program :

    #include <stdio.h>

    #define SIZE 100

    main()
    {
    char s[SIZE];
    int k;

    printf("bir yazı giriniz :");
    gets(s);
    for (k = 0; s[k + 1] != '\0'; ++k)
    ++k;
    for (; k >= 0; --k)
    putchar(s[k]);
    return 0;
    }


    Uygulama 9: Karakter dizisindeki, büyük harfleri küçük harfe küçük harfleri büyük harfe çeviren bir program :

    #include <stdio.h>
    #include <ctype.h>

    #define SIZE 100

    main()
    {
    char s[SIZE];
    int k;

    printf ("bir yazı giriniz : ");
    gets(s);
    for (k = 0; s[k] != '\0'; ++k)
    s[k] = isupper(s[k]) ? toupper(s[k]) : tolower(s[k]);
    printf("dönüştürülmüş yazı \n");
    puts(s);
    return 0;
    }

    Uygulama 10 : Karakter dizisini tersyüz eden program :

    #include <stdio.h>

    main()
    {
    char s[50];
    int n, j, temp,

    gets(s);
    for (n = 0; s[n] != ‘\0’; ++n)
    ;
    for (j = 0; j < n / 2; ++j) {
    temp = s[n – j – 1];
    s[n – j – 1] = s[j];
    s[j] = temp;
    }
    puts(s);
    }

    Uygulama 11 : bir karakter dizisi içindeki harfleri küçükten büyüğe doğru sıralayan bir program

    #include <stdio.h>

    #define SIRALI 1
    #define SIRASIZ 0

    main()
    {
    char s[100];
    int flag, k, temp;

    clrscr();
    printf("bir yazı giriniz :");
    gets(s);
    do {
    flag = SIRALI;
    for (k = 0; s[k + 1] != '\0'; ++k)
    if (s[k] > s[k + 1]) {
    temp = s[k];
    s[k] = s[k + 1];
    s[k + 1] = temp;
    flag = SIRASIZ;
    }
    } while (flag == SIRASIZ);
    printf("sıralanmış yazı :\n");
    puts(s);
    getch();
    }




    1. GÖSTERİCİLER

    Adres Kavramı

    Adres kavramı hem donanıma hem de yazılıma ilişkindir. Donanımsal açıdan adres bellekte yer gösteren bir sayıdan ibarettir. Mikroişlemci bellekte bir bölgeye ancak o bölgenin adres bilgisiyle erişebilir. Bellekte (RAM'de) her byte (8 bit) diğerlerinden farklı bir adresle temsil edilir. Sıfır sayısından başlayarak her byte’a artan sırada bir karşılık getirerek elde edilen adresleme sistemine doğrusal adresleme sistemi (linear addressing), bu sistem kullanılarak elde edilen adreslere de doğrusal adresler denilmektedir. Donanımsal olarak RAM'deki her bir byte'a okuma ya da yazma yapma amacıyla ulaşılabilir.

    Örneğin, 64 K’lık bir belleğin doğrusal olarak adreslenmesi aşağıdaki biçimde yapılabilir :

    BELLEK
    DOĞRUSAL ADRES

    0 (0000)

    1

    2

    3

    4
    ...........
    ..................

    65533 (FFFD)

    65534 (FFFE)

    65535 (FFFF)


    Bilgisayara ilişkin pekçok nitelikte olduğu gibi doğrusal adreslemede de 10’luk sistem yerine 16’lık sistemdeki gösterimler tercih edilir. Bundan böyle yalnızca adres dendiğinde doğrusal adres anlaşılmalıdır.

    Nesnelerin Adresleri

    Her nesne bellekte yer kapladığına göre belirli bir adrese sahiptir. Nesnelerin adresleri, sistemlerin çoğunda, derleyici ve programı yükleyen işletim sistemi tarafından ortaklaşa olarak belirlenir. Nesnelerin adresleri program yüklenmeden önce kesin olarak bilinemez ve programcı tarafından da önceden tespit edilemez. Programcı, nesnelerin adreslerini ancak programın çalışması sırasında (run time) öğrenebilir. Örneğin:

    char ch;

    biçiminde bir tanımlamayla karşılaşan derleyici bellekte ch değişkeni için 1 byte yer ayıracaktır. Derleyicinin ch değişkeni için bellekte hangi byte'ı ayıracağını önceden bilemeyiz. Bunu ancak programın çalışması sırasında öğrenebiliriz. Yukarıdaki örnekte ch yerel bir değişkendir. ch değişkeni ilgili blok icra edilmeye başlandığında yaratılıp, bloğun icrası bittiğinde de yok olmaktadır. ch değişkeninin 1A03 adresinde olduğu varsayılarak aşağıdaki şekil çizilmiştir:


    1A00

    1A01

    1A02
    ch
    1A03

    1A04

    1A05


    Tanımlanan nesne 1 byte’dan daha uzunsa, o zaman nesnenin adresi nasıl belirlenir?

    int b;


    1C00

    1C01

    1C02
    x
    1C03
    1C04

    1C05

    1C06

    1C07



    1 byte’dan uzun olan nesnelerin adresleri, onların ilk byte’larının (düşük anlamlı byte’larının) adresleriyle belirtilir. Yukarıdaki örnekte x değişkeninin adresi 1C03’tür. Zaten x değişkeninin tamsayı türünden olduğu bilindiğine göre diğer parçasının 1C04 adresinde olacağı da açıktır.

    Benzer biçimde long türünden olan y değişkeninin bellekteki yerleşiminin aşağıdaki gibi olduğu varsayılırsa, adresinin 1F02 olduğunu söyleyebiliriz:


    1F00

    1F01
    x
    1F02
    1F03
    1F04
    1F05

    1F06

    1F07


    Yerel ya da global olsun, ardışıl (contigious) bir biçimde tanımlanmış nesnelerin bellekte de ardışıl bir biçimde tutulacağının bir garantisi yoktur. Örneğin:

    {
    int x;
    char ch;
    ...
    }

    Buradaki ch değişkeninin x değişkeninden 2 byte sonra bulunacağının bir garantisi yoktur.

    Ayrı Bir Tür Olarak Adres

    Yazılımsal açıdan RAM'de belirli bir byte'dan ne amaçla bahsetmek isteyebiliriz? O byte içinde yer alan nesnenin değerini tespit etmek için! Eğer tüm nesnelerimiz 1 byte uzunluğunda olsalardı, o zaman yazılımsal olarak adres ile fiziksel adres arasında herhangi bir fark bulunmayacaktı. Oysa RAM'deki belirli bir byte'dan söz ettiğimizde, o byte'ı (belki onu izleyen bazı byte'larla birlikte) herhangi türden bir nesne kaplıyor olabilir. Yazılımsal olarak adres bilgisi yalnızca bellekte yer gösteren bir sayıdan ibaret değildir; aynı zamanda o adresin gösterdiği yerdeki bilginin ne biçimde yorumlanacağını belirten bir tür bilgisini de içermektedir.

    Örneğin yazılımsal olarak 1250 adresindeki değerden söz etmek isteseydik aşağıdaki soruların cevabı açıkta kalacaktı.

    Söz konusu değer yalnızca 1250 adresindeki byte da mı saklanmış yoksa bunu izleyen (1251, 1252 vs.) bytelar da nesnenin değerinin tespit edilmesinde kullanılacak mı? Söz konusu değer hangi formatta yorumlanacak? (Yani işaretli tamsayı aritmetiğine göre mi, işaretsiz tam sayı aritmetiğine göre mi, gerçek sayı formatına göre mi, vs.)

    yazılımsal olarak bir adres bilgisinin iki bileşeni vardır:

    1. Bellekte yer gösteren sayısal bir değer
    2. Adresin türü

    Adreslerin Türleri

    Bir adresin türü demekle o adrese ilişkin bellek bölgesinde bulunan bilginin derleyici tarafından yorumlanış biçimi anlaşılmalıdır. Örneğin:

    char ch;

    gibi bir bildirim sonrasında ch değişkeninin belleğe aşağıdaki biçimde yerleşmiş olduğunu kabul edelim:


    1A00

    1A01

    1A02
    ch
    1A03

    1A04

    1A05


    Yukarıdaki şekle baktığımızda 1A03 sayısının bir adres belirttiğini anlıyoruz. 1A03 adresinde bulunan bilgi char türdendir. Çünkü derleyici 1A03 adresinde bulunan bilgiyi char türden bir nesne olarak yorumlamaktadır. Adres yalnız başına sayısal bileşeniyle bir anlam taşımaz, gösterdiği türle birlikte belirtilmesi gerekmektedir.

    C’de adres bilgisi ayrı bir veri/nesne türüdür. Daha önce gördüğümüz 11 temel veri türüne ilaveten 11 ayrı adres türünün de varlığından söz edebiliriz.

    Adres Sabitleri

    Adresler tamsayı görünümünde olsalar da tamsayı sabitleri gibi belirtilmezler. Çünkü tür bilgilerinin de belirtilmesi gerekir. Adres sabitleri, tamsayı türlerindeki sabitler üzerinde bilinçli tür dönüşümü yapılarak elde edilirler. Bir tamsayı sabitini adres türüne çevirmek için tür dönüştürme operatörü kullanılır.

    (<tür> *) <tamsayı sabiti>

    Tür dönüştürme operatörünün içindeki * adres ya da göstericiyi temsil etmektedir. Örneğin

    0x1F00

    hexadesimal olarak gösterilmiş int türden bir sabittir. Ancak:

    (int *) 0x1F00

    int türünden bir adres sabitidir.

    0x16C0L

    long türden bir tamsayı sabittir. Ancak:

    (long *) 0x16C0L

    long türden bir adres sabitidir. Bunun anlamı şudur: Derleyici 16C0 adresiyle belirtilen yerdeki bilgiyi long olarak yorumlayacaktır.

    Adres türüne bilinçli dönüşüm yalnız sabitlerle yapılmayabilir, örneğin:

    (char *) var

    var isimli değişken hangi türden olursa olsun, yukarıdaki ifade ile char türden bir adres haline dönüştürülmüştür.

    Bir adres bilgisini bir nesne içinde tutmak isteyelim. (Şu an için neden böyle bir şey isteyebileceğimiz sorusu akla gelebilir, ama ileride bunun çok doğal ve gerekli bir işlem olduğunu göreceğiz.) Bu işi nasıl gerçekleştireceğiz? İlk olarak aklımıza şu gelebilir: int türden bir nesne tanımlayalım ve adresin sayısal bileşenini tanımladığımız int türden nesne içinde tutalım. Yani adresin sayısal bileşeni olan tamsayıyı int türden bir nesneye atayalım. Bu durumda iki önemli sakınca ortaya çıkacaktır:

    Söz konusu int nesnenin değerine programın herhangi bir yerinde tekrar ulaştığımızda, bunun bir adres bilgisinin (yani iki bileşenli bir bilginin) sayısal bileşeni mi yoksa int türden normal bir değer mi olarak yorumlanacağını nereden bileceğiz? Peki örneğin değişkenin isminden, değerinin bir adres bilgisinin sayısal bileşeni olduğu sonucunu çıkardığımızı düşünelim, bu durumda adresin tür bileşeninin ne olduğunu nereden anlayacağız?

    O zaman belki de şu önerilecektir: int türden bir adres bilgisini int türden bir nesnede, char türden bir adres bilgisini char türden bir nesnede, double türden bir adres bilgisini ise double türden bir nesnede saklayalım, vs. Ama örneğin 64K'lık bir adres alanının söz konusu olduğunu düşünelim. toplam 65535 byte söz konusu olabileceğine göre bize 2byte uzunluğunda bir nesne gerekecek. Örneğin 1 byte uzunlukta bir nesne içinde ancak ilk 255 byte'a ilişkin sayısal bileşenleri tutarken, 8 byte uzunluğunda bir nesne içinde de gerekmediği halde çok büyük bir alana ilişkin adres bilgisini tutabiliriz.

    int sabitleri int türden değişkenlere, gerçek sayı sabitleri gerçek sayı türünden değişkenlere doğal olarak atanıyorlar. Yani her sabit türünün doğal olarak atanabileceği türden bir değişken tanımlayabiliyoruz. İşte adres de ayrı bir tür olduğuna göre adres türünün de doğal olarak atanabileceği değişkenler tanımlanabilir. Bu değişkenlere (nesnelere) gösterici (pointer) denir.

    ‘a’ /* char türden bir sabittir */
    char a; /* a char türden bir değişkendir */
    int b; /* b int türden bir değişkendir */
    2000L /* long türden bir sabittir */
    long l; /* l long türden bir değişkendir */
    (<tür> *) 0x1B00 /* adres sabiti */
    pointer /* içinde adres tutan bir değişken */

    Gösterici (pointer) içinde adres bilgisi tutan bir değişkendir (nesnedir). Göstericiler de nesne oldukları için bellekte bir yer kaplarlar. Göstericilerin bellekte kapladıkları yerdeki 1’ler ve 0’ların bir tamsayı olarak yorumlanır, ve yorumlanan bu değer bir adres bilgisinin sayısal bileşenini gösterir.

    Peki adres bilgisinin iki bileşenli bir bilgi olduğunu söylemiştik. Göstericinin değeri adresin sayısal bileşeni olarak yorumlanacaksa adresin tür bileşeni nasıl elde edilecek? Zira bellekte yalnızca 1 ler ve 0 lar var. Göstericilerin tuttuğu adres bilgilerinin sayısal bileşenleri göstericiler içinde saklanan tamsayının değeridir. Adres bilgisinin tür bileşeni ise göstericinin tanımlanması sırasında bildirilen türdür.

    Göstericilerin Bildirimleri

    Göstericiler adres bilgilerini saklamak ve adreslerle ilgili işlemler yapmak için kullanılan nesnelerdir. Göstericilerin içlerinde adres bilgileri bulunur. Bu nedenle gösterici ile adres hemen hemen eş anlamlı olarak düşünülebilir. Ancak gösterici deyince bir nesne, adres deyince bir tür akla gelmelidir.

    Gösterici bildirimlerinin genel biçimi şöyledir:

    <tür> *<gösterici ismi>;

    <tür> göstericinin (içerisindeki adresin) türüdür. char, int, float... gibi herhangi bir tür olabilir.

    Burada * göstericiyi ya da adresi temsil etmektedir.

    Örnek gösterici bildirimleri:

    float *f;
    char *s;
    int *dizi;
    unsigned long *PDWORD;

    Gösterici bildirimlerinin diğer türlere ilişkin bildirimlerden * atomu ile ayrılmaktadır.

    char s;

    bildiriminde s char türden bir değişken iken

    char *s;

    bildiriminde s char türden bir göstericidir. İçerisine char türden bir adres konulmalıdır. Bu bildirimden derleyici şu bilgileri çıkaracaktır:

    s bir nesnedir, yani bellekte bir yer kaplar. s nesnesi için bellekte ayrılan yerdeki 1 ler ve 0 lar char türden bir adresin sayısal bileşeni olarak yorumlanırlar. Bu adresin tür bileşeni ise char türüdür.

    Burada * bir operatör değildir. Sentaks olarak nesnenin bir gösterici olduğunu anlatmaktadır. Ritchie stilinde, okunabilirlik açısından * ile gösterici ismi bitişik yazılmalıdır. Gösterici bildirimleri ile normal bildirimler bir arada yapılabilir. Örneğin :

    int *p, a;

    Burada p int türden bir göstericidir ama a int türden bir normal bir değişkendir.

    Aynı türden birden fazla göstericinin bildirimi yapılacaksa araya virgül konularak, her gösterici değişkenin bildirimi * atomu ile yapılmalıdır.

    char *str, *ptr;

    Yukarıdaki bildirimde str ve ptr char türden göstericilerdir.

    long *p1, *p2, l, k[20];

    Yukarıdaki bildirimde p1 ve p2 long türden göstericiler, l long türden bir değişken ve k ise long türden 20 elemanlı bir dizidir.

    Göstericilerin Uzunlukları

    Bir gösterici tanımlamasıyla karşılaşan derleyici –diğer tanımlamalarda yaptığı gibi- bellekte o gösterici için bir yer tahsis eder. Derleyicilerin göstericiler için tahsis ettikleri yerlerin uzunluğu donanım bağımlı olup, sistemden sisteme değişebilmektedir. 32 bit sistemlerde (örneğin UNIX ve Windows 95 sistemlerinde) göstericiler 4 byte uzunluğundadır. 8086 mimarisinde ve DOS altında çalışan derleyicilerde ise göstericiler 2 byte ya da 4 byte olabilirler. DOS’ta 2 byte uzunluğunda ki göstericilere yakın göstericler (near pointer), 4 byte uzunluğundaki göstericilere ise uzak göstericiler (far pointer) denilmektedir. Biz uygulamalarımızda şimdilik göstericilerin 2 byte olduğunu varsayacağız.

    Göstericilerin uzunlukları türlerinden bağımsızdır. Örneğin:

    char *str;
    int *p;
    float *f;

    DOS altında str, p ve f isimli göstericilerin hepsi de bellekte 2 byte ya da 4 byte yer kaplarlar. Çünkü göstericilerin türü yalnızca içinde tuttukları adres bilgisinin hangi tür olarak yorumlanacağı ile ilgilidir.

    Bir göstericiye aynı türden bir adres bilgisi yerleştirilmelidir. Örneğin :

    int *p;

    p = 100;

    Burada p göstericisine adres olmayan bir bilgi atanmaktadır. C dilinin kurallarına göre, derleme zamanında bir hata oluşumuna yol açmaz ama yanlıştır. (Derleyiciler bu durumu bir uyarı mesajı ile bildirirler.) Bu durum ileride detaylı olarak ele alınacaktır.

    int *p;
    p = (char *) 0x1FC0;

    Burada int türden p göstericisine char türden bir adres bilgisi atanmaktadır. C dilinin kurallarına göre, derleme zamanından bir hata oluşumuna yol açmaz ama yanlıştır. (Derleyiciler bu durumu bir uyarı mesajı ile bildirirler.)

    int *p;

    p = (int *) 0x1FC5; /* geçerli ve uygun bir atamadır */

    Bir adres bilgisi göstericiye atandığında adresin sayısal bileşeni gösterici içerisine yerleştirilir.

    int *p;

    p = (int *) 0x1FC3;

    Burada bellekte p gösterici değişkeninin tutulduğu yere 0x1FC3 sayısal değeri yerleştirilecektir.

    ...


    0001 1111
    p
    p göstericisinin bellekte bulundugu yere 1FC3 değeri yazılıyor.
    1100 0011
    ...




    Göstericilere kendi türlerinden bir adres bilgisi atanmasının gerektiğini söylemiştik. Şimdiye kadar adres bilgilerini yalnızca adres sabitleri şeklinde gördük ve örneklerimizde de göstericilere adres sabitlerini atadık. Peki adres sabitleri dışında adres bilgileri taşıyan başka ifadeler mevcut mudur? Evet. Örneğin diziler konusunu incelerken dizi isimlerinin, nesne göstermediğini, bir adres bilgisi olduğunu söylemiştik, şimdi bu konuyu daha detaylı olarak inceleyeceğiz:

    Dizi isimleri bir adres bilgisi belirtir. Adres bilgisinin iki bileşeni olduğuna göre örneğin:

    char s[10];

    gibi bir dizi tanımlaması yapıldığında dizi ismi olan s bir adres bilgisi belirtecektir. Peki bu bilginin tür bileşeni ve sayısal bileşenleri nelerdir?

    Dizi isimleri, türleri dizinin türleriyle aynı ve sayısal bileşenleri dizi için bellekte ayrılan bloğun başlangıç yerini gösteren bir adres bilgisi olarak ele alınırlar. Örneğin yukarıdaki örnekte dizinin bellekte aşağıdaki şekilde yerleştirildiğini düşünelim:

    s[0]
    1C00
    s[1]
    1C01
    s[2]
    1C02
    s[3]
    1C03
    s[4]
    1C04
    s[5]
    1C05
    s[6]
    1C06
    s[7]
    1C07
    s[8]
    1C08
    s[9]
    1C09


    Bu durumda dizi ismi olan s, char türden 1C00 adresine eşdeğerdir.

    Göstericilere kendi türlerinden bir adres bilgisi atamak gerektiğine göre aşağıdaki atamaların hepsi legal ve doğrudur:

    int a[100];
    long l[20];
    char s[100];
    double d[10];
    int *p;
    long *lp;
    char *cp;
    double *dp;

    p = a;
    lp = l;
    cp = s;
    dp = d;

    Bir göstericinin içine aynı türden bir dizinin ismi atanmalıdır. Örneğin:

    int *p;
    char s[] = “Necati”;

    p = s;

    Yukarıdaki örnekte int türden bir göstericiye char türden bir adres bilgisi atanmıştır. Derleme zamanında error oluşumuna neden olmaz ama yanlıştır, ileride detaylı olarak incelenecektir.

    C dilinde hiçbir değişkenin ya da dizinin tahsisat yeri programcı tarafından belirlenemez. Programcı değişkeni tanımlar, derleyici onu herhangi bir yere yerleştirebilir.

    Dizi isimleri göstericiler gibi sol taraf değeri olarak kullanılamaz. Örneğin, s bir dizi ismi olmak üzere

    ++s;

    deyimi error oluşturur. Çünkü dizi isimleri nesne göstermezler.

    Özetle, adres belirten 3 tür ifade ele alındı.

    1. Adres sabitleri.
    2. Göstericiler.
    3. Dizi isimleri.

    Göstericiler içlerinde adres bilgileri taşıdıklarına göre bir göstericiye aynı türden başka bir göstericinin değerinin atanması da tamamen uygundur.

    int *p, *q;

    p = (int *) 0x1AA0;
    q = p;

    Yukarıdaki atama ile q göstericisine p göstericisinin değeri atanmıştır. Yani bu atama deyiminden sonra q göstericisinin de içinde (int *) 0x1AA0 adresi bulunacaktır.

    ...


    0001 1010
    p
    p göstericisine (int *) 1AA0 adresi atanıyor.
    1010 0000
    ...


    ...


    ...


    0001 1010
    q
    q göstericisine de p göstericisinin içindeki değer atanıyor
    1010 0000





    int k;

    gibi bir tanımlama yaptığımızda k değişkeni int türdendir, içindeki değer int olarak yorumlanacaktır.

    20

    gibi, tek bir sabitten oluşan bir ifade de int türdendir, çünkü 20 int türden bir sabittir. Başka bir deyişle

    k

    ifadesiyle

    20

    ifadesinin türleri aynıdır. Her iki ifadenin türü de int türüdür. Ancak k ifadesi nesne gösteren bir ifade iken 20 ifadesi nesne göstermeyen bir ifadedir. (sağ taraf değeridir)

    int *ptr;

    gibi bir tanımlama yapıldığında ptr nesnesinin türü nedir? ptr içinde int türden bir adres sakladığına göre ptr nesnesinin türü int türden bir adrestir.

    int a[100];

    Yukarıdaki tanımlamadan sonra

    a

    gibi bir ifade kullanılırsa bu ifadenin türü de "int türden bir adres" dir. Ancak ptr ifadesi nesne gösteren bir ifadeyken, yani bir sol taraf değeriyken, a ifadesi nesne gösteren bir ifade değeridir. Sol taraf değeri olarak kullanılamaz.

    Yine bir adres sabitinden oluşan

    (int *) 0x1A00

    ifadesinin türü de int türden bir adrestir. Ancak bu ifade de sol taraf değeri değildir.

    Görüldüğü gibi göstericiler, belirli bir adres türünden, nesnelerdir. (sol taraf değerleridir.)

    Gösterici Operatörleri

    C dilinde toplam 4 tane gösterici operatörü vardır. Bu operatörler göstericiler ve adres bilgileriyle kullanılabilirler.

    Gösterici operatörleri şunlardır :

    *
    içerik operatörü
    indirection operator (dereferencing operator)
    &
    adres operatörü
    address of operator
    [ ]
    köşeli parantez operatörü
    index operator (subscript operator)
    ->
    ok operatörü
    arrow operator


    Bu operatörlerden ok operatörü yapı türünden adreslerle kullanıldığı için yapılar konusunda detaylı olarak incelenecektir.

    Gösterici Operatörlerinin Ayrıntılı İncelenmesi

    & Adres Operatörü (adress of operator)

    Adres operatörü tek operand alan önek konumunda bir operatördür. (unary prefix). Operatör öncelik tablosunun ikinci seviyesinde yer alır. Bu operatörün ürettiği değer operandı olan nesnenin adresidir. Yani operatörün ürettiği adres bilgisinin sayısal bileşeni nesnenin bellekteki fiziksel adres numarası, tür bileşeni ise nesnenin türü ile aynı türdür. & operatörünün operandı mutlaka bir nesne olmalıdır. Çünkü yalnızca nesnelerin (sol taraf değerlerinin) adres bilgilerine ulaşılabilir. & operatörüne operand olarak nesne olmayan bir ifade gönderilirse bu durum derleme zamanında hata oluşumuna neden olacaktır. (Borland derleyicileri bu durumda şöyle bir error mesajı verirler : “must take address of memory location”)

    int k;

    &k

    ifadesini ele alalım. Bu ifadenin ürettiği değer int türden bir adres bilgisidir. Adres bilgilerinin iki bileşeni olduğunu söylemiştik. Yukarıdaki ifadenin ürettiği bilginin sayısal bileşeni k nesnesinin bellekte yerleştirildiği yerin başlangıç adresi, tür bileşeni ise int türüdür. Zira k değişkeni int türden bir değişkendir.

    & operatörü diğer tek operand alan (unary) operatörler gibi, operatör öncelik tablosunun 2. seviyesinde bulunur. Bilindiği gibi bu öncelik seviyesinin öncelik yönü “sağdan sola”dır.

    Adres operatörü ile elde ettiğimiz adres aynı türden bir göstericiye atanmalıdır. Örneğin aşağıdaki programda bir göstericiye farklı türden bir adres atanmıştır:

    char ch = 'x';
    int *p;

    p = &ch; /* error değil ama yanlış */

    Bu durumda C'de adres belirten 4 tür ifade görmüş olduk:

    1. Adres sabitleri.
    2. Göstericiler.
    3. Dizi isimleri.
    4. & operatörü ile oluşturulmuş ifadeler.

    Tabi bu operatörün ürettiği adres bilgisi nesne değildir. Örneğin:

    int x;

    ++&x /* error */

    gibi bir işlem error ile neticelenir. ++ operatörünün operandı nesne olmalıdır. Yukarıdaki ifadede ++ operatörüne operand olan &x ifadesi bir nesne göstermemektedir. Yalnızca bir adres değeridir.

    * İçerik Operatörü (indirection operator)

    İçerik operatörü de önek konumunda bulunan ve tek operand alan bir operatördür (unary prefix). Bir gösterici, * operatörünün operandı olursa, elde edilen ifade p göstericisinin içerisindeki RAM adresinde bulunan, nesneyi temsil eder. Dolayısıyla, * operatörü ile oluşturulan bir ifade bir nesneyi temsil etmektedir, ve sol taraf değeri olarak kullanılabilir.

    int a;

    gibi bir bildirimde a nesnesinin türü int’dir. Çünkü a nesnesi içerisinde int türden bir bilgi tutulmaktadır.

    int *p;

    bildiriminde p nin türü int türden bir adrestir.

    char *ptr;

    gibi bir bildirimden iki şey anlaşılır:

    ptr char türden bir göstericidir. İçerisine char türden bir adres bilgisi yerleştirilir. ptr göstericisi * operatörü ile birlikte kullanıldığında elde edilen nesne char türdendir. Yani *ptr char türden bir nesnedir.

    Örneğin:

    int *p;

    p = (int *) 0x1FC3;
    *p = 100;

    Burada *p’nin türü int’dir. Dolayısıyla *p = 100 gibi bir işlemden yalnızca 0x1FC3 byte’ı değil, 0x1FC3 ve 0x1FC4 bytelarından her ikisi birden etkilenir.

    Göstericinin içerisindeki adresin sayısal bileşeni nesnenin düşük anlamlı byte’ının adresini içerir.
    Bu durumda bir göstericinin içerisine RAM’deki herhangi bir bölgenin adresi atanabilir. Daha sonra * operatörü ile o RAM bölgesine erişilebilir.

    * operatörünün operandı bir adres bilgisi olmak zorundadır. Yani operand adres sabiti olabilir. Dizi ismi olabilir. Bir gösterici olabilir. Adres operatörü ile elde edilmiş bir adres ifadesi olabilir.

    * operatörü yalnız göstericilerle değil, her tür adres bilgisi ile (adres sabitleri ve dizi isimleri vs.) de kullanılabilir. Bu operatör operandı ile belirtilen adresteki nesneye erişmekte kullanılır. Bu operatör ile elde edilen değer, operandı olan adreste bulunan değerdir.

    * operatörü bir adrese uygulandığında ifade bir nesne belirtir. Nesnenin türü operand olarak kullanılan adresin türü ile aynı türdendir. Örneğin :

    int main()
    {
    char s[] = “Balıkesir”;
    putchar(*s);

    return 0;
    }

    * operatörü operatör öncelik tablosunun 2. düzeyinde sağdan sola öncelikli bulunmaktadır. Örneğin :

    *s + 1;

    ifadesinde önce *s yapılır. Sonra + operatörü yapılır. Oysa ifade *(s + 1) biçiminde olsaydı önce + operatörü yapılırdı.

    Derleyiciler * operatörünün çarpma operatörü mü yoksa adres operatörü mü olduğunu ifade içerisindeki kullanımına bakarak anlar. Çarpma operatörü iki operand alırken adres operatörü önek konumundadır ve tek operand alır. Örnek :

    *s * 2

    ifadesinde 1. * adres operatörü iken 2. * aritmetik çarpma operatörüdür.

    [] Köseli Parantez (index) Operatörü :

    Dizi elemanlarına erişmekte kullandığımız köşeli parantez aslında unary prefix bir gösterici operatörüdür.

    [] içerisine tamsayı türünden bir ifade yazılır. (İfadenin değeri pozitif olmak zorunda değildir.)

    [] operatörünün operandı dizi ismi olmak zorunda değildir. Bir adres bilgisi olmak zorundadır.

    [] operatörünün operandı bir dizi ismi olabilir. Gösterici olabilir. Diğer adres belirten ifadeler olabilir.

    p[n] ile *(p + n) tamamen eşdeğerdir.

    Yani köşeli parantez operatörü bir adresten n ilerisinin içeriğini almak için kullanılır. [] operaötü ile elde edilen nesnenin türü operandı olan adresin türü ile aynı türdendir. Örneğin:

    #include <stdio.h>

    int main()
    {
    char s[] = "İstanbul";
    char *p;

    p = s + 1;
    putchar(p[2]);
    return 0;
    }

    [] operatörü öncelik tablosunun en yüksek düzeyinde bulunur. Örneğin:

    &p[n]

    ifadesinde önce [] yapılır, nesne elde edilir daha sonra nesnenin adresi alınır.

    [] içerisindeki ifadenin sayısal değeri negatif olabilir. Örneğin p[-2] geçerli bir ifadedir.

    #include <stdio.h>

    int main()
    {
    char ch = '0';

    (&ch)[0] = 'b'
    putchar(ch);
    return 0;
    }


  10. #10
    Ehil Üye zeet06 - ait Kullanıcı Resmi (Avatar)
    Üyelik tarihi
    Jul 2008
    Mesajlar
    1.023

    Lightbulb


    &ch ifadesi parantez içine alınmasaydı [] operatörünün önceliği söz konusu olacağından önce ch[0] yapılırdı. Buradan da bir nesne elde edilemeyeceğinden (& operatörünün operandının nesne olması gerektiğinden) hata oluşurdu.

    Adreslerle İşlemler / Adreslerin Artırılması ve Eksiltilmesi (gösterici aritmetiği)

    C dilinde adreslerle tamsayı türünden sayılar toplama ve çıkarma işlemlerine sokulabilir. İki adres aynı türden ise karşılaştırma operatörleri ile işleme sokulabilir. Burada adreslerin sayısal bileşenleri işleme sokulmaktadır.

    C dilinde bir adres bilgisi tamsayı türleri ile artırılabilir veya eksiltilebilir. Sonuç aynı türden bir adres bilgisi olur. Bir adres bilgisi 1 artırıldığında adresin sayısal bileşeni adresin türünün uzunluğu kadar artmaktadır. Bu durumda örneğin DOS'da char türden bir göstericiyi, 1 artırdığımızda adresin sayısal bileşeni 1 int türden bir göstericiyi 1 artırdığımızda ise adresin sayısal bileşeni 2 artar. Bu durumda p[n] ifadesi p adresinden n byte ilerisinin içeriği değil, p adresinden n * p göstericisinin türünün uzunluğu kadar byte ilerinin içeriği anlamına gelir.
    İki adres bilgisinin toplanması faydalı bir işlem olmadığı gerekçesiyle yasaklanmıştır. Ancak iki adres bilgisi birbirinden çıkartılabilir. İki adres birbirinden çıkartılırsa sonuç int türden olur. İki adres birbirinden çıkartıldığında önce adreslerin sayısal bileşenleri çıkartılır, sonra elde edilen değer adresin türünün uzunluğuna bölünür. Örneğin a int türden bir adres olmak üzere:

    &a[2] - &a[0] ifadesinden elde edilen değer 2'dir.

    #include <stdio.h>

    int main()
    {
    int a[5] = {1, 2, 3, 4, 5};
    int *p;

    p = a;
    printf("%p adresindeki değer = %d\n", p, *p);
    ++p;
    printf("%p adresindeki değer = %d\n", p, *p);
    return 0;
    }

    ++ ve -- operatörlerinin gösterici operatörleriyle birlikte kullanılması

    ++*p durumu


    x = ++*p;

    *p = *p + 1; anlamına gelir. *p bir nesne gösterdiği için nesnenin değeri 1 artırılacaktır.

    Yukarıdaki deyimle x değişkenine *p nesnesinin artırılmış değeri atanacaktır.

    *++p durumu

    p göstericisinin değeri 1 artırılır. Artırılmış adresteki nesneye ulaşılmış olur.

    x = *++p;

    deyimi ile x değişkenine artırılmış adresteki bilgi atanır.

    *p++ durumu

    ++ operatörü ve * operatörünün ikisi de 2. öncelik seviyesindedir ve bu öncelik seviyesine ilişkin öncelik yönü sağdan soladır. Önce ++ operatörü ele alınır ve bu operatör ifadenin geri kalan kısmına p göstericisinin artmamış değerini gönderir. Bu adresteki nesneye ulaşılır ve daha sonra p göstericisinin değeri 1 artırılır.

    x = *p++;

    deyimi ile x değişkenine *p nesnesinin değeri atanır daha sonra p göstericisinin değeri 1 artırılır.

    &x++ /* error */

    x nesnesinin artmamış değeri ifadenin geri kalanına gönderilir. (Adres operatörüne operand olur.) Bu da bir nesne olmadığı için error oluşur. (Adres operatörünün operandı nesne olmak zorundadır.)

    &++x /* error */

    x nesnesinin artmış değeri ifadenin geri kalanına gönderilir. (Adres operatörüne operand olur.) Bu da bir nesne olmadığı için error oluşur. (Adres operatörünün operandı nesne olmak zorundadır.)

    ++&x /* error */

    x nesnesinin adresi ++ operatörüne operand olur. ++ operatörünün operandı nesne olmak zorunda olduğu için derleme zamanında error oluşacaktır.

    & operatörü ile ++ ya da -- operatörlerinin her türlü kombinasyonu derleme zamanında hata oluşmasına neden olur.

    ++p[i] Durumu

    İndex operatörü 1. öncelik seviyesinde, ++ operatörü ise 2. öncelik seviyesindedir. Bu durumda önce derleyici tarafından indeks operatörü ele alınır. p[i] ifadesi bir nesne gösterir. Dolayısıyla ++ operatörüne operand olmasında bir sakınca yoktur. Söz konusu ifade

    p[i] = p[i] + 1;

    anlamına gelmektedir. Yani p[i] nesnesinin değeri 1 artırılacaktır.

    p[i]++ Durumu


    x = p[i]++;

    Önce p[i] nesensinin artmamış değeri üretilir, ifadenin geri kalanına p[i] nesnesinin artmamış değeri kullanılır. Yani yukarıdaki örnekte x değişkenine p[i] nesnesinin artırılmamış değeri atanır, daha sonra p[i] nesnesi 1 artırılır.

    p[++i] Durumu


    x = p[++i];

    Önce i 1 artırılır. Daha sonra artırılmış adresteki bilgi x değişkenine atanır.

    p[i++] Durumu


    x = p[i++];

    Burada p[i] nesnesinin değeri x değişkenine atanır. Daha sonra i değişkeni 1 artırılır.

    p++[i] durumu


    x = p++[i];

    Pek tercih edilen bir ifade değildir.Burada önce p[i] x değişkenine atanır. Sonra p göstericisinin değeri 1 artırılır.

    Göstericilere İlk Değer Verilmesi

    Diğer türden değişkenlerde olduğu gibi göstericilere de tanımlanmaları sırasında ilk değer verilebilir. Göstericilere ilk değer verme işlemi göstericinin türünden bir adres bilgisi ile yapılmalıdır. Örnekler:

    char s[100];
    double x;
    int *ptr = (int *) 0x1A00;
    char * str = (char *) 0x1FC0;
    chat *p = s;
    double *dbptr = &x;

    Fonksiyon Parametre Değişkeni Olarak Göstericilerin Kullanılması

    Bir fonksiyonun parametre değişkeni herhangi bir türden gösterici olabilir.

    void func(int *p)
    {
    ...
    }

    Bir fonksiyonun parametre değişkeni bir gösterici ise fonksiyon da aynı türden bir adres bilgisi ile çağırılmalıdır.

    Bir fonksiyonun başka bir fonksiyonun yerel değişkenini değiştirebilmesi için o fonksiyonun yerel değişkeninin adresini parametre olarak alması gerekir.

    #include <stdio.h>

    void func(int *p)
    {
    *p = 20;
    }

    int main()
    {
    int a = 10;

    func(&a);
    printf("%d\n", a);
    return 0;
    }

    Bir fonksiyon bir değer elde edip, çağıran fonksiyona bu değeri iletmek isterse 2 yöntem kullanılabilir:

    1. Elde edilen değer çağırılan fonksiyon tarafından geri dönüş değeri olarak üretilir.

    2. Elde edilen değer çağıran fonksiyonun göndermiş olduğu adrese yerleştirilir. Tabi bunun için çağırılan fonksiyonun parametre değişkeninin bir gösterici olması gerekir. Bir örnekle gösterelim:

    Kendisine gönderilen bir sayının faktoriyelini hesaplayana ve bu değeri parametre olarak gönderilen adrese kopyalayan bir fonksiyon yazalım.

    void factorial(int n, long *p);

    #include <stdio.h>

    int main()
    {
    long a;

    factorial(7, &a);
    printf ("%d! = %ld", 7, a);

    return 0;
    }

    void factorial(int n, long *p);
    {
    if (n == 0 || n == 1)
    *p = 1;
    for (*p = 1; n > 0; n--)
    *p *= n;
    }

    a bir yerel değişken olsun. C dilinde bir fonksiyon

    func(a);

    biçiminde çağırılmışsa, çağırılan bu fonksiyonun, a değişkenini değiştirme şansı yoktur. (Bu tür fonksiyon çağırımına "değer ile çağırma" (call by value) denmektedir. Fonksiyonun a değişkenini değiştirebilmesi için

    func(&a);

    biçiminde çağırılması gerekir. Örneğin scanf fonksiyonuna & operatörü ile bir nesnenin adresinin arguman olarak yollanmasının nedeni budur. Bu şekilde fonksiyon çağırmaya C'de "adres ile çağırma" (call by reference) denmektedir.

    int türden iki yerel nesnenin değerlerini değiştirmek (swap etmek) istediğimizi düşünelim. Bunu bulunduğumuz fonksiyon içerisinde aşağıdaki gibi yapabiliriz:

    int main()
    {
    int a = 10, b = 20, temp;

    temp = a;
    a = b;
    b = temp;
    /*....*/
    }

    swap işleminin bir fonksiyon tarafından yapılmasını istersek, aşağıdaki gibi bir fonksiyon işimizi görür müydü?

    void swap(int x, int y)
    {
    int temp = x;
    x = y;
    y = temp;
    }

    int main()
    {
    int a = 10, b = 20;
    swap(a, b);
    printf("a = %d\nb = %d\n", a, b);
    return 0;
    }

    Yukarıdaki program çalıştılıdığında ekrana

    a = 10
    b = 20

    yazacaktır. Zira swap fonksiyonu a ve b değişkenlerinin değerlerini değiştirmemiştir. Zaten yerel nesneler olan a ve b değişkenlerinin değerleri ancak adresleri bir fonksiyona gönderilerek değiştirilebilirdi. Oysa bizim yukarıdaki swap fonksiyonumuz a ve b değişkenlerinin değerlerini parametre değişkenleri olan x ve y değişkenlerine kopyalıyor. Yani değerleri değiştirilen parametre değişkenleri x ve y'nin değerleri. İstediğimiz amacı gerçekleştirecek fonksiyon dışarıdan adres alacağı için gösterici parametre değişkenlerine sahip olmalı:

    void swap(int *p1, int *p2)
    {
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
    }

    Dizilerin Fonksiyonlara Göstericiler Yoluyla Geçirilmesi

    Bir diziyi fonksiyona geçirebilmek için dizinin başlangıç adresinin ve uzunluğunun geçirilmesi yeterlidir. Dizinin başlangıç adresini alacak parametre değişkeninin aynı türden bir gösterici olması gerekir. Fonksiyonun 2. parametresi dizinin uzunluğunu tutacak int türden bir değişken olabilir. Bir dizinin başlangıç adresini parametre olarak alan fonksiyon dizi elemanlarına köşeli parantez operatörü ya da içerik operatörü ile erişebilir. Ancak dizi elemanlarının kaç tane olduğu bilgisi fonksiyon tarafından bilinemez. Bu nedenle dizi uzunluğunu ikinci bir parametre olarak fonksiyona yolluyoruz. Örnek:

    #include <stdio.h>

    void display (int *p, int size)
    {
    int i;

    for (i = 0; i < size; ++i)
    printf("%d ", p[i]);
    }

    int main()
    {
    int a[5] = {3, 8, 7, 6, 10};

    display(a, 5);
    return 0;
    }

    Dizi uzunluğu hakkında fonksiyona bilgi vermek amacı ile başka teknikler de kullanılır. Dizi uzunluğunu dizinin birinci parametresinde saklayabiliriz. Yukarıdaki fonksiyonu, dizi uzunluğunu birinci dizi elemanında saklayarak yeniden yazalım:

    void disp (int *p)
    {
    int i;

    for (i = 1; i < p[0]; ++i)
    printf("%d ", p[i]);
    }

    int main()
    {
    int a[6] = {6, 3, 8, 7, 6, 10}; /* birinci eleman dizi uzunluğu bilgisini saklıyor */

    disp(a);
    return 0;
    }

    Bir dizi içerisinde, çözülen problemin koşullarına bağlı olarak, belirli bir değerin bulunması olanaksızsa, bu değer dizi sonunu belirten bir işaret olarak kullanılabilir. Bir dizi içinde öğrencilerin aldıkları notları sakladığımızı varsayalım. Öğrencilerin negatif bir not almaları mümkün olmadığına göre dizi içerisinde negatif bir sayı bulunamaz. Bu durumda biz -1 gibi bir değeri dizi sonunu belirtmek amacıyla kullanabiliriz. Yukarıdaki örneği yeniden yazalım:

    void display (int *p)
    {
    int i;

    for (i = 0; p[i] != -1; ++i)
    printf("%d ", p[i]);
    }

    int main()
    {
    int a[6] = {3, 8, 7, 6, 10, -1};

    display(a, 5);
    return 0;
    }

    n elemanlı int türden bir dizinin aritmetik ortalamasını bulan getavg fonksiyonunu tasarlanması:

    #include <stdio.h>

    double getavg(int *p, int size);

    int main()
    {
    int s[10] = {1, 23, 45, -4, 67, 12, 22, 90, -3, 44};
    double average;

    average = getavg(s, 10);
    printf("dizinin aritmetik ortalaması = %lf\n", average);
    return 0;
    }

    double getavg(int *p, int size)
    {
    int i;
    double total = 0;

    for (i = 0; i < size; ++i)
    total += p[i];
    return total / size;
    }

    size elemanlı int türden bir dizi içerisindeki en büyük sayıya geri dönen getmax isimli fonksiyonun yazılması:

    #include <stdio.h>

    int getmax(int *p, int size);

    int main()
    {
    int s[10] = {1, 23, 45, -4, 67, 12, 22, 90, -3, 44};

    printf("%d\n", getmax(s, 10));
    return 0;
    }

    int getmax(int *p, int size)
    {
    int i, max;

    max = p[0];
    for (i = 1; i < size; ++i)
    if (max > p[i])
    max = p[i];

    return max;
    }

    Bubble sort yöntemiyle int türden bir diziyi küçükten büyüğe sıraya dizen fonksiyon örneği:

    #include <stdio.h>

    #define SIZE 10

    void bsort(int *p, int size);

    void bsort(int *p, int size)
    {
    int i, k, temp;

    for (i = 0; i < size - 1; ++i)
    for (k = 0; k < size - 1 - i; ++k)
    if (p[k] > p[k+1]) {
    temp = p[k];
    p[k] = p[k+1];
    p[k+1] = temp;
    }
    }

    int main()
    {
    int a[SIZE] = {3, 4, 5, 8, 78,12, -2, 11, 41, -34};
    int i;

    bsort(a, SIZE);
    for (i = 0; i < SIZE; ++i)
    printf("a[%d] = %d\n",i a[i]);
    return 0;
    }

    Geri Dönüş Değeri Adres Türünden Olan Fonksiyonlar

    Bir fonksiyon parametre değişkeni olarak bir gösterici alabildiği gibi, adres türlerinden bir geri dönüş değerine de sahip olabilir. Geri dönüş değerinin adres olduğu, fonksiyon tanımlanırken * operatörü ile aşağıdaki biçimde belirtilmektedir.

    <adresin türü> *<fonksiyon ismi> ([parametreler])
    {
    ...
    }

    Örneğin int türden bir adrese geri dönen ve parametre değişkeni almayan sample isimli fonksiyon aşağıdaki gibi tanımlanabilir:

    int *sample(void)
    {
    ...
    }

    Yukarıdaki fonksiyon tanımlama ifadesinden * atomu kaldırılırsa fonskiyon int türden bir değer döndürür.

    Bir adres türüne geri dönen bir fonksiyonun çağırılma ifadesi, aynı türden bir göstericiye atanmalıdır.

    int *ptr;

    ptr = sample();

    gibi.

    Benzer şekilde:

    char *str;

    char *func(void) {
    ...
    }

    str = func();
    ...

    Adrese geri dönen fonksiyonlara C programlarında çok rastlanır. Standart C fonksiyonlarından bir çoğu, adres türünden bir değer döndürür.

    Bir dizinin en büyük elemanını bulup bu elemanın değerine geri dönen getmax fonksiyonunu daha önce tasarlamıştık. Şimdi aynı fonksiyonu en büyük elemanın adresine geri dönecek şekilde tasarlayalım:

    #include <stdio.h>

    int *getmax(int *p, int size);

    int main()
    {
    int s[10] = {1, 23, 45, -4, 67, 12, 22, 90, -3, 44};

    printf("%d\n", *getmax(s, 10));
    return 0;
    }

    int *getmax(int *p, int size)
    {
    int *pmax, i;

    pmax = p;
    for (i = 1; i < size; ++i)
    if (*pmax > p[i])
    pmax = p + i;
    return pmax;
    }

    Göstericilere İlişkin Uyarılar ve Olası Gösterici Hataları

    Bir Göstericiye Farklı Türden Bir Adres Atanması:

    Bir göstericiye farklı türden bir adres atandığında, C derleyicileri durumu şüpheyle karşılayarak bir uyarı mesajı verirler. Yani bu işlem hata (error) değil uyarı gerektirmektedir. Ancak derleyici yine de farklı türden adresin sayısal bileşenini hedef göstericiye atar. Borland derleyicileri bu durumda aşağıdaki uyarı mesajını verirler:

    warning : suspicious pointer coversion in function ......

    Bu işlemin faydalı bir gerekçe ile yapılma ihtimali zayıftır. Ancak bazan bilinçli olarak da yapılabilir. O zaman bilinçli olarak yapıldığı konusunda derleyiciyi ikna etmek gerekmektedir.

    int *p;
    char s[ ] = "Ankara";

    p = s; /* uyarı */

    Uyarı bilinçli tür dönüştürmesiyle kesilebilir.

    p = (int *) s;

    Tür dönüştürme operatörü kullanılarak char türden bir adres int türden bir adrese dönüştürülmüştür.

    Bir göstericiye farklı türden bir adres atanması durumunda, otomatik tür dönüşümü sonucunda, atama operatörünün sağ tarafındaki ifadenin türü, atama operatörünün sol tarafındaki nesnenin türüne dönüştürülecek, ve dönüştürülmüş olan adres bilgisi nesneye atanacaktır.

    Örneğin int türden bir göstericiye char türden bir adres atadığımızı düşünelim:

    int main()
    {
    char s[20] = "Necati Ergin";
    int *p;

    p = s;
    ...
    return 0;
    }

    Bu durumda int türden p göstericisini içerik operatörü ya da indeks operatörüyle kullandığımızda elde ettiğimiz nesne char türden değil int türden olacaktır. Böyle bir durumun bilinçli olarak yapılmış olma ihtimali azdır, ve bilinçli olarak yapılması durumunda tür dönüştürme operatörü kullanılarak, derleyicinin vereceği uyarı mesajı kesilmelidir. (derleyici ikna edilmelidir)

    C++ dilinde bir göstericiye farklı türden bir adres atanması durumunda, derleme zamanında hata oluşacaktır. Yani bu durum uyarı seviyesinden error seviyesine yükseltilmiştir.

    Bir Göstericiye Adres Olmayan Bir Değerin Atanması

    Bu da bilinçli olarak yapılma ihtimali çok az olan bir işlemdir. C derleyicileri şüpheli olan bu durumu bir uyarı mesajı ile programcıya bildirirler. Örneğin bu uyarı mesajı Borland derleyicilerinde:

    "nonportable pointer conversion"

    şeklindedir. Peki bir göstericiye adres bilgisi olmayan bir değer atarsak ne olur? Yine otomatik tür dönüşümü söz konusudur. Atama operatörünün sağ tarafındaki ifadenin türü, atama operatörünün sol tarafında bulunan nesne gösteren ifadenin türüne çevrilerek, atama yapılacaktır. Dolayısıyla, atanan değer göstericinin türünden bir adrese çevrilecek ve göstericiye atanacaktır. Örneğin:

    int main()
    {
    int k, *ptr;

    k = 1356;
    ptr = k; /* uyarı */
    ...
    return 0;
    }

    Yukarıdaki örnekte ptr = k; atama deyiminden sonra bellekte ptr göstericisi için ayrılan yere 1356 yazılacaktır. Yani *ptr ifadesi ile int türden bir nesneye ulaşılacaktır ki bu nesne de 1356 ve 1357 adreslerinde bulunan nesnedir.

    Bu durumun bilinçli olarak yapılması durumunda yine tür dönüştürme operatörü kullanılarak derleyici ikna edilmeli ve uyarı mesajı kesilmelidir:

    int main()
    {
    int k, *ptr;

    k = 1356;
    ptr = (int * ) k; /* uyarı yok */
    ...
    return 0;
    }

    Yazıların Fonksiyonlara Parametre Olarak Geçirilmesi

    Yazılar karakter dizilerinin içerisinde bulunurlar. Bir yazıyı fonksiyona parametre olarak geçirebilmek için yazının yalnızca başlangıç adresini fonksiyona geçirmek yeterlidir. Yani fonksiyon yazının (karakter dizisinin) başlangıç adresi ile çağırılır. Yazıyı içinde tutan char türden dizinin uzunluk bilgisini fonksiyona geçirmeye gerek yoktur. Çünkü yazıları saklamak amacıyla kullanılan karakter dizilerinin sonu null (ASCII karakter tablosında 0 sıra numaralı karakter.) karakterle işaretlenmiştir. Karakter dizileri üzerinde işlem yapan algoritmalar dizinin sonunu null karakter yardımıyla belirlerler.

    Yazılarla işlem yapan fonksiyon char türden bir gösterici ile üzerinde işlem yapacağı karakter dizisinin başlangıç adresini alır. Fonksiyon, null karakter görene kadar bir döngü ile yazının bütün karakterlerine erişir.

    str char türünden bir gösterici olmak üzere yazı üzerinde null karakter görene kadar işlem yapabilecek döngüler şöyle oluşturulabilir:

    while (*str != '\0') {
    ...
    ++str;
    }

    for (i = 0; str[i] != '\0'; ++i) {
    ...
    }

    Örneğin puts fonksiyonunun parametre değişkeni char türünden bir göstericidir. puts fonksiyonunu myputs ismi ile yeniden yazalım:


    #include <stdio.h>

    void myputs(char *str)
    {
    while (*str != '\0') {
    putchar(*str);
    ++str;
    }
    putchar('\n');
    }

    int main()
    {
    char s[] = "Deneme";

    myputs(s);
    return 0;
    }

    Yazılarla İlgili İşlem Yapan Fonksiyonlar

    Bir grup standart C fonksiyonu vardır ki, bu fonksiyonlar bir yazının başlangıç adresini parametre olarak alarak yazı ile ilgili birtakım faydalı işlemler yaparlar. Bu fonksiyonlara string fonksiyonları denir. String fonksiyonlarının prototipleri string.h dosyası içindedir. Bu fonksiyonlardan bazılarını inceleyelim :

    strlen Fonksiyonu

    Bu fonksiyon bir yazının karakter uzunluğunu (kaç karakterden oluştuğu bilgisini) elde etmek için kullanılır.

    Fonksiyonun prototipi:

    unsigned int strlen(char *str);

    şeklindedir. Fonksiyonun parametre değişkeni uzunluğu hesaplanacak yazının başlangıç adresidir. Fonksiyon null karakter görene kadar karakterlerin sayısını hesaplar.

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char s[100];

    printf("bir yazı giriniz : ");
    gets(s);
    printf("%d\n", strlen(s));
    return 0;
    }

    standart C fonksiyonu olan strlen fonksiyonunu kendimiz yazsaydık aşağıdaki biçimlerde yazabilirdik:

    #include <stdio.h>

    unsigned strlen1 (char *str);
    unsigned strlen2(char *str);
    unsigned strlen3(char *str);


    int main()
    {
    char s[100];

    printf("bir yazı giriniz : ");
    gets(s);
    printf("yazının uzunluğu : %d\n", strlen1(s));
    printf("yazının uzunluğu : %d\n", strlen1(s));
    printf("yazının uzunluğu : %d\n", strlen1(s));

    return 0;
    }

    unsigned int strlen1(char *str)
    {
    unsigned int length = 0;

    while (*str != '\0) {
    ++length;
    ++str;
    }
    return length;
    }

    unsigned int strlen2(char *str)
    {
    unsigned int len;

    for (len = 0; str[len] != '\0'; ++len)
    ;
    return len;
    }

    unsigned int strlen3(char *str)
    {
    char *ptr = str;

    while (*str != '\0')
    str++;
    return str - ptr;
    }

    strlen2 fonksiyonunda len değişkeni hem döngü değişkeni hem de sayaç olarak kullanılmıştır.
    null karakter '\0' sayısal değer olarak 0 değerine eşit olduğu için yukarıdaki döngülerin koşul ifadelerini aşağıdaki şekilde yazabilirdik:

    while (*str)
    for (i = 0; str[i]; ++i)

    Yukarıdaki koşul ifadelerinde de *str ya da str[i] 0 değerine eşit olduğunda kodun akışı döngü dışındaki ilk deyimle devam ederdi. Ancak okunabilirlik açısından null karakterle karşılaştırmanın açıkça yapılması daha uygundur.

    strchr fonksiyonu

    Fonksiyonun ismi olan strchr "string character" sözcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir. strchr fonksiyonu bir karakter dizisi içinde belirli bir karakteri aramak için kullanılan standart bir C fonksiyonudur. Prototipi string.h dosyası içinde bulunmaktadır.

    strchr fonksiyonunun prototipi aşağıdaki gibidir:

    char *strchr(char *str, int ch);

    Bu fonksiyon, 2. parametresi olan ch karakterini, 1. parametresi olan str adresinden başlayarak null karakter görene kadar arar. (Aranan karakter null karakterin kendisi de olabilir.) Fonksiyonun geri dönüş değeri, ch karakterinin yazı içinde bulunabilmesi durumunda ilk bulunduğu yerin adresidir. Eğer ch karakteri yazı içinde bulunamazsa, fonksiyon NULL adresine geri dönecektir.

    strchr fonksiyonunu kendimiz yazsaydık aşağıdaki şekilde yazabilirdik:

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char s[100];
    char *p, ch;

    printf("bir yazı giriniz : ");
    gets(s);
    printf("yazı içinde arayacağınız karakteri giriniz : ")
    scanf("%c", &ch);
    p = strchr(s, ch);
    if (p == NULL) {
    printf("aranan karakter bulunamadı\n");
    else
    puts(p);
    return 0;
    }

    char *my_strchr(char *str, int ch)
    {
    while (*str != '\0) {
    if (ch == *str)
    return str;
    ++str;
    }
    if (ch == '\0')
    return str;

    return NULL;
    }

    Yukarıda verilen main fonksiyonununda strchr fonksiyonunu çağırdığımız yerde kendi yazdığımız my_strchr fonksiyonunu çağırarak fonksiyonun doğru çalışıp çalışmadığını test edebiliriz.

    if (ch == '\0')
    return str;

    deyimleri aranan karakterin null karakter olup olmadığını test etmek için eklenmiştir. while döngüsü yerine for döngüsü de kullanabilirdik;

    ...
    int i;
    for (i = 0; str[i] != '\0'; ++i)
    if (ch == str[i])
    return (str + i);
    ...

    strcpy fonksiyonu

    fonksiyonun ismi olan strcpy, string ve copy sözcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir.Fonksiyon ikinci parametresinde tutulan adresten başlayarak, NULL karakter görene kadar, (NULL karakter dahil olmak üzere) tüm karakterleri sırasıyla birinci parametresinde tutulan adresten başlayarak sırayla yazar. Fonksiyonun prototipi string.h dosyası içindedir. Fonksiyonun prototipi aşağıdaki gibidir:

    char *strcpy(char *dest, char *source);

    Fonksiyonun geri dönüş değeri kopyalamanın yapılmaya başlandığı adrestir. (Yani dest adresi)

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char dest[100] = "C öğreniyoruz!";
    char source[100];

    printf("kopyalanacak yazıyı giriniz : ");
    gets(source);
    printf("kopyalama yapılmadan önce kopyalamanın yapacağı yerde bulunan yazı : \n");
    puts(dest);

    strcpy(dest, source);
    printf("kopyalamadan sonra kopyalamanın yapıldığı yerde bulunan yazı : \n");
    puts(dest);
    return 0;
    }


    strcpy fonksiyonunu kendimiz yazmak isteseydik aşağıdaki şekilde yazabilirdik :

    char *_strcpy(char *dest, char *source)
    {
    int i;

    for (i = 0; (dest[i] = source[i]) != '\0'; ++i)
    ;
    return dest;
    }

    fonksiyon içinde kullanılan for döngüsünde önce atama yapılmış, daha sonra atama ifadesinin değeri (k, bu da atama operatörünün sağ tarafında bulunan değerdir) NULL karakter ile karşılaştırılmıştır. Böylece ilgili adrese NULL karakter de kopyalandıktan sonra döngüden çıkılmıştır.
    fonksiyonu aşağıdaki gibi de yazabilirdik :

    ...
    for (i = 0; source[i] != '\0'; ++i)
    dest[i] = source[i];
    dest[i] = '\0';
    ...

    for döngüsünde indeks operatörü kullanıldığı için, birinci parametre değişkenine kopyalanan dest adresi değiştirilmemiş ve fonksiyonun sonunda bu dest adresi ile geri dönülmüştür. Fonksiyonun tasarımında whilde döngüsü kullansaydık, ve dest içindeki adresi değiştirseydik, fonksiyonun dest göstericisinin ilk değeriyle geri dönebilmesini sağlayabilmek için, dest göstericisindeki değeri değiştirmeden önce, bu değeri başka bir gösterici içinde saklamak gerekecekti :

    char *_strcpy(char *dest, char *source)
    {
    char *temp = dest;

    while ((*source++ = *dest++) != '\0')
    ;
    return temp;
    }

    Yazılan fonksiyonların doğru çalışıp çalışmadıklarını aynı main fonksiyonu ile test edebiliriz.

    strcat fonksiyonu

    fonksiyonun ismi string ve concatanate sözcüklerinin kısaltılarak birleştirilmesiyle elde edilmiştir.
    strcat fonksiyonu bir karakter dizisinin sonuna başka bir karakter dizisinin kopyalanması amacıyla kullanılmaktadır. Fonksiyonun prototipi string.h dosyası içindedir. Fonksiyonun prototipi aşağıdaki gibidir:

    char *strcat(char *s1, char *s2);

    strcat fonksiyonu eklemenin yapılacağı ve başlangıç adresi s1 birinci parametre değişkeninde tutulan yazının sonundaki NULL karakteri ezerek, başlangıç adresi ikinci parametre değişkeninde tutulan yazıyı birinci yazının sonuna (NULL karakter de dahil olmak üzere) eklemektedir. Yani işlem sonunda s1 dizisi s2 dizisi kadar büyümektedir.
    Fonksiyonun geri dönüş değeri, sonuna eklemenin yapıldığı yazının başlangıç adresidir. (Yani s1 adresi)


    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char s1[100], s2[100];

    printf("sonuna ekleme yapılacak yazıyı giriniz : ");
    gets(s1);
    printf("girdiğiniz yazının uzunluğu = %d\n", strlen(s1));
    printf("eklemek istediğiniz yazıyı giriniz : ");
    gets(s2);
    printf("eklenecek yazının uzunluğu = %d\n", strlen(s2));
    strcat(s1, s2);
    printf("ekleme yapıldıktan sonra 1. yazı : ");
    puts(s1);
    printf("ekleme yapıldıktan sonra yazının uzunluğu : %d\n", strlen(s1));

    return 0;
    }

    strcat fonksiyonunu kendimiz yazsaydık aşağıdaki şekilde yazabilirdik :

    char *_strcat(char *s1, char *s2)
    {
    char *temp = s1;

    while (*s1 != '\0')
    ++s1;
    while ((*s1++ == *s2++) != '\0') /* strcpy(s1, s2); */
    ;
    return temp;
    }

    Yazılan fonksiyonun doğru çalışıp çalışmadığını aynı main fonksiyonu ile test edebiliriz.


    strset fonksiyonu

    Standart olmayan bu fonksiyon derleyicilerin çoğunda bulunur. Fonksiyonun ismi string ve set sözcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir. Bir karakter dizisinin belirli bir karakterle doldurulması amacıyla kullanılmaktadır. Prototipi string.h dosyası içinde bulunmaktadır. Fonksiyonun prototipi aşağıdaki gibidir :

    char *strset(char *str, int ch);

    Fonksiyon birinci parametre değişkeninde başlangıç adresi olan yazıyı NULL karakter görene kadar ikinci parametre değişkeninde tutulan karakterle doldurur. (yazının sonundaki NULL karaktere dokunmaz) .
    Fonksiyonun geri dönüş değeri yine doldurulan yazının başlangıç adresidir.

    #include <stdio.h>
    #include <conio.h>

    int main()
    {
    char s[100];
    int ch;

    printf("bir yazı giriniz :");
    gets(s);
    printf("yazıyı hangi karakterle doldurmak istiyorsunuz : ");
    ch = getchar();
    printf("\nyazının %c karakteriyle doldurulduktan sonraki hali : %s\n", ch,
    strset(s, ch));

    return 0;
    }

    strset fonksiyonunu kendimiz yazsaydık aşağıdaki şekillerde yazabilirdik :

    #include <stdio.h>

    char *_strset(char *str, int ch)
    {
    int i;

    for (i = 0; str[i] != '\0'; ++i)
    str[i] = ch;
    return str;
    }

    char *_strset2(char *str, int ch)
    {
    char *temp = str;

    while (*str != '\0') {
    *str = ch;
    ++str;
    }
    return temp;
    }

    strcmp fonksiyonu

    Standart bir C fonksiyonudur. Fonksiyonun ismi string ve compare sözcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir. Fonksiyon iki karakter dizisini karşılaştırmakta kullanılır. Karşılaştırma, iki karakter dizisi içindeki yazının kullanılan karakter seti tablosu gözönünde bulundurularak, öncelilk ya da eşitlik durumunun sorgulanmasıdır. Örneğin :

    Adana yazısı Ankara yazısından daha küçüktür. Çünkü eşitliği bozan 'n' karakteri ASCII tablosunda 'd' karakterinden sonra gelmektedir.

    ankara yazısı ANKARA yazısından daha büyüktür. Çünkü küçük harfler ASCII tablosunda büyük harflerden sonra gelmektedir.

    kalem yazısı kale yazısından daha büyüktür.

    strcmp fonksiyonunun string.h içerisindeki prototipi aşağıdaki gibidir :

    int strcmp(char *s1, char *s2);

    fonksiyon 1. parametre değişkeninde başlangıç adresi tutulan yazı ile, ikinci parametre değişkeninde başlangıç adresi tutulan yazıları karşılaştırır.

    fonksiyonun geri dönüş değeri

    1. yazı 2.yazıdan daha büyükse pozitif bir değere
    1. yazı 2. yazıdan daha küçükse negatif bir değere
    1.yazı ve 2. yazı birbirine eşit ise 0 değerine geri döner.

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char s[20];
    char password[ ] = "Mavi ay";

    printf("parolayı giriniz : ");
    gets(s);
    if (!strcmp(s, password))
    printf("Parola doğru!..\n");
    else
    printf("Parola yanlış!..\n");
    return 0;
    }

    strcmp fonksiyonunu kendimiz yazsaydık aşağıdaki şekillerde yazabilirdik :

    int _strcmp(char *s1, char *s2)
    {
    while (*s1 == *s2) {
    if (*s1 == '\0')
    return 0;
    ++s1;
    ++s2;
    }
    return *s1 - *s2;
    }


    strrev fonksiyonu

    Standart olmayan bu fonksiyon derleyiclerin çoğunda bulunur. Fonksiyonun ismi ingilizce string ve reverse söcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir. Karakter dizilerini ters çevirmek amacıyla kullanılır. string.h içerisinde yer alan prototipi aşağıdaki gibidir :

    char *strrev(char *str);

    fonksiyon parametre değişkeninde başlangıç adresi tutulan yazıyı tersyüz eder. Fonksiyonun geri dönüş değeri tersyüz edilen yazının başlangıç adresidir.

    #include <stdio.h>
    #include <string.h>

    main()
    {
    char s[100];

    printf("ters çevirilecek yazıyı giriniz : ");
    gets(s);
    printf("yazınızın ters çevrilmiş hali : \n");
    puts(setrev(s));
    return 0;
    }

    strrev fonksiyonunu kendimiz yazsaydık aşağıdaki şekilde yazabilirdik :

    #include <string.h>
    #include <stdio.h>

    char *strrev(char *str)
    {
    int i, temp;
    int length = strlen(str);

    for (i = 0; i < length / 2, ++i) {
    temp = str[i];
    str[i] = str[length - i - 1];
    str[length - i - 1] = temp;
    }
    return str;
    }

    strncpy fonksiyonu

    Standart bir C fonksiyonudur. Fonksiyonun ismi ingilizce, string number copy sözcüklerinin kısaltılarak birleştirilmesinden elde edilmiştir. Fonksiyon bir yazının (karakter dizisinin) ilk n karakterini başka bir yazıya(karakter dizisine) kopyalamakta kullanılır. Fonksiyonun string.h içerisindeki prototipi aşağıdaki gibidir :

    char *strncpy(char *dest, char *source, int n);


    fonksiyon 1. parametre değişkeninde başlangıç adresi tutulan yazıya, ikinci parametre değişkeninde adresi tutulan yazıdan, üçüncü parametresinde tutulan sayıda karakteri kopyalar.
    Fonksiyonun geri dönüş değeri kopyalamanın yapılacağı adrestir. (Yani dest adresi)

    üçüncü parametre olan n sayısı eğer kopyalanacak yazının uzunluğundan daha küçük ya da eşit ise fonksiyon kopyalama sonunda NULL karakteri birinci dizinin sonuna eklemez.

    n <= strlen(source) ise NULL karakter eklenmiyor.

    üçüncü parametre olan n sayısı eğer kopyalanacak yazının uzunluğundan daha büyük ise fonksiyon kopyalama sonunda NULL karakteri birinci dizinin sonuna ekler.

    n > strlen(source) ise NULL karakter ekleniyor.

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char dest[100], source[100];
    int n;

    printf("birinci yazıyı giriniz : ");
    fflush(stdin);
    gets(dest);
    printf("ikinci yazıyı giriniz : ");
    gets(source);
    printf("ikinci yazıdan 1. yazıya kaç karakter kopyalamak istiyorsunuz : ");
    scanf("%d", &n);
    strncpy(dest, source, n);
    printf("kopyalamadan sonra 1. yazının yeni şekli : ");
    puts(dest);
    return 0;
    }

    strncpy fonksiyonunu kendimiz yazsaydık aşağıdaki şekilde yazabilirdik :

    #include <stdio.h>

    char *_strncpy(char *dest, char *source, int n)
    {
    int i;

    for (i = 0; i < n && source[i] != '\0'; ++i)
    dest[i] = source[i];
    if (n > i)
    dest[i] = '\0';
    return dest;
    }

    strncat fonksiyonu

    Standart bir C fonksiyonudur. Fonksiyonun ismi ingilizce string number concatanate sözcüklerinin kısaltılarak birleştirilmesiyle elde edilmiştir. Bir karakterden dizisinin sonuna başka bir karakter dizisinden belirli bir sayıda karakteri kopyalamak amacıyla kullanılır. string.h içinde bulunan prototipi aşağıdaki gibidir :

    char *strncat(char *s1, char *s2, int n);

    fonksiyon 1. parametre değişkeni içinde başlangıç adresi verilen yazının sonuna (NULL karakteri ezerek), 2. parametresinde başlangıç adresi tutulan karakter dizisinden, 3. parametresinde tutulan tamsayı adedi kadar karakteri kopyalar.

    fonksiyonun geri dönüş değeri sonuna ekleme yapılacak yazının başlangıç adresidir. (yani s1 adresi)

    fonksiyonun çalışmasını açıklayacak bir örnek aşağıda verilmiştir :


    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char dest[100], source[100];
    int n;

    printf("birinci yazıyı giriniz : ");
    fflush(stdin);
    gets(dest);
    printf("ikinci yazıyı giriniz : ");
    gets(source);
    printf("ikinci yazıdan 1. yazının sonuna kaç karakter kopyalamak istiyorsunuz : ");
    scanf("%d", &n);
    strncat(dest, source, n);
    printf("eklemeden sonra 1. yazının yeni şekli : ");
    puts(dest);
    return 0;
    }

    strncmp fonksiyonu

    Standart bir C fonksiyonudur. Fonksiyonun ismi ingilizce string number compare sözcüklerinin kısaltılarak birleştirilmesiyle elde edilmiştir. strcmp fonksiyonuna benzer, ancak bu fonksiyon iki yazının tümünü değil de, belirli bir sayıda karakterlerini karşılaştırma amacıyla kullanılır.
    fonksiyon 1. parametre değişkeninde başlangıç adresi tutulan yazı ile, ikinci parametre değişkeninde başlangıç adresi tutulan yazıların, üçüncü parametresinde tutulan sayıdaki karakterlerini karşılaştırır.

    fonksiyonun geri dönüş değeri

    1. yazının n karakteri 2.yazının n karakterinden daha büyükse pozitif bir değere
    1. yazının n karakteri 2.yazının n karakterinden daha küçükse negatif bir değere
    1.yazı ve 2. yazının n kakateri birbirine eşit ise 0 değerine geri döner.
    /* büyük harf küçük harf duyarlığı ile (case sensitive) bir yazı içinde başka bir yazıyı arayan mystrstr fonksiyonu. Fonksiyon aranan yazıyı aramanın yapılacağı yazı içinde bulursa bulduğu yazının başlangıç adresine, bulamazsa NULL adresine geri dönmektedir.
    */

    #include <stdio.h>
    #include <conio.h>
    #include <string.h>

    char *mystrstr(char *s1, char *s2);

    int main()
    {
    char s1[100];
    char s2[100];
    char *ptr;

    clrscr();
    printf("aramanın yapılacağı yazıyı girin : ");
    gets(s1);
    printf("aranacak yazıyı girin :");
    gets(s2);
    ptr = mystrstr(s1, s2);
    if (ptr)
    puts(ptr);
    else
    puts("aradığınız yazı bulunamadı\n");
    getch();

    return 0;
    }

    char *mystrstr(char *s1, char *s2)
    {
    int i, j;
    int lens1, lens2;

    lens1 = strlen(s1);
    lens2 = strlen(s2);

    for (i = 0; lens1 - i >= lens2; ++i, ++s1) {
    for (j = 0; s1[j] == s2[j]; ++j)
    if (s2[j + 1] == '\0')
    return s1;
    }
    return NULL;
    }



    strupr ve strlwr fonksiyonları

    Standart C fonksiyonları olmamalarına karşın hemen hemen her derleyicide bulunurlar.
    İsimleri string upper ve string lower kelimelerinin kısaltılarak birleştirilmesinden elde edilmiştir. Bu fonksiyonların bir yazının tüm karakterleri için büyük harf küçük harf dönüştürmesi yaparlar. Fonksiyonların geri dönüş değerleri parametresi ile verilen adresin aynısıdır. Geri dönüş değerlerine genellikle ihtiyaç duyulmaz. Her iki fonksiyon da ingiliz alfabesinde olan harfler için dönüşüm yaparlar. Türkçe karakterler için de dönüşüm yapacak bir fonksiyon kullanmak istiyorsak kendimiz yazmalıyız.

    #include <stdio.h>
    #include <string.h>

    int main()
    {
    char s[ ] = "C programcısı olmak için çok çalışmak gerekir!";

    strupr(s);
    puts(s);
    return 0;
    }


    strupr ve strlwr fonksiyonlarını kendimiz de aşağıdaki şekilde

    #include <stdio.h>,
    #include <ctype.h>

    char *_strupr(char *str)
    {
    char *temp = str;

    while (*str != '\0') {
    if (islower(*str)) /* if (*str >= 'a' && *str <= 'z') */
    * str = toupper(*str); /* *str = *str - 'a' + 'A'; */
    ++str;
    }

    return temp;
    }


+ Konu Cevaplama Paneli

Konu Bilgileri

Users Browsing this Thread

Şu an 1 kullanıcı var. (0 üye ve 1 konuk)

     

Benzer Konular

  1. 13.Şua... Beraber Okuyalım....
    By Barla in forum Risale-i Nur'dan Vecize ve Anekdotlar
    Cevaplar: 3
    Son Mesaj: 06.01.09, 23:21
  2. Risale-i Nur'u Hergün mü Okuyalım?
    By Şakird in forum Risale-i Nur'u Yeni Tanıyanlara
    Cevaplar: 23
    Son Mesaj: 09.09.08, 08:13
  3. Daha çok okuyalım!
    By umut46 in forum Serbest Kürsü
    Cevaplar: 10
    Son Mesaj: 27.10.07, 14:36
  4. Sözler'i Okuyalım...
    By aşur in forum Risale-i Nur'dan Vecize ve Anekdotlar
    Cevaplar: 1
    Son Mesaj: 10.08.06, 13:06

Bu Konudaki Etiketler

Yetkileriniz

  • Konu Acma Yetkiniz Yok
  • Cevap Yazma Yetkiniz Yok
  • Eklenti Yükleme Yetkiniz Yok
  • Mesajınızı Değiştirme Yetkiniz Yok
Yemek Tarifleri ListeNur.de - islami siteler listesi
Google Grupları
RisaleForum grubuna abone ol
E-posta:
Bu grubu ziyaret et

Search Engine Friendly URLs by vBSEO 3.6.0