/*---------------------------------------------------------------------------------------------------------------------- C ve Sistem Programcıları Derneği C Programlama Dili Sınıfta Yapılan Örnekler ve Özet Notlar Eğitmen: Kaan ASLAN Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu İle her türlü alıntı yapılabilir. (Notları sabit genişlikli font kullanan programlama editörleri ile açınız.) (Editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.) Son Güncelleme: 08/04/2025 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C Programlama Dili 1971-1972 yıllarında Dennis Ritchie tarafından AT&T Bell Lab'ta UNIX işletim sisteminin bir yan ürünü olarak geliştirilmişir. AT&T Bell Lab. Ken Thompson, Brian Kernighan gibi önemli kişilerle UNIX isimli işletim sistemini geliştiriyordu. UNIX işletim sistemi o zamanın DEC PDP-8 makineleri için yazılıyordu. İlk UNIX sistemleri ağırlıklı sembolik makine dilinde yazılmıştır. Ancak yazımı kolaylaştırmak için Ken Thompson'ın B ismini verdiği programlama dilinden (dilciğinen de diyebiliriz) de faydalanılmıştır. İşte Dennis Ritchie bu B programlama Dilini geliştirerek C haline getirmiştir. UNIX işletim sistemi 1973 yılında tamamen C kullanılarak yeniden yazılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 1978 yılında Dennis Ricthie ve Brian Kernighan C'yi tanıtan "The C Programming Laanguage" isimli bir kitap yazdılar. Daha sonra bu kitabın 1987 yılında 2. Baskısı (Second Edition) oluşturuldu. Nihayet 1989 yılında C Programlama Dili ANSI (American National Standard Institute) tarafıında standardize edildi. C'nin ANSI standartları "ANSI C" olarak bilinmektedir ve kısaca bu standartlara C89 denilmektedir. C'de 1989 öncesi devire "standart öncesi devir" denir. 1990 yılında ISO kurumu C'nin ANSI standratlarını alarak bölüm numaralandırmalarını değiştirip tasdik etmiştir. Bu standartlar "ANSI/ISO 9899:1990" kod numarasıyla basılmıştır. Bu standarda halk arasında C90 denilmektedir. C Programlama Dili 90'lı yıllarda dünyanın en popüler ve yaygın programlama dili haline gelmiştir. ISO 1999 yılında C'ya bazı kurallar ekleyerek yeni bir standart oluşturdu. Bu standartlar da "ISO/IEC 9899:1999" kod numarasını verdi. Bu standartlar da halk arasında kısaca C99 olarak ifade edilmektedir. Daha sonra ISO yine C'ya bazı eklemeler yaparak 2011 standartlarını oluşturdu. Bu standartların kod numarası "ISO/IEC 9899:2011" biçimindedir. Bu standartlar da kalk arasında C11 olarak ifade edilmektedir. Nihayet ISO 2017 yılında yeni bir standart oluşturdu. Ancak bu standart C11'in bir düzeltmesi biçimindedir. Şu anda C'nin en son standardı "ISO/IEC 9899:2017" kod numarasıyla tasdik edilen biçimidir. Bu da halk arasında C17 olarak ifade edilmektedir. C'nin son standardı olan C23 üzerinde çalışılmaktadır. Halen tam olarak tasdik edilmemiştir. Bu standartlar 2023 yılında basılamadığı için artık C2Y olarak isimlendirilmiştir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C++ Programlama Dİli C Programlama Dİlinin nesne yönelimli bir biçimi olarak düşünülebilir. Tamamen olmasa da C++ C'yi kapsamaktadır ve fazlalıkları vardır. C++'a bu fazlalıklar "Nesne Yönelimli Programlama Tekniğini (NYPT)" uygulamak için eklenmiştir. Derneğimizde C++ Programlama Dilinin eğitimi C bilenlere yönelik olarak verilmektedir. C++ Programlama Dilinin standart gelişimi de şöyledir: - ISO/IEC 14882: 1998 (C++98) - ISO/IEC 14882 :2003 (C++03) - ISO/IEC 14882 :2011 (C++11) - ISO/IEC 14882 :2014 (C++14) - ISO/IEC 14882 :2017 (C++17) - ISO/IEC 14882 :2020 (C++20) - ISO/IEC 14882 :2020 (C++23 Henüz basılmadı) ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bilgisayar donanımını yöneten, donanım ile kullanıcı arasında arayüz oluşturan temel sistem programlarına "işletim sistemi (operating system)" denilmektedir. İşletim sistemleri iki katman olarak düşünülebilir. Çekirdek (kernel) işletim sisteminin donanımı yöneten motor kısmıdır. Kabuk (shell) ise kullanıcı ile arayüz oluşturan kısmıdır. İşletim sistemleri çeşitli bakımlardan sınıflandırılabilir. Örneğin: - Tek prosesli işletim sistemleri - Çok prosesli işletim sistemleri - Gerçek zamanlı işletim sistemleri - Masaüstü (desktop) ve mobil işletim sistemleri - Server işletim sistemleri Bugün masaüstü işletim sistemlerinin en yaygın kullanılanı Windows sistemleridir (75 civarı). Bunu macOS izlemektedir (22 civarı) bunu da Linux izlemektedir. (%2.5 civarı). Mobil işletim sistemlerinin en yaygın olanı ise %70 civarında kullanıma sahip olan Android sistemleridir. Bunu %25 civarlarında kullanıma sahip olan IOS sistemleri izlemektedir. Diğer mobil işletim sistemleri %1'in oldukça altındadır. Linux sistemleri Server dünyasında en yaygın kullanılan işletim sistemleridir. Server sistemlerinin %70 civarı Linux makinelerden oluşmaktadır. Artık pek gömülü sistem projelerinde de Linux işletim sistemi kullanılmaktadır. Bazı işletim sistemleri sıfırdan yazılmıştır. Bazı işletim sistemleri ise mevcut işletim sistemlerinin kodlarıdan faydalanılarak oluşturulmuştur. Örneğin Android büyük ölçüde Linux çekirdeğinin kodlarına sahiptir. Windows, Linux özgün kod temeline sahip olan işletim sistemleridir. Eskiden BSD sistemleri özgün değildi. Sonra tamamen sıfırdan yeniden yazıldı. Benzer biçimde Solaris gibi sistemlerde sıfırdan yazılmıştır. Anak macOS sistemleri böyle değildir. macOS sistemleri FreeBSD ve Mach isimli çekirdeklerin birleşimiyle oluşturulmuştur. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir bilgisayar sisteminde en önemli üç birim "CPU", "RAM" ve "Disk" birimleridir. Bütün işlemler CPU (Central Processing Unit) tarafından yapılır. CPU işlemlerin yapıldığı kavramsal birimdir. Bunun entegre devre biçiminde üretilmiş haline "mikroişlemci (microprocessor)" denilmektedir. CPU'nun elektiriksel olarak bağlantılı olduğu belleklere "ana bellek (main memory)", ya da "Birincil bellek (primary memory)" denilmektedir. Ana belleklere ise halk arasında RAM denilmektedir. Programlama dillerindeki değişkenler program çalışırken RAM'de bulunurlar. Ancak işlemler CPU tarafından yapılır. Örneğin: a = b + c; gibi bir işlemde aslında a, b, ve c RAM'de bulunmaktadır. Bu işlem yapılırken b ve c CPU'ya çekilir. CPU içerisindeki elektrik devreleri toplama işlemini yapar. Sonuç RAM'deki a'ya aktarılır. Bilgisayarın güç kaynağı kapatıldığında RAM'deki bilgiler silinmektedir. Bunun için bu bilgilerin daha kalıcı bir bellekte saklanması gerekir. Bu tür belleklere "ikinci bllekler (secondary storage device)" denilmektedir. Eskiden ikincil bellek olarak floppy disketler, CD/DVD ROM'lar ve hard diskler kullanılıyordu. Ancak günümüzde artık SSD (solid State Disk) denilen "flash bellekler" kullanılmaktadır. Genellikle bilgisayar sistemlerinde ikincil belleklerle birincil bellekler arasında bir aktarım yolu bulunmaktadır. Bu aktarım yardımcı işlemciler (bunlara DMA (Dynamic Memory Access) denilmektedir) tarafından yapılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C doğal kodlu bir çalışma sistemi için tasarlanmıştır. Biz C'de bir kod yazıp bunu derlediğimizde ve bağladığımızda çalıştırılabilir (executable) bir dosya elde ederiz. Bu dosyanın içerisinde o anda çalışmakta olduğumuz mikroişlemcinin doğrudan çalıştırabileceği makine komutları bulunur. Yani C'de yazdığımız ve derlediğimiz program mikroişlemci tarafından doğrudan çalıştırılmaktadır. Ancak 1990'lı yılların ortalarında Java ortamıyla (Java framework) birlikte ve sonra da 2002 yılında .NET ortamıyla birlikte "arakodlu çalışma sistemi" yaygınlaşmaya başlamıştır. Bu sistemde derleyicilerin ürettiği kodlar gerçek bir mikroişlemcinin makine kodları değildir. Kendi içerisinde belli bir standardı olan ancak hiçbir mikroişlemcinin makine kodu olmayan yapay bir ara koddur (intermediate code). Dolayısıyla bu arakod mikroişlemci tarafında çalıştırılamaz. İşte bu arakodlar çalıştırılmak istendiğinde bu ortamların (frameworkds) bir alt sistemi devreye girmekte ve bu arakodları o anda gerçek makine komutlarına dünüştürüp çalıştırmaktadır. Bu sürece "tam zamanın derleme (just-in time compilation)" denilmektedir. Java ismi hem bir ortam (framework) belirtmekte hem de bir programlama dili belirtmektedir. Oysa .NET platformun ismi C# ise programlama dilinin ismidir. Java Programlama Dilinde yazılmış olan kodun derlenmesiyle elde edilen ara koda "Java Byte Code" denilmektedir. Benzer biçimde C# ile yazılmış kodun derlenmesiyle elde edilen arakoda ise "Common Intermediate Language (CIL)" denilmektedir. Her iki ortamda da bu kodlar doğrudan değil bu ortamların alt sistemleri tarafından çalıştırılmak istedniğinde belli bir düzen içerisinde o anda gerçek komutlarına dönüştürülmektedir. Tabii böyle bir arakod sistemi JIT derlemesi nedeniyle doğal kodlu sistemlere göre daha yavaş bir çalışma sunmaktadır. Microsoft kendi .NET sistemi için buradaki zaman kaybının %18 civarında olduğunu belirtmektedir. C'de yazılmış ve derlenmiş olan bir program hem işletim sistemine hem de işlemciye bağımlıdır. Yani biz Windows sistemlerinde x86 serisi Intel işlemcilerinin bulunduğu bir bilgisayareda yazdığımız ve derlediğimiz kodu Linux'ta çalıştıramayız. İşte Java gibi .NET gibi ortamlarda yazılmış ve derlenmiş olan kodlar işletim sisteminden ve işlemciden bağımsız arakoda dönüştürülmektedir. Böylece bu ortamlar çeşitli işletim sistemi ve mikroişlemci mimarileri için yazılmış olduğundan Java ve .NET ortamları için yazılan programlar "platform bağımsız" bir biçimde her yerde çalışabilmektedir. Java ve .NET gibi ortamlara İngilizce "framework" denilmekltedir. Platform sözcüğü İngilizce daha çok "işletim sistemi ve işlemcinin" oluşturduğu küme için söylenmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Taşınabilirlik (portability) eski bir terimdir ve hala kullanılmaktadır. Taşıanbilirlik denildiğinde "kaynak kodun taşınabilirliği" anlaşılmaktadır. Bu bağlamda taşınabilirlik "yazılmış olan kaynak kodun başka sistemlere götürüldüğünde sorunsuz derlenmesi ve aynı biçimde çalışması" anlamına gelemektedir. Örneğin C'nin taşınabilir bir dil olması demek C programlarının standart bir dilde yazıldığından her C derleyicisinin bunu kabul etmesi demektir. Ancak son 30 yıldır derlenmiş olan programların taşınabilirliği biçiminde "binary portability" terim de kullanılmaya başlanmıştır. Derlenmiş programın taşınabilirliği onun başka platformlara götürüldüğünde sorunsuz çalışabilmesi anlamına gelmektedir. Java gibi .NET gibi rtamlar bunu hedeflemektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir programlama dilinde yazılmış olan bir programı eşdeğer olarak başka bir dile dönüştüren araçlara "çevirici programlar (translators)" denilmektedir. Çevirici programlarda çevrilecek dile "kaynak dil (source language)" çevrime işleminin sonunda elde edilen programın diline ise "hedef dil (target language)" denilmektedir. Hedef dili alçak seviyeli olan çevirici programlara ise "derleyici (compiler)" denilmekltedir. Saf makine dilleri, sembolik makine dilleri ve ara kodlar alçak seviyeli dillerdir. Yorumlayıcılar (interpreters) kaynak kodu okuyup hiç hedef kod üretmeden doğrudan çalıştıran programlardır. Dolayısıyla yorumlayıcılar aslında çevirici programlar değildir. Bazı dillerde yalnızca derleyicilerle çalışılır (örneğin C, C++). Bazı dillerde ise yalnızca yorumlayıcılar bulunmaktadır. (Örneğin Ruby, R gibi). Bazı dillerde ise hem derleyiciler hem de yorumlayıcılarla programlar çalıştırılabilir (örneğin Basic gibi). (Biz İngilizce "interpreter" sözcüğünü Türkçe "yorumlayıcı" biçiminde çeviriyoruz. Aslında İngilizce "interpreter" sözcüğü "translator" sözcüğü dikkate alınarak "mütercim tercüman" anlamında uydurulmulmuştur.) Derleyici yazmak yorumlayıcı yazmaktan daha zordur. Derleyiciler ile yazılan kod genel olarak daha hızlı çalıştırılmaktadır. Yorumlayıcılarla çalışırken biz kaynak kodu gizleyemeyiz. Ancak derleyicilerle çalışırken bir üretilen makine kodlarını karşı tarafa verebiliriz. Eğer bir derleyici kendisinin çalıştığı işlemciden farklı bir işlemci için kod üretiyorsa o tür derleyicilere "çapraz derleyiciler (cross compilers)" denilmektedir. Örneğin x86 işlemcilerinin bulunduğu Windows sistemlerinde çalışan bir C derleyicisi eğer örneğin PIC işlemcileri kod üretiyorsa bu bir çapraz derleyicidir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Kendisi bir bilgisayar olmayıp asıl amacı başka işlemleri yapmak olan aygıtlardaki bilgisayar devrelerine "gömülü sistemler (embedded systems)" denilmektedir. Örneğin ölçü aletlerinin, kapı güvenlik sistemlerinin, turnike geçiş sistemlerinin, çamaşır makinelerinin, buzdolaplarının, fırınların, kahve makinelerinin içerisindeki bilgisayar sistemlerini gömülü sistemlere örnek olarak verebiliriz. Gömülü sistemlerdeki bilgisayar devreleri genel olarak düşük güç harcayan, düşük kapasiteli, ancak ucuz olma eğilimindedir. Mikrodenetleyiciler bu tür gömülü sistemlerde yoğun biçimde kullanılmaktadır. Dolayısıyla gömülü sistemler dünyasında aşağı seviyeli bir çalışma söz konusu olduğu için C Programlama Dili de bu sistemlerde yoğun olarak kullanılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Dil (language) iletişimde kullanılan semboller kümesidir. Dil karmaşık bir olgudur. Bir olgunun dil olarak değerlendirilmesi için iki kural topluluğunun o olguda bulunyor olması gerekir: Sentaks ve smantik. Bir dilin en yalın öğelerine "atom (token)" denilmektedir. Örneğin doğal dillerde atomlar zözcüklerdir. İşte sentaks atomların doğru yazılmasına ve doğru dizilmesine ilişkin kurallardır. Örneğin: I going to am school Burada bir sentakshatası yapılmıştır. Buradaki atomlar uygun bir biçimde yan yana getirilmemiştir. Burada yapılan hata aşağıdaki C kodunda yapılan hataya tamamen benzemektedir: if a > ( 10 ) printf("Ok"); Sentaks bakımından doğru olan atom dizilimlerinin ne anlam ifade ettiğine ilişkin kurallara "semantik" denilmektedir. Sentaks ve semantik kurallara sahip her olguya dil denilmektedir. Örneğin HTML'de bir senataks vardır. Oluşturulan tag'ların bir anlamı da vardır. O zaman HTML bir dildir. Diller doğal diller ve kurgusal diller olmak üzere iki ayrılır. Doğal diller Türkçe gibi İngilizce gibi doğal yaşam sonıcında oluşmuş dillerdir. Doğal dillerde sentaksın matematiksel düzeyde kesin ifade edilmesi mümkün değildir. Çünkü doğal dillerde çok istisnalar vardır. Kurgusal diller insanların belli bir mantık çerçevesinde belli bir amaç doğrultusunda tasarladığı dillerdir. Bunların sentaksları kesindir. İki anlamlılık ve istisna çok yoktur ya da çok azdır. Bilgisayar alanında kullanılan kurgusal dillere "bilgisayar dilleri (computer languages)" denilmektedir. Bir bilgisayar dilinde bir akış varsa ona aynı zamanda "programlama dili (programming language)" denilmektedir. Örneğin HTML bir bilgisayar dilidir. Ancak bir programlama dili değildir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Programlama dilleri çeşitli biçimlerde sınıflandırılabilmektedir. En çok kullanılan sınıflandırma biçimleri şunlardır: 1) Seviyelerine göre sınıflandırma 2) Uygulama alanlarına göre sınıflandırma 3) Programlama modeline göre sınıflandırma ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Seviye (level) bir programalama dilinin insan algısına yakınlığının bir ölçüsüdür. Yüksek seviyeli diller insana yakın alçak seviyeli diller makineye yakın dillerdir. Seviyelerine göre diller yüksekten alçağa kategorik olarak genellikle şöyle sınıflandırılmaktadır: - Çok Yüksek Seviyeli Diller - Yüksek Seviyeli Diller - Orta Seviyeli Diller - Sembolik Makine Dilleri - Saf Makine Dilleri ve Arakodlar C ortra seviyeli (middle level) bir programlama dilidir. Ancak Java, C#, Python gibi diller yüksek seviyeli diller olarak gruplanmaktadır. Çok yüksek seviyeli dillerde artık algoritma da ortadan kalkmaktadır. Genellikle bu tür diller "belli bir alana yönelik (domain specific)" biçimdedirler. Saf makike dilleri ve arakodlar 1'lerden ve 0'lardan oluşmaktadır. Bunların sembolik biçimlerine "sembolik makine dilleri (assembly languages)" denilmektedir. Sembolik makine dilleri, saf makine dilleri ve arakodlara da "alçak seviyeli diller" denir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Programlama dillerinin uygulama alanlarına göre sınıflandırılması "hangi tür uygulamalar için hangi dillerin daha uygun bir araç olacağı" ile ilgilidir. Bu bakımdan pek çok alt sınıflandırma yapılabilmektedir. Aşağıda birkaç önemli alt sınıf verilmiştir: - Bilimsel ve Mühendislik Diller (Scientific and Enginnering Languages): Fortran, C, C++, Pascal, Java, C#, ... - Veritabanı Dilleri (Database Languages): SQL, Clipper, ... - Web Dilleri (Web Languages): Java Script, PHP, Ruby, Java, C#, Python, ... - Yapay Zeka Dilleri (Artificial Intelligence Langauges): Lisp, Prolog, Python, C, C++,... - Görsel ve Animasyon Dilleri (Visual and Animation Languages): Action Script, ... - Sistem Proramlama Dilleri (System Programming Languages): C, C++, Sembolik Makine Dilleri, Rust, Go - Genel Amaçlı Diller (General Purpose Languages): C, C++, Java, C#, Pascal, ... C Programlama Dİli bilimsel ve mühendislik alanlarda kullanılan, genel amaçlı, uzmanlığı sistem programlama olan bir dildir. Sistem programlama "bilgisayar donanımı ile arayüz oluşturan, uygulama programlarına çeşitli bakımlardan hizmet veren, aşağı seviyeli temel yazılımların oluşturulması amacıyla yapılan programlama faaliyetlerine denilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Programlama modeli (programming paradigm) programlama yaparken kullandığımız genel yöntemleri ve biçimleri anlatan bir kavramdır. Programlama dilleri belli programlama modellerini uygulayabilmek için özel tasarlanmıştır. Bu bakımdan dilleri tipik olarak aşağıdaki gibi sınıflara ayırabiliriz: - Prosedürel Diller (Procedural Languages): Bu dillerde programlar altprogramların (bunlara fonksiyon, prosedür ya da "subroutine" de denilmektedir) birbirlerini çağırması ile oluşturulmaktadır. Fortran, C, Basic, Pascal gibi 90 öncesi klasik programlama dillerinin büyük bölümü böyledir. - Nesne Yönelimli ve Nesne Tabanlı Diller (Object Oriented Languages): İçerisinde sınıf kavramı geçen diller. Bunlar programların sınıflar kullanılarak yazılmabilmesine olanak sağlamaktadır. - Fonksiyonel Diller (Functional Languages): Bunlar adeta "formül yazar gibi program yazmaya olanak sağlayan" dillerdir. Aslında bu diller de kendi aralarında bir spektrum oluşturmaktadır. Fonksiyonel dil demekle genellikle yüksek oranda fonksiyonal olan (pure functional) diller kastedilmektedir. - Imperative Diller (Imperative Languages): Programların deyim deyim çalıştırıldığı mantıksal, görsel ve fonksiyonel dillerin dışındaki diller genel olarak imperative diller olarak da bilinmektedir. - Mantıksal Diller (Logical Languages): Mantıksal ifadelerin ve sonuç çıkartma işlemlerinin yoğun kullanıldığı dillerdir. Lisp ve Prolog gibi. - Görsel Diller (Visual Languages): Fare hareketleriyle tamamne görsel biçimde program akışının oluşturulduğu çok yüksek seviyeli dillerdir. Programlama eğitiminde kullanılan Scratch buna örnek verilebilir. - Çok Modelli Diller(Multiparadigm Languages): Yukarıdaki modellerden birden fazlasını belli ölçülerde destekleyen dillerdir. Örneğin C++'ta biz C gibi kod yazabiliriz. Ama sınıflar kullanarak da kod yazabiliriz. Fonksiyonel bazı dil özelliklerini C++11'den sonra C++'ta kullanabilmekteyiz. O zaman C++ çok modelli bir programlama dilidir. Yeni tasarlanan diller zaten genel olarak çok modelli olma eğilimindedir. Bu yeni diller hem nesne yönelimli hem de fonksiyonel özellikleri bünyesinde barındırmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C Programalama Dili orta seviyeli, prosedürel, imperative, genel amaçlı, bilimsel ve mühendislik çalışmalarda kullanılan ancak uzmanlık alanı "sistem programlama" olan bir dildir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Uygulama geliştirmeyi kolaylaştıran, kendi içerisinde editörü olan, menüleri olan, genellikle debugger'ları olan, başka birtakım araçları bulunan yazılımlara "IDE (Integrated Developmen Environment)" denilmektedir. IDE derleyici değildir. IDE derleyiciyi barındırmaz. IDE'de derleme işlemi yapılırken IDE derleyiciyi dışarıdan çalıştırmaktadır. Derleyiciler genel olarak komut satırında çalışan GUI arayüzü olmayan araçlar biçiminde oluşturulmaktadır: C için kullanılabilecek önemli IDE'ler şunlardır: - Windows sistemleri için Microsoft'un "Visual Studio" IDE'si. Kursumuzda bunun "Community-2022" parasız versiyonunu kullanacağız. - QtCreator IDE'si. Cross platform bir IDE'dir. Aslında "Qt" denilen framework için yazılmıştır. Ancak genel amaçlı C/C++ IDE'si olarak da kullanılabilmektedir. macOS ve Linux sistemlerinde iyi bir alternatiftir. - Eclipse IDE'si. Bu IDE bir Java IDE'si olarak çıkmıştı. Ondan sonra pek çok programlama dili için "plugin" yöntemiyle kullanılabilmeye başlandı. Bu IDE'nin C/C++ versiyonu doğrudan indirilebilir. - CLion IDE'si. Bu JetBrains firmasının C/C++ IDE'sidir. Ancak paralıdır ve community versiyonu yoktur. - Visual Studio Code IDE'si Aslında bu yazılım IDE ile editör arasında bir yerdedir. Ancak cross platform özelliği vardır. Kullanımı biraz dah zahmetli olsa da çok az yer kaplamaktadır (lightweight IDE). Plug-in'lerle (extensions) işlevleri genişletilebilmektedir. - XCode IDE'si. Bu IDE Apple firmasının temel IDE'sidir. Dolayısıyla adeta Visual Studio IDE'sinin Apple versiyonu gibi düşünülebilir. Her ne kadar Apple'ın temel dili Swift ve Objective C olsa da XCode C/C++ çalışmasını desteklemektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 5. Ders - 07/06/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Biz 10'luk sistemi (decimal system) kullanmaktayız. 10'luk sistemde sayıları ifade etmek için 10 sembol vardır: 0 1 2 3 4 5 6 7 8 9 10'luk sistemde sayının her bir basamağı 10'nun kuvvetleriyle çarpılıp toplanmaktadır. Örneğin: 123.25 = 3 * 10^0 + 2 * 10^1 + 1 * 10^2 + 2 * 10^-1 + 5 * 10^-2 Halbuki bilgisayarlar 'lik sistemi (binary system) kullanmaktadır. ikilik sistemde sayıları ifade etmek için 2 sembol kullanılmaktadır: 0 1 ikilik sistemde sayının her bir basamağına "bit ("binary digit" sözcüklerinden kısaltma)" denilmektedir. ikilik sistemde sayının her basamağı 2'nin kuvvetiyle çarpılarak sayı elde edilir. Bit en küçük bellek birimidir. 8 bite 1 byte denilmektedir. Genellikle bitler 4'erli gruplanarak yazılırlar. Örneğin: 1010 0010 Burada 1 byte'lık bir bilgi vardır. Byte temel bellek birimidir. Byte da küçük bir birimdir. Kilo diğer bilimlerde "1000 katı" anlamına gelmektedir. Ancak bilgisayarlar ikilik sistemi kullandığj için 1000 katı iyi bir kat değildir. Bu nedenle genellikle olarak Kilo byte için 2'nin 10'uncu kuvveti olan 1024 kat kullanılır. Yani 1KB (kısaca 1K) 1024 byte'tır. Mega diğer bilimlerde kilonun 1000 katıdır. Dolayısıyla milyar kat anlamına gelmektedir. Ancak bilgisayar bilimlerinde genellikle mega kilonun 1024 katı olarak alınır. Bu durumda 1 MB = 1020 * 1024 (2^20) KB'dir. Giga ise meganın 1024 katıdır. Bu durumda 1 GB = 1024 * 1024 * 1024 byte'tır ( 2^30). Giga'dan sonra tera, tera'dan sonra peta, ondan sonra da exa gelmektedir. Bazı kaynaklar kilo, mega ve giga byte terimlerini diğer bilimlerde olduğu gibi 10^3, 10^6 ve 10^9 biçiminde kullanmaktadır. Bu konudaki karışıklık daha sonraları "International System of Units (SI)" tarafından giderilmeye çalışılmıştır. SI'ye göre artık 1 Kilobyte 1000 byte, 1 Kikibyte ise 1024 byte'tır. Ancak Microsoft gibi bazı kurumlar Kilobyte tanımını 1024 olarak kullanmaya devam etmektedir. 1 byte içerisinde yazılabilecek en küçük ve en büyük sayılar şöyledir: 0000 0000 ---> 0 1111 1111 ---> 255 1 byte içerisinde 1 ve 0'ların bütün permütasyonları 256 tanedir. 2 byte içerisinde en büyük sayıyı yazacak olsak şöyle olurdu: 1111 1111 1111 1111 ---> 65535 Biz burada ikilik sistemde tamsayıları ifade ettik. Ama bütün sayıları pozitif kabul ettik. Pekiyi negatif tamsayılar nasıl ifade edilmektedir? Bugün negatif sayıların ifade edilmesi için "ikiye tümleyen (two's complement)" sistemi denilen bir sistem kullanılmaktadır. Bu sistemde pozitif ve negatif sayılar birbirlerinin ikiye tümleyenidirler. ikiye tümleyen bire tümleyene bir eklenerek elde edilmektedir. Bir sayının bire tümleyeni sayıdaki 0'ların 1, 1'lerin 0 yapılmasıyla elde edilir. Bu durumda ikiye tümleyen şöyle hesaplanır. Örneğin aşağıdaki sayının ikiye tümleyenini bulmaya çalışalım: 0101 0110 Sayının bire tümleyenine bir ekleyeceğiz. Sayının bire tümöleyeni şöyledir: 1010 1001 Şimdi ona 1 ekleyelim: 1010 1001 0000 0001 --------- 1010 1010 Aslında ikiye tümleyeni bulmanın kolay bir yolu da vardır: Sayıda sağdan sola ilk 1 görene ilk 1 dahil olmak üzere aynısı yazılarak ilerlenir. Sonra 0'lar 1, 1'ler 0 yapılarak devam edilir. Örneğin: 0101 0110 sayının ikiye tümleyenini tek hamlede bulalım: 10101010 Negatif sayıları ifade edebilmek için kullanılan ikiye tümleme sisteminde en soldaki bir işaret bitidir. Bu bit 0 ise sayı pozitif, 1 ise negatiftir. Negatif ve pozitif sayılar birbirlerinin ikiye tümleyenidir. Örneğin bu sistemde +10 yazmak isteyelim. Bunu işaret bitini 0 yaparak yazabiliriz: 0 000 1010 ---> +10 Şimdi -10 yazmak isteyelim. Bunun için +10'un ikiye tümleyenini alalım: 1 111 0110 ---> -10 Bu sistemde +n ile -n toplandığında 0 elde edilir: 0 000 1010 ---> +10 1 111 0110 ---> -10 --------------------- 0 000 0000 ---> 0 Bu sistemde tek bir sıfır vardır. O da tüm bitleri 0 olan sıfırdır. Bu sistemde 1 byte içerisinde yazılabilecek en büyük pozitif sayı şöyledir: 0 111 1111 ---> +127 Şimdi bunun ikiye tümleyenini alalım: 1 000 0001 ---> -127 Pekiyi bu sistemde en küçük negatif sayı nedir? Bu sistemde bir tane sıfır olduğuna göre 255 tane permütasyon eşit bölünemez. Demek ki ya pozitif sayılar ya negatif sayılar bir tane daha fazla olmak zorundadır. Bu sistemde ikiye tümleyeni olmayan iki sayı vardır: 0000 0000 1000 0000 Birincisi 0'dır. İkinci sayı -127'den bir eksik olan sayıdır. O halde bu sayının -128 kabul edilmesi daha uygundur. Demek ki bu sistemde n byte içerisinde yazılabilecek en büyük pozitif sayı ilk biti 0 olan diğer tüm birleri 1 olan sayıdır. En küçük negatif sayı ise ilk biti 1 olan diğer tüm bitleri 0 olan sayıdır. Örneğin bu sistemde iki byte ile yazabileceğimiz en büyük pozitif sayı şöyledir: 0111 1111 1111 1111 ---> +32767 En küçük negatif sayı ise şöyledir: 1000 0000 0000 000 ---> -32768 Bu sisteme ilişkin tipik sorular ve yanıtları şöyledir: SORU: Bu sistemde +n sayısını nasıl yazarsınız? YANIT: En soldaki bit 0 yapılıp n sayısı ikilik sistemde yazılır. SORU: Bu sistemde -n nasıl yazarsınız? CEVAP: Yazabiliyorsanız doğrudan yazın. Ancak doğrudan yazamıyorsanız önce +n değerini yazın ve ikiye tümleyenini alın. Örneğin bu sistemde -1 yazalım. Önce +1 yazalım: 0000 0001 ---> +1 Şimdi bunun ikiye tümleyenini alalım: 1111 1111 ----> -1 SORU: Bu sistemde bir sayının kaç olduğu bize sorulsa bunu nasıl yanıtlarız? CEVAP: Eğer en soldaki bit 0 ise sayının değeri doğrudan hesplanır. Eğer en soldaki bit 1 ise bu sayının negatif olduğunu gösterir. Bu durumda sayının ikiye tümleyeni alınır. Pozitifinden hareketle negatifi bulunur. Örneğin 1110 1110 sayısı kaçtır? Burada işaret biti 1 olduğuna göre sayı negatiftir. Negatif ve pozitif sayılar birbirlerinin ikiye tümleyenidirler. O zaman bu sayının ikiye tümleyenini alıp pozitifinden faydalanarak sayıyı bulalım: 0001 0010 ---> +18 o zaman bize sorulan sayı -18'dir. Bu sistemde örneğin 1 byte içerisinde yazılabilecek en büyük pozitif sayıya 1 toplayalım: 0111 1111 ---> +127 1000 0000 ---> -128 Demek ki bu sistemde bir sayıyı üst limitten taşırırsak yüksek bir negatif sayıyla karışılaırız. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Tamsayılar ikilik sistemde "işaretsiz (unsigned)" ya da "işaretli (signed)" sistemde yorumlanabilirler. İşaretsiz sistemde sayının en soldaki biti olarak yorumlanmaz. Sayı herzaman sıfır ya da pozitiftir. İşaretli sistemde ise sayının en solundaki bit işaret bitidir. Sayı ikiye tümleyen aritmetiğine göre yorumlanır. Pekiyi sayının işaretli mi işaretsiz mi olduğuna nasıl karar verilmektedir? Programcı sayıyı tutacağı değişkeni C'de işaretli ya da işaretsiz tamsayı türü olarak belirleyebilir. İşlemciler aslında genellikle işaretli ve işaretsiz ayırımını yapmazlar. Çünkü bu tür de aslında aynı biçimde işleme sokulmaktadır. Sonucun yorumu değişmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Pekiyi noktalı sayılar ikilik sistemde nasıl ifade edilmektedir? İşte insanlar noktalı sayıları ifade etmek için iki format geliştirmişlerdir. Bunlardan birine "sabit noktalı formatlar (fixed point formats)" diğerine "kayan noktalı formatlar (floating point formats)" denilmektedir. Sabit noktalı formatlar eski devirlerde basit bir mantıkla tasarlanmıştır. Bu formatlar bugün hala kullanılıyor olsa da büyük ölçüde artık bunların çağı kapanmıştır. Bugün kayan noktalı format denilen formatlar kullanılmaktadır. Sabit noktalı formatlarda noktalı sayı için n byte yer ayrılır. Noktanın yeri önceden bellidir. Örneğin sayı 4 byte ile ifade edilsin. Noktanın yeri de tam ortada olsun. Bu durumda syının tam kısmı 2 byte ile noktalı kısmı 2 byte ile ifade edilir. Ancak sayının noktalı kısmı 2'nin negatif kuvvetleriyle kodlanmaktadır. VBöylece iki sabit noktalı sayıyı paralel toplayıcılarla kolay bir biçimde toplayabiliriz: Örneğin bu sistemde 5.25 ile 6.25 sayılarını ifade edip toplayalım: 0000 0000 0000 0101 . 0100 0000 0000 0000 ---> 5.25 0000 0000 0000 0110 . 0100 0000 0000 0000 ---> 6.25 ------------------------------------------------------- 0000 0000 0000 1011 . 1000 0000 0000 0000 ---> 11.5 Pekiyi bu yöntemin ne dezavantajı vardır? Yöntemin en önemli dezavantajı dinamik olmamasıdır. Yani sayının tam kısmı düşükse tam kısım için gereksiz bir yer ayrılmış olur. Benzer biçimde sayının noktadan sonraki kısmı düşükse noktadan sonrakşi kısım için gereksiz bir biçimde yer ayrılmış olur. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 6. Ders - 09/06/2022 ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Sabit noktalı formatların dinamik olmaması nedeniyle kayan noktalşı formatlar geliştirilmiştir. Bu formatlarda noktanın yeri sabit değildir. Noktanın yeri format içerisinde ayrıca tutulmaktadır. Noktalı sayının noktası yokmuş gibi ifade edilmesi durumunda sayının bu haline "mantis (mantissa)" denilmektedir. İşte kayan formatlarda sayı için ayrılan alanın bir bölümünde mantis bir bölümünde de "noktanın yeri" tutulmaktadır. Noktanın yerini belirleyen kısma "üstel kısım (exponential part)" denilmektedir. Tabii bir de sayının başında işaret biti bulunur. Bu durumda kayan noktalı bir sayının format aşağıdakine benzerdir: [işaret biti] [mantis] [noktanın yeri (exponential)] Bugün ağırlıklı kullanılan kayan noktalı format IEEE 754 denilen formattır. Bu formatın üç farklı genişlikte biçimi vardır: IEEE 754 - Short Real Format (4 byte) IEEE 754 - Long Real Format (8 byte) IEEE 754 - Extended Real Format (10 byte) Bugün Intel, ARM, MIPS, Alpha, Power PC gibi yaygın işlemciler donanımsal olarak bu formatı desteklemektedir. Aynı zamanda bu format yaygın olarak Reel Sayı Ünitesi olmayan mikrodenetleyicilerdeki derleyiciler tarafından da kullanılmaktadır. Kayan noktalı formatların (örneğin IEEE 754 formatının) en ilginç ve problemli tarafı "yuvarlama hatası (rounding error)" denilen durumdur. Yuvarlama hatası noktalı sayının tam olarak ifade edilemeyip onun yerine ona yakın bir sayının ifade edilmesiyle oluşan hatadır. Yuvarlama hatası sayıyı ilk kez depolarken de oluşabilir, aritmetik işlemlerin sonucunda da oluşabilir. Tabii noktalı sayıların bir bölümü bu formatta hiçbir yuvarlama hatasına maruz kalmadan ifade edilebilmektedir. Ancak bazı sayılarda bu hata oluşabilmektedir. Bu hatayı ortadan kaldırmanın yolu yoktur. Tabii sayı için daha fazla alan ayrılırsa yuvarlama hatasının etkisi de azalacaktır. Yuvarlama hatalarından dolayı programlama dillerinde iki noktalı sayının tam eşitliğinin karşılaştırılması anlamlı değildir. Örneğin aşağıdaki işlemde yuvarlama hatasından dolayı sayılar sanki eşit değişmiş gibi ele alınacaktır. 0.3 - 0.1 == 0.2 (false) Pekiyi yuvarlama hatasının önemli olduüu ve bunun istenmediği tarzda uygulamalarda (örneğin finansal uygulamalarda, bilimsel birtakım uygulamalarda) ne yapak gerekir? İşte bunun tek yolu noktalı sayıları kayan noktalı formatta tutmamak olabilir. Bazı programlama dillerinde noktalı sayıyı kayan noktalı formatta tutmayan böylece yuvarlama hatalarına maruz bırkmayan özel türler (örneğin C#'taki decimal) vardır. Ancak bu türler işlemciler tarafından desteklenmediği için yapay türlerdir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Yazılar da aslında bilgisayar belleğinde ikilik sistemde sayılar biçiminde tutulmaktadır. Bir yazıyı oluşturan elemanlara "karakter" denilmektedir. İşte bir yazıda her bir karakter ikilik sistemde bir sayı ile ifade edilir. Böylece yazı aslında ikilik sistemde bir sayı dizisi gibi tutulmaktadır. İşte bir karakter için hangi sayının karşı geldiğini belirten tablolara "karakter tabloları" denilmektedir. Karakter tablosundaki karakter şekillerine "glyph" denilmektedir. Her karaktere tabloda bir sıra numarası verilmiştir. Buna da "code point" denilmektedir. Dünyanın ilk standart karakter tablosu "ASCII (American Standard Code Information Interchange)" denilen tablodur. ASCII tablosu aslında 7 bit bir tablodur. Dolayısıyla tabloda 128 tane glyph için code point bulundurulmuştur. ASCII dışında IBM EBCDIC tablosunu geliştirmiştir. Wang firması WISCII tablosunu kullanmıştır. ASCII tablosu Amerikalılar tarafından yalnızca İngilizce karakterleri ifade etmek için oluşturulmuştur. Bilgisayarlar yaygınlaşmaya başladığında farklı karakterlere sahip olan Türkiye gibi, Yunanistan gibi, Almanya gibi ülkeler bu ASCII tablosunu 8 bite çıkartıp elde edilen 128'lik yeni alanı kendi karakterlerini ifade etmek için kullanmışlardır. ASCII tablosunun ilk yarısı (yani [0, 128] numaraları karakterleri) standarttır. Ancak ikinci yarısı "code page" adı altında farklı ülkeler tarafından farklı yerleşimler yapılarak kullanılmaktadır. DOS zamanlarında Türkçe karakterler için OEM 857 denilen code page kullanılıyordu. Daha sonra Microsoft Windows sistemlerinde Türkçe karakterler için 1254 code page'i düzenledi. ISO bu code page'leri standart hale getirmiştir. Bugün Türkçe karakterler ISO tarafından ASCII 8859-9 Code page'i ile düzenlenmiştir. ASCII tablosu ve onların code page'leri uzun süre kullanılmış ve hala kullanılmakta olsa da maalesef karışıklıklara yol açmaktadır. İşte son 20 yıldır artık karakterleri 2 byte içerisinde ifade ederek dünyanın bütün dillerinin ve ortak sembollerinin tek bir tabloya yerleştirilmesi ile ismine Unicode denilen bir tablo oluşturulmuştur (www-Unicode.org). Unicode tablo ISO tarafından 10646 ismiyle de bazı farklılıklarla standardize edilmiştir. Unicode tablonun ilk 128 karakteri standart ASCII karakterleri, ikinci 128 karakteri ISO 8859-9 code page'indeki karakterlerdir. Bir karakter tablosundaki code point'lerin ikilik sistemde ifade edilme biçimine "encoding" denilmektedir. ASCII code page'lerinde encoding doğrudan code point'in 1 byte'lık sayı karşılığıdır. Ancak Unicode tablonun değişik encoding'leri kullanılmaktadır. Unicode tablonun klasik encoding'i UTF-16'dır. Burada code point doğrudan 16 bir bir sayı biçiminde ifade edilir. UTF-32 encoding'inde ise code point 32 bitlik bir sayı biçiminde ifade edilmektedir. (Unicode tablodaki karakterler için başlangıçta 2 byte yetiyordu. Ancak daha sonraları tabloya pek çok karakter eklendi ve 2 byte yetmez hale geldi.) Ancak Unicode tablonun en yaygın kullanılan encoding'i UTF-8 encoding'idir. UTF-8 kodlamasında standart ASCII karakterler 1 byte ile, diğer karakterler 2 byte, 3 byte, 4 byte ve 5 byte kodlanabilmekedir. Türkçe karakterler UTF-8 encoding'inde 2 byte yer kaplamaktadr. UTF-8 encoding'i Unicode bir yazının adeta sıkıştırılmış bir hali gibi düşünülebilir. Bugün pek çok programlama editörleri default durumda dosyayı Unicode UTF-8 encoding'ine göre saklamaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bilgisayar dünyasında çok kullanılan diğer bir sayı sistemi de 16'lık sistemdir. 16'lık sisteme İngilizce "hexadecimal system" denilmektedir. 16'lık sistemde syaıları ifade etmek için 16 sembol bulunmaktadır. İlk 10 sembol 10'luk sistemdeki sembollerden alınmıştır. Sonraki 6 sembol alfabetik karakterlerden alınmıştır: 0 1 2 3 4 5 6 7 8 9 A B C D E F 16'lık sistemdeki her bir basamağa "hex digit" denilmektedir. Örneğin: 1FC8 Burada 4 hex digit'lik bir sayı vardır. 16'lık sistemdeki bir sayıyı 10'luk sisteme dönüştürmek için her hex digit 16'lık kuvvetleriyle çarpılıp toplanır. Ancak 16'lık sistemdeki sayı kullanım gereği bakımından aslında 10'lu sisteme pek dönüştürülmez. 16'lık sistemdeki her bir hex digit 4 bit ile ifade edilebilmektedir: 0 0000 1 0001 2 0010 3 0011 4 0100 5 0101 6 0110 7 0111 8 1000 9 1001 A 1010 B 1011 C 1100 D 1101 E 1110 F 1111 16'lık sistemden ikilik sisteme dönüştürme yapmak çok kolaydır. Tek yapılacak şey bir hex digit'e karşılık yandaki tablodaki 4 biti getirmektir. Örneğin: 1FC9 = 0001 1111 1100 1001 FA3D = 1111 1010 0011 1101 ikilik sistemdeki bir sayı da 16'lık sisteme çok kolay dönüştürülür. Tek yapılacak şey sayıyı dörderli gruplayıp ona karşı gelen hex digit'i yazmaktır. Örneğin: 1010 0001 1110 1000 0011 0101 = A1E835 A 1 E 8 3 5 Bilgisayar dünyasında 16'lık sistem aslında ikilik sistemin yoğun bir gösterimi olarak kullanılmaktadır. Yani ikilik sistem çok yer kapladığı için kişiler ikilik sistem yerine 16'lık sistemi kullanırlar. Bu nedenle belleğin, dosyanın içeriğini görüntüleyen programlar bunları ikilik sistem yerine 16'lık sistemde görüntülemektedir. 1 byte 2 hex digit ile ifade edilmektedir. Örneğin: 1A 23 5C 78 Burada 4 byte'lık bir bilgi vardır. Örneğin 2 byte içerisinde yazılabilecek en küçük negatif işaretli sayının hex karşılığı 8000 biçimindedir. Örneğin bir byte'lık işaretli sistemde yazılabilecek en büyük pozitif sayı 7F biçimindedir. İşareti tamsayı sisteminde 4 byte içerisinde -1 sayısı FFFFFFFF biçimindedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Eskiden daha fazla kullanılıyor olsa da toplamda oldukça seyrek kullanılan dğer bir sayı sistemi de 8'lik sayı sistemidir. Bu sisteme İngilizce "octal system" denilmektedir. 8'lik sayı sistemindeki her bir basamağa "octal digit" denir. Octal digit sembolleri olarak 10'luk sistemin ilk 8 sembolü kullanılmaktadır: 0 1 2 3 4 5 6 7 Her octal digit 3 bit ile ifade edilebilir: 0 000 1 001 2 010 3 011 4 100 5 101 6 110 7 111 Bu durumda bir octal sayı ikilik sisteme kolay bir biçimde dönüştürülebilir: 476 100 111 110 741 111 100 001 Benzer biçimde ikilik sistemdeki bir sayı da sağdan sola üçer bir gruplandırılarak 8'lik sisteme dönüştürülebilmektedir. Örneğin: 1011 1011 = 273 0111 1110 = 176 8'lik sistem de ikilik sistemin yoğun bir gösterimi olarak kullanılmaktadır. Ancak 8'i tam ortalayamadığı için kullanımı seyrektir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 7. Ders - 14/06/2022-Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Klavyeden bastımız tuşlara ilişkin karakterlerin İngilizce isimleri şöyledir: + plus - minus, dash, hyphen * asterisk / slash \ back slash % percent sign () paranthesis (left, right, opening, closing) {} (curly) brace (left, right) [] (square) bracket (left, right) = equal sign # sharp, number sign ' single quote "" doueble quote _ underscore ^ caret & ampersand ! exclamation mark , comma : colon ; semicolon | pipe < less than > greater than . period ? question mark ` back tick ~ tilde @ at ... ellipsis $ dollar sign ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Ekrana Merhaba Dunya yazısını çıkartan örnek C programı aşağıdaki gibidir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { printf("Hello World\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bir C programı en yalın olarak önce bir text editör ile yazılır ve diske uzantısı ".c" biçiminde kaydedilir. Sonra komut satırından C derleyicisi ile derlenir. Derleyiciler genel olarak komut satırından çalıştırılacak biçimde yazılırlar. En çok kullanılan C derleyicileri şunlardır: - Microsoft C Derleyicisi - gcc Derleyicisi - clang Derleyicisi - Intel C derleyicisi Bu derleyicilerin dışında daha pek çok C derleyicisi vardır ve çeşitli kesimler tarafından kullanılmaktadır. Windows'ta ağırlıklı olarak Microsoft'un C derleyicileri kullanılmaktadır. UNIX/Linux ve macOS sistemlerinde ise çoğu kez "gcc" ya da "clang" derleyicileri tercih edilmektedir. "gcc" derleyicisinin ve clang derleyicisinin Windows versiyonu da vardır. "gcc" derleyicisinin Windows için gerçekleştirimine (Windows port;'una) "mingw" denilmektedir. C programını C derleyicisi ile derledikten sonra eğer hiçbir hata yoksa derleyici "relocatable object module" denilen bir dosya oluşturmaktadır. Bu dosyaya biz Türkçe "amaç dosya" da diyeceğiz. Amaç dosya (relocatable object module) daha sonra "bağlayıcı (linker)" denilen bir programa sokulur. Bu bağlayıcı programı da "çalıştırılabilir (executable)" dosya üretir. Biz de nihayetinde bu dosyayı çalıştırırız. .c ---> C Derleyicisi ---> Amaç Dosya (Object file) ---> Bağlayıcı (Linker) ---> Çalıştırılabilir (executable) Dosya Bağlayıcı (linker da diyeceğiz) aslında bir grup amaç dosyayı alıp tek bir çalıştırılabilir dosya oluşturabilmektedir. Bir amaç dosyanın içerisinde derlenmiş kodların yanı sıra bağlayıcnın birleştirme yapabilmesi için çeşitli bilgiler de vardır. Windows'ta bağlayıcı olarak genellikle Microsoft'un "link.exe" isimli programı kullanılmaktadır. UNIX/Linux sistemlerinde de ağırklıklı olarak GNU'nun "ld" isimli bağlayıcı ya da LLVM projesindeki "ldd" bağlayıcısı kullanılır. Derleyicinin ürettiği amaç dosyanın uzantıları Windows sistemlerinde ".obj", UNIX/Linux ve macOS sistemlerinde ise ".o" biçimindedir. Bağlayıcının ürettiği "çalıştırılabilir" dosya ise Windows sistemlerinde ".exe" uzantılıdır. UNIX/Linux ve macOS sistemlerinde dosyanın çalıştırılabilir olup olmadığı uzantı ile değil dosya erişim hakları ile ("x" hakkı ile) belirlenmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Windows sistemlerinde Merhaba Dünya programının komut satırından derlenip çalıştırışması şöyle yapılır: 1) Program bir editörde yazılır ve ".c" uzantılı biçimde saklanır. Biz bunun "sample.c" olduğunu varsayalım. 2) Daha sonra komut satırı programı çalıştırılır ve dosayanın saklandığı dizine gidilir. Komut satırı programı olarak "cmd.exe" programını doğrudan kullanmayınız. Çünkü bu program gerekli "path ayarlarına" sahip değildir. Bunun yerine komut satırına geçmek için "Developer Command Prompt for VS 2022" programını kullanınız. Bu kısa yolu masaüstüne taşırsanız rahat edersiniz. 3) Microsoft'un C derleyicisi "cl.exe" isimli programdır. Bu program en basit olarak şöyle çalıştırılır: cl Örneğin: cl sample.c "cl.exe" programı da derlemeyi yaptıktan sonra zaten bağlayıcıyı (linker) da kendisi çalıştırmaktadır. 4) Artık "cl.exe" derleme işlemini yapıp bağlayıcı programı da çalıştırdığı için çalıştırılabilir dosya oluşturulmuş olur. Tek yapacağmız şey çalıştırılabilir programın ismini yazarak ENTER tuluna basmaktır. "cl.exe" derleyicisinin yalnızca derleme yapmasını ancak bağlayıcıyı çalıştırmamasını istiyorsak "/c" seçeneğini (switch) kullanmamız gerekir. Örneğin: cl /c sample.c Şimdi artık derleyici linker programını çalıştırmayacaktır. Yalnızca ".obj" dosyayı oluşturacaktır. Biz istersek bağlayıcıyı da bağımsız olarak çalıştırabiliriz. Microsoft'un bağlayıcı programı "link.exe" isimli programdır. link sample.obj Baradan "sample.exe" programı elde edilecektir. cl.exe derleyicisinde çalıştırılabilir dosyanın ismini değiştirebilmek için /Fe: seçeneği kullanılmaktadır. Örneğin: cl /Fe:test.exe sample.c Artık çalıştırılabilir dosyanın ismi "sample.exe" değil "test.exe" olacaktır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Linux sistemlerinde Merhaba Dünya programının derlenerek çalıştırılması da şöyle yapılmaktadr: 1) Yine önce bir editörde program yazılır ve ".c" dosyası olarak kaydedilir. Biz kaynak dosyamıza "sample.c" ismini vermiş olalım. 2) Komut satırından kaynak dosyanın bulunduğu dizine geçilir. gcc derleyicisi ile clang derleyicilerinin komut satırı seçenkleri tamamen aynıdır. Bu derleyicilerle erleme işlemi şöyle yapılır: gcc clang Örneğin: gcc sample.c gcc de tıpkı cl.exe programında olduğu gibi önce derleme işlemini yapar. Sonra bağlayıcı programı çalıştırıp çalıştırılabilen dosyayı oluşturur. gcc derleyicisi derlemeyi bitirip bağlayıcıyı çalıştırdıktan sonra "object dosyayı" silmektedir. Bu biçimde oluşturulan çalıştırılabilen dosya "a.out" ismindedir. Bu dosyanın çalıştırılması şöyle yapılmalıdır: ./a.out Windows sistemlerinde çalıştırılabilir dosyanın yalnızca isminin yazılması yeterlidir. Ancak UNIX/Linux ve macOS sistemlerinde ./isim biçiminde çalıştırma yapılır. gcc derleyicisinde çalıştırılabilir dosyaya isim vermek için "-o isim" çeneği kullanılmaktaır. Örneğin: gcc -o sample sample.c Burada "sample.c" dosyası derlenir ve "sample" isimli çalıştırılabilir dosya oluşturulur. Tabii istersek gcc derleyicilerinde de yalnızca derleme yapıp bağlayıcıyı çalıştırmayabiliriz. Bunun için "-c" seçeneği kullanılmaktadır. Örneğin: gcc -c sample.c Burada derleme işlemi yapılır, "sample.o" object dosyası oluşturulur ancak başlayıcı çalıştırılmaz. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Micrsoft Visual Studio IDE'sinde bir C programının derlenip çalıştırılabilmesi için tipik olarak şunlar yapılmalıdır: 1) Önce Visual Studio IDE'si çalıştırılır. (Kurs yapıldığı sırada kullanılan IDE Visual Studio 2022 Community Edition biçimidedir.) IDE açlıştırıldıktan sonra bir giriş sayfası gözükür. Oradan "Continue without code" seçilerek ana ekrana geçilir. 2) Visual Studio IDE'sinde bir çalışma yapmak için bir proje yaratılmalıdır. Ancal projeler de "çözüm (solution)" denilen kapların içerisindedir. O halde aslında bir proje yaratmak için bir çözüm de yaratılmaktadır. Bir çözüm aslında birdne fazla projeyi tutan bir kap gibidir. Proje yaratmak için "File/New/Project" seçilir. Proje türü olarak "C++ Empty Project" seçilir. 3) Bundan sonra projeye bir isim verilir. Visual Studio proje bilgilerini burada ismi verilen bir dizin yaratarak onun içerisine yerleştirmektedir. "Location" proje dizininin hangi dizinin altında yaratılacağını belirtir. "Place solution and project in the same directory" checkbox'ı çarpılanmalıdır. Sonra proje yaratılır. Artık elimizde içi boş bir proje vardır. Bir proje yaratıldığında aynı zamanda bir "çözüm (solution)" de yaratılmış olur. Çözümü idare etmek için "Solution Explorer" denilen pencereden faydalanılmaktadır. 4) Artık sıra projeye bir kaynak dosya eklemeye gelmiştir. Bu işlem "Project/Add New Item" menüsü ile ya da "Solution Explorer"da proje üzerinde bağlam menüsünü açıp "Add/New Item" seçilerek de yapılabilir. Artık karşımıza başka bir diyalog penceresi çıkacaktır. Burada "C++ File" seçilip dosya ismi uzantısı ".c" olacak biçimde seçilmelidir. Microsoft C++ demekle aynı zamanda C'yi de kastetmektedir. Aslında "cl.exe" derleyicisi hem C hem de C++ derleyicisidir. Bu derleyici kaynak kodun uzantısına bakarak hangi dile göre derleme yapacağına karar verir. Dolayısıyla bizim kesinlikle dosya uzantısını ".c" biçiminde girmemiz gerekir. 5) "Kaynak dosya" projeye eklendikten sonra kod yazılır. 6) Build/Compile seçilirse dosya yalnızca derlenir. "Build" kavramı C/C++ dünyasında "derleme ve bağlama işlemini" anlatmaktadır. "Build/Build Solution" seçilirse solution içerisindeki tüm projeler derlenip link edilir. Build/Build XXX" seçilirse (burada XXX aktif projenin ismidir) bu durumda yalnızca aktif proje derlenerek bağlanır. 7) Programı çalıştırmak için "Debug/Start Without Debugging" seçilir. Bunun kısa yol tuşu "Ctrl + F5"tir. Zaten Ctrl+F5'e bastığımızda dosyada bir değişiklik varsa build işlemi yapılmaktadır. O halde aslında bizim tek yapacağımız şey Ctrl+F5 tuşlarına basmaktır. Projeyi (ve çöüzümü) kapatma işlemi File/Close Solution menüsü ile yapılabilir. Bir projeyi açmanın en kolay yolu giriş ekranındaki "Open recent" listesinden son projelerden birini seçmektir. Dğer yolu "File/Open-Project" menüsünü seçip buradan solution dizininne gelip uzantısı ".sln" olan dosyayı seçmektir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 8. Ders - 16/06/2022-Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C/C++ için çok tercih edilen diğer bir IDE de "Qt Creator" denilen IDE'dir. Buradaki çalışma biçimi ana hatlarıyla Visual Studio'ya benzemektedir. Önce yine bir proje yaratılmalıdır. Bunun için "File/New File or Project" menüsü seçilir. Açılan diyalog penceresinden template olarak "None Qt Project" seçilir. Buradan da "Plain C Application" seçilir. Projeye isim verilir ve projenin yaratılacağı dizin belirtilir. Qt Creator IDE'si bu seöenekle bir ".c" dosyasını projeye ekleyip onun içerisine birkaç satırlık "Merhaba Dünya" programını yazmaktadır. Derleme ve link işlemi ve çalıştırma işlemi tek tuşla (Ctrl+R) yapılabilir. Yine bu işlem GUI ekranındaki çalıştır düğmesine tıklanarak da yapılabilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Kod derleyici tarafından derlenirken derleyiciler bazı sorunlar karşısında "tanı mesajları (diagnositic messages)" ile bu sorunları programcıya iletirler. Derleyicilerin verdiği tanı mesajları üçe ayrılmaktadır: 1) Uyarılar (Warnings) 2) Hatalar (Errors) 3) Ölümcül Hatalar (Fatal Errors) Uyarılar amaç kod oluşumunu engellemeyecek derecede mantıksal hatalar için verilmektedir. Yani derleyici kodu anlamlandırmakla birlike programcının yapmış olabileceği olası bazı mantık hatalarına dikkat çekmek için uyarı vermektedir. Hatalar amaç dosyanın (object file) oluşumunu engelleyecek derecede olan ciddi hatalardır. Genellikle dilin kurallarına uyulmaması yani kodun hatalı bir biçimde yazılması sonucunda oluşur. Programcının mutlaka bu tür hataları düzeltmesi gerekir. Ölümcül hatalar (fatal errors) derleme işleminin bile devam ettirilmesini engelleyecek derecede önemli hatalardır. Normal olarak bir hata ile karşılaşıldığında tüm tanı mesajlarının topluca listelenmesi için derleme işlemine devam edilir. Ancak bir ölümcül hata ile karşılaşıldığında artık derleme işlemi bile sonlandırılır. Ölümcül hatalar genellikle sistemdeki önemli sorunlar yüzünden ortaya çıkmaktadır. Örneğin diskin tamamen dolu olması, derleyicinin işlemine devam edebilmesi için gereken ana belleğin yetersiz olması gibi durumlar tipik ölümcül hata gerekçeleridir. Derleyiciler genellikle tanı mesajllarında ilgili sorunun kaynak kodun neresinde eolduğunu da belirtirler. Pek çok derleyici hata mesajlarına içsel birer numara vermektedir. Bu numara yoluyla tanı mesajı hakkında daha fazla bilgi elde edilebilmektedir. Tanı mesajları standardize edilmemiştir. Derleyiciden derleyiciye tanı mesajlarının içeriği değişebilmktedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir programlama dilinde kendi başına anlamlı olan en küçük birime "atom (token)" denilmektedir. Örneğin aşağıdaki gibi C'de yazılmış bir kod parçası olsun: if (a > 10) x = 10; else y = 20; Bu kodu şöyle atomlarına ayırabiliriz: if ( a > 10 ) x = 10 ; else y = 20 ; Burada koddaki tüm öeğelerin en küçük anlamı birimlere ayrıştırıldığına dikkat ediniz. "Merhana Dünya" programı da şöyle atomlarına ayrılabilir: # include < stdio.h > int main ( void ) { printf ( "Hello World\n" ) ; return 0 ; } Gerçekten de derleyiciler derlemenin ilk aşamasında kaynak kodu bu biçimde atomlarına ayırmaktadır. Derlyicilerin bu işi yapan modüllerine "scanner" ya da "lexical analyzer" ya da "tokenizer" denilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Atomlar tipik olarak 6 gruba ayrılmaktadır: 1) Anahtar Sözcükler (Keywords/Reserved Words): Dil için özel anlamı olan, değişken olarak kullanılması yasaklanmış sözcüklerden oluşan atomlardır. Örneğin "if" gibi, "return" gibi, "int" gibi. Bu sözcükler doğrudan derleyici tarafından tanınmaktadır. 2) Değişkenler (Identifers): İsmini programcının istediği gibi verebildiği atomlardır. Örneğin bir programdaki i, count, a, b gibi atomlar tipik olarak değişken atomlardır. "Merhaba Dümya" programındaki printf ve main anahtar sözcük değildir. Değişken atom statüsündedir. Bir atomun anahtar sözcük olması için derleyicinin onu gördüğünden değişkenden farklı bir işlem uygulaması gerekir. 3) Sabitler (Literals): Bir sayı ya bir değişkenin içerisindedir ya da program içerisinde doğrudan kullanımıştır. İşte program içerisinde doğrudan yazılmış olan sayılara "sabit" denilmektedir. Örneğin: a = b + 10; Burada a ve b değişken atomdur, ancak 10 sabit atomdur. 4) Operatörler (Operators): Bir işleme yol açan ve işlem sonucunda bir değer üretilmesini sağlayan + gibi - gibi, * gibi atomlara operatör denilmektedir. Örneğin: a = b + c * 3 Bu ifadedeki atomlar ve türleri şöyledir: a değişken = operatör b değişken + operatör c değişken * operatör 3 sabit 5) Stringler (String literals): İki tırnak içerisindeki yazılar iki tırnaklarıyla birlikte tek bir atom belirtir. Bunlara "string" denilmektedir. C standartlarında string'ler de sabit atom olarak sınıflandırılmaktadır. 6) Ayıraçlar (Delimiters/Punctuators): Yukarıdaki grupların dışında kalan ifadeleri birbirinden ayırmak için kullanılan tüm atomlar ayıraç grubundadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Değişkenlerin, operatörlerin ve sabitlerin her bir kombinasyonuna "ifade (expression)" denilmektedir. Örneğin: a + b a + b - 2 10 3 * 4 3 - 2 * a foo() a = b * c birer ifadedir. Tek başına bir değişken ve tek başına bir sabit ifade belirtir. Ancak tek başına bir operatör ifade belirtmez. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bellekte yer kaplayan ve erişilebilen bölgelere "nesne (object)" denilmektedir. Programlama dillerindeki değişkenler genellikle nesne durumundadır. Örneğin: a = 10 gibi bir ifadede a bir nesne durumundadır. Biz bu a ismiyle a'nın bellek bölgesine erişebilmekteyiz. Bir olgunun nesne belirtmesi için yalnızca bellekte yer kaplaması yetmez. Aynı zamanada "erişilebilir" olması da gerekir. Örneğin sabitler de bellekte yer kaplarlar. Ancak erişilebilir olmadıkları için nesne değillerdir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de bir ifade ya nesne belirtir ya da nesne belirtmez. Nesne belirten ifadelere "sol taraf değeri (lvalue)", nesne belirtmeyen ifadelere "sağ taraf değeri (rvalue)" denilmektedir. Örneğin: a ifadesi nesne belirtir. Sol taraf değeridir. b[i] ifadesi nesne belirtir, sol taraf değeridir. a + b ifadesi nesne belirtmez, sağ taraf değeridir. 10 ifadesi nesne belirtmez, sağ taraf değeridir. printf("Helo World") ifadesi nesne belirtmez, sağ taraf değeridir. Sol taraf değeri (left value) ismi tipik olarak bu tür iafadelerin atama operatörünün soluna getirilebilmesi nedeniyle verilmiştir. Sağ taraf değeri (right value) atama operatörünün soluna getirilemeyen ifadelere denilmektedir. Tabii bunlar tipik olarak atama operatörünün sağına da getirilirler. Ancak atama operatörünün sağına getirilen her şey sağ taraf değeri değildir. Soluna getirilemeyenler sağ taraf değeridir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Sentaks açıklamak için çeşitli "meta diller (meta languages)" oluşturulmuştur. Bunların en çok kullanılanı "BNF (Backus Naur Form)" denilen notasyondur. BNF notasyonu "EBNF (Extended BNF)" biçiminde ISO tarafından standardize de edilmiştir. Gerçekten de programlama dillerinin standartları genellikle BNF notasyonu ile ya da onun bir türevi ile açıklanmaktadır. Ancak biz kurusumuzda "açısal parantez-köşeli parantez" tekniğini kullanacağız. Bu teknikte açısal parantezler içerisinde öğeler zorunlu öğeleri, köşeli parantezler içerisindeki öğeler "isteğe bağlı (optional)" öğeleri belirtmektedir. Bunların dışındaki tüm atomların belirtilen konumda bulundurulması gerekir. Örneğin if deyimini bu teknikle şöyle ifade edilebiliriz: if () [ else ] ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 9. Ders 21/06/2022-Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Boşluk oluşturmak için kullanılan karakterlere "boşluk karakterleri (white space)" denilmektedir. Boşuk karakterli şunlardır: SPACE (32) LF (Line Feed) (10) CR (Carriage Return) (13) TAB (9) VTAB (11) TAB karakter aslında tek bir karakterdir. Bu karakteri gören editörler imleci belli bir miktarda ilerletirler. Bazı editörler biz TAB tuşuna bastığımızda dosyaya TAB karakter basmaz bunun yerine editörün ayarlarında belirtildiği miktarda SPACE karakteri basar. Bunun nedeni kaynak kod başka bir TAB ayarına ayarlanmış bir editörde açıldığında aynı biçimde gözükmesini sağlamaktır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'nin yazım kuralı iki madde ile özetlenebilir: 1) #'li satırlar hariç atomlar arasında istenildiği kadar boşluk karakterleri bırakılabilir. Örneğin aşağıdaki program geçerlidir: #include int main ( void ) { printf ( "Hello World\n" ) ; return 0 ; } 2) #'li satırlar hariç atomlar istenildiği kadar bitişik yazılabilirler. Ancak anahtar sözcüklerle değişkenler ve sabitler btişik yazılamazlar. Merhaba Dünya programıını aşağıdaki gibi kompakt bir biçimde de yazabilirdik: #include int main(void){printf("Hello World\n");return 0;} Tabii programcının kodunu güzel gözükecek ve iyi okunabilecek biçimde yazması gerekir. C'de çeşitli yazım stilleri vardır. En yaygın kullanılan yazım stili Dennis Ritchie ve Brian Kernighan'ın "The C Programming Language" kitabında uyguladığı yazım biçimidir. Buna "Ritchie Kernighan Tarzı" denilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki Merhaba Dünya programının açıklaması şöyledir: Burada #include satırı "stdio.h" isimli bir dosyanın kaynak koda dahil edildiğini belirtmektedir. Yani bu satır "stdio.h" dosyasının içeriğinin oraya "paste edileceği" anlamına gelmektedir. Başka bir deyişle biz "stdio.h" dosyasını bu komutun bulunduğu yere yerleştirip bu komuttu silsek tamamen aynı durum oluşacaktır. Programda main isimli bir fonksiyon tanımlanmıştır. Bir fonksiyonun tanımlanması onun bizim tarafımızdan yazılması anlamına gelir. Yani bu programda biz main isimli bir fonksiyon yazmış durumdayız. Bir fonksiyonu tanımlamanın (yani yazmanın) genel biçimi şöyledir: [fonksiyonun geri dönüş değerinin türü] ([parametre bildirimi]) { /* ... */ } Fonksiyonun geri dönüş değerinin türü C90'da yazılmayabiliyordu. Bu durumda C90 bunun "int" olarak yazılmış olduğunu varsayıyordu. Ancak C99 ile birlikte geri dönüş değerinin türünün yazılması zorunlu tutulmuştur. main fonksiyonun geri dönüş değerinin türü standartlara göre "int" olmak zorundadır. C'de "main" özel bir fonksiyondur. C programları her zaman "main" isimli fonksiyondan çalışmaya başlar. Programlama dillerinde programın çalışmaya başladığı fonksiyonlara "entry point" denilmektedir. Bir fonksiyonun parametreleri olabilir ya da olmayabilir. Eğer fonksiyonun parametresi yoksa parametre parantezi boş bırakılabilir ya da oraya "void" yazılabilir. Tanımlama sırasında boş bırakmakla "void" yazmak arasında bir fark yoktur. Her fonksiyonun bir ana bloğu olmak zorundadır. C'de iki küme parantezi arasındaki bölgeye "blok (block)" denilmektedir. Bir fonksiyon çalıştırıldığında fonksiyonun ana bloğundaki deyimler sırasıyla çalıştırılır. Ana blok bittiğinde fonksiyon sonlanmış olur. main programı bittiğinde dolayısıyla tüm program sonlanmış olacaktır. Merhaba Dünya programında main fonksiyonun ana bloğunun içerisinde "printf" isimli bir fonksiyon çağrılmıştır. Bir fonksiyonun çağrılması (call) demek onun çalıştırılması demektir. Bir fonksiyon çağrıldığında akış fonksiyona gider, fonksiyonun içerisindeki deyimler tek tek çalıştırılır. Fonksiyon bitince akış çağırma noktasından devam eder. printf fonksiyonu çağrıldığında iki tırnak içerisindeki yazıları ekrana basmaktadır. Ekranda bir imleç (cursor) vardır. Yazı bu imlecin bulunduğu itibaren ekrana yazdırılır. Sonra imleç yazının sonunda bırakılır. İmleç program çalışmaya başladığında sol üst köşededir. printf fonksiyonunda iki tırnak içerisindeki "\n" "imleci aşğı satırın başına geçir" biçiminde özel bir anlama gelmektedir. Yani bundan sonra biz bir daha printf fonksiyonunu çağıracak olsak artık o yazı aşağı satırın başından itibaren yazılacaktır. printf bir standart C fonksiyonudur. Standart C fonksiyonları derleyicileri yazanlar tarafından yazılmış (tanımşlanmış) biçimde bulunan fonksiyonlardır. maşn fonksiyonun sonundaki return deyimi bulunmak zorunda değildir. Bu deyim ileride açıklanacktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { printf("Hello World\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'nin C90, C99, C11 ve C17 standartlarının olduğunu belirtmiştik. Derleyiciler genel olarak bu standartlara uygun olacak biçimde derleme yapabilmektedir. Visual Studio IDE'sinde derleyicinin uygulayacağı standardı ayarlamak için proje seçeneklerine gelinir. C-C++/Language/C Language Standart sekmesinden standart belirlenir. Biz kursumuzda burada "ISO C17" standardını aktif hale getireceğiz. Aynı şey Qt_Creator IDE'sinde PRO dosyasının içerisine aşağıdaki satırın eklenmesiyle yapılabilmektedir: CONFIG += c17 gcc ve clang derleyicilerinde komut satırında derleme yaparken -std=c90, -std=c99, -std=c11, -std=c17 seçenekleriyle derleme standardı ayarlanabilir. Örneğin: gcc -std=c17 -o sample sample.c Ayrıca Microsoft derleyicilerinde proje seçeneklerinden C-C++ sekmesinde "SDL Checks" "No" yapılarak kapatılmalıdır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Programlama dillerinde "tür (type)" bir nesnenin bellekte kapladığı alanı, onun içerisindeki 1'lerin ve 0'ların nasıl yorumlanacağını, o nesnenin hangi operatörlerle işleme sokulabileceğini belirten önemli bir bilgidir. C'de her nesnenin ve her ifadenin bir türü vardır. Türler çeşitli anahtar sözüklerle ifade edilirler. Aşağıda temel türleri açıklanmaktadır: [signed] int: Bu tür işaretli bir tamsayı türüdür. int türünün kaç byte yer kaplayacağı standartlarda derleyicileri yazanların isteğine bırakılmıştır. Ancak standartlara göre int türü minimum 2 byte olmalıdır. Buügn 32 bie ve 64 bir Windows ve UNIX/Linux ve macOS sistemlerindeki derleyicilerde int türü 4 bye (32 bit) uzunluktadır. Dolayısıyla int türden bir nesne bu sistemlerde [-2147483678, 2147483647] aralığında tamsayı değerler tutabilir. Bazı mikrodenetleyici derleyicilerinde ise int 2 byte (16 bit) uzunluğunda olabilmektedir. Derleyicileri yazanlar genellikle int türünü o sistemdeki CPU yazmaçlarının uzunluğu kadar ya da o uzunlukla ifade edilebilecek kadar almaktadır. Bu tür belirtilirken "int" demekle "signed int" demek arasında ya da "int signed" demek arasında bir fark yoktur. [unsigned] int: Her işaretli tamsayı türünün bir de işaretsiz biçimi vardır. "signed int" türünün işaretsiz biçimi "unsigned int" türüdür. Tamsayı türlerinin işaretli biçimleri ile işaretsiz biçimleri aynı miktarda yer kaplarlar. Aralarındaki tek fark işaret bitinin yorumudur. Dolayısıyla bu tür de 32 bir ve 64 bit UNIX/Linux ve macOS sistemlerinde 4 byte yer kaplamaktadır. unsigned int türünden bir nesne içerisine bu sistemlerde yerleştirilebilecek sayı sınırı [0, +4294967295] biçimindedir. Bu türü biz "unsigned" biçiminde ya da "unsigned int" biçiminde ya da "int unsigned" biçiminde ifade edebiliriz. [signed] long [int]: long türü int türünden uzun olabilir ya da int türüyle aynı uzunlukta olabilir. Ancak int türünden daha kısa olamaz. Standratlara göre long türü en az 4 byte (32 bit) uzunlukta olmak zorundadır. long türü de "işaretli" bir tamsayı türüdür. Buradaki "long" ismi "int türünden uzun olabilen" anlamına gelmektedir. 32 bit ve 64 bit Windows sistemlerindeki derleyicilerde long türü int türüyle aynı uzunluktadır (yani 4 byte). Ancak 32 bit UNIX/Linux ve macOS sistemlerindeki derleyicilerde long türü 4 byte iken, 64 bir UNIX/Linux ve macOS sistemlerindeki derleyicilerde long türü 8 byte (64 bit) uzunluğundadır. long türünü biz en kısa biçimde "long" olarak ifade edebiliriz. Ancak "signed long int", "long int", "signed int long" gibi anahtar sözcleri yer değiştirerek de ifade edebiliriz. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Hocam 10. Ders 23/06/2022-Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- unsigned long [int]: Bu tür long türünün işaretsiz biçimidir. Dolayısıyla sistemlerde long türüyle aynı uzunlukta yer kaplar ancak sayının başındaki bit işaret biti olarak ele alınmaz. 32 ve 64 bit Windows Sistemlerinde ve 32 bit UNIX/Linux ve macOS sistemlerinde bu tür long türünde olduğu gibi 4 byte (yani 32 bit) yer kaplamaktadır. Dolayısıyla bu sistemlerdeki sınıfı [0, +4294967295] biçimindedir. [signed] short [int]: Bu tür int türünden küçük olabilen ya da int türü ile aynı uzunlukta olabilen işaretli bir tamsayı türüdür. Standartlara göre short türü en az 2 byte (yan 16 bit) olmak zorundadır. 32 bit ve 64 bit Windows ve UNIX/Linux ve macOS sistemlerinde short türü 2 byte (yani 16 bit) uzunluktadır. Dolayısıyla bu sistemlerde bu türden bir nesnesinin içerisine biz [-32768, +32767] sınırları içerisinde bir tamsayı yerleştirebiliriz. unsigned short [int]: Bu tür signed short türünün işaretsiz biçimidir. Dolayısıyla short türü kadar yer kaplar. short türünün 2 byte olduğu sistemlerde bu türden bir nesnenin içerisine biz [0, +65535] arasında tamsayı değerler yerleştirebiliriz. signed char: C standartlarında "byte" lafı pek az yerde kullanılmıştır. Çünkü "byte" genellikle 8 bit için kullanılan bir terimdir. Oysa bazı bilgisayar sistemlerinde RAM'deki adreslenebilen birimler 8 bit değil 10, 11 bit değerlerde olabilmektedir. Tabii bu sistemler sonderece seyrektir. Ancak standartlar bu uç durumu da dikkate almaktadır. C standartlarında "char" terimi "RAM'de adreslenebilen en küçük birimin büyüklüğünü" temsil etmektedir. Örneğin tipik olarak eğer adreslenebilen en küçük birim 8 bit ise char 8 bittir. Ancak 10 bit ise char 10 bittir. Görüldüğü gibi eğer C standartlarında char yerine byte terimi kullanılsaydı byte 8 bit olduğu için bu uç durum temsil edilemeyebilirdi. Tabii bugünkü sistemlerin %99.9'unda adreslenebilen en küçük birim 8 bittir. Dolayısıyla yaygın sistemlerin hepsinde gerçekten char türü 8 bit yani bir byte uzunluğundadır. Zaten C standartlarında "byte" terimi tamamen bit uzunluğu farklı olabilen yani "adreslenebilen en küçük birim" anlamında kullanılmaktadır. Başka bir dyişle bu tanımla "char" ile "byte" aynı anlamdadır. Ayrıca standartlar ilgili sistemdeki adreslenebilen en küçük birimdeki bit sayısının kaç bit olduğunu (yani char türünün kaç bitten oluştuğunu) dosyası içerisinde CHAR_BITS sembolik sabitiyle derleyicinin belirtmesini zorunlu tutmaktadır. Mademki char türü yaygın sistemlerin hepsinde 8 bitten oluşmaktadır. O halde signed char türünün de bu sistemlerdeki sınırları [-128, +127] arasındadır. Özetle signed char bit byte'lık işaretli tamsayı türünü belirtmektedir. unsigned char: Bu tür char türünün işaretsiz biçimidir. Dolayısıyla bu türün de bellekte kapladığı alan ilgili sistemdeki adreslenebilen en küçük birimin bit uzunluğu kadardır. 8 bitlik yaygın sistemlerde unsigned char türünden bir nesneye [0, +255] arasında değerler yerleştirilebilir. char: Yalnızca char denildiğinde bunun "signed char" mı yoksa "unsigned char" mı anlamına geleceği C standartlarında derleyicileri yazanların isteğine bırakılmıştır. Microsoft C derleyicileri, gcc ve clang derleyicileri char türünü default olarak "signed char" kabul etmektedir. Fakat başka derleyiciler "unsigned char" kabul edebilirler. Aslında Microsoft derleyicilerinde, gcc ve clang derleyicilerinde char denildiğinde default durum derleyici ayarlarından da değiştirilebilmektedir. her ne kadar char türü ilgili sistemde "signed char" ya da "unsigned char" anlamına geliyorsa da "char", "signed char" ve "unsigned char" ne olursa olsun farklı türler gibi değerlendirilmektedir. Bunun önemi başka konularda ortaya çıkacaktır. [signed] long long [int]: Bu tür C99 ile birlikte standartlara dahil edilmiştir. Dolısıyla C90 uyumlu eski C derleyicilerinde bu türü kullanamayabilrsiniz. long long tütü long türünden uzun ya da long türüyle aynı uzunlukta olabilen işaretli bir tamsayı türüdür. Standartlarda minimum 8 byte (yani 64 bit) olabileceği belirtilmiştir. Şu andaki yaygın derleyicilerin hepsinde long long türü 8 byte uzunluktadır. 8 byte uzuluk için long long türünden bir nesneye yerleştirilebilecek sayı sınırı [-9223372036854775808, +9223372036854775807] (katrilyar mertebesinde, 8 exabyte) biçimindedir. unsigned long long [int]: Bu tür de long long türünün işaretsiz biçimidir. Dolayısıyla yaygın sistemlerin hepsinde 8 byte (yani 64 bit) uzunluktadır. unsigned long long türünden bir nesneye yerleştirilecek sayı sınırı da [0, +18446744073709551615] (16 exabyte) biçimindedir. Yukarıdaki tüm türlere C'nin tamsayı türleri denilmektedir. C'de ayrıca üç tane de gerçek sayı (noktalı sayı) türü vardır: float, double ve long double. Gerçek syaı türlerinin işaretli ve işaretsiz biçimleri yoktur. Bunlar zaten doğuştan işaretlidir. float: Bu tür 4 byte uzunluğunda gerçek sayı türüdür. Her ne kadar standartlar kullanılacak gerçek sayı formatını açıkça belirtmiş olmasa da "Implementation Limits" kısmında gerçek sayı türleri için belirtilen limitler "IEE 754" standardını ima etmektedir. Bu durumda float türü hemen her sistemde 4 byte uzunluktadır. float türünün yuvarlama hatalarına direnci zayıftır. Bu nedenle float türü aslında C programcıları tarafından az tercih edilen bir gerçek sayı türüdür. double: Standartlara göre double türü float türü ile aynı olabilir ya da ondan daha duyarlıklı olabilir. Yaygın sistemlerin büyük çoğunluğunda double türü 8 byte uzunluktadır ve ""IEEE 754 LLong Real Format" biçiminde temsil edilmektedir. Ancak bazı mikrodenetleyici derleyicilerinde double türü float ile tamamen aynı uzunlukta olabilmektedir. C prograöcılarının en fazla tercih ettiği gerçek sayı türü double türüdür. Çünkü bu türün yuvarlama hatalarına direnci float türünden çok daha iyidir. long double: long double standartala göre double ile aynı duyarlılıkta ya da double türünden daha duyarlıklı olabilen bir türdür. Bugün Microsoft C derleyicilerinde, gcc ve clang derleyicilerinde long double türü tamamen double türüyle aynı özelliktedir. Yani bu tür de bu derleyicilerde "IEEE 754 Long Real Format" biçiminde ifade edilmektedir. Fakat bazı derleyicilerde (Örneğin eski Borland firmasının C derleyicilerinde) long double 10 byte'lık "IEEE 754 Extended Real Format" biçiminde de derleyiciler tarafından alınabilmektedir. Bir C derleyicisinde aslında "float", "double" ve "long double" türlerinin hepsi 4 byte uzunlukta olabilir. C99 ile birlikte C'ye ikil değerler tutmak için _Bool isminde yeni bir tür daha eklenmiştir. (Bu tür isminin bu biçimde size tuhaf gelecek şekilde isimlendirilmesinin amacı geçmişe doğru uyumu koruyabilmek içindir. C99 çıktğında "bool" gibi bir ismi programcılar kendi programlarında kullanmış olabileceklerinden dolayı bu türü temsil etmek için "reserved" isimlerden biri tercih edilmiştir. C'de başı '_' ile başlayan ve ilk harfi büyük harf olan isimlerin zaten kullanılması yasaklanmış durumdaydı.) _Bool türü için standartlar 0 ve 1 değerlerini tutabilen bir büyüklükte olması gerektiğini belirtmişlerdir. Dolayısıyla _Bool türü aslında herhangi bir tamsayı türünün uzunluğu kadar olabilir. Tabii derleyiciler bu türden nesneler için genel olarak 1 byte yer ayırmaktadır. _Bool türü dosyası içerisinde "bool" ismiyle de typedef edilmiştir. Dolaysıyla programcı isterse başlık dosyasını include edip _Bool yerine bool ismini de kullanabilir. Genellikle bool türünün olduğu diğer programlama dillerinde "true" ve "false" biçiminde anahtar sözcükler de bulundurulmaktadır. Ancak C99'da bu biimde anahtar sözcüler yoktur. Ancak içerisinde "true" 1 olarak, "false" 0 olarak define edilmiştir. Dolayısıyla eğer dosyası include edilirse "true" ve "false" sözcükleri 1 ve 0 yerine kullanılabilir. Son olarak C99 ile birlikte C'ye karmaşık sayı (complex number) türü de eklenmiştir. Karmaşık sayı belirtmek için _Complex tür ismi anahtar sözcük olarak dile eklenmiştir. Ancak _Complex tek başına kullanılamaz. float, double ve long double tür isimleriyle birlikte kullanılabilir. Yani C99 ile birlikte üç karmaşık sayı türü dile eklenmiş durumdadır: float _Complex double _Complex long double _Complex Karmaşık sayılar gerçek ve sanal kısımları float olan, double olan ve long double olan iki bileşenli sayılardır. Karmaşık sayı için "i" sembolü C99'da _COMPLEX_I anahtar sözcüğü ile temsil edilmiştir. Dolaysyıyla örneğin double _Complex türünden bir z değişkenine biz 3.2 + 2.4i değerini şöyle atarız: z = 3.2 + 2.4 * _COMPLEX_I Ayrıca yazım kolaylığı için dosyası içerisinde _COMPLEX anahtar sözcüğü "complex" ismiyle typedef edilmiştir. Yani biz eğer dosyasını include edersek _COMPLEX yerine complex sözcüğünü de kullanabiliriz. Benzer biçimde içerisinde "I" isimli sembolik sabit de _COMPLEX_I olacak biçimde define edilmiştir. Yani biz dosyasını include etmiş isek i sayısı için _COMPLEX_I yerine I harfini de kullanabiliriz. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de bu kadar çok tür varken aslında programcılar özel bir neden olmadıktan sonra tamsayı türü olarak hep "int" türünü, gerçek sayı türü olarak da "double" türünü tercih ederler. C programcısı bir değişkenine içerisine küçük tamsayı değerleri yerleştirecek olsa bile o dğeişkeni char, short olarak değil yine int olark tanımlarlar. Fakat örneğin bir nicelik "int" türünün sınırları içerisine sığmıyorsa daha büyük türler seçilmelidir. int türünden küçük türler programcılar tarafından tekil nesneler için değil büyük diziler için tercih edilmektedir. Örneğin bir kişinin yaşını bir değişkende tutacak olalım. Biz yine bu ndeğişkeni int türden almalıyız. Ancak bir milyon kişinin yaşını tutacaksak artık bu bir milyonluk diziyi int türünden değil char türünden oluşturabiliriz. Aynı durum double türü için de geçerlidir. Programcı ancak çok miktarda noktalı sayıyı tutacaksa float türünü tercih etmelidir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 11. Ders 28/06/2022-Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C/C++, Java, C# gibi statik tür sistemine sahip programlama dillerinde bir değişkeni kullanmadan önce onun derleyiciyte tanıtılması gerekir. İşte değişkenlerin kullanılmadan önce derleyiciye tanıtılması işlemine "bildirim (declaration)" denilmektedir. Bildirim işleminin basit genel biçimi şöyledir: ; Buradaki değişken listesi aralarına ',' atomu getirilmiş bir ya da birden fazla değişkenden oluşabilir. Örneğin: int a; long b, c, d; double weight; Aslında bildirim işleminin genel biçimi biraz daha ayrıntılıdır. Burada biz basit bir genel biçim verdik. C "büyük harf küçük harf duyarlılığı olan (case sensitive)" bir programlama dilidir. Yani C'de değişken isimlendirmesinde büyük harflerle küçük harfler farklı karakterler olarak ele alınırlar. Değişken isimlerndirmesinde pek çok programlama dilindeki benzer kuralalr geçerlidir: - Değişken isimleri nümerik karakterlerle başlatılamaz. Ancak alfabetik karakterlerle başlatılıp nümerik karakterlerle devam ettirilebilir. - Değişken isimleri boşluk içeremez, operatör içeremez. - Değişkenler anahtar sözcüklerdne oluşturulamazlar. - Alt tire karakteri alfabetik karakter gibi kullanılabilmektedir. - Değişken isimlerindeki karakterler İnglizce küçük harfler, İngilizce büyük harfler ve alt tire karakterleri ve sayısal karakterler olabilir. Türkçe gibi diğer dillerin karakterlerinin kullanılıp kullanılmayacağı derleyicileri yazanların isteğine bırakılmıştır. Yani bazı derleyiciler örneğin Türkçe karakterlerden oluşan isimleri kabul ederken bazı derleyiciler etmeyebilirler. - Değişkenlerdeki karakter sayısı istenildiği kadar uzun olabilir. Ancak derleyiciler uzun değişken isimlerinin ilk n karakterini dikkate alırlar. Bu n değeri derleyiciden derleyiciye değişebilmektedir. Bugün kullanılan yaygın derleyiciler en az 256 karakteri desteklemektedir. Bu zaten oldukça uzundur. C'de başı iki alt tire ile başlayan ve başı bir alt tire ve büyük harfle başlayan tüm isimler "reserved" yapılmıştır. Programcıların bu biçimde isimlendirme yapmaması gerekir. Eğer programcılar böyle isimlendirme yaparsa kodları derlenir ancak çalışma sırasında olumsuzluklar gözükebilir. (Bu duruma "tanımsız davranış (undefined behavior)" denilmektedir. Bu kavram ileride açıklanacaktır.) Başı tek alt tire ile başlayan tüm isimler global faaliyet alanında "reserved" yapılmıştır. (Yani örneğin biz _count gibi bir ismi global değişken olarak kullanamayız. Ancak yerek değişken olarak kullanabiliriz. Global ve yerel faaliyet alanları konusu ileride ele alınacaktır.) C'de ağırlıklı bir biçimde küçük harfli isimlendirmeler tercih edilmektedir. Değişken isimlerinin anlamlı ve telefaffuz edilebilir olması tavsiye edilir. Birden çok sözcükten oluşan değişken isimlerinde sözcüklerin ayrımsanması için üç harflendirme biçimi kullanılmaktadır: 1) Klasik C Tarzı Harflendirme: Buna "yılan notasyonu (snake casting)" de denilmektedir. Burada sözcüklerin arasında alt tire bulundurulur. Örneğin: number_of_students total_count weight 2) Deve Notasyonu (Camel Casting): Burada ilk sözcüğün tamamı küçük harflerle yazılır. Ancak sonrakşi sözcüklerin yalnızca ilk harfleri büyük yazılır. Örneğin: numberOfStudents totalCount weight 3) Pascal Notasyonu (Pascal Casting): Burada da her sözcüğün ilk harfi büyük yazılır. Örneğin: NumberOfStudents CreateWindow SetWindowText Sample Biz kursumuzda ağırlıklı olarak klasik C tarzı yazımı (yılan notasyonu) kullanacağız. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir bildirim işlemiyle eğer derleyici bildirilen değişken için bellekte yer ayırıyorsa o bildirime aynı zamanda "tanımlama (definition)" da denilmektedir. Örneğin: int a; Bu bir bildirimdir. Ama aynı zamanda tanımlamadır. Çünkü derleyici bu bildirimde bildirilen a değişkeni için aynı zamandabellekte yer ayırmaktadır. Her tanımalam bir bildirimdir ancak her bildirim bir tanımlama değildir. Tabii bildirim olup da tanımlama olmayan durumlar seyrektir. Biz aksi söylenmediği sürece "bildirim" dediğimizde bildirilen değişken için yer de ayrıldığını varsayacağız. Bildirim olup datanımlama olmayan durumları özel olarak konular içerisinde vurgulayacağız. Örneğin: int a; /* bu hem bir bildirimdir hem de bir tanımlamadır */ extern int b; /* bu bir bildirimdir ama tanımlama değildir, tabii extern gibi bir konu henüz görülmedir */ ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir değişkene bildirim sırasında değer atayabiliriz. Bu işleme "ilkdeğer verme (initializtion)" denilmektedir. Örneğin: int a = 10, b, c = 20; Burada a ve c değişkenlerine ilkdeğer verilmiştir. Ancak b değişkenine ilkdeğer verilmemiştir. İlkdeğerverme ile değişkene ilk kez değer atama aynı şey değildir. Örneğin: int a; a = 10; Buradaki işlem bir ilkdeğer verme değildir. İlkdeğer verme bildirim sırasında değer atama anlamına gelmeketedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Aslında C standartlarında "ekran" ve "klavye" lafları hiç geçmemektedir. Örneğin C standartları printf fonksiyonun "ekrana yazdığını" söylememektedir. Standartlara göre printf fonksiyonu "stdout" denilen bir dosyaya yazmaktadır. Ancak bu dosya bir aygıta yönlendirilmiş olabilir. Örneğin klasik bilgisayar sistemlerimizde stdout ekranı kontrol eden terminal aygıt sürücüsüne yönlendirilmiş durumdadır. Dolayısıyla printf stdout dosyasına yazar ancak yazılanlar ekranda görülür. Tabii bir sistemde stdout başka aygıtlara ya da dosyalara yönlendirilmiş olabilir. Örneğin stdout seri porta yönlendirilmişse artık printf fonksiyonun yazdıkları seri porta aktarılır. Aynı durum klavye için de geçerlidir. Aslında klavyeden okumak diye bir şey yoktur. stdin dosyasından okumak diye bir şey vardır. stdin dosyası da klasik bilgisayar sistemlerinde genellikle klavyeye yönlendirilmiştir. Ancak örnğin stdin seri porta yönlendirilmişse artık seri porttan gelen bilgiler okunur. Görüldüğü gibi "stdout" ve "stdin" aslında değişik kaynakları temsil ediyor olabilir. Biz kurusumuzda bazen "ekran" ve "klavye" laflarını edeceğiz. Burada tenik olarak "stdout" ve "stdin" dosyaları anlaşılmalıdır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- printf aslında oldukça kapsamlı bir fonksiyondur. printf fonksiyonunda iki tırnak içerisindeki karakterler ekrana (yani stdout dosyasına) basılır. Ancak printf % karakterini gördüğünde % karakterini ve onun yanındaki bir ya da iki karakteri ekrana yazdırmaz. % karakterinin yanındaki bazı özel karakterlere format karakterleri denilmektedir. % karakteri ve format karakterleri "yer tutucu" belirtir. Bu yer tutucular printf'in iki tırnak argümanından sonraki argümanlarla sırasıyla eşleştirilmektedir. Böylece aslında format karakterleri değil de bu argümanların değerleri yer tutucu yerine yazdırlır. Örneğin: int a = 10, b = 20; printf("a = %d, b = %d\n", a, b); Burada %d yer tutucudur. İlk %d yerine a'nın değeri, ikinci %d yerine b'nin değeri yazdırılır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 12. Ders 30-06-2022 ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Format karakterleri eşleşen argümanın türünü ve yazdırma için kullanılan tabanı belirtecek biçimde farklılık göstermektedir. En çok kullanılan format karakterleri şunlardır: %d ---> signed char, short ve int türlerini 10'luk sistemde yazdırmak için %x ---> işaretli ve işaretsiz char, short ve int türlerini 16'lık sistemde yazdırmak için %o ---> işaretli ve işaretsiz char, short ve int türlerini 8'lik sistemde yazdırmak için %ld ---> long türünü 10'luk sistemde yazdırmak için %lld ---> long long türünü 10'luk sistemde yazdırmak için %lx ---> long türünü 16'lık sistemde yazdırmak için %llx ---> long long türünü 16'lık sistemde yazdırmak için %lo ---> long türünü 8'lik sistemde yazdırmak için %llo ---> long long türünü 8'lik sistemde yazdırmak için %u ---> unsigned char, unsigned short ve unsigned int türlerini 10'luk sistemde yazdırmak için %lu ---> uunsigned long türünü 10'luk sistemde yazdırmak için %f ---> float ve double türlerini 10'luk sistemde yazdırmak için (default durumda noktadan sonra 6 basamak yuvarlanarak yazdırılır) %e ---> float ve double türlerini üstel niçimde yazdırmak için. %c ---> char, short ve int türlerini karakter görüntüsü olarak yazdırmak için ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a = 97; double b = 12.45; unsigned int c = 32503456; int d = 100; double e = 12000.345678; printf("%d - %c\n", a, a); /* 97 - a */ printf("%f\n", b); /* 12.450000 */ printf("%u\n", c); /* 32503456 */ printf("%x\n", d); /* 64 */ printf("%e\n", e); /* 1.200035e+04 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- printf fonksiyonunda % karakterinden sonra fakat format karakterinden önce bir sayı belirtilirse ilgili argüman o sayı ile belirtilen genişlikte bir alan ayrılarak o alanda yazılır. Default durum sağa dayalı olarak yazdırılmasıdır. Solay dayalı yazdırmak için bu genişlik beliritlen sayının önüne ayrıca bir de '-' karakteri eklenir. Özellikle sütunsal hizalamalar için %-nd gibi (buarada n yerine bir sayı geitirilmelidir) format karakterleri kullanılmaktadır. Eğer genişlik belirten sayı yazdırılacak sayının basamak sayısından az ise sayının hepsi yazdırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include #include main(void) { int a = 10; int b = 7; int c = 121; int d = 1234567; printf("%-20d%f\n", a, sqrt(a)); printf("%-20d%f\n", b, sqrt(b)); printf("%-20d%f\n", c, sqrt(c)); printf("%-20d%f\n", d, sqrt(d)); return 0; } /*---------------------------------------------------------------------------------------------------------------------- float ve ouble sayılarda sayının toplam genişliği ve noktadan sonraki kısmın genişliği ayrı ayrı belirtilebilmektedir. Örneğin "%10.2f" toplam 10 alan içerisinde sayı noktadan sonra iki basamak olacak biçimde yazdırılır. Burada yalnızca noktanın sağ tarafının kaç basamak yazdırılacağı da belirtilebilir. Örneğin "%.3f" sayının tam kosmının tam olarak yazılacağı ancak noktadan sonraki kısmın üç basamak biçiminde yuvarlanarak yazdırılacağı anlamına gelir. ----------------------------------------------------------------------------------------------------------------------*/ #include main(void) { double a = 12.346; printf("___%10.2f___\n", a); /*___ 12.35___*/ printf("___%-10.2f___\n", a); /*___12.35 ___*/ printf("___%.10f___\n", a); /*___12.3460000000___*/ printf("___%.0f___\n", a); /*___12___*/ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de bildirimler üç yerde yapılabilir: 1) Fonksiyonların içerisinde. Fonskiyonların içerisinde bildirilen değişkenlere "yerel değişkenler (local variables)" denilmektedir. 2) Fonksiyonların dışında. Fonksiyonların dışında bildirilen değişkenlere "global değişkenler (globaş variables)" denilmektedir. 3) Fonksiyonların parametre parantezleri içerisinde. Fonksiyonların parametre parantezleri içerisinde bildirilen dğeişkenlere "parametre değişkenleri (parameters)" deni,lmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include int x; /* global deişken */ void foo(int n) /* Parametre değişkeni */ { int a; /* yerel değişken */ /* ... */ } int y; /* global değişken */ int main(void) { int b; /* yerel değişken */ /* ... */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de eküme parantezleri arasındaki bölgeye "block (block)" denilmektedir. Bir fonksionun ana bir bloğu olma zorundadır. Ancak o ana bloğun içeriside istenildiği kadar çok iç içe ya da ayrık blok bulundurulabilir. Örneğin: void foo(void) { ... { ... } { ... { ... } } .... } C90'da yerel değişkenler blokların başında bildirilmek zorundaydı. Burada blokların başı demekle blokların ilk işlemi olacak biçimde bildirim yapma kastedilmektedir. Ancak bu kural C99 ve ötesinde değiştirilmiştir. C99 ve ötesinde yerel değişkenler blokların herhangi bir yerinde bildirilebilirler. Örneğin: int main() { printf("this is a test\n"); int a; /* C90'da geçersiz! C99 ve ötesinde geçerli */ { int b; /* C90'da da geçerli */ printf("this is a test\n"); } int c; /* C90'da geçersiz! C99 ve ötesinde geçerli */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de bir tamsayı 10'luk, 16'lık ve 8'lik sistemde bir sabit biçiminde belirtilebilmektedir. Default sistem 10'luk sistemdir. Ancak bir tamsayı 0x ile ya da 0X ile başlanarak yazılırsa bu durumda sayının 16'lık sistemde yazılmış olduğu kabul edilir. Eğer bir sayı başına 0 getirilerek yazılırsa bu da sayının 8'lik sistemde yazılmış olduğu anlamına gelir. Örneğin: 100 (onluk sistemde 100) 0x64 (16'lık sistemdeki 64 yani 10'luk sistemde 100) 012 (8'lik sistemde 12 yani 10'luk sistemde 10) Tabii biz tamsayı değeri kaçlık sistemde yazarsak yazalım aslında bellekte her zaman bu sayı ikilik sistemde tutulmaktadır. C'de bir tamsayıyı ikilik sistemde yazmanın bir yolu yoktur. ----------------------------------------------------------------------------------------------------------------------*/ #include main(void) { int a; a = 100; printf("%d\n", a); /* 100 */ a = 0x64; printf("%d\n", a); /* 100 */ a = 012; printf("%d\n", a); /* 10 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de bir noktalı sayı üstel biçimde de yazılabilir. Bunun için sayıdan sonra 'e' ya da 'E' karakteri ve üs sayısı belirtilir. Buradaki üs 10'un kaçıncı kuvveti olduğunu belirtmektedir. Örneğin: a = 1.23e20; b = 1.23E-12 ----------------------------------------------------------------------------------------------------------------------*/ #include main(void) { double a; a = 123.456e5; printf("%f\n", a); /* 12345600.000000 */ a = 1.23e-5; printf("%f\n", a); /* 0.000012 */ a = 1e20; printf("%f\n", a); /* 100000000000000000000.000000 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Doğrudan yazılan sayılara "sabit (literal)" denilmektedir. C'de yalnızca değişkenlerin değil sabitlerin de türleri vardır. Bir sabitin türü onun nasıl yazıldığına ve niceliğine bakılarak belirlenmektedir. Bir C programcısının da bir sabiti gördüğünde onun türünü tespit edebilmesi gerekir. 1) Sayı nokta içermiyorsa ve sonunda bir ek de yoksa eğer sayı 10'luk sistemde yazılmışsa sırasıyla sayı "int", "long" ve ""long long" türlerinin hangisinin içerisinde ilk kez kalıyorsa sabit o türdendir. Örneğin: 0 int türden sabit 123 int türden sabit -123 Bu bir sabit değildir. Burada sabit olan 123'tür. Sayının başındaki '-' bir operatördür. Şimdi çalıştığımız sistemde int ve long türünün 4 byte ancak long long türünün 8 byte olduğunu varsayalım. Bu durumda: 3000000000 long long türden sabit 10000000000000 long long türden sabit 2) Sayı nokta içermiyorsa sonunda da ek yoksa ancak 16'lık sistemde ya da 8'lik sistemde yazılmışsa sayı sırasıyla "int", "unsigned int", "long", "unisgned long", "long long" ve "unsigned long long" sınırlarının hangisinin içerisinde ilk kez kalıyorsa sabit o türdendir. Çalıştığımız sistemde int ve long türünün 4 byte ancak long long türünün 8 byte olduğunu varsayalım. 0x10 int türden sabit 0xFC123478 unsigned int türden sabit 0x321223123123; long long türden sabit 3) Sayı nokta içermiyorsa ancak sayının sonunda ona yapışık bir biçimde 'u' ya da 'U' eki varsa sayı 10'luk, 16'lık, 8'lik sistemde yazıldığında sabit sırasıyla "unsigned int", "unsigned long int" ve "unsigned long long int" türlerinin hangisinin sınırları içeirsinde ilk kez kalıyorsa sabit o türdendir. Örneğin: 123U unsigned int türünden sabit 0u unsigned int türdne sabit 30000000000000U unsigned long long türünden sabit 0x1234u unsigned int türünden sabit 01234U unsigned int türünden sabit 4) Sayı nokta içermiyorsa ve sayının sonunda onunla yapışık bir biçimde 'l' ya da 'L' harfi varsa sayı 10'luk sistemde yazılmışsa sabit "long" ve "long long" türlerinin hangisinin sınırları içerisinde ilk kez kalıyorsa o türdendir. Örneğin: 1L long türden bir sabit 1234567890123L long long türden sabit 5) Sayı nokta içermiyorsa ve sayının sonunda onunla yapışık bir biçimde 'l' ya da 'L' harfi varsa sayı 16'lık ya da 8'lik sistemde yazılmışsa sabit "long", "unsigned long" "long long" ve "unsigned long long" türlerinin hangisinin sınırları içerisinde ilk kez kalıyorsa sabit o türdendir. Örneğin: 0x12L long türden sabit 0123L long türden sabit 6) Sayı nokta içermiyorsa ve sayının sonunda onunla yapışık "ul ya da lu" varsa ('u' ya da 'l' ler büyük ya da küçük olabilir) sayı 10'luk sistemde, 16'lık sistemde ya da 8'lik sistemde yazıldığında sabit sırasıyla "unsigned long" ve "unsigned long long" sınırlarının hangisinin içerisinde ilk kez kalıyorsa o türdendir. Örneğin: 12LU unsigned long int türden sabir 1234567890123ul unsigned long long türden sabit 7) Sayı nokta içermiyorsa ve sayının sonunda "ll" ya da "LL" soneki varsa sayı 10'luk sistemde yazıldığında "long long" türden sabit belirtir. Örneğin: 1LL long long türden sabit 100ll long long türden sabit 8) Sayı nokta içermiyorsa ve sayının sonunda "ll" ya da "LL" soneki varsa ve sayı 16'lık ya da 8'lik sistemde yazılmışsa "long long" ve "unsigned long long" türlerinin hangisinin sınırları içerisinde ilk kez kalıyorsa sabit o türdendir. Örneğin: 0x12LL long long türden sabit 9) Sayı nokta içermiyorsa ve sayının sonunda "ull" ya da "llu" "soneki varsa (burada 'u' ve "ll" büyk harf ya da jüçük harf olablir) bu durumda sabit unsigned long long türündendir. Örneğin: 1uLL unsigned long long türdne sabit 10) Sayı nokta içeriyorsa ve sayının sonunda bir ek yoksa sabit double türdendir. Örneğin: 1.2 double türden sabit 0.2 double türden sabit Noktanın solunda bir şey yoksa ve noktanın sağında bir şey yoksa orada 0 olduğu kabul edilmektedir. Bu Fortran zamanından beri kullanılan bir gelenektir. Örneğin. .12 double türden sabit, 0.12 ile aynı anlamda 12. double türden sabit, 12.0 ile aynı anlamda Sayı üstel biçimde yazılmışsa sayı nokta içermese bile double türden olur. Örneğin: 1e3 Bu sayı 1000 anlamına geliyor olsa da üstel biçimde yazıldığı için double türden sabit belirtmektedir. 11) Sayı nokta içeriyorsa ve sayının sonunda 'f' ya da 'F' soneki varsa sabit float türdendir. Örneğin: 12.3f float türden sabit .1F float türden sabit 12.F float türden sabit Sayı nokta içermiyorsa sayının sonuna 'f' ya da 'F' soneki getirilemez. Örneğin: 12F geçersiz sabit! 1e3F geçerli, burada noktaya gerek yok, çünkü sayı üstel biçimde yazılmış 12) Sayı nokta içeriyorsa ancak sayının sonunda 'l' ya da 'L' varsa sabit long double türden olur. Örneğin: 12L long türden sabit 12.3 double türden 12.3L long double türden ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 13. Ders 05/07/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 13) C'de tek tırnakla iki tırnak arasında çok fark vardır. (Halbuki bazı dillerde tek tırnak ile iki tırnak arasında farklılık yoktur.) C'de bir karakter tek tırnak içerisine alınırsa bu ilgili karakterin karakter tablosundaki sıra numarasını (code point) belirten bir sayı anlamına gelir. Örneğin C'de 'a' ifadesi aslında eğer ASCII karakter tablosu kullanılıyorsa 97 sayısı ile aynı anlamdadır. C'de bir karakter tek tırnak içerisine alınırsa bu ifade int türden sabit kabul edilir. Bu biçimdeki ifadelere "int türden karakter sabitleri (integer character constants) de denilmektedir". Örneğin: #include int main(void) { int ch; ch = 'a'; printf("ch = %d, ch = %c\n", ch, ch); /* ch = 97, ch = a */ return 0; } Ancak karakter tablolarındaki bazı karakterlerin görüntü karşılığı yoktur. Yani bu karakterleri ekrana yazdırmak istediğimizde bir şey görmeyiz. Ancak bazı eylemler gerçekleşir. Bu tür karakterlere "görüntülenemeyen karakterler (non-printable characters)" de denilmektedir. İşte bu görüntülenemeyen bazı karakterlere ilişkin karakter sabitleri özel bir biçimde ifade edilmektedir. ASCII karakter tablosunun (dolayısıyla Unicode karakter tablosunun da) ilk 32 karakteri görüntülenemeyen özel kontrol karakterinden oluşmaktadır. İşte çok kullanılan bazı görüntülenemeyen karakterler tek tırnak içerisinde "önce bir ters bölü sonra özel bazı karakterler ile" temsil edilmektedir. Bu karakter sabitlerine "ters bölü karakter sabitleri (escape sequnces)" denilmektedir. Bunların listesi şöyledir: '\a' alert (7 numaralı ASCII karakteri), beep sesi çıkar '\b' back space (8 numaralı ASCII karakteri), sanki back space tuşuna basılmış etkisi oluşur '\f' form feed (12 numaralı SCII karakterş), bir sayfa atar '\n' line feed (10 numaralı ASCII karakteri), imleç aşağı satırın başına geçer '\r' carriage return (13 numaralaı ASCII karakteri), imleç bulunduğu satırın başına geçer) '\t' tab (9 numaralı ASCII karakteri), imle. bir tab ileri gider '\v' vertical tab (11 numaralı ASCII karakteri), imleç düşey olarak kaydırılır. Burada önemli olan nokta '\n' gibi bir karakter sabitinin her ne kadar tırnak içerisinde iki karakter bulunuyorsa da aslında tek bir karaktere ilişkin karakter sabiti belirttiğidir. Yani '\n' karakter sabiti ne ters bölü karakterinin ne de 'n' karakterinin karakter sabitidir. Tamamen başka bir karakter olan LF (line feed) denilen karakterin karakter sabitidir. Ters bölü karakter sabitleri iki tırnak içerisinde tek bir karakter olarak ele alınmaktadır. Örneğin: #include int main(void) { printf("ali\aveli\nselami\tfatma\nsacit\n"); return 0; } Ters bölü karakterinin kendisine ilişkin karakter sabiti '\' biçiminde yazılamaz. Eğer biz böyle bir şey yazarsak derleyici "sanki ters bölü karakter sabitlerinden birisini yazmak istiyormuşuz da onu yazamamışız gibi" durumu değerlendirir. Ters bölü karakterinin kendisine ilişkin karakter sabitini '\\' biçiminde yazabiliriz. Örneğin: #include int main(void) { char ch; ch = '\\'; printf("%c\n", ch); return 0; } Benzer biçimde iki tırnak içerisinde de ters bölü karakterinin kendisini yazdırmak istiyorsak iki ters bölü karakteri kullanmalıyız. Örneğin: #include int main(void) { printf("c:\temp\a.dat\n"); /* yanlış yazım */ printf("c:\\temp\\a.dat\n"); /* doğru yazım */ return 0; } Tek tırnak karakterine ilişkin karakter sabiti ''' biçiminde yazılamaz. Bu durumda derleyici "sanki tek tırnağın içerisine bir şey yazılmamış gibi" durumu değerlendirecektir. Tek tırnak karakterinin karakter sabiti '\'' biçiminde yazılmalıdır. Örneğin: #include int main(void) { char ch; ch = '\''; printf("%c\n", ch); return 0; } İki tırnağın içerisinde tek tırnak karakterini ters bölüsüz de yazabilriz. Yani iki tırnak içerisindeki tek tırnak karakterleri bir soruna yol açmamaktadır. Örneğin: printf("Izmir'in merkezi\n"); /* geçerli */ Tabii istersek yine de bu tek tırnağı ters bölü karakteri biçiminde de yazabilirdik: printf("Izmir\'in merkezi\n"); /* geçerli, yukarıdaki ile aynı */ Benzer biçimde iki tırnak içerisinde iki tırnak karakteri de doğrudan yazılamaz. Örneğin: printf(""Ankara""); /* geçersiz! */ İki tırnak içerisinde iki tırnak karakteri \" biçiminde belirtilmelidir. Örneğin: printf("\Ankara\""); /* geçerli "Ankara" yazısı çıkacak. Tebii tek tırnak içerisinde iki tırnak karakteri de sorunsuz olarak kullanılabilir. Örneğin: ch = '"'; /* geçerli, sorun yok */ Ancak sorun yaratmıyor olsa da biz istersek tek tırnak içerisinde iki tırnak karakterini yine \" biçiminde de yazabiliriz. Örneğin: ch = '\"'; Aslında C'de tek tırnak içerisine tek bir karakterin yerleştirilmesi zorunlu değildir. Tek tırnak içerisine int türünün byte uzunluğu kadar karakter yerleştirilebilir (örneğin int türü 4 byte ise 4 karakter, 8 byte ise 8 karakter yerleştirilebilir). Tek tırnak içerisine birden fazla karakter yerleştirildiğinde bunlara "multibyte karakterler" denilmektedir. Multibyte karakterlerin ne belirttiği derleyicileri yazanların isteğine bırakılmıştır. Biz bu multibyte karakter kavramını ileride yeniden ele alacağız. Bir karakter sabitinin başına onunla yapışık bir L harfi (L harfi büyük harf olmak zorundadır) getirilebilir. Bu tür karakter sabitlerine "geniş karakter sabitleri (wide character constants)" denilmektedir. Örneğin: L'a' Geniş sabitleri wchar_t türündendir. Bu konu ileride ele alınacaktır. C11 ile birlikte karakter sabitlerinin önüne yine onunla yapışık 'u' ve 'U' getirilebilmektedir. Örneğin: u'a' U'b' 'u' öneki getirilmiş karakter sabitleri Unicode UTF-16 encoding'ini, 'U' öneki getirilmiş karakter sabitleri de Unicode UTF-32 encoding'ini belirtir. Bunlar sırasıyla char16_t ve char32_t türündendir. (char16_t ve char32_t türleri içerisinde typedef edilmiş türlerdir.) Bu konu da ileride ele alınacaktır. Aslında C23'e kadar C standartlarında 'u' ve 'U' önekli karakter sabitlerinin açıkça Unicode UTF-16 ve Unicode UTF-32 encoding'ine ilişkin olduğu belirtilmemişti. Bu türden karakter sabitlerinin hangi encoding'e göre code point belirttiği derleyiciyi yazanların isteğine bırakılmıştı. Ancak C23'te artık açıkça bu sabitlerin Unicode UTF-16 ve Unicode UTF-32 türünden sabit belirttiği ifade edilmiştir. Bugün Microsoft'un C derleyicileri ve gcc ve clang derleyicileri geniş karakter sabitlerini Unicode UTF16 ya da Unicode UTF32 olarak ele almaktadır. Dolayısıyla geniş karakter sabitlerinin hangi karakter tablosu ve hangi encoding'e ilişkin olduğu derleyiciden derleyiciye değişebilmektedir. Halbuki artık C23 ile birlikte 'u' ve 'U' öneki sayesinde taşınabilir bir biçimde Unicode UTF-16 ve Unicode UTF-32 karakter sabitleri oluşturulabilmektedir. 14) C'de int türden küçük türlerin sabitleri yoktur. Yani C'de char, signed char, unsigned char, short ve unsigned short türünden sabitler yoktur. En küçük sabit int türündendir. Tek tırnak içerisine yazılmış karakter sabitlerinin de aslında int türdne olduğunu anımsayınız. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir C programında Türkçe karakterlerin düzgün görünmemesinin birkaç nedeni olabilir. C standartlarına göre derleyici için iki karakter kümesi söz konusudur: "Kaynak karakter kümesi (source character set)" ve "çalıştırma karakter kümesi (execution character set)". Kaynak karakter kümesi derleyicinin kaynak dosyadaki karakterlerin hangi karakter kodlamasına göre kodlandığını kabul ettiği kümedir. Örneğin derleyicimizin kaynak karakter kümesi "Unicode UTF-8" ise bu durumda derleyici kaynak dosyanın "Unicode UTF-8" olarak kodlanmış bir dosya olduğunu varsayacaktır. Türkçe için başka tek byte'lı karakter kodlamaları da vardır. Örneğin Micsosoft 1254, ISO 8859-9, OEM 857 gibi. Çalıştırma karakter kümesi ise derleyicinin kaynak karakter kümesinde kabul ettiği karakterlerin nasıl depolandığına ilişkin karakter kümesidir. Derleyicilerde genellikle default durumda bu iki karakter kümesi aynıdır. Örneğin: int ch; ch = 'ş'; Buradaki program parçasında biz Unicode UTF-8 editörünü kullanmışsak bu editör buradaki 'ş' karakteri için iki byte'lık bir sayı yazacaktır. Eğer derleyimizin kaynak karakter kümesi örneğin Microsoft 1254 ise derleyici bu iki karakteri sanki tek tırnak içerisinde birden fazla karakter yazmışız gibi yani "multibyte" olarak ele alacaktır. Bu da Türkçe 'ş' karakterini derleyicinin tanıyamaması anlamına gelecektir. Halbuki derleyicimizin kaynak karakter kümesi de "Unicode UTF-8" olsaydı derleyicimiz bu iki byte'ın aslında Türkçe 'ş' karakteri olduğunu anlayacaktı. Buradan şu sonucu çıkartabiliriz: Eğer biz Türkçe karakterleri derleyicinin tanımasını istiyorsak editörümüzün karakter kodlaması ile derleyicimizin kaynak karakter kodlamasını aynı yapmalıyız. Pekiyi derleyicimizin Türkçe 'ş' harfini düzgün anladığinı varsayalım. Bu durumda derleyicimiz yukarıdaki örnekte ch değişkenin içerisine hangi byte'ları yerleştirecektir. İşte bu da "çalıştırma karakter kümesi" ile ilgilidir. Eğer derleyicimizin çalıştırma karakter kümesi de "Unicode UTF-8" ise bu durumda derleyicimiz ch değişkenin içerisine 'ş' karakteri için yine byte'ları kodlayacaktır. Durumun böyle olduğunu varsayalım. Şimdi ch değişkenin içerisinde iki byte'lık Unicode UTF-8 Türkçe 'ş' karakteri vardır. İşte biz bu karakteri stdout dosyasına yazdırdığımızda ekran yine de 'ş' karakterini göremeyebiliriz. Çünkü terminal aygıt sürücüsünün de beklediği bir karakter kodlaması vardır. Terminal aygıt sürücüsü eğer bizden örneğin Windows 1254 karakter kodlaması bekliyorsa bu durumda bizim gönderdiğimiz iki byte'ı terminal aygıt sürücüsü iki ayrı karakter olarak yorumlayacaktır. Biz de muhtemelen 'ş' yerine anlamsız iki karakter göreceğiz. Bu durumda terminal aygıt sürücüsünün karakter kodlamasının da bizimle uyumlu olması gerekmektedir. Linux ortamında genellikle her şeyin default karakter kodlaması "Unicode UTF-8"dir. Eğer biz Linux'ta "Unicode UTF-8" dosyası olarak Türkçe karakterleri oluşturursak tüm yukarıdaki öğeler aynı ayarda olduğu için sorun çıkmayacaktır. Windows'ta Visual Studio IDE'sinde ayarlamalar biraz daha karmaşıktır. Eğer bilgisayarımızın bölgesel ayarları Türkçe ise Microsoft buradaki editörün default karakter kodlamasını, derleyicinin kaynak ve çalıştırma karakter kümesini "ASCII 1254 Code Page" olarak ayarlamaktadır. Eğer terminal aygıt sürücüsü de aynı ayardaysa bir sorun oluşmayacaktır. Ancak eğer terminal aygıt sürücüsü "Unicode UTF-8" olarak ayarlanmışsa bu durumda bu uyumsuzluğu gidermek gerekir. Şimdi terminal aygıt sürücüsünün kodlamasının UTF-8 olduğunu varsayalım. Bu durumda bizim IDE tarafında uyumu sağlayabilemiz için kaynak dosyanın "Unicode UTF8" olarak kodlanması, derleyicinin kaynak ve çalıştırma karakter kmelerinin de "Unicode UTF-8" olarak ayarlanması gerekir. Microsoft C derleyicilerinde kaynak ve çalıştırma karakter kümeleri "/source-charset:utf-8 /execution-charset:utf-8" komut satırı seçenekleri UTF-8 olarak ayarlanabilmektedir. Aynı ayar gcc ve clang derleyicilerinde ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de karakter sabitleri sayısal işlemlere sokulabilir. Çünkü zaten onlar birer sayı belirtmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int result; result = 'a' + 1; printf("%c, %d\n", result, result); /* b, 98 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- putchar fonksiyonu bizden int türden bir değer alır. O değere karşı gelen karakter numarasına ilişkin karakterin görüntüsünü ekrana (stdout dosyasına) yazar. Yani putchar(ch) çağrısıyla printf("%c", ch) çağrısı işlevsel olarak tamamen eşdeğerdir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; a = 48; putchar(a); /* 0 */ putchar('\n'); putchar('?'); /* ? */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- getchar fonksiyonu adeta putchar fonksiyonun tersini yapmaktadır. Bu fonksiyonun parametresi yoktur. Fonksiyon çağrıldığında klavyeden (stdin dosyasından) bir karaktere basılıp ENTER tuşuna basılır. getchar bu karakterin karakter tablosundaki sıra numarasına geri döner. getchar bize int türden bir değer vermektedir. Örneğin: int ch; ch = getchar(); getchar fonksiyonunu yanlışlıkla aşağıdaki gibi kullanmaya çalışmayınız: getchar(ch); getchar fonksiyonun parametresi yoktur. Bunun verdiği değeri bir değişkene yerleştirmelisiniz: ch = getchar(); ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int ch; ch = getchar(); putchar(ch); putchar('\n'); printf("ch %d, ch = %c\n", ch, ch); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aslında klavyeden (stdin dosyasından) okumalar bir tapon eşliğinde yapılmaktadır. Biz getchar fonksiyonunda birden fazla karakter girebiliriz. Bu durumda girilen tüm karakterler önce bir "tampona (buffer)" yerleştirilir sonra o tampondan alınarak verilir. getchar için basıl ENTER tuşu da tampona '\n' karakteri olarak eklenmektedir. getchar (ve stdin dosyasından okuma yapan diğer fonksiyonlar) eğer tamponda zaten karakter varsa bizden karakter istemezler. stdin tamponunda karakter yoksa yeniden okuma talep ederler. Örneğin: int ch; ch = getchar(); putchar(ch); ch = getchar(); putchar(ch); Biz burada ilk getchar için 'a' karakterine basıp ENTER tuşuna basmış olalım. Bu durumda taponun içeriği şöyle olacaktır: Tampon => a\n İlk getchar tampondaki sıradaki karakter olan 'a' okuyacaktır. Ancak ikinci getchar tapon dolu olduğu için klavyeden yeni bir giriş istemeyecektir. Tampondaki '\n' karakterini alıp geri dönecektir. Ancak bir tane daha getchar çağrısı yaparsak artık o cgetchar tampon boş olduğu için klavyedne okuma isteyecektir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- printf fonksiyonunun klavyeden (stdin dosyasından) okuma yapan scanf isimli kardeşi vardır. scanf temel olarak printf gibi kullanılmaktadır. Ancak scanf fonksiyonundaki format karakterleri çıktı ile ilgili değil yapılan giriş ile ilgili bilgi verir. Örneğin printf fonksiyonunda %d "int bir değeri 10'luk sistemde ekrana yaz" anlamına gelirken scanf fonksiyonunda %d "int bir nesne için "10'luk sistemde giriş yap" anlamına gelmektedir. scanf fonksiyonunda iki tırnaktan sonraki değişkenlerin önümne & operatörü getirilir. (Bu operatör ileride ele alınacaktır). Örneğin: int a; scanf("%d", &a); Burada klavyeden girilen sayı a nesnesinin içerisine yerleştirilir. scanf fonksiyonundaki iki tırnak içerisine format karakterlerindne başka bir şey yazmayınız. Buraya yazdığınız başka karakterler başka anlamlara gelmektedir. scanf buradaki karakterleri ekrana yazdırmaz. Ekrana bir şey yazdırmak istiyorsanız printf fonksiyonunu kullanmalısınız. Örneğin: int a; scanf("%x", &a); Burada %x klavyedne girilen değerin 16'lık sistemde girilmiş olduğunu varsayarak a nesnesine yerleştirecektir. printf fonksiyonuyla scanf fonksiyonu arasındaki format karakterleri aynı biçimdedir. Ancak birkaç istisna vardır. printf fonksiyonunda hem float hem de double %f ile yazdırılır. Ancak scanf fonksiyonunda float %f ile double %lf ile okunmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { double a; printf("Bir sayi giriniz:"); scanf("%lf", &a); /* double %lf ile okunmalıdır */ printf("%f\n", a); /* printf fonksiyonunda %lf diye bir formak karakteri yoktur */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Tek bir scanf ile birden fazla nesne için okuma yapılabilir. Burada format karakterlerinin dışında şimdilik başka bir karakter bulundurmayınız. Girişler sırasında istenildiği kadar boşluk karakteri (SPACE, TAB, ENTER) bulundurulabilir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; printf("Iki deger giriniz:"); scanf("%d%d", &a, &b); printf("a = %d, b = %d\n", a, b); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki programda klavyeden (stdin dosyasından) iki int değe rokunmuş bunların çarpımı ekrana (stdout dosyasına) yazdırılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; printf("Iki deger giriniz:"); scanf("%d%d", &a, &b); printf("%d\n", a * b); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Yeni öğrenen tarafından yanlışlıkla scanf'teki format karakterlerinin sonuna \n konulabilmektedir. Bu tamamen başka bir anlama gelir. Böyle yapmayınız. ----------------------------------------------------------------------------------------------------------------------*/ #include int ain(void) { int a, b; printf("Iki deger giriniz:"); scanf("%d%d\n", &a, &b); /* dikkat! yanlışlıkla \n konulmuş! */ printf("%d\n", a * b); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Tabii scanf ile biz getchar gibi karakter de okuyabiliriz. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { char ch; scanf("%c", &ch); putchar(ch); return 0; } /*---------------------------------------------------------------------------------------------------------------------- 14. Ders 07/07/2022 - Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir işleme yol açan, işlem sonucunda bir değer üretilmesini sağlayan atomlara "operatör" denilmektedir. Bir operatörün işleme soktuğu ifadeleri ise "opeand (operand)" denir. Örneğin a + b ifadesinde + bir operatördür. a ve b bu operatörün operand'larıdır. Operatör konusunu iyi anlayabilmek için operatörleri sınıflandırmak gerekir. Operatörler genel olarak üç biçimde sınıflandırılmaktadır: 1) İşlevlerine Göre 2) Operand Sayılarına Göre 3) Operatörün Konumuna Göre İşlevlerine göre sınıflandırmada operatörün hangi amaçla kullanıldığına göre sınıflandırma yapılır. Tipik sınıflandırma şöyle yapılmaktadır: 1) Artirmetik Operatorleer (Arithmetic Operators): Bunlar toplama, çarpma gibi klasik operatörlerdir. 2) Karşılaştırma Operatörleri (Comparision Operators): Bunlar >, <, >=, <=, ==, != gibi iki değeri karşılaştırmak için kullanılan operatörlerdir. Bu operatörlere "ilişkisel operatörler (relational operators)" da denilmektedir 3) Mantıksal Operatörler (Logical Operators): Bunlar AND, OR, NOT işlemleri yapan operatörlerdir. 4) Gösterici Operatörleri (Pointer Operators): Adreslerle işlemler yapan operatörlerdir. Bunlar her programlama dilinde bulunmazlar. 5) Bit Operatörleri (Bitwise Operators): Bit operatörleri de pek çok dilde bulunmaktadır. Bunların sayıların karşılıklı bitlerini işleme sokan operatörlerdir. 6) Özel Amaçlı Operatörler (Special Purpose Operators): Değişik konulara ilişkin işlem yapan yukarıdaki gruplar içerisine girmeyen operatörlerdir. Operand sayılarına göre operatörler üç grubu ayrılmaktadır: 1) İki operand'lı Operatörler (Binary Operators): Bunlar iki operand alırlar. Yani bir şeyle bir şeyi işleme sokarlar. Örneğin '+', '*', '/', '-' operatörleri iki operand'lı operatörlerdir. 2) Tek operand'lı Operatörler (Unary Operators): Bunlar tek bir değeri işleme sokarlar. Örneğin NOT operatörü programlama dillerinde bir değerin NOT'ını alır, iki değerin NOT'ını almaz. Ya da örneğin -5 ifadesindeki '-' operatörü çıkartma operatörü değildir. İşaret eksi operatördür ve tek operand'lı bir operatördür. 3) Üç operand'lı operatörler (Ternary Operators): Üç operand'lı operatörler aslında çok seyrek bulunurlar. Örneğin C'de üç operand'lı tek bir operatör vardır. Operatörler operatörün operan'larına göre konumuna göre de üçe ayrılmaktadır: 1) Araek Operatörler (Infix Operators): Bu operatörler iki operand'lıdır ve operand'larının arasına getirilerek kullanılmaktadır. Örneğin a + b işleminde '+' operatörlerinin araek bir operatör olduğuna dikkat ediniz. 2) Önek Operatörler (Prefix Operators): Bunlar operand'larının önüne getirilerek kullanılırlar. Örneğin !a gibi bir kullanımda ! operatörü operand'ının önüne getirilmiştir. 3) Sonek Operatörler (Postfix Operators): Bunlar da operand'larının sonuna getirilerek kullanılırlar. Örneğin foo() gibi bir ifadede parantezler operatör görevindedir. foo ise bu operatörün operandıdır. Burada operatör operand'ının sonuna getirilmiştir. Bir operatör ele alınırken önce yukarıdaki üç sınıflandırmada da operatörün nereye düştüğü ifade edilmelidir. Sonra operatöre ilişkin başka özellikler belirtilmelidir. Örneğin, "/ operatörü iki operand'lı araek (binart infix) bir artimetik operatördür." Ya da örneğin "! operatörü tek operand'lı öncek (unary prefix) bir mantıksal operatördür". Ya da örneğin "& operatörü iki operand'lı araek bir bit operatörüdür". ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir ifadede birden fazla operatör kullanıldığında bunlar birbirlerine göre belli bir sırada yapılırlar. Bu duruma "operatörler arasındaki öncelik ilişkisi (operator precedency)" denilmektedir. Operatörlerin arasındaki öncelik ilişkisi "operatörlerin öncelik tablousu" denilen bir tabloyla betimlenmektedir. Bu tablo satırlardan oluşur. Üst satırrdaki operatörler alt satırdaki operatörlerden daha önceliklidir. Aynı satırdaki operatörler eşit öncelikli biçimde bulunurlar. Ancak aynı satırdaki operatörlerin önceliği "soldan sağa (left to right)" ya da "sağdan sola (right to left)" biçimde olabilir. Soldan sağa öncelik demek o satırda bulunanlardan ifade içerisinde hangisi soldaysa o önce yapılır demektir. Sağdan sola öncelik de benzerdir. Aşağıda operatmrlerin öncelik tablosunun iskelet hali verilmiştir: () Soldan-Sağa * / Soldan Sağa + - Soldan Sağa = Sağdan Sola Buradaki () operatörü öncellik parantezini ve fonksiyon çağırma operatörünü anlatmaktadır. Örneğin: a = b - c * d; İ1: c * d İ2: b - İ1 İ3 a = İ2 Burada aslında b'dn c * d'nin çıkartıldığına dikkat ediniz. Örneğin: a = b / c * d Burada / ve * soldan-sağa eşit önceliklidir. İfade içerisinde (öncleik tablosunda değil) solda / olduğu için önce / sonra * yapılacaktır: İ1: b / c İ2: İ1 * d İ3: a = İ2 Örneğin: a = b + c + d; Burada solda olan '+' önce yapılacaktır: İ1: b + c İ2: İ1 + d İ3: a = İ2 Örneğin: a = b = c; Atama operatörünün sağdan-sola grupta olduğuna dikkat ediniz: İ1: b = c İ2: a = İ1 Öncelik tablosundaki satırlarda bulunan operatörler o satırda değişik sırada yazılabilirler. Çünkü aynı satırdaki operatörlerin o satırdaki sırasının bir önemi yoktur. "Soldan-sağa" ya da "sağdan-sola" ifade içerisindkei duruma ilişkindir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- *, /, + ve - operatörleri "iki operand'lı araek (binary infix)" aritmetik operatörlerdir. Bunlar klasik dört işlemi yaparlar. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- % operatörü iki operand'lı araek bir aritmetik operatördür. Bu operatör sol taraftaki operandın sağ taraftaki operanda bölümünden elde edilen kalan değerini üretir. Bu operatörün her iki operandı da tamsayı türlerine ilişkin olmak zorundadır. Öncelik tablosunda * ve / ile soldan sağa eşit öncelik grupta bulunur. Negarit sayının pozitif sayıya bölümünden elde edilen kalan nagtiftir. Pozitif sayının negatif sayıya bölümündne elde edilen kalan pozitiftir. () Soldan-Sağa * / % Soldan Sağa + - Soldan Sağa = Sağdan Sola ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int result; result = 10 % 4; printf("%d\n", result); /* 2 */ result = -10 % 4; printf("%d\n", result); /* -2 */ result = 10 % -4; printf("%d\n", result); /* 2 */ result = -10 % -4; printf("%d\n", result); /* -2 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- + ve - sembolleri hem toplama ve çıkartma operatörü hem de işaret - ve işaret + operatörünü temsil etmektedir. İşaret + ve işaret - operatörleri "tek operand'lı öncek (unary prefix)" operatörlerdir. İşaret - operatörü operand'ının negatif değerini üretir. İşaret + operatörü ise operand'ı ile aynı değeri üretmektedir. (Yani aslında işaert + operatörü bir şey yapmamaktadır). Bu iki operatör öncelik tablosunun ikinci düzeyinde sağdan-sola gruğta bulunurlar: () Soldan-Sağa + - Sağdan-Sola * / % Soldan Sağa + - Soldan Sağa = Sağdan Sola Örneğin: a = b - - - c; İ1: -c İ2: -İ1 İ3: b - İ2 İ4: a = İ3 Burada işl - sembolün "çıkartma" diğerlerinin "işaret -" olduğuna dikkat ediniz. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int result; int a = -4; result = 10 - - - - -a; printf("%d\n", result); /* 14 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de programın atomlarına ayrılma aşamasında yan yana en uzun karakter topluluğundan atom yapılmaya çalışılır. C'de sonraki konuda göreceğiniz gibi ++ ve -- operatmrleri de vardır. Dolayısıyla ++ ve -- yan yana yazılırsa iki ayrı işaret + ve işaret - operatörü değil ++ ve -- operatörleri anlaşılır Benzer biçimde a>=3 gibi bir ifadede a, >= ve 3 biçiminde üç farklı atom vardır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- ++ ve -- operatörleri "tek operand'lı, öncek ve sonek olarak kullanılabilen" operatörlerdir. Yani biz bu operatörleri ++a gibi de a++ gibi de kullanabiliriz. Bu operatörlerin önek ve sonek kullanımlarında semantik farklılık vardır. ++ operatörüne "artırma (increment)", -- operatörüne "eksiltme (decrement)" operatörleri denilmektedir. ++ operatörü "operandı içerisindeki değeri 1 artır, -- operatörü operandı içerisindeki değeri 1 eksilt anlamına gelir." ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; a = 3; ++a; /* a = a + 1 */; printf("%d\n", a); /* 4 */ a++; printf("%d\n", a); /* 5 */ a = 3; --a; /* a = a - 1 */ printf("%d\n", a); /* 2 */ a--; printf("%d\n", a); /* 1 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- ++ ve -- operatörleri öncelik tablosunun ikinci düzeyinde sağda-sola grupta bulunmaktadır: () Solda-Sağa + - ++ -- Sağdan-Sola * / % Soldan Sağa + - Soldan Sağa = Sağdan Sola Aslında C'nin tek operand'lı (unary) bütün operatörleri zaten öncelik tablosunun ikinci düzeyinde sağdan-sola gruba yerleştirilmiştir. ++ ve -- operatörleri her zaman tablodaki öncelikte yapılır. Ancak sonraki işleme eğer operatörler önek olarka kullanılmışsa artırılmış ya da eksiltilmiş değer, sonek olarak kullanılmışsa artırılmamış ya da eksiltilmemiş değer sokulmaktadır. Örneğin: a = 3; b = ++a * 2; Burada 3 operatör vardır. En önceliklisi ++ operatördür. O halde a 1 artırılacak ve 4 olacaktır. Sonraki işlem * işlemidir. O halde * işlemine artırma öncek yapıldığı için artırılmış değer olan 4 sokulacaktır. Bu duurmda 4 değişkeni 4 değerine olurken b değişkeni 8 olacaktır. Şimdi aynı işlemi sonek olarak yapalım: a = 3; b = a++ * 2; Burada da a önce artırılır 4 olur. Ancak sonraki işlem olan * işlemine a'nın artırılmış değeri olan 3 sokulur. Bu durumda a 4 olurken b ise 6 olacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; a = 3; b = ++a * 2; printf("a = %d, b = %d\n", a, b); /* a = 4, b = 8 */ a = 3; b = a++ * 2; printf("a = %d, b = %d\n", a, b); /* a = 4, b = 6 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Önek ve sonek etki aşağıdaki basit örnekle de daha iyi anlaşılabilir: a = 3; b = --a; Burada önce a eksiltilir, 2 olur. Sonraki işlem atama işlemidir. O halde b'ye a'nın eksiltilmiş değeri atanır. Yani b de 2 olacaktır. Fakat örneğin: a = 3; b = a--; Burada yine a bir eksiltilir ve 2 olur. Ancak sonraki işlem olan atama işlemine a'nın eksiltilmemiş değeri olan 3 sokulur. Böylece b 3 olur. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; a = 3; b = --a; printf("a = %d, b = %d\n", a, b); /* a = 2, b = 2 */ a = 3; b = a--; printf("a = %d, b = %d\n", a, b); /* a = 2, b = 3 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Tabii ++ ve -- operatörleri tek başlarına kullanılıyorsa bunların önek ve sonek kullanımları arasında bir fark oluşmaz yani örneğin: ++a; ile a++; arasında bir fark yoktur. Fark ifadede başka operatörler varsa ortaya çıkmaktadır. Örneğin: a = 3; b = 2; c = ++a * b--; Burada önce b eksiltilir 1 olur. Sonra a artırılır 4 olur. Çarpma işlemine a'nın artırılmış değeri ancak b'nin eksiltilmemiş değeri sokulur. Bu durumda c'ye 8 atanacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b, c; a = 3; b = 2; c = ++a * b--; printf("a = %d, b = %d, c = %d\n", a, b, c); /* a = 4, b = 1, c = 8 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- 15. Ders 19/07/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Tabii başka bir hiçbir operatör yoksa ++ ve -- operatörlerinin önek ve sonek kullanımları arasında bir fark oluşmaz. Örneğin: ++a; ile a++; arasında bir farklılık yoktur. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; a = 3; ++a; printf("a = %d\n", a); /* a = 4 */ a = 3; a++; printf("a = %d\n", a); /* a = 4 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- ++ ve -- operatörlerinin operand'larının nesne belirtmesi yani sol tarafa değeri olması gerekir. Örneğin aşağıdaki gibi bir ifade geçerli değildir: ++3; /* geçersiz! */ ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C (ve C++) standartlarında "tanımsız davranış (undefined behavior)" denilen bir kavram vardır. Tanımsız davranış terimi standartlarda semantik bir tanımın yapılmadığı kodlar için kullanılmaktadır. Tanımsız davranışa yol açan kodlar sentaks bakımdan geçerlidir. Dolayısıyla başarılı bir biçimde derlenirler. Ancak programın çalışma zamanı sırasında sorunlar ortaya çıkabilmektedir. Bu sorunlar "programın çökmesi", "umulmadık biçimde programın çalışması", "hatalı birtakım değerlerin ortaya çıkması" biçiminde olabilir. Bazen tanımsız davranışa yol açam kodlar görünüşte bir soruna yol açmayabilir. Ancak programın değişik zamanlarda çalışmtırılması sırasında tutarsızlıklar oluşturabilmektedir. Sonuç olarak bir kod eğer "tanımsız davranışa" yol açıyorsa programcının o kodu kullanmaması gerekir. Kullanırdsa artık programın sağlıklı çalışması gaeranti olmaz. Tanımsız davranışların "derleme aşamasına ilişkin değil", "prıogramın çalışma zamanına ilişkin" oluşsuzluklar doğrabildiğine dikkat ediniz. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C (ve C++) standartlarında karşılaşılan diğer bir kavram da "derleyiciye bağlı davranış (implementation depnedent (defined) behavior)" kavramıdır. Standratlarda bazı durumlarda ilgili özelliğin derleyiciden derleyiciye değişebileceği belirtilmiştir. Yani ilgili özellik için açık bir belirleme yapmak yerine standartlar bu belirlemenin derleyicileri yazanlar tarafından yapılacağını belirtmektedir. Örneğin int türünün (ve char dışındaki diğer türlerin) uzunlukları derleyiden derleyiciye değişebilmektedir. Bu uzunluklar "derleyiciye bağlı bir davranışa" yol açmaktadır. Ancak derleyiciye bağlı davranışların ilgili derleyicinin dokümantasyonunda dokümante edilmiş olması gerekmektedir. Yani derleyicilerin bir referans gibi kitapları olmalıdır. Orada standartlarda belirtilen "derleyiciye bağlı davranışların" o derleyicide nasıl ele alındığının belirtilmesi gerekmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C (ve C++) standartlarında geçen diğer önemli bir kavram da "belirsiz davranış (unspecified behavior)" kavramıdır. Belirsiz davranışta sınırlı sayıda seçenek söz konusudur. Ancak bu seçeneklerin hangisinin uygulnadığı derleyiciden derleyiciye değişebilir. Bu seçeneklerin hiçbiri normal koşullarda programın çökmesine ya da hatalı sonuçların oluşmasına yol açmamaktadır. Derleyiciler belirsiz davranışlarda hangi seçeneği seçtiklerini dokümante etmek zorunda değillerdir. Belirsiz davranışın tanımsız davranıştan en önemli farkı tanımsız davranışın tamamen patolojik bir durum olması ancak belirsiz davranışın patolojik bir durum olmamasıdır. Belirsiz davranışının derleyiciye bağlı davranıştan en önemli farkı, belirsiz davranış için derleyicilerin bunları dokümante etme zorunluluklarının olmamasıdır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C (ve C++) standartlarındaki önemli bir durum da şudur: C standartları dilin sentaks ve semantik kısıtlarına uyulmadığı durumlarda bu durum için derleyicilerin en az bir hata mesajı vermesi gerektiğini belirtmektedir. Ancak standartlara göre geçerli bir program derleyici tarafından başarılı bir biçimde derlenmek zorundadır ancak geçersiz bir program derleyici tarafından yine de başarılı bir biçimde derlenebilir. Yani standartlar geçersiz programların başarılı bir biçimde derlenip derlenmeyeceği konusunda bir yargıda bulunmamaktadır. Gerçekten de pek çok derleyici bazı geçersiz kodları birer uyarı vererek başarılı bir biçimde derlemektedir. Ancak bu durum kodun geçerli olduğu anlamına gelmemektedir. (Dolayısıyla C'de bizim bir durumun geçerliliği hakkında derleyicinin kodu başarılı bir biçimde derleyip derlemediğine bakarak karar vermemeiz gerekir. Çünkü derleyiciler geçersiz kodları da başarılı bir biçimde derleyebilmektedir.) Tabii bizim dilin kuralarrına tamamen uymamız gerekir. Çünkü bir derleyici geçersiz programı derliyor olsa da diğer bir derleyici onu derlemeyebilir. Ancak kodumuz geçerliyse her derleyici kodumuzu derlemek zorundadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de bir nesne bir ifadede ++ ya da -- operatörleriyle kullanılmışsa artık o ifadede bir daha o nesne kullanılmamalıdır. Eğer kullanılırsa bu durum tanımsız davranışa yol açmaktadır. Bu durumda aşağıdaki gibi kodların hepsi geçerli ancak tanımsız davranışa yol açan kodlardır: b = ++a + a; b = a++ + a; b = ++a + ++a; a = ++a; b = a + a--; Bu kodlarda nasıl bir sonuç elde edileceğinin bir garantisi yoktur. Ancak yukarıdaki kodlar örneğin Java ve C# gibi dillerde "tanımlı (well defined)" kodlardır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de toplam 6 tane karşılaştırma operatörü vardır: <, >, <=, >= == != Öncelik tablosunda karşılaştırma operatörleri aritmetik operatörlerden daha düşük öncelikli biçimde bulunmaktadır: () Soldan-Sağa + - ++ -- Sağdan-Sola * / % Soldan-Sağa + - Soldan-Sağa < > <= >= Soldan-Sağa != == Soldan-Sağa = Sağdan-Sola Karşılaştırma operatörlerinin de öncelik tablosunda iki farklı düzeyde bulunduuna dikkat ediniz. C'de karşılaştırma operatörlerinin ürettiği değerler int türdendir. Eğer önerme doğruysa bu operatörler 1 değerini, yanlışsa 0 değerini üretirler. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int result; result = 3 > 1; printf("%d\n", result); /* 1 */ result = 3 == 1; printf("%d\n", result); /* 0 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki ifadeye dikkat ediniz: b = 10 < a < 20; Bu ifade matematikte a'nın 10 ile 20 arasında olduğuna ilişkin bir anlama gelse de C'de böyle bir anlama gelmemektedir. C'de bu ifade şöyle ele alınmaktadır: İ1: 10 < a (1 ya da 0 elde edilir) İ2: İ1 < 20 İ3: b = İ2 Karşılaştırma operatörleri aritmektik operatörlerden düşük önceliklidir. Örneğin: a + b > c + d Böyle bir işlemde a + b ile c + d karşılaştırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int result; result = 1 + 2 < 3 + 4; printf("%d\n", result); /* 1 */ result = 1 + (2 < 3) + 4; printf("%d\n", result); /* 6 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de üç mantıksal operatör vardır: ! NOT && AND || OR && ve || operatörleri iki operand'lı arek operatörlerdir. Ancak ! operatörü tek operand'lı önek bir operatördür. Öncelik tablosunda ! operatörü diğer tek operand'lı operatörlerin bulunduğu ikinci düzeydedir. Ancak && ve || operatörleri karşılaştırma operatörlerinden daha düşük önceliklidir. () Soldan-Sağa + - ++ -- ! Sağdan-Sola * / % Soldan-Sağa + - Soldan-Sağa < > <= >= Soldan-Sağa != == Soldan-Sağa && Soldan-Sağa || Soldan-Sağa = Sağdan-Sola ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Mantıksal operatörler her zaman int türden değer üretirler. İşlem sonucu Doğru ise 1 değerini, yanlış ise 0 değerini üretmektedirler. Bu operatörler önce operand'larını Doğru ya da Yanlış olarak ele alırlar. Eğer operand sıfır dışı (non-zero) herhangi bir değerdeyse Doğru olarak, sıfır değerindeyse Yanlış olarak ele alınmaktadır. Örneğin: -3 && 5.7 Burada -3 Doğru olarak, 5.7 de Doğru olarak ele alınır. Doğru ve Doğru işlemi Doğru sonucunu verir. Doğru için 1 değeri üretilecektir. Örneğin: -1 || 0 Buradan 1 değeri üretilir. Örneğin: 0 && -8 Buradan 0 değeri üretilir. ! operatörü Doğruyu Yanlış, Yanlışı Doğru yapan bir operatördür. Öncelik tablosunun ikinci düzeyinde sağdan sola öncelikte bulunur. Örneğin: result = !3.5; Burada 3.5 Doğru olarak ele alınır. ! operatörü Yanlış değeri için 0 üretmektedir. Örneğin: result = !!!-3.2; İ1: !-3.2 ---> 0 İ2: !İ1 ---> 1 İ3: !İ2 ---> 0 İ4: result = İ3 Örneğin: result = !0 + 2 İ1: !0 ---> 1 İ2: İ1 + 2 ---> 3 İ3: result = İ2 && ve || operatörlerinin karşılaştırma operatörlerinden düşük öncelikli olması karşılaştırmanın sonuçlarının mantıksal işlemesokulacağı anlamına gelmektedir. Örneğin: result = a > 10 && a < 20; Burada iki koşulk da doğruysa 1 değeri diğer durumlarda 0 değeri elde edilecektir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; int result; printf("Bir deger giriniz:"); scanf("%d", &a); result = a >= 10 && a <= 20; printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- && ve || operatörlerinin "kısa devre (short circuit)" özelliği vardır. Bu operatörler klasik öncelik tablosu kuralına uymazlar. Bu operatörlerin sağında ne kadar öncelikli operatör olursa olsun bunların önce sol tarafı yapılır. Eğer && operatöründe sol taraf sıfır ise sağ taraf hiç yapılmaz sonuç hemen 0 olarak belirlenir. Eğer && operatöründe sol taraf sıfır dışı bir değer ise bu durumda sağ taraf yapılmaktadır. Aynı dırım || operatörü için de geçerlidir. Bu operatörün sol tarafı eğer sıfır dışı bir değerdeyse sağ tarafı hiç yapılmaz ve sonuç 1 olarak belirlenir. Eğer bu operatörün sol tarafı sıfır dışı bir değerdeyse bu durumda sağ tarafı yapılır. Aşağıdaki program bu durumun anlaşılması için verilmiştir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; int result; a = 1; b = 3; result = a > 10 && ++b > 2; printf("result = %d, b = %d\n", result, b); /* result = 0, b = 3 */ a = 20; b = 3; result = a > 10 && ++b > 2; printf("result = %d, b = %d\n", result, b); /* result = 1, b = 4 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Her ne kadar henüz fonksiyonlar konusunu görmediysek de aşağıdaki örnekte bar fonksiyonu çağrılmayacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int foo(void) { printf("foo\n"); return 0; } int bar(void) { printf("bar\n"); return 1; } int main(void) { int result; result = foo() && bar(); printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- 16. Ders 21/07/2022 - Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- && ve || operatörleri aynı ifadede kullanıldığında yine en soldaki operatörün sol tarafı önce yapılır. Aslında kısa devre özelliği yalnızca sonucun hızlı bir biçimde bulunmasına yol açmaktadır. Yoksa kısa devre özelliğinin olmadığı durumla olduğu durum arasında bir sonuç farklılığı oluşmaz. Örneğin: ifade1 || ifade2 && ifade3 Burada önce ifade1 yapılır. Eğer ifade1 sıfır dışı bir değerse başka hiçbir şey yapılmaz. Sonuç 1 olarak elde edilir. Eğer ifade1 sıfır ise bu durumda ifade2 yapılır. İfade2 de sıfır ise ifade3 yapılmaz. Burada tüm ifadelerin yapılması için ifade1'in sıfır, ifade2'nin sıfır dışı bir değer vermesi gerekir. Örneğin: ifade1 && ifade2 || ifade3 Burada yine ifade1 önce yapılır. İfade1 sıfır ise ifade2 yapılmaz. Ancak ifade3 yapılır. Eğer ifade1 sıfır dışı bir değerde ise bu durumda ifade2 yapılır. Eğer ifade2 de sıfır dışı ise ifade3 yapılmaz. Aşağıdaki ifadede önce ifade'ün yapılması daha hızlı sonucun elde edilmesine yol açabileceği halde her zaman && ve || operatörlerinin sol tarafı önce yapılmaktadır. Yani aşağıdaki örnekte yine ifade1 ince yapılacaktır.: ifade1 && ifade2 || ifade3 Her ne kadar henüz fonksiyonları görmemiş olsak da aşağıdaki örnek kısa devre özelliğini incelemek amacıyla kullanılabilir. Tabii aslında parantezler de işlemlerin yappılma sırası bakımından bir şeyi değiştirmeyecektir. Örneğin: ifade1 && (ifade2 || ifade3) Burada her ne kadar || işlemi paranteze alınmışsa da bu parantez içi önce yapılmaz. Çünkü önce yapılsaydı && operatörünün sağ tarafı önce yapılmış olurdu. Burada da yine önce ifade1 yapılır. İfade1 0 ise başka bir şey yapılmaz. ----------------------------------------------------------------------------------------------------------------------*/ #include int foo(void) { printf("foo\n"); return 0; } int bar(void) { printf("bar\n"); return 0; } int tar(void) { printf("tar\n"); return 1; } int main(void) { int result; result = foo() || bar() && tar(); printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Atama operatörü iki operand'lı araek özel amaçlı bir operatördür. Bu operatörün sol tarafındaki operand'ın bir nesne belirtmesi gerekir. Yani sol taraf değeri (LValue) olması gerekir. Atama operatörü de bir değer üretmektedir. Atama operatörünün ürettiği değer sol taraftaki nesneye atanmış olan değerdir. Atama operatörü öncelik tablosunda düşük düzeyde sağdan sola grupta bulunmaktadır. () Soldan-Sağa + - ++ -- ! Sağdan-Sola * / % Soldan-Sağa + - Soldan-Sağa < > <= >= Soldan-Sağa != == Soldan-Sağa && Soldan-Sağa || Soldan-Sağa = Sağdan-Sola Bu durumda örneğin: a = b = 10; İ1: b = 10 --> 10 İ2: a = İ1 Böylece burada 10 hem b'ye hem de a!ya atanmış olur. Örneğin: a = b = 10 + 20; Burada a ve b'ye 30 atanmaktadır. Ancak örneğin: a = (b = 10) + 20; Burada parantez içi önce yapılacağına göre b'ye 10 atanacak ve bu işlemden 10 değeri elde edilecektir. Sonra bu 10 değeri 20 ile toplanıp a'ya atanacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; a = b = 10 + 20; printf("a = %d, b = %d\n", a, b); /* a = 30, b = 30 */ a = (b = 10) + 20; printf("a = %d, b = %d\n", a, b); /* a = 30, b = 10 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Tanımlama sırasında tanımlanan değişkene '=' atomu ile ilkdeğer verilebiliyordu. İlkdeğer vermedeki '=' bir operatör olarak değerlendirilmemektedir. Bu işlem bildirim işleminin bir parçasıdır. Dolayısıyla buradaki '=' bir operatör olarak ele alınmaz. Böyle olunca da buradaki '=' atomunun bir değer üretmesi söz konusu değildir. Örneğin aşağıdaki gibi bir bildirim geçerli değildir: int a = b = 10; /* geçersiz! Buradaki '=' bir operatör değil */ Ancak aşağıdaki gibi bir bildirim geçerlidir: int a = 10, b = a; /* geçerli */ C'de bir değişken dekleratörden sonra (bu kavram ileride açıklanacaktır) ancak ilkdeğer vermeden önce faaliyet alanına sokulmuş olmaktadır. Dolayısıyla C'de aşağıdaki gibi bir bildirim geçerli ancak anlamsızdır. Örneğin: int a = a; Burada a yerel bir değişkense a'ya çöp değer, global bir değişkense 0 atanmaktadır. Bazen programcı bir değeri önce atayıp, atanmış değeri başka bir değerle karşılaştırmak isteyebilir. Bunun için atama operatörüne öncelik vermek gerekir. Örneğin: (ch = getchar()) != 'q' Burada önce getchar ile klavyeden (stdin dosyasından) okunan değer ch değişkenine atanmıştır. Sonra bu atanan değer karşılaştırma işlemine sokulmuştur. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de bir grup +=, -=, *=, /=, %=, ... biçiminde "bileşik atama operatörü (compund assignment operator)" vardır. Bu operatörlerin hepsi iki operand'lı araek operatörlerdir. "op", +, -, *, / gibi bir operatör belirtmek üzere: a op= b işlemi tamamen, a = a op b işlemi ile eşdeğerdir. Örneğn: a += 2; ile a = a + 2; eşdeğerdir. Örneğin: a *= b; ile a = a * b; eşdeğerdir. Bileşik atama operatörleri öncelik tablosunda atama operatör ile sağdan sola aynı grupta bulunmaktadır. () Soldan-Sağa + - ++ -- ! Sağdan-Sola * / % Soldan-Sağa + - Soldan-Sağa < > <= >= Soldan-Sağa != == Soldan-Sağa && Soldan-Sağa || Soldan-Sağa =, +=, /=, *=,... Sağdan-Sola Örneğin: a *= 2 + 3; Burada önce 2 ile 3 toplanır. Sonra *= işlemi yapılır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a = 2; a *= 2 * 3; printf("%d\n", a); /* 12 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bileşik atama operatörleri de değer üretmektedir. Bu operatörlerin ürettiği değerler yine sol taraftaki nesneye atabnuş olan değerlerdir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a = 2, b; b = (a *= 2) * 3; printf("a = %d, b = %d\n", a, b); /* a = 4, b = 12 */ a = 2; b = a *= 2 * 3; printf("a = %d, b = %d\n", a, b); /* a = 12, b = 12 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Virgül de iki oepardn'lı araek bir operatördür. Önceli tablosunun en düşük öncelikli operatörüdür. () Soldan-Sağa + - ++ -- ! Sağdan-Sola * / % Soldan-Sağa + - Soldan-Sağa < > <= >= Soldan-Sağa != == Soldan-Sağa && Soldan-Sağa || Soldan-Sağa =, +=, /=, *=,... Sağdan-Sola , Soldan-Sağa Virgül operatörü aslında iki ifadeyi tek bir ifade biçiminde ifade edebilmek için düşünülmüştür. Tipik kullanım biçimi şöyledir: ifade1, ifade2 Virgül operatörünün sağında ne kadar yüksek öncelikli bir operatör olursa olsun, önce onun sol tarafı tamamen yapılır birilir, sonra sağ tarafı tamamen yapılır bitirilir. Virgül operatöründen elde edeilen değer sağ taraftaki ifadenin değeridir. Örneğin: a = 10; b = 20; Burada iki ayrı ifade vardır. Ancak örneğin: a = 10, b = 20; Burada tek bir ifade vardır. Bazen sentaks olarak tek bir ifadein gerektiği yerde birden fazla ifade kullanılabilmesi için bu iki ifadenin , operatörü ile birleştirilmesi gerekebilmektedir. Virgül öncelik tablosunun en düşük öncelikli operatörüdür. Dolayısıyla örneğin: a = 10, b = 20; gibi bir işlem şu sırada yapılır: İ1: a = 10 İ2: b = 20 İ3: İ1, İ2 Virgül operatörünün ürettiği değer sağ taraftaki ifadenin değeridir. Yani virgül operatörünün solundaki ifadenin değer üretmekte bir etkisi yoktur. Örneğin: c = (a = 10, b = 20); Burada parantezler sayesinde en soldaki atama operatörü virgül operatöründen ayrıştrılmıştır. Burada önce parantez içi yapılacaktır. Parantez içerisinde virgül operatörü vardır. O zaman viegül operatörünün sol tarafı önce yapılacağında göre önce a = 10 işlemi sonra b = 20 işlemi yapılır. Virgül operatöründen elde edilen değer sağ taraftaki ifadenin değeri olduğuna göre buradan 20 elde edilecektir. İşte bu 20 aynı zamanda c'ye atanmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b, c; c = (a = 10, b = 20); printf("a = %d, b = %d, c = %d\n", a, b, c); /* a = 10, b = 20, c = 20 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Tabii her virgül virgl operatörü değildir. Örneğin bildirim yaparken kullandığımız virgüller bu bağlamda bir operatör belirtmezler. Buradaki virgüller ayıraç atom görevindedir. Örneğin: int a, b, c; /* Buradaki virgüller operatör görevinde değil */ Örneğin bir fonksiyon çağırırken argümanları ayırmak için kullandığımız virgül de ayrıraç atom görevindedir: foo(a, b, c); /* Buradaki virgüller de operatör görevinde değil */ Eğer argümandaki ',' atomumun virgül operatör olması isteniyorsa bu durumda parantezler kullanılmalıdır. Örneğin: foo(a, b); Buradaki ',' operatör görevinde değildir. Dolayısıyla foo fonksiyonunun iki parametresi vardır. Fakat örneğin: foo((a, b)); Buradaki virgül artık paranteze alındığı için operatör görevinddir. Parantez içerisinden b'nin değeir elde edilecektir. Dolayısıyla fonksiyonun aslında tek parametresi vardır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; printf("%d\n", (a = 10, b = 20)); /* tuhaf ama geçerli, b yazdırılıyor */ printf("%d\n", a); /* 10 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Virgül operatörü de soldan-sağa önceliklidir. Yani bir ifadede birden fazla virgül operatörü bulunabilir. Örneğin: ifade1, ifade2, ifade3 Burada işlemler şöyle yütülür: İ1: ifade1, ifade2 İ2: İ1, ifade3 Yani burada sonuçta bu ifadeler soldan sağa sırasıyla yapılacaktır. Buradan elde edilen toplam sonuç en sağdaki ifadenin değeridir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; a = (10, 20, 30); /* geçerli ama tuhaf, a'ya 30 atanır */ printf("%d\n", a); /* 30 */ return 0; } *---------------------------------------------------------------------------------------------------------------------- C'de ';' ifadeleri sonlandırmak için kullanılmaktadır. Bu görevdeki atomlara programlama dillerinde "sonlandırıcı (terminator)" denilmektedir. Biz ifadenin sonuna ';' koyduğumuzda artık o ifadeyle sonraki ifadenin ayrı ifadeler olduğunu derleyiciye söylemiş oluruz. Eğer bir ifadenin sonundaki ';' unutulursa derleyici önceki ifadeyle sonraki ifadeyi tek bir ifade olarak ele alır. Bu da sentaks hatasına yol açar. Örneğin: a = 10 b = 20; Burada muhtemelen a = 10'dan sonraki ';' atomu unutulmuştur. O halde derleyiciye göre burada tek bir ifade vardır. Ancak bu ifade geçerli değildir. Bazı dillerde sonlandırıcı olarak LF karakteri kullanılmaktadır. Dolayısıyla o dillerde aynı satıra tek bir ifade yazılmak zorundadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Biz şimdiye kadar zaten var olan fonksiyonları çağırdık. Artık biz de fonksiyon yazacağız. Bir fonksiyonun yazılmasına C standartlarında "fonksiyonun tanımlanması (function definition)" denilmektedir. Fonksiyon tanımlamanın genel biçimi şöyledir: ([parametre bildirimi]) { /* ... */ } Örneğin: int foo() { /* ... */ } Burada int fonksiyonun geri dönüş değerinin türüdür. foo ise fonksiyonun ismini belirtir. Fonksiyon parametre değişkenlerine sahip değildir. Fonksiyonun geri dnüş değerinin türü klasik C'de (yani C90'da) yazılmak zorunda değildi. Bu duurmda sanki "int" yazılmış gibi işlem yapılıyordu. Ancak C99 ile birlikte fonksiyonun geri dönüş değerinin türünün yazılması zorunlu hale getirilmiştir. bar() /* C90'da geçerli C99 ve sonrasında geçerli değil */ { /* ... */ } Eğer fonksiyonun parametresi yoksa parametre parantezinin içi boş bırakılabilir ya da parametre parantezinin içerisine void yazılabilir. İkisi arasında hiçbir farklılık yoktur. Biz kursumuzda genel olarak parametresiz fonksiyonlarda parametre parantezinin içine void anahtar sözcüğünü yazacağız. Ancak programcıların bir bölümü hiçbir şey yazmamayı tercih etmektedir. Biz kurusumuzdki örneklerde "öylesine uydurulmuş fonksiyon isimleri" olarak foo, bar, tar, zar gibi isimleri kullanacağız. Bu isimlerin hiçbir özel anlamı yoktur. Örneklerde öylesine uydurulmuş isimlerdir. Tanımlanan her fonksiyonun bir ana bloğu vardır. Buna "fonksiyonun gövdesi (function body)" de denilmektedir. C'de iç içe (nested) fonksiyon tanımlaması yapılamaz. Her fonksiyon biribirinin dışında ve global düzeyde tanımlanmak zorudadır. Örneğin: int foo() { int bar() /* geçersiz! */ { /* ...*/ } /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 17. Ders 26/07/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir fonksiyon çağrıldıktan sonra onu çağıran fonksiyona ilettiği değere "geri dönüş değeri (return value)" denilmektedir. Fonksiyonun geri dönüş değerinin bir türü vardır. Bu tür fonksiyon isminin soluna yazılır. Geri dönüş değerini oluşturmak için return deyimi kullanılır. return deyiminin genel biçimi şöyledir: return [ifade] return deyimi hem fonksiyonu sonlandırır hem de geri dönüş değerini oluşturur. ----------------------------------------------------------------------------------------------------------------------*/ #include int foo(void) { printf("foo\n"); return 100; } int main(void) { int result; result = foo() * 2; printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bir fonksiyonun geri dönüş değerinin olması onu kullanmayı zorunlu hale getirmez. Yani fonksiyonların geri dönüş değerlerini fonksiyonu çağıran hiç kullanmayabilir. ----------------------------------------------------------------------------------------------------------------------*/ #include int foo(void) { printf("foo\n"); return 100; } int main(void) { foo(); /* foo çağrıldı ancak geri dönüş değeri kullanılmadı, tamamen geçerli */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Fonksiyon tanımlarken geri dönüş değerinin tütü yerine "void" anahtar sözcüğü yazılırsa bu durum "fonksiyonun bir değer geri föndürmediği" anlamına gelmektedir. Böyle fonksiyonlar geri dönüş değerinin kullanıldığı bir ifadede kullanılamazlar. Örneğin: void foo(void) { printf("foo\n"); } ... x = foo(); /* geçersiz! foo'nun geri dönüş değer yok */ x = foo() * 2; /* geçersiz! foo'nun geri dönüş değeri yok */ foo(); /* geçerli */ ----------------------------------------------------------------------------------------------------------------------*/ #include void foo(void) { printf("foo\n"); } int main(void) { foo(); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Geri dönüş değeri void olan fonksiyonlara "void fonksiyonlar" da denilmektedir. void fonksiyonlar da return deyimi kullanılabilir ancak return deyiminin yanına bir ifade yazılamaz. Örneğin: void foo(void) { printf("foo\n"); return; /* geçerli */ } void bar(void) { printf("foo\n"); return 10; /* geçersiz! void fonksiyon bir değerle geri döndürülemez */ } Pekiyi o zaman void fonksiyonlardaki return deyimi ne işe yaramaktadır? İşte void fonksiyonlardaki return deyimleri fonksiyonu bir koşul altında erken sonlandırmak için kullanılabilir. void fonksiyonlarda return kullanılmazsa akış fonksiyonun ana bloğunu bitirdiğinde zaten fonksiyon sonlanmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include void foo(void) { printf("foo\n"); return; /* geçerli */ } int main(void) { foo(); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Geri dönüş değeri void olmayan fonksiyonlarda eğer akış return deyimini görmeden ana blok sonlanırsa bu durum C'de geçerlidir (halbuki C#, Java gibi dillerde geçersizdir). Bu durumda geri dönüş değeri olarak çöğ bir değer elde edilmektedir. Genellikle derleyiciler böylesi durumlarda bir uyarı mesajı ile programcıyı uyarmaktadır. Ancak geri dönüş değeri void olmayan fonksiyonlarda return deyiminde return ifadesinin mutlaka bulundurulması gerekir. Örneğin: int foo(void) { printf("foo\n"); } /* dikkat! fonksiyon çöp değerle geri dönüyor */ int bar(void) { printf("bar\n"); return; /* geçersiz! return anahtar sözcüğünün yanında bir ifade olması gerekirdi */ } ----------------------------------------------------------------------------------------------------------------------*/ #include int foo(void) { printf("foo\n"); } int main(void) { int result; result = foo(); /* dikkat! geçerli ama çöp değer elde ediliyor */ printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Mademki akış return deyimini göründe fonksiyon sonlanmaktadır. O halde return deyiminin altına deyim yazmanın bir anlamı yoktur. Bu durum anlamsız olsa da C'de geçerlidir. Örneğin: int foo(void) { printf("foo\n"); return 100; printf("foo ends...\n"); /* unreachable code */ } Akışın asla ulaşamayacağı erişilemeyen bölgelere İngilizce "unreachable code" denilmektedir. Derleyiciler erişilemeyen kodları tespit edip bir uyarı mesajı ile programcıya bildirebilmektedir. Pek çok derleyici erişilemeyen kodları tamaman koddan çıkartarak bir optimizasyon yapmaktadır. Bu optimizasyon temasına "dead code elimination" denilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de main fonksiyonun geri dönüş değeri int türden olmak zorundadır. Ancak derleyiciler eğer isterlerse main fonksiyonunun başka türlerden geri dönüş değerine sahip olmasına izin verebilirler. main fonksiyonundaki return deyimi aynı zamanda programı da sonlandırmaktadır. İşletim sistemleri dünyasında çalışmakta olan programlara "process" denilmektedir. main fonksiyonu sonlandığında return deyimindeki ifade işletim sistemine "exit code" olarak iletilmektedir. İşletim sistemleri bu exit kodu alır, eğer başka bprosesler isterse belli koşullarda onlara verebilir. Ancak exit kodunun kaç olduğuyla ilgilenmez. Fakat geleneksel olarak C'de başarılı ve mutlu sonlanmalar için exit kodu olarak 0, başarısız sonlanmalar için sıfır dışı değerler kullanılmaktadır. Biz örneklerimizde main fonksaiyonunu her zaman 0 ile geri döndüreceğiz. Aslında C standartlarında main fonksiyonuna özgü olarak, eğer main fonksiyonunda hiç return kullanılmazsa sanki ana bloğun sonuna return 0 yazılmış gibi işlem uygulanmaktadır. Yani main fonksiyonunda biz hiç return yazmasak da zaten return 0 yazmış gibi bir durum oluşmaktadır. Tabii bu durum yalnızca main fonksiyonuna özgüdür. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Fonksiyonların geri dönüş değerleri geçici nesne yoluyla onu çağıran fonksiyona iletilmektedir. Programın akışı return deyimini gördüğünde önce derleyici geri dönüş değeri türünden geçici bir nesne yaratır. Sonra return ifadesini bu geçici nesneye atar. Biz geri dönüş değerini kullandığımızda aslında o geçici nesneyi kullanmış oluruz. Fonksiyonun çağrısı bittiğinde bu geçici nesne de derleyici tarafındna otomatik olarak yok edilmektedir. Örneğin: int foo(void) { /* ... */ return ifade; } ... x = foo() * 2; Burada aslında arka planda aşağıdaki gibi işlemler gerçekleşmektedir: int temp = ifade; /* akış return deyimine geldiğinde */ ... x = temp * 2; /* temp yok ediliyor */ Bir nesne yaratılırken ona değer atanmasına "ilkdeğer verme (initialization)" denildiğini anımsayınız. Fonksiyonun geri dönüş değerinin atanacağı geçici nesne return ifadesiyle yaratıldığı için aslında return işlemi geçici nesneye bir ilkdeğer verme işlemi olarak da alınmaktadır. O halde fonksiyonun geri dönüş değerinin türü aslında return işlemiyle yaratılacak olan geçici nesnenin türünü belirtir. Bizim ileride atama işlemi hakkında söyleceğimiz her şey return işlemi için de geçerlidir. Derleyiciler genel olarak mümkün olduğunca return işlemi sırasındaki geçici nesneleri CPU yazmaçlarında yaratmaktadır. Fonksiyon çağrıları C'de her zaman sağ taraf değeri (rvalue) belirtmektedir. Yani return işlemiyle yaratılanm bu geçici nesneye biz atama yapamayız. Örneğin: foo() = 10; /* geçersiz! */ void fonksiyonlarda böyle bir geçici nesnenin hiç yaratılmayacağına da dikkat ediniz. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Fonksiyonların dış dünyadan aldıkları değerlere "parametre (parameter)" denilmektedir. C'de fonksiyon parametreleri parametre parantezinin içerisinde tür ve değişken ismi belirtilerek ve ',' atomu ile parametreler ayrılarak bildirilmektedir. Örneğin: void foo(int a, long b, double c) { /* ... */ } void bar(double a, int b) { /* ... */ } Parametreler aynı türden olsa bile tür belirten sözcüğünyeniden yazılması gerekmektedir. Örneğin: void foo(int a, b) /* geçersiz! */ { /* ... */ } Yuklarıdaki tanımlama geçersizdir. Şöyle yapılması gerekirdi: void foo(int a, int b) { /* ... */ } Parametreli bir fonksiyon parametre sayısı kadar "argümanla" çağrılmalıdır. Argümanlar herhangi birer ifade olabilir. Örneğin: void foo(int a, int b) { /* ... */ } ... foo(10 + 20, 30 + 40); /* geçerli */ Argüman olan ifadeler yine ',' atomu ile ayrılmaktadır. Fonksiyonu çağırırken yazılan ifadelere "argümen (argument)" denilmektedir. Parametreli bir fonksiyon çağrıldığında önce argümanların değerleri hesaplanır. Sonra argümanlardan parametre değişkenlerine karşılık bir atama yapılır. Sonra da akış fonksiyona geçirilir. Yani C'de parametre aktarımı atama (ya da kopyalama) biçiminde yapılmaktadır. Örneğin: void foo(int a, int b) { /* ... */ } /* ... */ int x = 10, y = 20; foo(x + 1, y + 2) Burada foo fonksiyonu çağrıldığında önce x + 1 ve y + 2 ifadelerinin değerleri hesaplanacak sonra x + 1 değeri a'ya, y + 2 değeri ise b'ye atanacaktır. Sonra da akış fonksiyona geçirilecektir. Parametre değişkenlerinin bağımsız ayrı nesneler olduğuna dikkat ediniz. Fonksiyon çağırma işlemi argümanlardan parametre değişkenlerine yapılan gizli bir atama işlemini gerektirmektedir. Aslında izleyen paragraflarda da göreceğimiz gibi fonkdiyonun parametre değişkenleri fonksiyon çağrıldığında yaratılmaktadır. Dolayısıyla fonksiyon çağırma işlemi aslında parametre değişkenlerine ilkdeğer verme işlemi gibi de ele alınabilir. O halde C'de atama anlamına gelen üç durum vardır: 1) Açıkça '=' operatörü ile yapılan atamalar 2) return işlemi sırasında geçici nesneye yapılan atamalar 3) Fonksiyon çağırma sırasında argümanlardan parametre değişkenlerine yapılan atamalar Biz ileride atama işlemi için söyleyeceğimiz her şey bu üç durum için de geçerli olacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include void foo(int a, int b) { printf("a = %d, b = %d\n", a, b); } int main(void) { int x = 100, y = 200; foo(10, 20); foo(10 + 1, 20 + 2); foo(x, y); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bazen fonksiyonlar parametreleriyle aldıkları değeri birtakım işlemlere sokup onu geri dönüş değeri olarak verebilirler. ----------------------------------------------------------------------------------------------------------------------*/ #include int add(int a, int b) { return a + b; } int main(void) { int result; result = add(10, 2); printf("%d\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de bir atama işleminde kaynak tür ile hedef tür farklı olabilir. Örneğin: a = b; Burada a ve b'nin türleri farklı olabilir. Ancak bu konu ileride özel olarak ele alınacaktır. Siz şimdilik bu konu ele alınana kadar atama işleminde kaynak türle hedef türü aynı türden yapmaya özen gösteriniz. Örneğin: #include double add(int a, int b) { int result; result = a + b; return result; /* dikkat! farklı türler birbirlerine atanıyor, ileri ele alınacak */ } int main(void) { double x = 2.3, y = 4.5; int result; result = add(x, y); /* dikkat farklı türler biribirene atanıyor, ileride ele alınacak */ printf("%d\n", result); return 0; } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de parametreleriyle aldıkğı değerler üzerinde işlemler yaparak sonucu geri dönüş değeri olarak veren bir standart matematiksel fonksiyon vardır. Bu fonksiyonları kullanmadan önce dosyası include edilmelidir. Örneğin sqrt fonksiyonun parametrik yapısı şöyledir: double sqrt(double x); Fonksiyon parametresiyle aldığı double sayının kareköküne geri dönmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double val, result; printf("Bir deger giriniz:"); scanf("%lf", &val); result = sqrt(val); printf("%f\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- pow fonksiyonu bir sayının belli bir kuvvetine geri dönmektedir. Fonksiyonun parametrik yapısı şöyledir: double pow(double a, double b); Fonksiyon a üzeri b işlemine geri dönmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double a, b, result; printf("Taban:"); scanf("%lf", &a); printf("Us:"); scanf("%lf", &b); result = pow(a, b); printf("%f\n", result); return 0; } /*---------------------------------------------------------------------------------------------------------------------- sin, cos, tan, asin, acos, atan fonksiyonları trigonometrik işlemler yapmaktadır. Bu fonksiyonların parametreleri ve geri dönüş değerleri double türdendir. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double result, radian; result = sin(3.141592653589793238462643 / 2); printf("%f\n", result); radian = asin(result); printf("%f\n", radian); return 0; } /*---------------------------------------------------------------------------------------------------------------------- round fonksiyonu double bir değeri parametre olarak alıp ona en yakın tamsayıyı yine double bir değer olarak vermektedir. Fonksiyonun parametrik yapısı şöyledir: double round(double x); ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double result; result = round(3.6); printf("%f\n", result); /* 4 */ result = round(3.4); printf("%f\n", result); /* 3 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C99 ile birlikte roundf ve roundl fonksiyonları da standartlara eklenmiştir. Bunların parametrik yapıları şöyledir: float roundf(float x); long double roundl(float x); Yine C99 ile birlikte tamsayı değerlere geri dönen aşağıdakai fonksiyonlar da eklenmiştir. long int lround(double x); long int lroundf(float x); long int lroundl(long double x); long long int llround(double x); long long int llroundf(float x); long long int llroundl(long double x); ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { long result; result= lround(3.6); printf("%ld\n", result); /* 4 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- floor isimli fonksiyon double bir sayıya en yakın ondan küçük ya da ona eşit tamsayıyı bize double türden vermektedir. ceil ise tam ters işlem yapar. Yani bir double sayıdan büyük ya da ona eşit en yakın tamsayıyı double türden vermektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double result; result= floor(3.9); printf("%f\n", result); /* 3 */ result = floor(-3.9); printf("%f\n", result); /* -4 */ result = ceil(3.1); printf("%f\n", result); /* 4 */ result = ceil(-3.1); printf("%f\n", result); /* -3 */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- C99 ile birlikte float ve long double için aşağıdaki fonksiyonlar da standartlara eklenmiştir: float floorf(float x); long double floorl(long double x); float ceilf(float x); long double ceill(long double x); ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 18. Ders 28/07/2022 - Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bildirilen bir değişkenin kullanılabildiği program aralığına "faaliyet alanı (scope)" denilmektedir. C'de üç faaliyet alanı vardır: 1) Blok Faaliyet Alanı (Block Scope): Yalnızca bir blokta o bloğun kapsadığı bloklarda tanınma aralığıdır. 2) Dosya Faaliyet Alanı (File Scope): Tüm fonksiyonlarda yani her yerde tanınma aralığıdır. 3) Fonksiyon Faaliyet Alanı (Function Scope): Bir fonksiyonun her yerinde tanınma aralığıdır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de yerel değişkenler blok faaliyet alanı kuralına uyarlar. Yani bildirildikleri yerden bildirildikleri bloğun sonuna kadarki bölgede kullanılabilirler. Anımsanacağı gibi C90'da yerel değişkenler blokların başlarında yani blokların ilk işlemleri olacak biçimde bildirilmek zorundaydı. Bu kural C99'da kaldırıldı. Yerel değişkenlerin bloğun herhangi bir yerinde bildirilmeleri sağlandı. Örneğin: void foo(void) { printf("foo\n"); int a; /* C90'da geçersiz! C99'dan itibaren geçerli */ } void bar(void) { int a; /* C90'da da geçerli */ printf("bar\n"); } Bir fonksiyonun içerisinde içerisinde istenildiği kadar iç içe ve ayrık blok oluşturulabilir. Örneğin: void foo(void) { { printf("Ok\n"); /* .... */ } { printf("Ok\n"); } } Yerel değişkenler bildirildikleri yerden itibaren bildirildikleri bloğun sonuna kadarki bölgede kullanılabilirler. Örneğin: void foo(void) { int a; { int b; a = 10; /* geçerli, a faaliyet gösteriyor */ b = 20; /* geçerli, b faaliyet gösteriyor */ } printf("%d\n", a); /* geçerli, a faaliyet gösteriyor */ printf("%d\n", b); /* geçersiz! b burada faaliyet göstermiyor */ } void bar(void) { a = 100; /* geçersiz, a burada faaliyet göstermiyor */ } Tabii C99 ve sonrasında bildirilen bir yeral değişken bildirim yerinden önce de kullanılamaz. Örneğin: void bar(void) { a = 10; /* geçersiz! a faaliyet göstermiyor */ int a; a = 20; /* geçerli */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de aynı faaliyet alanına ilişkin aynı isimli birden fazla değişken tanımlanamaz. Ancak farklı faaliyet alanlarına ilişkin aynı isimli değişkenler tanımlanabilir. Aynı bloğun farklı yerlerinde tanımlanan değişkenler bu bakımdan aynı faaliyet alanı içerisinde kabul edilirler. Bu nedenle C'de aynı blok içerisinde aynı isimli bir den fazla değişken tanımlanamaz. Ancak farklı bloklarda aynı isimli değişkenler tanımlanabilir. Örneğin: void foo(void) { int a; /* ....*/ double a; /* geçersiz! aynı blok içerisinde aynı isimli tek bir değişken tanımlanabilir */ } Örneğin: void bar(void) { int a; { double a; /* geçerli iç içe bloklarda aynı isimli değişkenler tanımlanabilir */ /* ... */ } /* ... */ } void tar(void) { int a; /* geçerli */ /* ... */ } Farklı bloklardaki aynı isimli değişkenler aslında tamamen farklı nesneler belirtirler, bunların yalnızca isimleri aynıdır. Örneğin: void foo(void) { int a; { int a; /* Bu tamamen farklı bir a */ /* ... */ } } C'de aynı blokta birden fazla aynı isimli değişken faaliyet gösteriyorsa o blokta o değişken kullanıldığında her zaman "dar faaliyet alanına sahip olan" değişkenin kullanılmış olduğu kabul edilir. Örneğin: { int a; { int a; a = 10; /* dar faaliyet alanına sahip olan iç bloktaki a'dır */ } a = 20; /* dış bloktaki a, zaten iç bloktaki a burada faaliyet göstermiyor */ } İç içe bloklarda aynı isimli değişkenlerin bildirildiği durumda iç blokta dış bloktaki değişkene erişmenin herhangi bir yolu yoktur. Bu duruma "iç bloktaki değişkenin dış bloktaki gizlemesi (hiding)" denilmektedir. C99 ve sonrasında bir yerel değişken bloğun herhangi bir yerinde bildirilebildiğine göre bildirim yerine kadar üst bloktaki dğeişken faaliyet gösteriyor durumdadır ve henüz üst bloktaki aynı isimli değişken gizlenmemiştir. Örneğin: #include int main(void) { int a; { a = 20; /* ana bloktaki a */ int a = 30; printf("%d\n", a); /* iç bloktaki a */ } printf("%d", a); /* ana bloktaki a */ return 0; } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bildirimi fonksiyonların dışında yapılan global değişkenler "dosya faaliyet alanı (file scope)" kuralına uyarlar. Yani kaynak dosyanın her yerinde, tüm fonksiyonların içerisinde biz global değişkenleri kullanabiliriz. Ancak C'de derleme işleminin de bir yönü vardır. Bu yön yukarıdan aşağıya doğrudur. Bir değişken bildirilmeden önce kullanılamaz. Bu nedenle bir global değişkeni aşağıda bir yerde bildirirsek bildirim yerinde aşağıya kadar her yerde kullanırız. Ancak genel olarak global değişkenler kaynak dosyanın tepesinde bildirilirler. Öneğin: int a; void foo(void) { a = 20; /* global olan a */ } int main(void) { a = 10; /* global a */ foo(); printf("%d\n", a); /* global a, 20 çıkacak */ } Bir global değişken için en iyi tanımlama yeri kaynak kodun tepesidir. Örneğin: void foo(void) { a = 10; /* geçersiz! henüz derleyici a'yı görmedi */ } int a; void bar(void) { a = 20; /* geçerli */ } void tar(void) { a = 30; /* geçerli */ } Bir global değişkenle aynı isimli bir yerel değişken tanımlanabilir. Çünkü bunların faaliyet alanları farklıdır. Bir blokta aynı isimli birden fazla değişken faaliyet gösteriyorsa o blokta dar faaliyet alanına sahip olan değişkene erişilmektedir. Örneğin: #include int x; void foo(void) { double x; x = 20; /* yerel x kullanılıyor */ } int main(void) { x = 10; /* global x */ foo(); printf("%d\n", x); /* global x, 10 çıkacak */ return 0; } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de istisnai olarak ilkdeğer verilmemiş birden fazla aynı isimli global değişken tanımlanabilir. Buna "tentative definition" denilmektedir. Bu durumda aslında toplamda tek bir nesne yaratılır. Yani birden fazla tanımlama bu istisnasi durumda birden fazla nesne anlamına gelmez. Aynı nesnenin gereksiz bir biçimde yeniden "tentative" olarak belirtlmesi anlamına gelir. Tabii bu durum yerel değişkenler için söz konusu değildir. Tentative tanımlama tamamen istisnai bir durumdur ve yalnızca global değişkenler için söz konusudur. Bu nedenle aşağıdaki tanımlama geçerlidir. Ancak aşağıdaki kodda tek bir x nesnesi vardır. "Tentative" sözcüğü "deneme niteliğinde" gibi bir anlama gelmektedir: #include int x; int x; /* geçerli, özel bir durum, tentative definition */ int main(void) { x = 10; printf("%d\n", x); return 0; } int x; /* geçerli, tentative definiton */ Tentative tanımlama olması için global değişkene ilkdeğer verilmemiş olması gerekmektedir. Aynı isimli bir global değişkene bir kez ilkdeğer verilebilir. Ancak birden fazla kez ilkdeğer verilemez. Örneğin: int a = 10; /* geçerli, tentative değil */ int a; /* geçerli, tentative, aslında burada bir a yaratılmıyor */ Ancak örneğin: int a = 10; int a = 20; /* geçersiz! tentative değil */ Bu kural ileride yeniden başka bir konunun içerisinde ele alınacaktır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Daha önce de belirtidliği gibi içerisine henüz değer atanmamış yerel değişkenin içeisinde bellekte daha önceden kalmış olan "çöp bir değer (garbage value)" bulunur. Ancak içerisine henüz değer atanmamış global bir değişkende her zaman 0 değeri olması garanti edilmiştir. Ayrıca C'de içerisinde çöp değerlerin olduğu yerel değişkenlerin kullanılması "tanımsız davranışa (undefined behavior)" yol açmaktadır ----------------------------------------------------------------------------------------------------------------------*/ #include int x; int main(void) { int a; printf("%d\n", a); /* geçerli, içerisine henüz değer atanmamış yerel değişkenler içerisinde çöp değer vardır (tanımsız davranış) */ printf("%d\n", x); /* içerisine henüz değer atanmamış global nesneler içerisinde her zaman 0 olur */ s return 0; } /*---------------------------------------------------------------------------------------------------------------------- Genel olarak global değişkenlerin yalnızca gerektiği durumlarda kullanılması gerekir. Yerel değişkenlerle yapabileceğimiz şeyler için global değişken tanımlamak kötü bir tekniktir. Örneğin programımızda yalnızca main fonksiyonu olsun. Bu durumda global bir değişken tanımlamaya hiç gerek yoktur. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Fonksiyonların parametre değişkenleri faaliyet alanı bakımından ana bloğun başında tanımlanmış olan yerel değişkenler gibidir. Örneğin: void foo(int a, int b) { /* ... */ } Buradaki a ve b parametre değişkenleri faaliyet alanı bakımından aşağıdaki fonksiyonla eşdeğerdir: void foo() { int a; int b; /* ... */ } Görüldüğü gibi fonksiyonların parametre değişkenleri "blok faaliyet alanı (block scope)" uymaktadır. Yani yalnızca o fonksiyonda kulalnılabilirler. Dolayısıyla farklı iki fonksiyonun parametre değişkenleri aynı isimde olabilir. Örneğin: void foo(int a) { /* ... */ } void bar(int a) /* geçerli, a yalnızca bu fonksiyonda kullanılabilir. { /* ... */ } Mademki fonksiyonun parametre değişkenleri faaliyet alanı bakımından ana bloğun başında bildirilen değişkenler gibidir, o halde parametre değişkeni ile aynı isimli fonksiyonun ana bloğunda bir değişken bildirilemez. Örneğin: void foo(int a) { int a; /* geçersiz! parametre değişkeni olan a da aynı faaliyet alanına sahip */ /* ... */ } Fakat örneğin: void foo(int a) { { int a; /* geçerli! iç içe yerel bloklarda aynı isimli değişkenler tanımlanabilir */ /* ... */ } } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de fonksiyon faaliyet alanına sahip tek değişken "goto etiketleridir". Goto deyimi deyimlerin ele alındığı geşecek bölümlerde görülecektir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Daha önceden de belirtildiği gibi C Programlama Dili "Prosedürel Programlama Modeline (Procedural Programming Paradigm)" uygun tasarlanmıştır. Prosedürel programlamada "fonksiyonlar" birer yapı taşıdır. Programlar fonksiyonların birbirlerini çağırması biçiminde organize edilirler. Halbuki Nesne Yönelimli Programlama Modelinin yapı taşı "sınıf" denilen kavramdır. C++ Programala Dili zaten C Programa Dilinin "Nesne Yönelimli Programalama Modelinin" uygulanabilmesi için genişletilmiş bir biçimidir. Pekiyi prosedürel teknikte neden program fonksiyonların birbirlerini çağırması biçiminde organize edilmektedir? Yani neden bürün program main fonksiyonunda yazılıp bitirilmemektedir? Programın fonksiyonlar biçiminde organize edilmesinin birkaç açık sebei vardır: 1) Mühendislikte karmaşık bir problem genellikle parçalarına ayrılarak çözülmektedir. İşte fonksiyonlar karmaşık problemin parçalarını oluşturmak için kulalnılmaktadır. Karmaşık işlemin parçaları fonksiyonlara yaptırılır. Sonra bu fonksiyonların çağrılmasıyla karmaşık işlem gerçekleştirilir. Örneğin bir otomobil aslında çok fazla parçadan oluşmaktadır. Bu parçalar birbirleriyle monte edilmiştir. Sonuçta otomobil çalışır hale gelmiştir. Aynı yöntem yazılımda da izlenmektedir. 2) Fonksiyonlar "yeniden kullanılabilirliği (reusability)" mümkün hale getirmektedir. Yani işin bir kısmını yapan kodları fonksiyon olarak yazarsak başka projelerde de aynı fonksiyonları kullanabiliriz. Fonksiyonların oluşturduğu topluluğa "kütüphane (library)" denilmektedir. Örneğin standart C fonksiyonları kütüphane biçiminde oluşturulmuştur. Biz onları farklı projelerde kullanabilmekteyiz. 3) Fonksiyonlar tekrarı engellemek amacıyla kullanılmaktadır. Bir iş kodun çeşitli yerlerinde yineleniyorsa onu fonksiyon olarak yazarsak toplamda bu kodlardan bir tane projemizde bulundurmuş oluruz. Fonksiyonlar olmasaydı aynı kodu tekrar tekrar yazmak zorunda kalırdık. Bu durumda kod tekrarı toplamda kodun fazla yer kaplamasına yol açardı. O kısımda yapılacak değişikler programın pek çok yerinde yapılmak zorunda kalırdı. Bu durum kodun aynı zamanda daha karmaşık gözükmesine yol açardı. 4) Fonksiyonlar okunabilirliği de artırmaktadır. Fonksiyonların isimleri olduğu için kodu inceleyen kişiler onu daha kolay anlamlandırırlar. Bu isimler aslında o kodun ne yaptığı hakkında da bilgi verir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 19. Ders - 02.08.2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir programlama dilindeki "çalıştırma birimlerine" "deyim (statement)" denilmektedir. Yani program aslında deyim denilen kod parçalarının peşi sıra çalıştırılmasıyla çalışmaktadır. Deyimler C'de 5 gruba ayrılmaktadır: 1) Basit Deyimler (Simple Statements): Bunlar bir ifadenin sonuna ';' atomu konularak elde edilen deyimlerdir. Yani ifade; biçiminde bir görünüme sahiplerdir. Örneğin: a = b + c; foo(); Bunlar birer basit deyimdit. İfade (expression) kavramının ';' atomunu içermediğine ifadenin sonuna ';' getirildiğinde onun bir deyim olduğuna dikkat ediniz. 2) Bileşik Deyimler (Compound Statements): Bir blok içerisine sıfır tane ya da daha fazla deyim yerleştirilirse bloğun kendisi de bir deyim olur. Ona "bileşik deyim" denilmektedir. Örneğin { ifade1; ifade2; ifade3; } Burada bu bloğun tamamı dışarıdan bakıldığında tek bir deyimdir. 3) Kontrol Deyimleri (Control Statements): Programlama dillerinde programın akışı üzerinde etkili olan, if gibi, while gibi, for gibi deyimlere "kontrol deyimleri" denilmektedir. Kontrol deyimleri dışarıdan bakıldığında tek bir deyim olarak ele alınırlar. 4) Bildirim Deyimleri (Declarartion Statements): Bildirim yapmakta kullandığımız sentaks biçimi de aslında bir deyim belirtir. Bunlara bildirim deyimleri denilmektedir. Örneğin: int a, b, c; 5) Boş Deyimler (Null Statements): Solunda ifade olmadan kullanılan noktalı virgüller de bir deyim belirtir. Bunlara boş deyim denilmektedir. Örneğin: x = 10;; Burada iki deyim vardır. Birincisi x = 10; deyimidir. Bu bir basit deyimdir. İkincisi bundan sonraki noktalı virgüldür. Boş deyimler için bir şey yapılmıyor olsa da bunlar yine bir deyim statüsündedir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Yukarıda da belirtildiği gibi program deyimlerin çalıştırılmasıyla çalıştırılmaktadır. Her deyim çalıştığında bir şeyler yapılır. Şimdi bu deyimler çalıştırıldığında ne olacağı üzerinde duralım: - Bir basit deyimin çalıştırılması demek o basit deyimdeki ifadenin çalıştırılması demektir. - Bir bileşik deyimin çalıştırılması bileşik deyimi oluşturan deyimlerin sırasıyla çalıştırılması anlamına gelmektedir. Örneğin: { ifade1; ifade2; { ifade3; ifade4; } } ifade5; Burada dışarıdan bakıldığında iki deyim vardır: Bileşik deyim ve basit deyim. Bir bileşik deyimin çalıştırılması onu oluşturan deyimlerin sırasıyla çalıştırılması anlamına geldiğine göre burada sırasıyla aslında ifade1, ifade2, ifade3, ifade4, ifade5 çalıştırılacaktır. - Kontrol deyimleri çalıştırıldığında nelerin olacağı zaten sonraki başlıklarda ele alınacaktır. - Bir bildirim deyimi çalıştırıldığında bildirilen değişkenler için bellekte yerler ayrılmaktadır. Örneğin: int a, b, c; Burada a, b ve c nesneleri için yerler ayrılacaktır. - Boş deyimin çalıştırılması sırasında bir şey yapılmamaktadır. Yani boş deyimler bir yan etkiye yol açmamaktadır. Bir fonksiyon çağrıldığında fonksiyonun belirttiği ana blok yani bileşik deyim çalıştırılır. Bu durumda bir C programının çalışması demek aslında main fonksiyonun çağrılması demektir. Örneğin: int add(int a, int b) { return a + b; } int main(void) { int a; a = add(10, 20); printf("%d\n", a); return 0; } Burada main fonksiyonu çağrıldığında onun ana bloğunun belirttiği bileşik deyim çalıştırılır. Bu bileşik deyim içerisinde bir bildirim deyimi, 2 tane basit deyim ve bir tane kontrol deyimi vardır. a = add(10, 20) basit deyimi çalıştırılırken de add fonksiyonun ana beloğunun belrttiği bileşik deyim çalıştırılmış olur. Yani görüldüğü gibi aslında program deyimlerin çalıştırılmasıyla çalıştırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Şimdi tek tek kontrol deyimlerini ele alacapız. En yaygın kullanılan kontrol deyimi if deyimidir. if deyiminin genel biçimi şöyledir: if () [ else ] if anahtar sözcüğünden sonra parantezler içerisinde bir ifadenin bulunması gerekir. if deyiminin "doğru" ve "yanlış" kısımları vardır. Doğru ve yanlış kısımlarında tek bir deyim bulunmak zorundadır. Programcı burada birden fazla deyim bulundurmak istiyorsa onu bileşik deyim olarak ifade etmelidir. if deyimiin yanlış kısmı olmak zorunda değildir. if deyiminin tamamı dışarıdan bakıldığında tek bir deyim olarak ele alınmaktadır. if deyimi şöyle çalıştırılmaktadır: Önce if parantezi içerisindeki ifadenin sayısal değeri hesaplanır. Bu değer sıfır dışı bir değerse deyimin yalnızca "doğru" kısmındaki deyim çalıştırılır. Bu ifadenin değeri 0 ise deyimin yalnızca "yanlış" kısmındaki deyim çelıştırılır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; printf("Bir deger giriniz:"); scanf("%d", &a); if (a > 0) printf("pozitif\n"); else printf("negatif ya da sifir\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki programda ikinci derece denklemin kökleri yazdırılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include #include void disp_roots(double a, double b, double c) { double delta; double x1, x2; delta = b * b - 4 * a * c; if (delta >= 0) { x1 = (-b + sqrt(delta)) / (2 * a); x2 = (-b - sqrt(delta)) / (2 * a); printf("x1 = %f, x2 = %f\n", x1, x2); } else printf("Gercek kok yok!..\n"); } int main(void) { double a, b, c; printf("a:"); scanf("%lf", &a); printf("b:"); scanf("%lf", &b); printf("c:"); scanf("%lf", &c); disp_roots(a, b, c); return 0; } /*---------------------------------------------------------------------------------------------------------------------- İç içe (nested) if deyimi söz konusu olabilir. Örneğin aşağıda üç sayının en büyüğünü bulan bir if deyimi kullanılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b, c; printf("a:"); scanf("%d", &a); printf("b:"); scanf("%d", &b); printf("c:"); scanf("%d", &c); if (a > b) if (a > c) printf("%d\n", a); else printf("%d\n", c); else if (b > c) printf("%d\n", b); else printf("%d\n", c); return 0; } /*---------------------------------------------------------------------------------------------------------------------- if deyiminin "yanlış" kısmı yani else kısmı olmak zoruda değildir. Eğer derleyici if deyiminin "doğru" kısmından sonra else anahtar sözcüğünü göremezse bunun "else kısmı olmayan bir if" olduğuna karar verir ve if deyiminin bittiğini düşünür. Örneğin: if (ifade1) ifade2; ifade3; Burada if eyiminin doğru kısmı ifade2 ile birmiştir ve else anahtar sözcüğü gelmemiştir. Bu udurmda artık ifade3 if içerisinde değildir. Bu kod parçasına dışarıdan bakıldığında iki deyim vardır: if deyimi ve bir basit deyim. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; printf("Bir deger giriniz:"); scanf("%d", &a); if (a > 0) printf("pozitif\n"); printf("son\n"); /* if deyiminin dışında */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bazen yeni programcılar if deyiminin doğru kısmını yanlışlıkla boş deyimle kapatırlar. Budurumda kod geçerli olduğu halde istenileni yapmaz hale gelir. Örneğin: if (ifade1); /* dikkat! yanlışlıkla yerleştirilmiş boş deyim */ ifade2 Burada artık if deyiminin "doğru" kısmında boş deyim vardır. Boş deyimden sonra else anahtar sözcüğü gemediği için if deyimi bitmiştir. Dolayısıyla ifade2; if deyimi dışındadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; printf("Bir deger giriniz:"); scanf("%d", &a); if (a > 0); /* dikkat! yanlışlıkla yerleştirilmiş boş deyim */ printf("pozitif\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki kod parçasında programcı if deyiminin "doğru" kısmına birden fazla deyim yerleştirmiştir. Bunları bloklayarak tek deyim biçiminde ifade etmesi gerekirdi: if (ifade1) ifade2; ifade3; else ifade4; Derleyici bakış açısıyla kodu incelediğimizde derleyici if parantezinden sonra blok açılmadığını gördüğünde yalnızca ifade2; deyiminin if deyiminin doğru kısmını oluşturduğunu düşünmektedir. ifade2; deyiminden sonra else gelmediği için derleyiciye göre if deyimi sonlanmıştır. Derleyici daha sonra else anahtar sözcüğünü gördüğünde durumu "sanki if olmadan yalnız başına else anahtar sözcüğü kullanılmış gibi" ele almaktadır. Bu durumda verilen mesaj "error: else without if" gibi bir şey olabilir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir koşul doğru iken diğerlerinin doğru olma olasılığı yoksa bu koşullara "ayrık koullar" denir. Yani ayrık koşullarda koşulların yalnızca bir tanesi doğru olabilmektedir. Örneğin: a > 0 a < 0 a == 0 Bu koşullar ayrıktır. Örneğin: a == 1 a == 2 a == 3 Bu koşullar da ayrıktır. Ancak örneğin: a > 0 a > 10 Bu koşullar ayrık değildir. Ayrık koşulların ayrı if deyimleri ile ele alınması kötü bir tekniktir. Örneğin: if (a == 1) printf("bir\n"); if (a == 2) printf("iki\n"); if (a == 3) printf("uc\n"); Burada a == 1 ise gereksiz bir biçimde diğer iki koşul da -doğrulanmayacağı halde*- gereksiz bir biçimde yapılmaktadır. a == 2 ise de a == 3 koşulu gereksiz biçimde yapılacaktır. İşte ayrık koşullar "else if" ile ele alınmalıdır. Örneğin: if (a == 1) printf("bir\n"); else if (a == 2) printf("iki\n"); else if (a == 3) printf("üc\n"); Burada dışarıdan bakıldığında tek bir if deyimi vardır. Her if diğerinin else kısmı içeisindedir. Pek çok programcı böyle else-if merdivenlerini aşağıdaki gibi alt alta yazmaktadır: if (a == 1) printf("bir\n"); else if (a == 2) printf("iki\n"); else if (a == 3) printf("uc\n"); else if (a == 4) printf("dort\n"); else printf("hicbiri\n"); ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; printf("Bir deger giriniz:"); scanf("%d", &a); if (a == 1) printf("bir\n"); else if (a == 2) printf("iki\n"); else if (a == 3) printf("uc\n"); else if (a == 4) printf("dort"); else printf("bes\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte sayının işareti yazdırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a; printf("Bir deger giriniz:"); scanf("%d", &a); if (a > 0) printf("pozitif\n"); else if (a < 0) printf("negatif\n"); else printf("sifir\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- C, C++, Java ve C# gibi dillerde "dangling else" denilen bir durum vardır. Eğer iki if için tek bir else varsa bu else içteki if deyimine ilişkin kabul edilmektedir. Örneğin: if (ifade1) if (ifade2) ifade3; else ifade4; Buradaki else içteki if deyiminin else kısmıdır. Bunu daha güzel şöyle yazabiliriz: if (ifade1) if (ifade2) ifade3; else ifade4; Bazen deneyimli programcılar bile bu "dangling else" durumunda hata yapabilmektedir. Örneğin aşağıdaki gibi bir kodla karşılaşmış olalım: if (ifade1) if (ifade2) ifade3; else ifade4; Burada muhtemelen programcı ifade4; deyiminin dıştaki if deyimin else kısmında olmasını istemiştir. Çünkü hizalaması bunu düşündürmektedir. Ancak derleyici hizalamaya bakmamaktadır. Dolayısıylla derleyici buradaki else kısmının içteki if deyiminin else kısmı olduğuna karar verir. O halde programcı bir "bug" yapmıştır. Bu tür "dangling else" durumlarında eğer gerçekten else kısmın dıştaki if deyimine ilişkin olması isteniyorsa bilinçli bloklama yapılmalıdır. Örneğin: if (ifade1) { if (ifade2) ifade3; } else ifade4; Burada artık else kısmı dıştaki if deyimine ilişkindir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 20.Ders 04/08/2022 ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- if deyiminin yalnızca yanlış kısmı bulunamaz. Bu işlem koşulun tersi oluşturularak dolaylı biçimde sağlanabilir. Örneğin: if (a > 0) else { /* geçersiz */ /* ... */ } Burada aslında a > 0 değilse bir işlem yapılmak istenmiştir: if (a <= 0) { /* ... */ } Tabii mademki if deyiminin yalnızca else kısmı bulunamaz. O halde doğru kısmına bir boş deyim yerleştirilerek de aynı durum sağlanabilir: if (a > 0) ; else { /* geçerli */ /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Bir program parçesının yinelemeli olarak çalıştırılmasını sağlayan kontrol deyimlerine "döngü (loop)" denilmektedir. C'de döngüler iki kısma yarılmaktadır: 1) while Döngüleri (while Loops) 2) for Döngüleri (for loops) while döngüleri de kendi aralarınd "kontrolün başta yapıldığı while döngüleri" ve "kontrolün sonra yapıldığı while döngüleri" olmak üzere ikiye ayrılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Kontrolün başta yapıldığı while döngülerinin genel biçimi şöyledir: while () while anahtar sözcüğünden sonra parantez içerisinde bir ifadenin bulunması gerekir. while döngüsü bir deyim içerir. Tabii bu deyim, basit, bileşik ya da herhangi bir deyim olabilir. Yani döngünün içerisine birden fazla deyim yerleştirilecekse bloklama yapılmalıdır. while döngüsü şöyle çalışmaktadır: Derleyici while parantezinin içerisindeki ifadenin sayısal değerini hesaplar. Bu değer sıfır dışı bir değerese (yani doğru ise) döngü deyimi çalıştırılıp başa dönülür. Döngü while parantezi içerisindeki ifadenin değeri 0 olduğunda sonlanır. Aşağıdaki örnekte 0'dan 10'a kadar (10 dahil değil) sayılar ekrana (stdout dosyasına)yazdırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; while (i < 10) { printf("%d\n", i); ++i; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte 10'dan başlanarak 0'a kadar (0 dahil değil) sayılar ekrana yazdırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 10; while (i) { printf("%d\n", i); --i; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte klavyeden (stdin dosyasından) 'q' karakteri girilene kadar döngü devam etmektedir. Burada atama operatörüne öncelik vermek için parantez kullanıldığına dikkat ediniz. Ayrıca getchar fonksiyonun ve stdin dosyasından okuma yapan diğer fonksiyonların tamponlu (buffered) çalıştırklarını anımsayınız. Eğer tampon doluysa getchar yeni bir giriş istememektedir. Ancak tampon boşsa yeni bir giriş istemektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int ch; while ((ch = getchar()) != 'q') putchar(ch); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Virgül operatörünün önce sol tarafının sonra sağ tarafının yapıldığını ve virgül operatörünün sağ tarafındaki ifadenin değerini ürettiğini anımsayınız. O halde yukarıdaki döngü eşdeğer olarak aşağıdaki gibi de olabilirdi. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int ch; while (ch = getchar(), ch != 'q') putchar(ch); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte 1'den n'e kadar sayıların toplamı hesplanmaktadır. (Tabii aslında bu toplam tek bir ifade ile de hesaplanabilirdi). ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i, n, total; printf("Bir sayi giriniz:"); scanf("%d", &n); i = 1; total = 0; while (i <= n) { total += i; ++i; } printf("%d\n", total); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aslında scanf fonksiyonun da bir geri dönüş değeri vardır. scanf fonksiyonu başarılı bir biçimde yerleştirilen parça sayısı ile geri dönmektedir. scanf stdin tamponunun başındaki boşluk karakterlerini (leading space) atar. Sonra format karakterlerine uygun olmayan ilk karakter gördüğünde onu tampona geri bırakıp işlemini sonlandırır. Örneğin: result = scanf("%d", &val); Burada biz bir sayı yerine "ali" bir yazı girmiş olalım. Bu durumda scanf a karakterini tampondan aldığında bunun %d format karakterine uygun olmadığını tespit eder. Bu a karakterini tampona geri bırakıp 0 değeri ile geri döner. Örneğin: result = scanf("%d%d", &a, &b); Burada klavyeden şunları girmiş olalım: 100 ali scanf burada yalnızca a için yerleştirme yapabilecektir. Tamponda ali kalacaktır ve b için yerleştirme yapmayacaktır. Bu durumda scanf 1 değeri ile geri dönecektir. Bu nedenle aşağıdaki örnekte eğer biz klavyeden bir syaı girmezsek sonsuz döngü oluşacaktır: ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int val; while (scanf("%d", &val), val != 0) printf("%d\n", val * val); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Yukarıdaki örnekte biz eğer klavyeden geçersiz bir karakter girildiğinde de döngüyü sonlandırmak istiyorsak scanf fonksiyonun geri dönüş değerine de bakmalıyız. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int val; while (scanf("%d", &val) && val != 0) printf("%d\n", val * val); return 0; } /*---------------------------------------------------------------------------------------------------------------------- while parantezi içerisindeki ifadede önek ya da sonek ++ ya da -- operatörü kullanılabilir. Aşağıdaki örnekte önek ++ operatörü kullanılmıştır. burada artırım önce yapılıp artırılmış değer karşılaştırmaya sokulacaktır. Dolayısıyla ilk yazılacak değer 1, son yazılacak değer 9 olacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; while (++i < 10) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Eğer wehile parantezi içerisindeki ++ ya da -- operatörüğ sonek durumundaysa artırım ya da eksiltim öncelik sırasına göre yapılmakla birlikte sonraki işleme artırılmamış ya da eksiltilmemiş değer sokulacaktır. Aşağıdaki örnekte ilk yazdırılacak değer 1'dir. Son yazdırılacak değer ise 10 olacaktır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; while (i++ < 10) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- n bir nesne belirtmek üzere biz n defa yinelenen bir döngüyü while ile şöyle oluşturabiliriz: while (n-- > 0) { /* ... */ } Bu bir kalıp olarak kullanılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int n; n = 3; while (n-- > 0) printf("ok\n"); /* üç kere yinelenecek */ return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aslında while parantezinin içerisinde yalnızca sonek ++ ya da -- operatörü varsa artırmak ya da eksiltme yapılır. Ancak kontrole nesnenin artırılmamış ya da eksiltilmemiş değeri sokulur. Dolayısıyla n pozitif olmak üzere aşağıdaki işlevsel olarak döngüler eşdeğerdir: while (n-- > 0) { /* ... */ } while (n--) { /* ... */ } while (n-- != 0) { /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Kontrolün sonda yapıldığı while döngüleri (do-while döngüleri) seyrek kullanılmaktadır. Genel biçimleri şöyledir: do while (); while parantezi sonundaki ';' boş deyim belirtmez. Kullanılması zorunlu olan sentaksın bir parçasını oluşturmaktadır. Döngünün do anahtar sözcüğü ile başlatıldığına dikkat ediniz. Yine döngü içerisinde tek bir deyim vardır. Bu deyim basit, bileşik ya da herhangi bir deyim olabilir. Örneğin: do ifade1; while (ifade2); Örneğin: do { ifade1; ifade2; ifade3; } while (ifade4); do-while döngüsünde kontrol noktasının sonda olduğuna dikkat ediniz. Dolayısıyla döngü en az bir kez yinelenmektedir. Burada do anahtar sözcüğü olmasaydı döngü kontrolün başta yapıldığı while döngüsü olarak ele alınırdı. Örneğin: { ifade1; ifade2; ifade3; } while (ifade4); Derleyiciye göre buarada iki deyim vardır: Bileşik deyim ve ondan bağımsız olarak kontrolün başta yapıldığı while döngüsü. Dolaysıyla buradaki ';' boş deyim anlamına gelmektedir. Aşağıdaki örnekte ekrana ilk çıkacak değer 0 son çıkacak değer 9'dur. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; do { printf("%d\n", i); ++i; } while (i < 10); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Kontrolünm sonda yapıldığı while döngülerine yukarıda da belirttiğimiz gibi aslında oldukça seyrek gereksinim duyulmaktadır. Aşağıdaki örnekte kullanıcıdan 'e' ya da 'h' karakteri ile bir seçim yapması istenmiştir. Eğer kullanıcı e' ya da 'h' karakterinden birini girmemişse aynı soru yinelenmiş ve kullanıcı bu karakterlerdne birini girmeye zorlanmıştır. Buradaki döngünün kontrolün sonda yapıldığı while döngüsü olması çok daha anlamlıdır. ----------------------------------------------------------------------------------------------------------------------*/ #include void clear_stdin(void) { while (getchar() != '\n') ; } int main(void) { int ch; do { printf("(e)vet/(h)ayir?"); ch = getchar(); if (ch != '\n') clear_stdin(); } while (ch != 'e' && ch != 'h'); if (ch == 'e') printf("evet\n"); else printf("hayir\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Programcılar yanlışlıkla kontrolün başta yapıldığı while döngülerini boş deyim ile kapatabilmektedir. Örneğin: while (n-- > 0); printf("%d\n", n); Burada while parantezinin sonuna yerleştirilen ';' boş deyim belirtir. Dolayısıyla artık aşağıdaki printf while döngüsünün içerisinde değildir. Eüer programcı gerçekten döngüyü boş deyim kapatmak istiyorsa (örneğin bir gecikme sağlamak istemiş olabilir) bu durumda ';' sanki bir deyim gibi hizalanmalıdır. Çünkü kodu gören kişi bunun yanlışlıkla yapılmadığını anlayacaktır: while (n-- > 0) ; ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int n; n = 5; while (n-- > 0); /* dikkat! yanlışlıkla yerleştirilmiş boş deyim */ printf("%d\n", n); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Bazen sonsuz döngülerin oluşturulması gerekebilir. Bunun için while parantezi içerisine sıfırın dışında herhangi bir sayı yerleştirilebilir. Tabii genellikle programcılar 1 sayısını tercih ederler. Örneğin: while (1) { /* sonsuz döngü (infinite loop) */ /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- 21.Ders 16/08/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- En çok kullanılan döngüler for döngüleridir. for döngülerinin genel biçimi şöyledir: for ([ifade1]; [ifade2]; [ifade3]) for anahtar sözcüğündne sonra parantezler içerisinde iki tane ';' bulunmak zorundadır. Bu iki ';' for döngüsünü üç kısma ayırır. for döngüsünün bu kısımlarında "ifade (expression)" tanımna uyan herhangi ifadeler bulunabilir. for döngüsünün içeirsindeki deyim yine herhangi bir deyim olabilir. for döngüleri en fazla aşağıdaki gibi karşımıza çıkar: for (ilkdeğer; koşul; artırım) { /* ... */ } Örneğin: for (i = 0; i < 10; ++i) { /* ... */ } for döngüsü şöyle çalışmaktadır: Önce döngüye girişte for döngüsünün birinci kısmındaki ifade bir kez çalıştırılır. Artık bu ifade bir daha çalıştırılmaz. İkinci kısımdak ifade ilk girişte ve her yinelemede çalıştırılmaktadır. Döngü bu ikinci kısımdaki ifade sıfır dışı bir değerde olduğu sürece yinelenmektedir. Döngünün üçüncü kısmı döngü deyimi çalıştırıldıktan sonra başa dönerken çalıştırılmaktadırç for döngüsünün çalışması tamamen aşağıdaki ile eşdeğerdir: ifade1; while (ifade2) { ifade3; } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 0; i < 10; ++i) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Örneğin biz artırımı ikişer ikişer de yapabiliriz. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 0; i < 10; i += 2) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki döngüde belli bir değerden eksiltim uygulanarak sıfıra kadar yinelenme sağlanmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 10; i != 0; --i) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün üçüncü kısmında ++ ya da -- operatörü kullanıldığında bunun önek mi yoksa sonek mi olduğunun hiçbir önemi yoktur. Örneğin: for (i = 0; i < 10; ++i) { /* ... */ } ile for (i = 0; i < 10; i++) { /* ... */ } eşdeğerdir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün üç kısmında da ifade tanımına uyan herhangi ifadeler yerleştirilebilir. Önemli olan bunun programcının amacına uygunluğudur. Örneğin aşağıdaki gibi bir for döngüsü tamamen geçerlidir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; for (printf("ifade1\n"); i < 3; printf("ifade3\n")) { printf("deyim\n"); ++i; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte 0'dan 6.28'e kadar sayıların sinüz değerleri 0.1 artırımla yazdırılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int main(void) { double x, y; for (x = 0; x < 6.28; x += 0.1) { y = sin(x); printf("%f\t%f\n", x, y); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte 1'den klavyeden girilen sayıya kadar sayıların toplamı hesaplanmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int n, total, i; printf("Bir sayi giriniz:"); scanf("%d", &n); total = 0; for (i = 1; i <= n; ++i) total += i; printf("%d\n", total); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte isprime fonksiyonu bir sayının asal olup olmadığını tespit etmektedir. Eğer sayı asalsa fonksiyon 1 değeri ile, asal değilse 0 değeri ile geri dönmektedir. Bir döngü içerisinde return deyimini kullanırsak fonksiyon sonlanır dolayısıyla döngü de sonlanmış olur. Aşağıdaki örnekte isprime fonksiyonundan faydalanılarak 2'den 1000'e kadar asal sayılar yan yana yazdrılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int isprime(int val) { int i; for (i = 2; i < val; ++i) if (val % i == 0) return 0; return 1; } int main(void) { int i; for (i = 2; i < 1000; ++i) if (isprime(i)) printf("%d ", i); printf("\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Öklit teoremine göre aslında sayı asal değilse mutlaka sayının kareköküne kadar bir çarpanı vardır. Yani sayının kareköküne kadar kontrol yapmak yeterlidir. Ayrıca çift sayılın kontrol edilmesine de gerek yoktur. Ancak 2 için özel bir durum vardır. 2 çift olmasına karşın asal bir sayıdır. O halde yukarıdaki isprime fonksiyonunu daha etkin çalışacak biçimde aşağıdaki gibi düzeltebiliriz. ----------------------------------------------------------------------------------------------------------------------*/ #include #include int isprime(int val) { int i; double val_sqrt; if (val % 2 == 0) return val == 2; val_sqrt = sqrt(val); for (i = 3; i <= val_sqrt; i += 2) if (val % i == 0) return 0; return 1; } int main(void) { int i; for (i = 2; i < 1000; ++i) if (isprime(i)) printf("%d ", i); printf("\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün birinci kısmındaki ifade hiç yazılmayabilir. Örneğin döngünün birinci kısmındaki ifade yukarıya alınırsa dğeişhen hiçbir şey olmaz: for (ifade1; ifade2; ifade3) ile ifade1; for(; ifade2; ifade3) eşdeğerdir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; i = 0; for (; i < 10; ++i) printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün üçüncü kısmı da yazılmayabilir. Örneğin: for (ifade1; ifade2; ifade3) ile ifade1; for (; ifade2; ) { ifade3; } eşdeğerdir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- Birinci ve üçüncü kısmı olmayan for döngüleri tamamen while döngüleriyle eşdeğerdir. Örneğin: while (ifade) { /* ... */ } ile for (; ifade; ) { /* .... */ } eşdeğerdir. Görüldüğü gibi for döngüsü while döngüsü gibi, while döngüsü de for döngüsü gibi kullanılabilmektedir: ifade1; while (ifade2) { ifade3; } ile for (ifade1; ifade2; ifade3) eşdeğerdir. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- for döngülerinin ikinci ksımındaki ifade de hiç yazılmayabilir. Bu durumda koşulun sürekli bir biçimde sağlandığı kabul edilmektedir. Örneğin: for (ifade1;; ifade2) { /* .... */ } Burada döngü sürekli yinelenir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 0;; ++i) /* sonsuz döngü */ printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aslında for döngüsünün hiçbir kısmı olmayabilir. Ancak her zaman iki tane ';' parantez içerisinde bulunmak zorundadır. Böyle for döngüleri "sonsuz döngü" oluşturmak için kullanılabilmektedir. Örneğin: for (;;) { /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { double val; for (;;) { /* sonsuz döngü */ printf("Bir deger giriniz:"); scanf("%lf", &val); printf("%f\n", val * val); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün kısımlarında virgül operatörü kullanılarak ifadeler genişletilebilir. Örneğin biz for döngüsünün birinci kısmında birden fazla dğeişkene virgül operatöründen faydalanarak değer atayabiliriz. Benzer biçimde üçüncü kısımda da virgül operatörü ile birden fazla işlem yapabiliriz. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i, k; for (i = 0, k = 100; i + k > 50; ++i, k -= 2) printf("%d %d\n", i, k); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsü yanlışlıkla boş deyim ile kapatılabilmektedir. Bu durumda boş deyim döngü deyimi gibi ele alınır. Dolayısıyla kodun anlamı tamamen değişir. Örneğin: for (i = 0; i < 10; ++i); printf("%d\n", i); Burada döngü yanlışlıkla boş deyim ile kapatılmıştır. Bu durumda printf artık döngünün dışında kalmıştır. Tabii bazen döngü gerçekten boş deyim ile kapatılmak istenebilir. Bu durumda ';' bir tab içeden yazılarak okunabilirlik artırılabilir. Örneğin: for (i = 0; i < 1000000; ++i) ; ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 0; i < 10; ++i); /* dikkat! döngü yanlışlıkla boş deyim ile kapatılmış! */ printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte ilk getchar() klavyeden okunanları tampona yerleştirir ve ilk karakterin sıra numarasıyla geri döner. Sonraki getchar çağrıları tampondaki sıradaki karakterleri alır. Tamponun sonunda ENTER tulu nedeniyle '\n' karakteri bulunacaktır. O halde aşağıdaki kodda klavyedne girilen karakterlerin sayısı hesaplanmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i; for (i = 0; getchar() != '\n'; ++i) ; printf("%d\n", i); return 0; } /*---------------------------------------------------------------------------------------------------------------------- İç içe (nested) döngüler söz konusu olabilir. Döngü deyimleri de dışarıdan bakıldığında tek bir deyim durumundadır. Eğer bir döngünün içerisinde başka bir döngü varsa blok açmaya hiç gerek yoktur. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int i, k; for (i = 0; i < 10; ++i) for (k = 0; k < 10; ++k) printf("(%d, %d)\n", i, k); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte klavyeden okunan n satır sayısı olmak üzere şu kalıp bastırılmaktadır: * ** *** **** ... ****.... **** ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int n; int i, k; printf("Bir sayi giriniz:"); scanf("%d", &n); for (i = 1; i <= n; ++i) { for (k = 0; k < i; ++k) putchar('*'); putchar('\n'); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'ye C99 ile birlikte C++'ta da zaten olan "for döngüsünün birinci kısmında bildirim yapabilme" olanağı eklendi. Bu kurala göre biz döngü değişkenini doğrudan for döngüsünün birinci kısmında bildirebiliriz. Örneğin: for (int i = 0; i < 10; ++i) { /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { for (int i = 0; i < 10; ++i) printf("%d ", i); printf("\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün birinci kısmında bildirilen değişkene ilkdeğer vermek gerekir. Standartlarda burada bildirilen değişkenlere ilkdeğer vermemek geçerli kabul edilse de toplamda anlamsızdır. Örneğin: for (int i; i < 10; ++i) { /* geçerli ama anlamsız */ /* ... */ } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün birinci kısmında bildirilen değişkenler yalnızca o for döngüsünde kullanılabilir. Çünkü orada gizli bir bloğun olduğu kabul edilmektedir. Yani örneğin: for (bildirim; ifade2; ifade3) döngüsünü eşdeğeri şöyledir: { bildirim; for (; ifade2; ifade3) } Böylece örneğin: for (int i = 0; i < 10; ++i) { /* ... */ } printf("%d\n", i); /* geçersiz! i burada faaliyet göstermiyor */ Aşağıdaki örnekte her iki for döngüsündeki i aslında o for döngülerinde kullanılan farklı yerel i'lerdir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { for (int i = 0; i < 10; ++i) printf("%d ", i); printf("\n"); for (int i = 0; i < 10; ++i) printf("%d ", i); printf("\n"); return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki gibi bir durum da mümkündür: int i; for (int i = 0; i < 10; ++i) { /* burada i'yi kullanırsak for döngüsünün birinci kısmında bildirilen i olur */ } Aşağıdaki durum da geçerli olsa da bu tür koslardan kaçınınız: for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i) { /* geçerli */ /* Burada i kullanılırsa iç for döngüsündeki i anlaşılır */ } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i) /* geçerli ama kötü teknik */ putchar('.'); return 0; } /*---------------------------------------------------------------------------------------------------------------------- for döngüsünün birinci kısmında birden fazla değişkenin bildirimi yapılabilir. Bu durumda bu değişkenlerin aynı türdne olması gerekir. Farklı türlerden değişkenlerin birinci kısımda bildirilme olanağı yoktur. Örneğin: for (int i = 0, k = 100; i + k > 50; ++i, k -= 2) { /* geçerli /* ... */ } Ancak örneğin: for (int i = 0, double k = 0; ;) { /* geçersiz! böyle bir sentaks yok! */ /* .... */ } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { for (int i = 0, k = 100; i + k > 50; ++i, k -= 2) printf("%d %d\n", i, k); return 0; } /*---------------------------------------------------------------------------------------------------------------------- break deyimi döngü deyimlerinin içerisinde ya da switch deyiminin içerisinde kullanılabilir. Genel biçimi şöyledir: break; Programın akışı break deyimini gördüğünde içinde bulunulan döngü deyimi sonlandırılır. Programın akışı döngü deyiminden sonraki deyim ile devam eder. Yani break döngüyü bitirmektedir. Tabii döngü break genellikle bir koşul altında kullanılır. Örneğin: for (;;) { /* .... */ if (koşul) break; /* ... */ } Sonsuz döngülerden çıkmak için break tek seçenektir. Ancak breajk deyimi sonsuz olmayan döngülerde de kullanılabilir. Bazen döngülerden çıkış koşulları çok çeşitli olabilmektedir. Bu tür durumlarda programcılar döngüyü sonsuz döngü yapıp içeriden break ile çıkmayı tercih edebilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { double val; for (;;) { printf("Bir sayi giriniz:"); scanf("%lf", &val); if (val == 0) break; printf("%f\n", val * val); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- 22.Ders 18/08/2022 - Perşembe ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- İç içe döngülerde break deyimi yalnızca kendi döngüsünü sonlandırmaktadır. Yani break hangi döngünün içerisinde kullanılmışsa yalnızca onu kırmaktadır. Aşağıdaki örnekte ENTER tuşuna basıldığında iç döngü sonraki yinelemeyle devam eder. q tuşuyna basıldığında önce iç döngüdeki break ile iç döngüden çıkılır, sonra dış döngüdeki break ile dış döngüden de çıkılır. ----------------------------------------------------------------------------------------------------------------------*/ #include void clear_stdin(void) { while (getchar() != '\n') ; } int main(void) { int ch; for (int i = 0; i < 10; ++i) { for (int k = 0; k < 10; ++k) { printf("(%d, %d)\n", i, k); printf("Press ENTER to continue or q to exit:"); ch = getchar(); if (ch != '\n') clear_stdin(); if (ch == 'q') break; } if (ch == 'q') break; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte bir prompt çıkartılmıştır. Bu prompt eşliğinde tek karakterli komutlar istenmektedir. q tuşuna basıldığında komut yorumlayıcıdan çıkılmaktadır. ----------------------------------------------------------------------------------------------------------------------*/ #include void clear_stdin(void) { while (getchar() != '\n') ; } int main(void) { int ch; for (;;) { printf("CSD>"); ch = getchar(); if (ch != '\n') clear_stdin(); if (ch == 'q') break; if (ch == 'r') printf("remove command executes...\n"); else if (ch == 'c') printf("copy command executes...\n"); else if (ch == 'd') printf("dir command executes...\n"); else if (ch == 'm') printf("move command executes...\n"); else printf("invalid command: %c\n", ch); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Yukarıdaki örnekteki programı baştaki SPACE ve TAB karakterlerini atacak biçimde geliştirebiliriz. Aynı zamanda baştak SPACE ve TAB atıldıktan sonra ENTER tuşuna basılırsa yeni bir prompt'a geçecek biçimde programı düzenleyebiliriz. ----------------------------------------------------------------------------------------------------------------------*/ #include int get_command(void) { int ch; while ((ch = getchar()) == ' ' || ch == '\t') ; if (ch != '\n') while (getchar() != '\n') ; return ch; } int main(void) { int ch; for (;;) { printf("CSD>"); ch = get_command(); if (ch != '\n') { if (ch == 'q') break; if (ch == 'r') printf("remove command executes...\n"); else if (ch == 'c') printf("copy command executes...\n"); else if (ch == 'd') printf("dir command executes...\n"); else if (ch == 'm') printf("move command executes...\n"); else printf("invalid command: %c\n", ch); } } return 0; } /*---------------------------------------------------------------------------------------------------------------------- if deyiminin doğru kısmında break ya da return gibi deyimler varsa if deyimine else kısmının konulmasının bir anlamı olmaz. Örneğin: if (ifade1) break; else ifade2; ile aşağıdaki eşdeğerdir: if (ifade1) break; ifade2; Aşağıdaki if deyimine bakınız: if (ifade1 && ifade2) ifade3; Bu işlemin eşdeğeri şöyledir: if (ifade1) if (ifade2) ifade3; ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- continue deyimi yalnızca döngü içerisinde kullanılaabilen bir deyimdir. Genel biçimi şöyledir: continue; Programın akışı continue deyimini gördüğünde döngünün içerisindeki deyim sonlandırılıp yeni bir yinelemeye geçilmektedir. Yani break deyimi döngü deyiminin kendisini sonlandırırken, continue deyimi döngü içerisindeki deyimin sonlandırılmasına yol açar. continue seyrek kullanılan bir deyimdir. continue deyimi for döngüsü içerisinde kullanılırsa yeni bir yineleme oluşacağı için for döngüsünün üçüncü kısmı başa dönüşte yapılacaktır. Aşağıdaki örnekte i çift iken akış continue deyimini görür. Böylece döngüde yeni bir yinelemeye geçilir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { for (int i = 0; i < 10; ++i) { if (i % 2 == 0) continue; printf("%d\n", i); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- continue deyimi özelllikle döngüler içerisindeki geniş if bloklarını elimine etmek için kullanılmaktadır. Örneğin: for (;;) { ch = get_command(); if (ch != '\n') { /* ... */ } } Bu işlemin eşdeğeri şöyle oluşturulabilir: for (;;) { ch = get_command(); if (ch == '\n') continue; /* ... */ } Tabii bazen bir döngü içerisinde pek çok yerde akışın başa sarılması istenebilir. Bu tür durumlarda continue tasarımı oldukça sade göstermektedir. continue deyimi de iç içe döngülerde yalnızca iç döngüyü başa sarar. ----------------------------------------------------------------------------------------------------------------------*/ #include int get_command(void) { int ch; while ((ch = getchar()) == ' ' || ch == '\t') ; if (ch != '\n') while (getchar() != '\n') ; return ch; } int main(void) { int ch; for (;;) { printf("CSD>"); ch = get_command(); if (ch == '\n') continue; if (ch == 'q') break; if (ch == 'r') printf("remove command executes...\n"); else if (ch == 'c') printf("copy command executes...\n"); else if (ch == 'd') printf("dir command executes...\n"); else if (ch == 'm') printf("move command executes...\n"); else printf("invalid command: %c\n", ch); } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Yalnızca sabitlerden ve operatörlerden oluşan ifadelere "sabit ifadeleri (constant expression)" denilmektedir. Örneğin: 3 2 + 5 2 + 5 * 3 1 + 2 + 3 + 4 birer sabit ifadesidir. Sabit ifadelerinin derleme aşamasında derleyici tarafından sayısal değerleri hesaplanabilmektedir. Pek çok derleyici sabit ifadelerini derleme işlemi sırasında hesaplar böylece bu işlemlerin gereksiz bir biçimde programın çalışma zamanı sırasında yapılmasını engeller. Bu optimizasyon temasına "constant folding" denilmektedir. C'de bazı durumlarda sabit ifadelerinin kullanılması zorunludur. Örneğin: - Global değişkenlere verilen ilkdeğerlerin sabit ifadesi olması zorunludur. - Global dizilerde (C99 öncesi tüm dizilerde) uzunluk sabit ifadeleriyle belirtilmek zorundadır. - case ifadeleri sabit ifadesi olmak zorundadır. ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- switch deyimi bir ifadenin çeş,tl, sayısal değerleri için farklı birtakım işlemlerin yapılması için düşünülmüş bir deyimdir. switch deyimi olmasaydı aslında gereksinim duyulan şeyler if deyimleriyle de yapılabilirdi. Ancak switch deyimi okunabilirliği artırmaktadır ve bazı durumlarda derleyicinin daha etkin kod üretmesini sağlamaktadır. switch deyiminin tipik genel biçimi şöyledir: switch () { case : /* ... */ [break;] case : /* ... */ [break;] case : /* ... */ [break;] /* ... */ [default: /* .... */ ] } switch anahttar sözcüğünden sonra parantez içerisinde bir ifade bulunmak zorundadır. switch deyimi tipik olarak case bölümlerinden oluşur. case anahtar sözcüğünden sonra sabit ifadesi bulunması gerekir. case bölümleri tipik olarak break deyimleriyle sonlandırılmaktadır. Ancak bu zorunlu değildir. switch deyiminin isteğe bağlı bir default bölümü olabilir. switch deyimi şöyle çalışmaktadır: Önce switch parantezi içerisindeki ifadenin sayısal değeri hesaplanır. Sonra bu değere tam eşit olan case bölümü araştırılır. Eğer bu değere eşit olan bir case bölümü varsa akış o bölüme aktarılır. O bölümdeki deyimler çalıştırılır. break deyimi döngülerde olduğu gibi switch deyiminin de sonlandırılmasına yol açmaktadır. Eğer switch parantezi içerisindeki ifadenin değeri ile eşit olan bir case bölümü yoksa ancak default bölüm varsa akış default bölüme aktarılmaktadır. default bölüm olmak zorunda değildir. Eğer switch parantezi içerisindeki ifadenin sayısal değerine eşit olan bir case bölümü yoksa ve default bölüm de yoksa akış switch deyimiminin dışındaki ilk deyimle devam eder. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int val; printf("Bir deger giriniz:"); scanf("%d", &val); switch (val) { case 1: printf("bir\n"); break; case 2: printf("iki\n"); break; case 3: printf("üc\n"); break; case 4: printf("dort\n"); break; case 5: printf("bes\n"); break; default: printf("hicbiri\n"); break; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aynı switch deyiminde aynı değerde birden fazla case bölümü olamaz. case anahtar sözcüğünün yanındaki ifadenin sabit ifadesi olması zorunludur. Bu sayede derleyici case ifadelerinin yinelenmediğini derleme aşamasında tespit edebilmektedir. case bölümlerinin tamsayı türlerine ilişkin sabit ifadesi olması zorunludur. Yani case anahtar sözcüğünün yanında float, double, long double gibi noktalı sayılar bulunamaz. Benzer biçimde switch parantezi içerisindeki ifade de tamsayı türlerine ilişkin olmak zorundadır. switch deyiminde case bölümlerinin sıralı (sorted) olması ya da default bölümün sonda olması bir zorunluluk değildir. Ancak case bölümlerinin sıralı olması ve default bölümün sonda olması okunabilirliği artırabilmektedir. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int val; printf("Bir deger giriniz:"); scanf("%d", &val); switch (val) { case 5: printf("bes\n"); break; case 1: printf("bir\n"); break; default: printf("hicbiri\n"); break; case 2: printf("iki\n"); break; case 4: printf("dort\n"); break; case 3: printf("üc\n"); break; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- 23. Ders 23/08/2022 - Salı ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- C'de switch deyiminde "aşağıda doğru düşme (fall through)" denilen bir özellik vardır. Akış bir case bölümüne devredildikten sonra o case bölümünün sonunda break yok ise aşağıda doğru akmaya devam eder. İlk break görüldüğünde switch'ten çıkılır. ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int val; printf("Bir deger giriniz:"); scanf("%d", &val); switch (val) { case 1: printf("bir\n"); // fallthrough case 2: printf("iki\n"); break; case 3: printf("üc\n"); // fallthrough case 4: printf("dort\n"); break; case 5: printf("bes\n"); break; default: printf("hicbiri\n"); break; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Frklı case değerleri için aynı şeylerin yapılması isteniyorsa bunun en pratik yöntemi aşağıdaki gibidir: case 1: case 2: ... break; Bunun daha pratik bir yolu yoktur. Burada switch ifadesi 1 ise fallthrouh nedeniyle zaten 2 ile aynı kod çalıştırılacaktır. Aşağıdaki örnekte komut satırı uygulaması swithc deyimi ile yapılmıştır. ----------------------------------------------------------------------------------------------------------------------*/ #include int get_command(void) { int ch; while ((ch = getchar()) == ' ' || ch == '\t') ; if (ch != '\n') while (getchar() != '\n') ; return ch; } int main(void) { int ch; for (;;) { printf("CSD>"); ch = get_command(); if (ch == '\n') continue; if (ch == 'q') break; switch (ch) { case 'e': case 'r': printf("remove command executes...\n"); break; case 'c': printf("copy command executes...\n"); break; case 'd': printf("dir command executes...\n"); break; case 'm': printf("move command executes...\n"); break; default: printf("invalid command: %c\n", ch); break; } } return 0; } /*---------------------------------------------------------------------------------------------------------------------- switch deyiminde hiçbir henüz case ya da default bölümlerinden önce kodlar yerleştirilebilir. Bildirimler yapılabilir. Burada yapılan bildirimler geçerlidir. Ancak buraya yerleştirilen kodlar çalıştırılmazlar. Dolayısıyla buradaki tanımlamada nesneye ilkdeğer veriliyor olsa bile bu ilkdeğerler işleme sokulmamaktadır. Örneğin: switch (expr) { int i = 4; f(i); //... default: printf("%d\n", i); } Burada i değişkeninin bildirimi geçerlidir. Ancak akış hiçbir zaman o noktaya gelemeyeceği için verilen ilkdeğer nesneye yerleştirilemeyecektir. f fonksiyonu da hiçbir zaman çağrılmayacaktır. Bu tür durumlarda bu işlemlerin switch yukarısına alınması gerekir. Örneğin: int i = 4; f(i); switch (expr) {} // ... default: printf("%d\n", i); } Ayrıca case ya da default bölümlerinde bildirim yapılamamaktadır. Örneğin: switch (expr) { case 1: int a; // geçersiz! //... } Yalnızca bir case ya da default bölümde değişkenin kullanılmasını istiyorsanız. Orada ayrı bir blok oluşturmalısınız. Örneğin: switch (expr) { case 1: { int a; // geçerli //... } //... } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- switch deyimlerinde case bölümlerinin çok uzatılması okunabilirliği bozmaktadır. Bu nedenle case bölümlerinde uzun işlemler yapılacaksa o işlemleri yapan fonksiyonlar tanımlanmalı ve case bölümlerinde bu fonksiyonlar çağrılmalıdır. ----------------------------------------------------------------------------------------------------------------------*/ #include int get_command(void) { int ch; while ((ch = getchar()) == ' ' || ch == '\t') ; if (ch != '\n') while (getchar() != '\n') ; return ch; } void erase_command(void) { printf("erase command executes...\n"); } void copy_command(void) { printf("copy command executes...\n"); } void dir_command(void) { printf("dircommand executes...\n"); } void move_command(void) { printf("move command executes...\n"); } int main(void) { int ch; for (;;) { printf("CSD>"); ch = get_command(); if (ch == '\n') continue; if (ch == 'q') break; switch (ch) { case 'e': case 'r': erase_command(); break; case 'c': copy_command(); break; case 'd': dir_command(); break; case 'm': move_command(); break; default: printf("invalid command: %c\n", ch); break; } } return 0; } /*---------------------------------------------------------------------------------------------------------------------- C'de case bölümlerinin hemen switch bloğüunun içerisinde olması zorunlu değildir. Bir case bölümü başka bir case bölümünün içerisinde bir yerlerde olabilir. C# ve Java gibi dillerde böyle bir özellik yoktur. Örneğin: switch (ifade) { case 1: ... ... if (falanca) { ... case 2: ..... ..... break; } break; ... } ----------------------------------------------------------------------------------------------------------------------*/ #include int main(void) { int a, b; printf("Bir deger giriniz:"); scanf("%d", &a); switch (a) { case 1: printf("bir\n"); printf("Bir deger daha giriniz:"); scanf("%d", &b); if (b > 0) { case 2: printf("islemler devam ediyor..\n"); break; } break; case 3: printf("uc\n"); break; default: printf("default\n"); break; } return 0; } /*---------------------------------------------------------------------------------------------------------------------- Aslında switch içerisind tek bir deyim varsa switch deyimi de bloklanmak zorunda değildir. Örneğin: switch (ifade) case 1: printf("bir\n"); printf("iki\n"); Burada yalnızca ilk printf printf çağrısı switch deyiminin içerisindedir. İkinci printf switch içerisinde değildir. Standartlarda case bir deyim olarak şöyle ifade edilmiştir: case : deyim switch (ifade) case 1: { printf("bir\n"); printf("iki\n"); } Burada switch içerisinde yine tek bir deyim vardır. O deyim de bileşik deyimdir. Dolayısıyla iki printf çağrısı da switch içerisinddir. Örneğin: switch (a) case 1: printf("bir\n"); break; /* geçersiz! */ Burada switch içerisinde yalnızca printf çağrısı vardır. break deyimi switch içerisinde değildir. switch ve döngü içerisinde olmayan break deyimleri geçersizdir. C standartlarında aslında switch deyiminin genel biçimi şöyle verilmiştir: switch () Yani bu genel biçime göre aslında switch deyimi case deyimini içermeyebilir. Ancak case içermeyen switch deyimleri geçerli olsa da anlamlı değildir. Örneğin: switch (ifade) { /* geçerli ama anlamlı değil */ ifade1; ifade2; } İşte switch deyiminde eğer bloklama yapılmazsa onun içeriinde tek deyimin olduğu kabul edilmektedir. Örneğin: switch (ifade) ifade1; ifade2; Burada switch içerisinde case bölümü ya da default bölümü olmadığına göre switch deyimi anlmasızdır. Ancak gerçerlidir. Burada ifade2 switch deyimi dışındadır. Bir döngü içerisinde bir switch deyimi olsun. Bu switch deyimi içerisinde break kullandığımızda biz switch deyimini sonlandırmış oluruz. Döngü deyimini sonlandırmış olmayız. Ancak continue deyimi switch için anlamlı olmadığına göre döngü içerisindeki switch deyiminde continue kullanıldığında switch deyimi de sonlanarak sonraki yinelemeye geçilir. Örneğin: for (;;) { switch (ifade) { case 1: ... if (falanca) continue; /* bu continue döngü başına dönüşü sağlar ... } .... } ----------------------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------------------------------------------- goto deyimi programın akışını koşulsuz biçimde belli bir noktaya aktarmak için kullanılan bir kontrol deyimidir. Genel biçimi şöyledir: goto