■ undefined behavior: behavior upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements ■ unspecified behavior - A program is said to have an unspecified behavior when the standard provides two or more possibilities but does not impose requirements on which should be chosen by the compiler writer. ■ implementation-defined behavior: unspecified behavior where each implementation documents how the choice is made . ▪ gcc derleyicisini browserdan kullanmak için www.wandbox.org ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 1.Ders C izledim not tutmamıştım, tekrar izleme hakkım yok ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 2.Ders C izledim not tutmamıştım, 1 izleme hakkım var ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 3.Ders C [Sayı Sistemleri ve Genel Kavramlar] ▪ Sayı (Number System) sistemleri, 2 ye tümleyen aritmetiği ▪ decimal - hex - octal - binary Low Nibble ---- ▪ 1 byte means:8 bits=binary digits 10101100 │ └ LSB Least Significant Bit └ MSB Most significant Bit ▪ 1 word = 2 bytes ▪ 1 double word = 2 words ▪ Unsigned olarak : Sayının direkt 2 lik tabandaki karşılığından bahsediyoruz. max value of a byte is decimal 255 , in binary that is 11111111 █ Örnek : işaretli tamsayı 36 nın bellekte saklandığı binary gösterimi bulunuz. Direkt 2 lik tabandaki gösterimidir 36 = 0010 0100 ▪ Signed olarak : Unsigned Sayının Two's Complement i ile elde edilir. Bunda actually, MSB, işaret biti [1 means negative, 0 positive] kabul edilir. ──────────────── └ ▪ Why Two's Complement ? For signed integers, representing them in Two's Complement has the advantage that the fundamental arithmetic operations such as addition, subtraction and multiplication are identical to those for unsigned binary numbers (as long as the inputs are represented in the same number if bits as the output and any overflow beyond those bits is discarded from the result.) Decimal To Two's Complement Conversion: ─────────────────────────────────────── Method 1 : ▪ Öncelikle sayıyı iki tabanına çevir. ▪ One's Complement : Bitleri toggle edince, o sayının 1'e tümleyenini elde etmiş oluruz. (BitwiseNOT operatörü) ▪ Two's Complement : One's Complement +1 formuluyle bulunur. █ Örnek : işaretli tamsayı -36 nın bellekte saklandığı binary gösterimi bulunuz. -36 sayısının binary gösterimi için, 36 nın gösterimini bulur ve Two's Complementini alırız. ▪ 36 = 0010 0100 ▪ One's Complement of 36 1101 1011 ▪ -36 = Two's Complement of 36 1101 1100 yorum : bu değer 2'lik tabandan decimal e çevrilse 220 Two's Complement To Decimal Conversion: ─────────────────────────────────────── Method 1 : ▪ One's Complement : Bitleri toggle et ▪ Add 1 ▪ Convert to decimal and Prepend - sign █ Örnek : işaretli tamsayı 1101 1100 nın decimal gösterimi bulunuz. ▪ One's Complement is: 0010 0011 ▪ Add 1 : 1 ────────── 0010 0100 ▪ Convert to decimal and Prepend - sign: - 36 ▪ object (nesne) : OOP dillerdeki anlamda değil. Programın çalışma zamanında bellekte bir yere sahip, belli nitelikleri olan varlıklar, okuma-yazma için kullanılan. C dilinde object ve variable aynı anlamda kullanılıyor. ▪ data type : nesnenin türü. nesne bellekte kaç byte yer kaplayacak? (storage) bellek bloğundaki 1 ve 0 lar nasıl yorumlanacak? bununla hangi işlemleri yapabilirim? tüm bunları ve birtakım özellikleri tür belirliyor. ▪ variable aspects ▪ global-local ▪ mutable-immutable ▪ data type ▪ address, value ▪ scope (faaliyet/bilinirlik alanı) ▪ linkage (bağlantı) (external-internal) ▪ storage duration/lifespan (ömür) (automatic-statik-dynamic ömürlü) Note that C dilinde storage duration ve lifespan hemen hemen aynı anlama gelsede, C++ da durum farklıdır. ▪ expression [değişkenlerin operatörlerin sabitlerin oluşturduğu yapılar] ▪ data type'i var [Bir istisna dışında] ▪ value'su var [Bir istisna dışında] ▪ value category'si var [C++ da farklı tanımları var] ▪ L value : Eğer bir ifade Programın çalışma zamanında bellekte bir yere karşılık geliyorsa (nesneye karşılık gelen ifade) - Pratik test: &(ifade) ; //syntax hatası vermiyorsa L value ▪ R value : Nesneye değilde bir hesaplamaya karşılık gelen ifade ▪ constant expression - öyle yerler var ki dilin kurallarına göre oralarda constant expression yazmak zorundayız ▪ Değeri compile zamanında belli olan/hesaplanabilen ifade (örn 10*20-50 gibi yerine 150 sabiti yazılabilen) ▪ Mesela nerelerde constant expression lazım? ▪ Dizi boyutlarını bildirirken ▪ ... ▪ Statement ▪ declaration statement ▪ expression statement --> x=5; ++x; func(); x+5;//geçerli bir statement ama mantıken uygun değil, topla bunları demişiz sonucunu kullanmıyoruz ▪ compound statement --> Blok içine yazılmış statementler { x=5; ++x; } ▪ null statement --> ; noktalı virgülün tek başına kullanıldığ deyim, i.e syntax bir deyim yazmanı istiyor ancak sen bir şey yapılmasını istemiyorsun yada null statement yazabildiğin her yere içi boş blok ta yazabilirsin yani {} ▪ control statement --> önceden belirlenmiş bir sentaksa sahip,buna uymak zorundasın, en az bir keyword gerekiyor. ▪ if statement ▪ loop statement - while / do-while / for ▪ switch statement ▪ goto statement ▪ break statement ▪ continue statement ▪ return statement │ └ 2 biçimi var: ▪ Yalın(ifadesiz) return deyimi. i.e return; Fonksiyonun çalışması sona eriyor. ▪ ifadeli return deyimi. i.e return expr; Fonksiyonun çalışması sona eriyor. Fonksiyonun geri dönüş değerinin üretilmesini sağlar. ▪ address,pointer,expression(ifade), value category, constant expression, , function, declaration, statement(deyim), definition, scope, lifespan ▪ Hello World #define --> Bu preprocessor için yazılmıştır. C bilmez. Derleme öncesi çalışır. Std 13 komutu var. stdio.h isimli dosyayı bul, içindeki bildirimleri buraya yapıştır talimatıdır. preprocessor çıktısına Translation Unit deniyor. int main(void) --> main isimli bir fonksiyon tanımlanmış. Maini diğerlerinden ayıran özellik, ilk çağrılan fonksiyon olması.- Bağlayıcı tarafından - main in çalışması bitince process sonlandırılıyor. Fonksiyonların bir ana block a sahip olması gerekiyor. ▪ Bir tamsayının asal olup olmadığını bildiren fonksiyon int isprime(int val) { if (val == 0 || val == 1) return 0; if (val % 2 == 0) return val == 2; if (val % 3 == 0) return val == 3; if (val % 5 == 0) return val == 5; for ( int k =7 ; k*k <= val ; k +=2) if (val % k == 0) return 0; return 1; } ▪ Block kavramı { } - İç içe (nested) block mümkün. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 4.Ders C [Basic Types] ■ Data Types - Veri türleri [C dilinde statik olarak - derleme zamanında - değişkenin türü biliniyor olmalı] ════════════ ■ Basic (Fundamental, built-in, Primitive) Types : Dil tarafından hazır gelen türler tipik olarak tamsayı ve gerçek sayı türlerini temsil eden türler ══════════════════════════════════════════════════ ┌───────────────────────────┬────────────┬────────────────────────────────────────────────────────────────────────────────┐ │ Basic Type │ Storage │ Notes │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ _Bool │ 1 Byte │ işaretsiz │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ char │ 1 Byte │ işaretli olup olmaması derleyiciye bırakılmış (implementation defined) │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ signed char │ 1 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ unsigned char │ 1 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ signed short int │ Min 2 Byte │ = short int = short (bildirilmezse zaten default signed ) │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ unsigned short int │ Min 2 Byte │ = unsigned short │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ signed int │ Min 2 Byte │ = int │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ unsigned int │ Min 2 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ signed long int │ Min 4 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ unsigned long int │ Min 4 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ signed long long int │ Min 8 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ unsigned long long int │ Min 8 Byte │ │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ float │ 4 Byte │ IEEE754'e göre 4 byte - Noktadan sonra hassasiyet 7 basamak , Mantissa:23 bit │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ double │ 8 Byte │ IEEE754'e göre 8 byte - Noktadan sonra hassasiyet 15 basamak, Mantissa:52 bit │ ├───────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ long double │ Min 8 Byte │ IEEE754'e göre Min 8 byte │ └───────────────────────────┴────────────┴────────────────────────────────────────────────────────────────────────────────┘ Bir türün bellekte ne kadar yer tutacağını öğrenmek istersen --> sizeof operatörünü kullanabilirsin. printf(" char turu bellekte su kadar byte yer kaplar : %zu \n",sizeof(char) ); printf(" int turu bellekte su kadar byte yer kaplar : %zu \n",sizeof(int) ); ■ User Defined Types : Dilin bazı araçlarını kullanarak oluşturduğumuz - kendi türünü kendin oluştur - türler. Kursun 2. yarısında göreceğiz. ═════════════════════ └ Structure, Union, Enum ■ Global namespace, Local namespace ? ═════════════════════════════════════ ▪ global namespace'de Sadece Bildirim (declaration) ▪ local namespace'de Hem bildirim hem deyim(statement) ■ Declaration (Bildirim) ════════════════════════ Bir ismin ne anlama geldiğini açıklayan yapılara. Dikkat ne ismi olduğu önemli değil(Typedef bildirimi, function bildirimi, değişken bildirimi ...) Tokenizing sonrası token in bir isim(identifier) olduğunu anlıyor - evet bubir issim ancak bu hangi varlığın ismi? sorusuna yanıt aramak için namelookup yapmaya çıkıyor ve bir declaration arıyor. ▪ İşaretsiz Tamsayı sınır değerleri ▪ İşaretli Tamsayı sınır değerleri ┌──────┬───────────────────────────────────────────────────────────────────┐ ┌──────┬───────────────────────────────────────────────────────────────────┐ │ Byte │ Min Max │ │ Byte │ Min Max │ ├──────┼───────────────────────────────────────────────────────────────────┤ ├──────┼───────────────────────────────────────────────────────────────────┤ │ 1 │ 0 255 │ │ 1 │ - 127 128 │ ├──────┼───────────────────────────────────────────────────────────────────┤ ├──────┼───────────────────────────────────────────────────────────────────┤ │ 2 │ 0 65535 │ │ 2 │ - 32 768 32 767 │ ├──────┼───────────────────────────────────────────────────────────────────┤ ├──────┼───────────────────────────────────────────────────────────────────┤ │ 4 │ 0 4 294 967 285 │ │ 4 │ - 2 147 483 648 2 147 483 647 │ ├──────┼───────────────────────────────────────────────────────────────────┤ ├──────┼───────────────────────────────────────────────────────────────────┤ │ 8 │ 0 18 446 744 073 709 551 615 │ │ 8 │ - 9 223 372 036 854 775 808 9 223 372 036 854 775 807 │ └──────┴───────────────────────────────────────────────────────────────────┘ └──────┴───────────────────────────────────────────────────────────────────┘ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 5.Ders C [Değişkenlerin bildirim ve tanımlanması] Initialization is not an assignment - C proverb Early optimization is evil - Donald Knuth ■ Değişkenlerin bildirim ve tanımlanması - C99 standardına kadar, yerel bildirimlerin yapıldıkları blokta bir deyim(statement) yazılmadan önce yapılması zorunluydu. ════════════════════════════════════════ ▪ Değişkenin Declaration (Bildirimi) T x; //Burada T değişkenin türü , x değişkenin ismi ▪ initialization - ilk değer verme, declaration in bir parçası [Meşhur cümleymiş : Initialization is not an assignment] T x = initializing_expression; │ └ C dilinde, statik ömürlü bir değişkene ilk değer veren ifade, "constant expression" olması zorunludur. C++ dilinde böyle bir zorunluluk yok. │ │ ▪ Globals nesne └ Constant expression, derleyici compile time da değerini biliyor olmalı. ▪ Static yerel nesne int x = 10; Buradaki = atama operatorü degil, initialization syntax inin bir parçası ilk değer verilmezse, hayata hangi değerle başlayacağı, değişkenin ömür kategorisi ile ilgili. ─────────────── Storage Duration - Lifespan ▪automatic storage : ilk değer verilmezse, Indetermined (Çöp değer) ile hayata gelir. ▪static storage : ilk değer verilmezse, Zero initialize edilirler. ▪dynamic storage : ■ , comma --> 2 kullanım [comma is a token that has 2 context] ══════════ operator olarak --> ileride incelenecek ayraç(delimiter) olarak --> i.e syntaxa ilişkin öğeleri ayırarak liste oluşturulması için (buna comma seperated list deniyor) Mesela , Aynı türden birden fazla değişken oluşturacağım zaman static int x,y,z; static int x=10,y=20,z=30; // static ve int hepsine etkir. Mesela func(1,2,3); Fonksiyon parametrelerini ayırmak için Mesela int ar[] = {1,2,3}; dizilere ilk değer vermek için ■ const anahtar sözcüğü - şimdilik ilk defa tanışalım ═════════ mutable - değiştirilebilir immutable - değiştirilemez oxymoron - birbiriyle çelişen yada tamamen zıt iki kavramın bir arada kullanılması ve bu şekilde oluşturulmuş ifade i.e: orijinal kopya, sessizce haykırmak vb... const variable ifadesi oxymoronic ! const int x = 10; const static int y = 20; Sabitleri kod içinde kullanmaya kolaylık sağlayan araçlar const int x = 10; #define PI 3.14 enum Color {White,Red,Black}; ■ storage duration - lifespan ══════════════════ ▪automatic storage --> açıkça ilk değer verilmezse, garbage(indetermined) değer sahibi olur, bu değeri kullanmak UB oluşturur. --> fonksiyonların parametre değişkenleride otomatik ömürlüdür. ▪static storage --> açıkça ilk değer verilmese bile, Zero Initialize edilir.Yani 0 gibi bir değer sahibi olur.(Örn, global namespacede tanımlanan ve local namespace de önüne static sözcüğü eklenenler) --> Ayrıca string literals de statik ömürlüdür. ▪dynamic storage ■ Scope (kapsam - bilinirlik alanı - faaliyet alanı) - isimlere ilişkin bir kavram, namelookup ile yakın temasta ═════════ Hangi kod alanında kullanabilirim? Derleyici bunu nerede arar? sorularının muhatabıdır. C de 4 scope kategorisi var: ▪file scope --> global isim alanında bildirilen, bildirildikleri noktadan dosyanın sonuna dek faaliyet alanına sahip ▪block scope ▪function prototype scope ▪function scope Kural : Aynı scopeta, aynı ismi birden fazla varlığa verilemez. ■ Name lookup (isim arama) ══════════════════════════ Dilin belirlediği bir sırayla yapılır, aranan ismin bulunmasıyla arama süreci biter Aynı scopeta, aynı ismi birden fazla varlığa verilemez. Scope Leakage (Kapsam sızıntısı) Tanımlanan değişken , kullanılan kod alanı dışında da erişim/kullanım hakkına sahip. Yanlışlıkla kullanıma çanak tutyor, hata arama/eklenti yapmak zorlaşıyor █ Örnek : int x(int); int main() { x=5; //Error, fakat sebebi name lookup değil, sebebi context kontrolü, x ismini yukarıda buldu çünkü ve isim arama bitti } int main() { int printf = 0; printf("hello"); //Syntax error, verdiği hata:printf not a function. Önce blok scope da - yukarı doğru - aradı ve buldu. bulamasaydı global isim alanında arayacaktı } █ Örnek : int x = 10; int main() { int x= 20; ++x; lokal deki x i bir artırdım //C de, Maskeleme altında , artık Global x e buradan ulaşma şansı yok! //C++ da, ::x ile direkt global deki x e ulaşılıyor. } █ Örnek : Meşhur bir soru imiş. int x = 10; int main() { int x= x; // Tanımladığımız lokal x 'e kendini atamaya çalışmış. İlk değeri olmadan kullanmaya çalışmak UB. Scope, = in sağından itibaren başlar. } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 6.Ders C [Functions] Bazı dillerde method, subroutine, procedure olarak da anılıyor. Define - Declare - Call a function Çağıran -----> Çağrılan <----- Çağıran bilgi döndürmek için: ┌── ▪ Geri dönüş değeri (C dilinde bir fonksiyonun 1 tane değer geri dönüş değeri olabilir.) │ ▪ Sen bana değişkenin adresini gönder, ben senin değişkenine hesapladığım değeri yazayım │ ▪ Global değişkenler üzerinden │ └──────────→ ■ Fonksiyonların geri dönüş değeri niçin var ve ne işe yarıyor? Fonksiyonları kategorize edelim: ═════════════════════════════════════════════════════════════════════════════════════════════════ ▪ Bir değer hesaplamaya yönelik fonksiyonlar, hesaplanan değer geri dönüş değeri olarak çağıran koda aktarılır. ▪ Bir soruya doğru/yanlış, geçerli/geçersiz, evet/hayır gibi yanıt veren fonksiyonlar [predicate : bool döndüren func, test func/query func] C de çakma _Bool yerine tipik olarak int döndürülür. 0: false NonZero: true anlamındadır. Böyle dönüşlere boolean int diye de anılır. ▪ Bazı fonksiyonların geri dönüş değeri "başarı" bilgisidir. - geri dönüş değeri varlık nedeni Mesela , bazı kütüphanelerde başarı SUCCESS 0, FAILURE Nonzero değer ile döndürülür. Mesela , bazı fonksiyonlarda başarı SUCCESS nesne adresi, FAILURE null pointer ile döndürülür. Mesela , bazı fonksşyonlar FAILURE durumunu global ERRNO değişkeni üzerinden bildiriyor. ▪ Bazı fonksiyonların geri dönüş değeri, yapılan işle ilgili tamamlayıcı bir bilgi - geri dönüş değeri varlık nedeni değil Mesela , printf fonksiyonu, geri dönüş değeri int ve bu değer ekrana yazdığı karakterlerin sayısıdır. Böylesi bir çağrıda , geri dönüş değerini discard ederek çağrıyı gerçekleştireceksek, bunu bilerek/kasıtlı yaptığımızı okuyana vurgulamak için başına (void) yazarız. ■ Declaration of Functions ══════════════════════════ Bildirim Açıklama ────────────────── ──────────────────────────────────────────────────────────────────────────────────────────── int func(int x) fonksiyonun parametre değişkeni x void func(int x) geri dönüş değeri yok, parametre değişkeni x void func(void) geri dönüş değeri yok ve aynı zamanda parametre değişkeni yok void func() geri dönüş değeri yok, ANCAK, parametre değişkeni olmamasını istediğin için böyle yazdın ama C dilinde burayı boş bırakmanın bir anlamı var [parametre değişkeni olabilirde olmayabilirde ben bu konuda bilgi vermiyorum] demektir, artık bu kural dilden kaldırıldı ama derleyiciler durumdan vazife çıkarıyor.[backward compatibility namına] void func(int x, ...) Variadic function using ellipses token func(double) Implicit int kuralı Geri dönüş tipi yazılmazsa, int varsayılır! Aslında bu geçmişte kaldı, artık böyle bir kural yok, ama derleyiciler durumdan vazife çıkarıyor.[backward compatibility namına] ■ Definition of Functions ══════════════════════════ int func(int x, int y) { //Buraya fonksiyonun ana bloğu deniyor } ▪ Fonksiyon içinde fonksiyon tanımlanamaz.Tüm fonksiyonlar global isim alanında tanımlanmalı. ■ Return statement - Aslında control statement kümesinin bir bileşeni - ══════════════════ ▪ Tek bir return dan oluşan fonksiyonlara oneliner func deniyor. Hatırlatma: Statement lar nelerdi? ▪ declaration statement ▪ expression statement --> x=5; ++x; func(); x+5;//geçerli bir statement ama mantıken uygun değil, topla bunları demişiz sonucunu kullanmıyoruz ▪ compound statement --> Blok içine yazılmış statementler { x=5; ++x; } ▪ null statement --> ; noktalı virgülün tek başına kullanıldığ deyim, i.e syntax bir deyim yazmanı istiyor ancak sen bir şey yapılmasını istemiyorsun yada null statement yazabildiğin her yere içi boş blok ta yazabilirsin yani {} ▪ control statement --> önceden belirlenmiş bir sentaksa sahip,buna uymak zorundasın, en az bir keyword gerekiyor. ▪ if statement ▪ loop statement - while / do-while / for ▪ switch statement ▪ goto statement ▪ break statement ▪ continue statement ▪ return statement │ └ 2 biçimi var: ▪ Yalın(ifadesiz) return deyimi. i.e return; Fonksiyonun çalışması sona eriyor. ▪ ifadeli return deyimi. i.e return expr; Fonksiyonun çalışması sona eriyor. Fonksiyonun geri dönüş değerinin üretilmesini sağlar. ▪ redundancy [Buradaki anlamı olmasa da olur anlamında] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 7.Ders C [Functions cont'd] █ Örnek : Bir tamsayının kaç basamaklı olduğunu bulduran bir program yazalım. #include int ndigit(int x) { ..... int digit_count = 0; . if (x==0) return 1; . while(x != 0) . Fonksiyon ana bloğu { . ++digit_count; . x /= 10; . } . } ..... int main() { int ival; printf("Bir tamsayi girin: "); scanf("%d",&ival); printf("%d sayisi %d basamaklidir\n", ival, ndigit(ival)); } ■ Fonksiyonların Çağrılması ═══════════════════════════ ▪ C ve C++ dillerinde Fonksiyon Çağrıları bir ifade oluşturur ve bir operatör [fonksiyon çağrı operatörü] ile gerçekleştirilir. function_designator(arguman ifadeleri) ▪ Çağrılan fonksiyon bir void fonksiyon ise - bir istisna dışında - fonksiyon çağrı ifadesinden yalnızca statement yapılabilir. │ │ Aslında soru şu: void bir fonksiyona yapılan çağrı ifadesinin türü void türüdür. Böyle ifadeler bir başka ifadenin alt ifadesi (sub-expression) olabilir mi? Cevap: Virgül operatörünün sol operandı olabilirler. Eğer değeri kullanılmayacaksa virgül operatörünün sağ operandı olabilirler. ■ Fonksiyonlara Parametre Aktarımı ══════════════════════════════════ ▪ Call/Pass by value (Değerle çağrı) : Fonksiyon nesnenin kendisine erişmiyor, kendisine nesnenin bir kopyası gönderiliyor. C dilinde default olarak tüm fonksiyon çağrıları call by value'dur. ▪ Call/Pass by reference(Referansla çağrı) : Fonksiyon nesneye erişiyor, fonksiyona nesneye erişim için bir referans gönderiliyor. Aslında adresin call by value ile yapılmasıdır. ■ Fonksiyonlar ile ilgili olarak C'de olmayanlar: [C++ 'a kıyasla] ═══════════════════════════════════════════════════════════════ ▪ C dilinde function overloading YOK! [Belirli kurallarla birden fazla aynı isimli fonksiyon olmasına Function Overloading deniyor.] ▪ C dilinde default argument mekanizması YOK! [Fonksiyonun belirli parametrelerine arguman göndermediğinde, bunların öntanımlı değerler yollanmış gibi işletilmesine Default Argument mekanizması deniyor.] ■ main hakkında notlar: ════════════════════════ ▪ Geri dönüş değeri tipik olarak sisteme bağlı. En sık kullanılan konvansiyon başarı bilgisi olması. 0 : SUCCESS , Non-Zero: FAILURE ▪ int main(void) --> Parmetre parantezinin içine void yazmakla boş bırakmak aynı anlamda değil! ▪ tipik bir main için --> int main(int argc, char **argv) Burada argc: argument count , argv: argument vector ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 8.Ders C [Constants ] Constants are also called literals. Tokenize edildiğinde token ların kategorize edildiğinden bahsetmiştik. Bu kategorilerden bir tanesi de Constants. Sabitlerin de türleri var. Sözgelimi, x=0; // 0 int mi? unsigned int mi? double mi? ■ Numeric Constants ═══════════════════ ┌───────────────┬───────────────┬───────────────┐ │ Önek │ Sayı │ Sonek │ │ Prefix │ 234 │ Suffix │ ▪ Basamakları ayırmak C de std da yok.(C++ da std var). └───────────────┴───────────────┴───────────────┘ C de compiler extension olarak bulunabiliyor.int x = 234'872'343 ; int x = 0b1101'1001; │ └ Type │ └ Sayı Tabanı ▪ Decimal : , int x = 234; ▪ Hex Base : 0x , int x = 0x234; ▪ Octal Base: 0 , int x = 0234; ▪ Binary : Compiler extension Non-Std in C but std in C++, int x = 0b11011001; ┌ Negatiflik ile ilgili ilginç gerçek: int x = -234; Burada -234 bir sabit ifadesi. │ 234 sabit tamsayı , - (işaret) operatörü ▪ Integer Constants (char ve short ve _Bool türleri burada yok) █ Örnek : -234, 15u, 0x345UL, 0234 Yani burada sabit sayı -234 değil, 234 dir. Sonek ────────────── ▪ signed int : yok ¹ i.e 234 ▪ unsigned int : u veya U ² i.e 0x234u ▪ signed long : l veya L i.e 0x234L ▪ unsigned long : ul veya UL i.e 0x234UL ▪ signed long long : ll veya LL i.e 0x234LL ▪ unsigned long long : ull veya ULL i.e 0x234ULL ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ¹ : Sayı , int sınırları içinde kalıyorsa, yazılan tamsayı int olarak alınır. kalmıyorsa, Decimal olarak yazılmışsa, sırasıyla long ve long long içine sığıp sığmadığına bakılır ve türüne karar verilir. Hex/Octal olarak yazılmışsa, unsigned int - unsigned long - unsigned long long a sığıp sığmadığına bakılır ve türüne karar verilir. ² : Sonek u ise, unsigned long - unsigned long long a sığıp sığmadığına bakılır ve türüne karar verilir. Yani asla işaretli türe hiç bakılmaz. ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Real/floating Constants █ Örnek : 15.12, -15.13f , 1.2E5 means 1.2 x 10^5 , 1.2E-5 ▪ float : f i.e 2.3f ▪ double : Yok ve . içeriyor i.e 2. ▪ long double : l veya L . içeriyor i.e 0.689985L Scientific Notation 1.2E5 = 1.2 x 10^5 1.2e+5 1.2E-5 ▪ İşaretli türlerde taşma (overflow) UB'dir. ▪ İşaretsiz türlerde taşma durumunda , modular aritmetik devreye girer. Bir hata oluşmaz. ■ Character Constants ═════════════════════ Karakter sabitlerinin türü int türüdür. [Note that C++ dilinde karakter sabitlerinin türü char türüdür.] Dolayısıyla int türden sabit yazmanın alternatif bir yolu karakter sabiti yazmaktır. ▪ Single Character Constants █ Örnek : 'A' ▪ String Constants █ Örnek : "murat" ▪ Backslash Character Constants █ Örnek : '\n' │ (also known as Escape Sequences) │ └ ▪ '\a' Alert - Çan sesi karakteri printf("alert karakteri Kodu %d\n",'\a'); ▪ '\b' Backspace - Geri boşluk ▪ '\n' New line printf("Newline karakteri Kodu %d\n",'\n'); ▪ '\r' Carriage Return ▪ '\f' Form feed ▪ '\t' Horizontal Yatay Tab printf("Yatay tab karakteri Kodu %d\n",'\t'); ▪ '\v' Vertical Dikey Tab ▪ '\0' null character ▪ '\\' \ backslash karakteri ▪ '\?' ? question mark --> '?' şeklindeki kullanımda uygun ▪ '\'' ' karakteri ▪ '\"' " karakteri --> '"' şeklindeki kullanımda uygun ■ Karakter sabitlerinin değeri - ASCII tablosu Bunlar int türü demiştik. Karakterin görüntüsüyle karakterin kendini birbirini karıştırmayalım. int x = '+'; // Okay , karakter sabiti ile ilk değer verme int x = '\x1A'; // Okay , karakter sabiti ile ilk değer verme int x = 'A'; // Okay , x is 65 Peki sabitlerin değeri nedir? Cevap : ASCII tablosuna bakmalıyız. Örn 'A' ascii karakter kodlamasında karakter kodu 65 ═══════════════════════════════════════════════════════════════ ASCII (American Standard Code for Information Interchange) is a 7-bit characters code, with values from 0 to 127. ASCII tablosu 128 karakter The ASCII code is a subset of UTF-8 code. ═══════════════════════════════════════════════════════════════ The ASCII code includes control characters and printable characters: digits, uppercase letters and lowercase letters. 0 NUL 24 CAN 48 0 65 A 97 a 26 adet büyük harf, 26 adet küçük harf mevcut.Rakamlar sıralı, Büyük harfler sıralı, küçük harfler sıralı. 1 SOH 25 EM 49 1 66 B 98 b Dikkat çeken hususlar: 2 STX 26 SUB 50 2 67 C 99 c ▪ Rakamlarda, binary karşılığının düşün anlamlı nibble ının değeri , rakamın kendisi 3 ETX 27 ESC 51 3 68 D 100 d ▪ A..Z 6 adet karakter a..z arası 6 karakterin amacı , 4 EOT 28 FS 52 4 69 E 101 e büyük harf kodunda 1 bit değiştirerek, küçük harfini elde edebilmek. 5 ENQ 29 GS 53 5 70 F 102 f 6 ACK 30 RS 54 6 71 G 103 g 7 BEL 31 US 55 7 72 H 104 h 8 BS 32 space 56 8 73 I 105 i 9 HT 33 ! 57 9 74 J 106 j 10 LF 34 " 58 : 75 K 107 k 11 VT 35 # 59 ; 76 L 108 l 12 FF 36 $ 60 < 77 M 109 m 13 CR 37 % 61 = 78 N 110 n 14 SO 38 & 62 > 79 O 111 o 15 SI 39 ' 63 ? 80 P 112 p 16 DLE 40 ( 64 @ 81 Q 113 q 17 DC1 41 ) 82 R 114 r 18 DC2 42 * 83 S 115 s 19 DC3 43 + 84 T 116 t 20 DC4 44 , 85 U 117 u 21 NAK 45 - 86 V 118 v 22 SYN 46 . 87 W 119 w 23 ETB 47 / 88 X 120 x 89 Y 121 y 90 Z 122 z 91 [ 123 { 92 \ 124 | 93 ] 125 } 94 ^ 126 ~ 95 _ 127 DEL 96 ` ═════════════════════════════════════════════════════════════════ ▪ Kontrol Karakterleri : Görüntüsü olmayan , özel amaçla kullanılan karakterlere denir. ▪ Alfabetik karakter [Harfler]┐ ├ İkisinin bileşimine alfanumerik karakterler [Alfabetik + Numeric] denir. ▪ Numerik karakter [Rakamlar]┘ ▪ Punctuation karakterleri : Görüntüsü olan ancak alfanumerik olmayanlara denir. │ └ ─────────────────────────────────────────────────────── $ dollar sign - C de kullanılmayan bir karakter @ at sign - C de kullanılmayan bir karakter ` back-quote(grave accent) - C de kullanılmayan bir karakter ─────────────────────────────────────────────────────── ? question mark ! exclamation mark " double quote # diyez / number sign % percent ' single quote ( ) paranthesis - opening /closing * asterisk & ampersand + plus - hypen . dot/period , comma : colon ; semicolon / slash \ backslash < less than > greater than = equal sign < > angular bracket [ ] square bracket left / right { } curly brace - opening /closing | pipe ~ tilda ^ caret / circumflex _ underscore/underline ═════════════════════════════════════════════════════════════════ ▪ Karakterlerle ilgili başlık dosyasında, bize verilen test fonksiyonları var. ▪ int isblank(int c) ▪ int isdigit(int c) ▪ int isalpha(int c) ▪ int isalnum(int c) ▪ int isxdigit(int c) ▪ int islower(int c) ▪ int isupper(int c) ▪ int isspace(int c) ▪ int isctrl(int c) ▪ int ispunct(int c) ▪ int isprint(int c) ▪ int isgraph(int c) █ Örnek : #include #include int main() { for(int i = 0; i<128;i++) { if(iscntrl(i)) printf("%#x %3d Kontrol Karakteri\n",i,i); else printf("%#x %3d %c\n",i,i,i); } } █ Örnek : printf("%.20f\n",0.4); // Bak bakalım ekrana ne yazdırıyor? Ondalıklı kısmı (. dan sonraki ) - mantissa - binary tutulduğu için aslında bir yaklaşımla (1/2+1/4+1/8..) - 2'nin azalan kuvvetleri ile hesaplandığından - ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 9.Ders C [Std Library : printf scanf getchar putchar] ■ Std C kütüphanesinin 3 varlık nedeni: ════════════════════════════════════════ ▪ Common Interface sağlaması - Ortak Arayüz ilkesi , herkes abs nin ne olduğunu anlayıveriyor ▪ Sınanmış/belirli bir verim düzeyi garantisi var/Hazır ▪ Taşınabilirlik sunması Farklı modüller ve her module ilişkin bir başlık dosyası var. Örn; ... Kendi oluşturduğumuz başlık dosyalarında "Muratutils.h" şeklinde include ediyoruz. ■ Input-Output Operations (Giriş-Çıkış İşlemler) ════════════════════════════════════════════════ input stream --> Prog --> output stream , bunlar byte stream standard input dediğimizde , tipik olarak klavye standard output dediğimizde , tipik olarak monitör ■ Formatted input-output : muhatabı INSAN printf scanf ═══════════════════════════ Bellekteki byte ların yorumlanarak - insanın anlayacağı ve insanın tercihine bağlı - metne dönüştürülerek aktarımı i.e; Bir sayıyı ekranda gösterirken hangi tabanda , .dan sonra kaç hanesini gösterilsin gibi... ▪ printf ---- Yazıyı ekrana yazıyor ▪ sprintf ---- Yazıyı verdiğimiz değişkene yazıyor ▪ fprintf ---- Yazıyı dosyaya yazıyor ▪ Hatırlatma :Variadic parametre tek başına bildirilemez. Öncesinde en az 1 tane normal parametre olmalı. void func(int x, ...) --> variadic function using ellipses token Bunu çağırırken, variadic taraf ile ilgili arguman sayısı konusunda serbestiz, ancak öncesindeki argumanlar gönderilmeli örn: func(); //Syntax error func(5); //Okay func(5,1,2,3,4); //Okay ■ int printf(const char * ptr, ...) ═══════════════════════════════════ Geri dönüş değeri : number of characters transmitted to the output stream or negative value if an output error or an encoding error Parameter const char * ptr : Bizden yazı istiyor, Peki yazı nedir? C dilinde yazı, null terminated byte stream = char array de tutuluyor . Yani yazı demek, char dizi demek. Dikkat! C dilinde bir dizinin bir fonksiyona gönderilmesinin tek yolu pointer semantiği, yani call by value mümkün değil. ... : istediğimiz kadar ifadeyi ekrana yazdırabilmemiz için printf("Bir tamsayi girin:"); printf("ival = %d \n", ival); printf("x= %d y=%d z=%d\n",x,y,z); ▪ Formatlama tercihi için % .. %d int türünü decimal olarak yazdırmak için %9d toplam yazdırma alanına sağa yaslı ve doldurma karakteri boşluk karakteri, ancak bu opsiyon budamaya sebep olmaz(yazılacak text ayrılan alandan daha uzun ise) %x hex %o octal %u unsigned int olarak yazdırmak için %f gerçek sayı türleri(float ve double) için fixed format ta decimal olarak yazdırmak için %.2f . dan sonra 2 basamağını yazdırır %zu size_t türüne karşılık Detaylarına kendin bak. █ Örnek: int x = 1879; printf( "%d",printf("%d", printf("%d",x))) Output : 187941 ▪ String Literallerine bir gönderme yapalım: ════════════════════════════════════════════ C dilinde "some text" gibi ifadelere, string Literal deniyor. Aslında bu da bir array. Ancak bu şekilde yazdığımızda , array i oluşturan derleyici. Bu dizinin sonunda da null karakteri var. Yani "alev" demek char[5] demek, "murat" demek char[6] demek Dikkat : string Literalindeki karakter sayısı başka, ama fiilen orada kullandığımız karakter sayısı başka. Sözgelimi, printf("\x42USR\x41); --> ekrana BURSA yazar. gördüğün gibi fiilen bir sürü karakter koyduk, ekrana ise başka şey çıktı. ■ int scanf(const char* ptr,..) ═══════════════════════════════════ Bir yada birden fazla değişkeni, std input tan gelecek karakterleri kullanarak set edecek. (std input klavyeye bağlı olduğunu düşünelim) Geri dönüş değeri = set ettiği nesne sayısı Gelen karakterleri, Standard input buffer denen bellek alanından alır. Line buffered input function dır. Yani,scanf çağrısı yapıldığında, std input buffer boşsa, girişin tamamlanması için newLine karakteri - enter a basmak - gerekiyor. ÖNEMLİ : Whitespace karakterleri skip eder (boşluk, carriage return, tab gibi..) █ Örnek: double x; │ int x,y,z; printf("bir gercek sayi girin"); │ printf("uc tamsayi girin"); scanf("%lf", &x); │ scanf("%d%d%d", &x,&y,&z); // %d ler arasında boşluk olmadığına dikkat edin printf("Girdiginiz sayi x=%f\n",x); │ printf("Girdiginiz sayilar %d %d %d\n",x,y,z); ▪ scanf değişkenimi setlemiyor, neden olabilir? Karakterler formatlama verisi ile uyumsuz olabilir. Format stringi ilave karakterler içeriyor olabilir. Std input buffer da kalan atıklar istenmeyen setlenmelere sebep olmuş olabilir. ▪ std input buffer a temizlemek Şimdilik şunu kullanalım: - (hemen internete yazıp da şunları görüp dersimizin şimdilik ilerleyişinde şu an asla kullanma fflush(stdin); ve rewind(stdin);) void clear_input_buffer(void) { int ch; while( (ch=getchar())!='\n' && ch!=EOF) ; //null statement } ■ UnFormatted input-output : muhatabı MAKINA getchar putchar ═══════════════════════════ Bellekteki byte ların olduğu gibi aktarımı, yani bir dönüşüm/yorum sözkonusu değil - düşük maliyetli işlem ■ int getchar(void) - Std input bufferdaki ilk karakterin kodunu bize bir tamsayi olarak verir ═══════════════════ Bu da, scanf gibi, Standard input buffer i kullanir. getchar da Line buffered. - Girişin tamamlanması için New Line a ihtiyaç duyuyor. Echo veriyor = Ekranda gösteriyor █ Örnek : printf("Bir karakter girin"); int c = getchar(); printf("c = %d\n",c); //Karakterin kodunu yazacak, sözgelimi a girişi için 97 yazacak █ Örnek : printf("Bir giris yapin"); //567ALI int c1 = getchar(); //5 i alır, input buffer içeriği 67ALI haline gelecek int c2 = getchar(); //6 i alır, input buffer içeriği 7ALI haline gelecek int c3 = getchar(); //7 i alır, input buffer içeriği ALI haline gelecek printf("c1 = %d\n",c1); printf("c2 = %d\n",c2); printf("c3 = %d\n",c3); █ Örnek : Bir döngü deyimi ile girilen karakterleri alt alta ekrana bastır printf("isminiz nedir"); int c; while( (c=getchar()) != '\n' ) { printf("%d\n",c) } █ Örnek : int x; | Bu prg da bir sayi girip entera bastiğimizda, hayir dedin yazisi ile karşilaşiyoruz. Neden? printf("bir tamsayi girin:"); | Çünkü, giriş mesela 123enter olsun. scanf("%d , &x"); | Scanf 123 ü alıyor printf("evet(e) hayir(h) ?"); | Buffer da enter kaliyor. getchar da bununla karşilaşip tetikleniyor ve new line döndürüyor. int c = getchar(); | tabi bufferda boşalmış oluyor. if(c=='e') printf("evet dedin"); | else printf("hayir dedin"); | █ Örnek : Klavyeden e yada h tuşuna basılana dek bekle, means e değilse ve aynı zamanda h değilse bekle int c; while( (c=getch()) != 'e' && c!='h' ) ; //Null statement Dikkat : Bir scanf çağrısından sonra , std input un buffer ın sonunda NewLine kalır. böyle bir anda getchar çağrısı yapılırsa, getchar NewLine karakterini yer. ▪ getchar alternatifleri: Non-Std! getch Non-Std komut, newline beklemiyor, echo vermiyor getche Non-Std komut, newline beklemiyor, echo veriyor ■ int putchar(int) - Std output buffer'ına tek bir karakter veriyor. ══════════════════ putchar('A'); //Ekrana A yazar putchar(65); //Ekrana A yazar for (int i = 'A' ; i <= 'Z' ; ++i) { putchar(i); } putchar( rand() %26 + 'A' ); //Rastgele bir karakter ekrana bastırıyorum. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 10.Ders C [operatorler] ■ Operatorler ══════════════ ▪ Operatörler de bir token [kaynak kodun anlam taşıyan en küçük birimi]. Hatırlayalım, token larında türleri vardı: keyword, identifier, operator ▪ C dilinde C99 u saymazsak 45 adet operatör var. ▪ Operatörler 1 - 2 - 3 karakterden olanları var. Bundan başka birde sizeof operatörü var. ▪ Operatörleri aldıkları operandlar açısından incelersek unary-binary-ternary olarak sınıflandırabiliriz. ▪ Operatörleri bulundukları konuma göre incelersek prefix-infix-postfix olarak da sınıflandırabiliriz. ▪ Operatörler bulunduklara yere göre farklı işlevler de üstlenebiliyor. i.e, &x (addressof) ile x&y (bitwiseAND) ▪ Operatör demek bir işlem yapılması demektir. İşlemin girdisi ile çıktısı sözkonusudur. Çıktısına operatörün ürettiği değer diyoruz. ▪ Ürettiği değerden başka bir de SIDE EFFECT sözkonusu. Yani bir durum değişikliği.Sözgelimi bir değer üretirken bir de değişkenin değerini değiştirme gibi. Bu değişken değerinin değişmesine ilişkin- Side Effect - SEQUENCE POINT denen konumalarda yerine getirilir. ■ Operator Onceliği : ════════════════════ Bir işlemin daha önce - sonra yapılması anlamında değildir. Birden fazla operator içeren bir ifade de hangi operatörün ürettiği değer hangi operatörün operandı olacak anlamındadır. Gruplama nasıl yapılacak bu garanti altındadır. İlginç ama gerçek: a = f1() + f2() * 5 ; ifadesinde f2() daha önce çağrılır demek yanlıştır. Tabiki f2() de çağrılabilirde. Aslında burada Unspecified behaviour var. Burada operatör önceliği tablosu bize şu gruplamayı verir a = f1() + ( f2() * 5 ) ; Bunu ORDER OF EVALUATION ile karıştırmamak lazım. a = x + y ; ifadesinde önce hangisi hesaplanır x mi y mi? İşte buna order of evaluation denir? ═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ Operatör Öncelik Tablosu - by NEC ═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ ┌────┬──────────────────────────────────────────────────────────────────────────── │ No │ Operator / Description ├────┼──────────────────────────────────────────────────────────────────────────── │ 1 │ [] () . -> Köşeli Parantez, Fonksiyon Çağrı, Nokta, Ok operatörleri ├────┼──────────────────────────────────────────────────────────────────────────── │◄2 │ + - ++ -- (type) sizeof & * ! ~ Unary Operators işaret,inc,dec,tür dönüştürme,adres,içerik, lojikdeğil, bitwiseComplement operatörleri ├────┼──────────────────────────────────────────────────────────────────────────── │ 3 │ * / % Multiplicative ├────┼──────────────────────────────────────────────────────────────────────────── │ 4 │ + - Additive ├────┼──────────────────────────────────────────────────────────────────────────── │ 5 │ >> << Bitwise Shift ├────┼──────────────────────────────────────────────────────────────────────────── │ 6 │ > >= < <= Comparison (Relational) ├────┼──────────────────────────────────────────────────────────────────────────── │ 7 │ == != Comparison Equality ├────┼──────────────────────────────────────────────────────────────────────────── │ 8 │ & Bitwise AND ├────┼──────────────────────────────────────────────────────────────────────────── │ 9 │ ^ Bitwise XOR ├────┼──────────────────────────────────────────────────────────────────────────── │ 10 │ | Bitwise OR ├────┼──────────────────────────────────────────────────────────────────────────── │ 11 │ && Logical AND ├────┼──────────────────────────────────────────────────────────────────────────── │ 12 │ || Logical OR ├────┼──────────────────────────────────────────────────────────────────────────── │◄13 │ ?: Ternary Operator ├────┼──────────────────────────────────────────────────────────────────────────── │◄14 │ = += -= *= /= %= >>= <<= &= ^= |= Assignment Operators ├────┼──────────────────────────────────────────────────────────────────────────── │ 15 │ , Comma Operator └────┴───────────────────────────────────────────────────────────────────────────── ◄ Sağdan sola doğru associativity (öncelik yönü) Diğer operatör öncelik seviyelerinde Soldan sağa doğru associativity ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ ▪ Associativity nedir? ══════════════════════ Her öncelik seviyesinin bir öncelik yönü (associativity si) var. Yani ifadenin içinde aynı öncelik seviyesine sahip operatörler yer alıyorsa, hangisinin olduğu taraftan başlayarak gruplama yapayım? sorusunun cevabıdır. █ Örnek : Associativity(öncelik yönü) nin anlaşılmasına ilişkin örnek: a % b / c * d ifadesini düşünelim. Buradaki operatörlerin hepsi 3. seviyede yer alıyor. 3. seviyenin öncelik yönü soldan - sağa (Left Associative) Gruplama: ( (a % b ) / c ) * d şeklinde olur. Burada en soldaki operatör den başlayarak gruplama yaptım █ Associativity Örnekleri: a * b + c > 10 | ~!x++ | a = b += c *= d ( (a * b) + c ) > 10 | ~( !(x++) ) | a = ( b += (c *= d) ) ■ Maximal Munch [En uzun atom] Kuralı: ═══════════════ Tokenlar arası boşluk karakteri kullanılmazsa, en uzun atomu elde edecek şekilde yapar. Bunu, ileride daha çok örnekle inceleyeceğiz. a = x+++y; // Derleyici bunu a = x++ + y; şeklinde ele alır. ■ Operatorler hakkında şunları bilmelisin! ══════════════════════════════════════════ ▪ Bazı operatörlerin operandlarının türlerine ilişkin constraint ler var. ▪ Bazı operatörlerin operandlarının değer kategorisine ilişkin constraint ler var. ▪ % operatörünün operandlarının tamsayı olmak zorunda. Aksi halde syntax hatası olur. ▪ Bitwise operatörlerin tüm operandları tamsayı olmak zorunda. Aksi halde syntax hatası olur. ▪ İşaretli türlerde taşma (overflow) UB'dir. ▪ İşaretsiz türlerde taşma durumunda , modular aritmetik devreye girer. Bir hata oluşmaz. ▪ 0 a bolme UB dir. ▪ tamsayı/tamsayı operatoru tamsayı üretir. 4/5 = 0 dır. Yuvarlama da yoktur. ▪ C ve C++ dillerinde ifadelerin değer kategorilerine ilşkin kurallar aynı değil. Ortak olan, farklı olan yerler var. ▪ Matematikle karıştırma çok yapılıyor. Aynı şey değil. █ Örnek : int int double double int int a * b * c c * b * a Soldaki işlemde int * int den taşma çıkarsa UB. Sağdakinden halbuki double*int den taşma çıkmaz. ve kalan işlem de double türünden devam eder. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 11.Ders C [operatorler cont'd] 12 Mayıs Perşembe ▪ Aritmetik Opratörler ▪ Karşılaştırma/Relational Operatörleri 11.1 (AYRI VİDEO DA ) ■ Aritmetik Opratörler ├────┼──────────────────────────────────────────────────────────────────────────── │◄2 │ + - ++ -- (type) sizeof & * ! ~ Unary Operators işaret,inc,dec,tür dönüştürme,adres,içerik, lojikdeğil, bitwiseComplement operatörleri ├────┼──────────────────────────────────────────────────────────────────────────── ++x x++ --y y-- Bunların ürettiği değeri kullanmak niyetinde değilsem, amacım salt değişkenin değerini 1 artırmak yada eksiltmek ise, ki bu durumda yukarıdaki ifadeleri, sonuna ; koyarak statement olarak kullanacaksam, önek yada sonek li olandan hangisini kullandığımın önemi kalmaz. Her ikiside bu durumda aynı işi yapar. a = ++x; foo(y--); if(x++>y) gibi kullanımlarda, operatörün ürettiği değeri kullanmış oluyoruz. Üretilen değerin önemi var. Sonek olanların değişkenin kendi değerini üretiyor. Önek olanlar değişkenin değerinin 1 fazla/eksiği ni üretiyor. Bir döngü içinde atama ve ardından artırım işlerini düşünelim. a[i] = val; i = i+1; Yukarıdaki iki satır yerine a[i++] = val; şeklinde yazma imkanı oluyor. Benzer örnekleri çoğaltmak mümkün. Mesela a[i++] = b[k++]; Maximal Munch Kuralı : Eğer tokenlar arasında boşluk karakteri bırakmaz isek, derleyici tokenizing yaparken en uzun atomu elde eedecek şekilde yorumlamaya çalışır. a = ++x+++y; // Derleyici bunu a = ++x + ++y; şeklinde ele alır. a = x++y; // Sentaks hatası! a = x+++y; // Derleyici bunu a = x++ + y; şeklinde ele alır. a = x++++y; // Sentaks hatası! a = x+++++y; // Sentaks hatası! Derleyici bunu a = x++ ++ +y; şeklinde ele alır. │ └ +y ifadesi R value └ ++ operatörü L value ister! ++ ++x; // Sentaks hatası! Çünkü ++x kısmı R value haline geliyor, dıştaki ++ ise L value operand istiyor ■ Relational Operators - Binary Infix Operators - NO SIDE EFFECTS ├────┼──────────────────────────────────────────────────────────────────────────── │ 6 │ > >= < <= Comparison (Relational) ├────┼──────────────────────────────────────────────────────────────────────────── │ 7 │ == != Comparison Equality ├────┼──────────────────────────────────────────────────────────────────────────── a > b == c ifadesi, öncelik seviyesini gözönüne aldığımızda gruplama (a>b) == c olarak ele alınır Ürettiği değer , evet öyle, hayır öyle değil anlamında , yani diğer dillerde bool türünden iken C dilinde int türünden 1 olumlu 0 olumsuz değeri üretilir. Üretilen değeri gözönüne alarak (a>b) == c ifadesini tekrar düşündüğümüzde 1 == 0 == haline geliyor! int main(void) { int x,y; printf("iki tamsayı girin:"); scanf("%d%d",&x,&y) printf(" %d > %d = %d \n",x,y,x>y); printf(" %d >= %d = %d \n",x,y,x>=y); printf(" %d < %d = %d \n",x,y,x0) ++pos_count; if(y>0) ++pos_count; //yukarıdaki iki satır yerine pos_count = (x>0)+(y>0); yazabiliriz Matematikte signum fonksiyonu printf("sign(%d) = %d", x, (x>0)-(x<0) ); Sık yapılan bir hatayı analiz edelim: if(10 y y < x x >= y !(x 10) kapanan parantezden itibaren bu noktada x in değeri 1 artmış olacaktır ▪ while - do while() - for (;;) parantezindeki ; ler de sequence point ▪ C dilinin 4 operatörü bir SEQUENCE POINT oluşturur. Bunlar ▪ && LogicAND - Aynı zamanda SHORT CIRCUIT BEHAVIOUR dan mütevellit expr1 && expr2 ifadesinde expr1 önce yürütülür, varsa expr1 deki yan etkiler gerçekleştikten sonra expr2 yürütülür. ▪ || LogicOR Aynı zamanda SHORT CIRCUIT BEHAVIOUR dan mütevellit expr1 || expr2 ifadesinde expr1 önce yürütülür, varsa expr1 deki yan etkiler gerçekleştikten sonra expr2 yürütülür. ▪ Ternary operator expr1 ? expr2 : expr3 expr1 logic yorumlama tabii. Doğru ise expr2 yanlışsa expr3 döndürür. expr1 önce yapılır. ? noktası bir sequence pointtir. ▪ , comma operatörü - zaten en önemli varlık nedenidir - expr1,expr2 ifadesinde önce expr1 yürütülür, buradaki yan etkiler geçerli kılınır, sonra expr2 yürütülür. Örnek : x = 10 y = x + x++; //UB ++x && foo(); //okay, burada && işleminin sol operandındaki yan etki gerçekleşmiştir, foo çağrıldığında x 11 dir. Eğer bir yan etkiye maruz kalmış nesneyi , bir yan etki noktası geçmeden aynı ifade içinde tekrar kullanılması UB ■ Comma Operatörü [15. öncelik seviyesi]: ═════════════════ ▪ Hatırlatma: ayraç delimiter olarak comma seperated listde de kullanılıyor.i.e ilk değer vermedeki virgüller, fonksiyon parmetrelerini ayırmada kullanılan virgüller ▪ 15. seviyede yani en alt öncelik seviyesinde. Binary infix operatör. bir ifade de yer alabilir ve ifadenin büyütülmesinde rol alabilir. x = 5,y ifadesinde sol operand x=5 - sağ operand 5 - dolayısyla gruplama (x=5),y şeklinde. x ++ , y++ , z = 5 , a = x * 5 tamamı bir ifade dir. Dilin sentaksında ifade gereken her yerde x ++ , y++ , z = 5 , a = x * 5 ifadesi yazılabilir. ▪ Bir SEQUENCE POINT tir. Yani sol operandı ele alındıktan sonra yan etkiler tamamlanır sonrasında sağ operand ele alınır. Ard arda birden fazla expression statement yazdığımızı düşünelim. x= 5; y=x; ++y; z=y; Yukarıdaki satırları x = 5, y = x, ++y , z=y; şeklinde de yazabilirdim. Yani n tane expression statementleri birleştirip bu şekilde de yazmak mümkün. Peki neden böyle yazalım: Blok eliminasyonu yada Mesela if while gibi kontrol deyimlerinde birden fazla expr kullanım imkanı veriyor. Aynı zamanda fonksiyonel makro yazarken de faydalı oluyor. if(x>10) | if(x>10) ++y; //Part of if | ++y, ++z; //Part of if ++z; //Not part of if | for( i = 1, k=i+ival ; i<100 ; i++ , k+=i) //okay Ürettiği değer : Sağ operandın değeridir. Mesela x,y ifadesinde , operatörünün ürettiği değer y değişkeninin değeridir. Ancak ürettiği değerin değer kategorisi L value olsa bile, operandın ürettiği değerin değer kategorisi R value haline getirir. x=5,10 ifadesinin değeri 10 dur. Burada sol operand x=5 sağ operand 10 y = (x,10); // y 10 olur y = x, 10; // y x olur Bunun gruplaması (y=x),10 ; y = x=5,10 ; // y 5 olur x 5 olur. Bunun gruplaması (y = (x=5)),10 ; (x,y) = 10 ; // syntax error, atama operatörünün sol operandı L value olmalı. comma operatör produces R value! [C++ da kural farklı!] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 13.Ders C [Control Statements ve test Fonksiyonları] ■ Test Functions [query function, predicate diye de anılıyor] ═════════════════ Genel anlamda, Geri dönüş değeri bool olan fonksiyonlar, yani bir soruya cevap veriyorlar. C de ise geleneksel olarak int döndürürler. 0: False NonZero:True döner, True için 1 Garantisi YOK. int isprime(int); isprime(7) için 7, isprime(13) için 13 döndürebilir. Dolayısıyla , her iki sayının asal olup olmadıklarını kontrol etmek istediğinde if ( isprime(x) == isprime(y) ) gibi yazarsan Dikkat TUZAKLANIRSIN. Buna ilişkin correction if ( !!isprime(x) == !!isprime(y) ) █ Örnek : Perfect Number = Kendisi hariç diğer Bölenlerinin toplamı, i.e 6 = 1+2+3 Bir sayının Perfect number olup olmadığını test eden bir fonksiyon yazmaya çalışsam. Mantık : Sayının yarısına dek dolaşırsın, sayıyı bölüyormu diye, bölüyorsa kümülatif toplama eklersin. Bakıp değerlendirirsin. █ Örnek : Bir yılın Artık yıl (leap year) olup olmadığını test eden fonksiyon yazalım. Kural : 4 'e tam bölünecek ve aynı zamanda ya 100'e tam bölünmeyecek ya 400 e tam bölünecek. int isleap(int x) { return x%4==0 && ( x%100 != 0 || x%400 == 0 ) } █ Örnek :Karakterlerle ilgili başlık dosyasında, bize verilen test fonksiyonları var. └ int döndüren, karakterlere ilişkin test işlemleri yapan func bildirimleri var.Bunlar isxxx(int c) şeklindeler. ▪ int isblank(int c) ▪ int isdigit(int c) ▪ int isalpha(int c) ▪ int isalnum(int c) ▪ int isxdigit(int c) ▪ int islower(int c) ▪ int isupper(int c) ▪ int isspace(int c) ▪ int isctrl(int c) ▪ int ispunct(int c) ▪ int isprint(int c) ▪ int isgraph(int c) .....................................................Not: Ascii 32, boşluk(space) karakteri ■ Control statements ═════════════════════ Önceden belirlenmiş bir sentaks sözkonusu ve bir keyword içerip, programın akışını değiştirebiliyorlar. ▪ if statement ▪ loop statement - while / do-while / for ▪ switch statement ▪ goto statement ▪ return statement ▪ break statement ▪ continue statement ■ if statement ═════════════════ if(expr) 0: False Non-Zero:True └ void türden olmayan bir ifade, bu logic yorumlamaya tabii olacaktır. Logic yorumlama tamsayı türü üzerinden yapılıyor. Yani 0: False Non-Zero:True {True Path} - block kullanımı optional, tek satır için yazmana gerek yok anlamında else {False Path} - block kullanımı optional, tek satır için yazmana gerek yok anlamında Bundan başka else if merdiveni olayı da var. Unutulmasın. Thus, if(++x, x>10) //geçerli bir if statement if(if olabilir mi?) //ERROR, if parantezinin içine expr gerekiyor, if is a statement if(675) //geçerli bir if statement, Non-Zero:True if(x>10) | if(x>10) ++y; //Part of if | ++y, ++z; //Part of if ++z; //Not part of if | //biraz içeriden yazarak çeldirici yapmış. if(10 < x < 20) //Always True olarak evaluate edilecektir, çünkü, (10 Bu duruma düşmemek için if (10 == x) gibi yazmak daha anlamlı,çünkü 10 = x yazarsan syntax hatası olacak if(x = 0) // Always False if( a>10 || b<20) //maliyeti düşük koşulu önce yazmak, short circuit davranışını gözönüne aldığımızda, avantaj sağlabilir. İdiomatic bir yapı: if(x != 0) yerine if(x) şeklinde yazılabilir. - Her ikisinde de x 0 dan farklı ise demektir İdiomatic bir yapı: if(x == 0) yerine if(!x) şeklinde yazılabilir. LogicNOT operatoru her türlü tamsayı için 0 ancak ve ancak x=0 için 1 üreteceğinden , böyle de kullanılabilir. Enterasan Durum : int foo(void) dan dönecek değer için if( foo() ) diyerek kullanıyoruz. Bu okay. Ancak if ( foo ) diye yanlışlıkla yazsak da derleyici bir hata vermeyecek. Çünkü, fonksiyon isimleri, fonksiyon çağrı operatörü kullanmadan yazıldığında, dil bunu function pointer olarak kabul eder. Bu durumda da, fonksiyon adresleri logic yorumlamaya tabii tutulduğunda Null Pointerdan farklı mı? ile kıyaslanırlanırlar. foo nun adresi null olmadığından, böylesi bir kullanımdan Always True döner. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 14.Ders C [ ve Loop Statements ] ■ Loop statements while - do while - for ═════════════════ ■ C dilinde bir döngüden çıkış yolları ▪ kontrol ifadesinin false hale gelmesiyle ▪ return statement - fonksiyondan çıkar, döngüden de çıkmış olur. ▪ break statement - zaten varlık nedeni döngüler ve switch den çıkışı sağlamak - bulunduğu döngüden çıkıyor ▪ goto statement ▪ exit ve abort fonksiyonlarına çağrı - programı sonlandırıyor ■ while statement ════════════════════════ while (expr) | while(expr) { [while(declaration yapılamaz!)] statement; | statement1; statement2; } Burada expr, void türden olmayan bir ifade, bu logic yorumlamaya tabii olacaktır. Logic yorumlama tamsayı türü üzerinden yapılıyor. Yani 0: False Non-Zero:True █ örnek: { } kullanılmayınca istemsizce ekrana sürekli 0 basan prg parçası int =0; while (i<100) printf("%d",i); // Bu döngüye dahil. { } kullanılmadığında ilk statement döngüye dahil. ++i; // Bu statement döngüye dahil değil. İçeriden yazılmış olması çeldiricilik. Bunu illa ki tek satırda yapmak istersek, while (i<100) printf("%d",i++); █ örnek: Aşağıdaki program çalıştırılınca ekranda ne görünür? int =0; while (i++<10); printf("%d",i); cevap : 11 yazar, çünkü while 'ın gövdesi null statement, dikkat while sonrası ; var, dolayısyla while dan çıkınca i 11 değerine sahip oluyor █ örnek: while(printf("Bir sayi girin:"), scanf("%d",&x), x > 0 ) printf("girilen sayi %d \n",x); █ örnek: Aşağıdaki program çalıştırılınca ne olur? double d =3.4; while (d<7,0) { printf("%f",d); d += .3; } cevap : Ekrana hiçbirşey yazmaz, çünkü döngüye girmez, çünkü while( (d<7),0 ) gibi yorumlanır [comma dan dolayı], bu da while(0) demek olacaktır. █ örnek: Sadece getchar ile ekrandan alınan sayıyı oluştur, i.e ekrandan 8172 girildiğinden bunu oluştur printf("Bir sayi girin:"); int x = 0, c = 0; while( (c=getchar()) != '\n' ) { x = x*10 + c-'0' ; } printf("x = %d \n",x); İdiomatic bir yapı: Infinite Loop : while(1) { ... } İdiomatic bir yapı: while((ch = getchar()) != '\n') //Enter-yani NewLine gelince döngüden çıkar. İdiomatic bir yapı:n kez dönen döngü int n=10; | while(n--) =(n-->0) | for (int i = 0; i < n ; ++i) { | { //code | //code } | } ■ break statement ════════════════════════ Döngü deyimi yada switch deyimi gövdesi içinde kullanılması gerekiyor. Aksi halde syntax hatası. Sadece içinde bulunduğu döngüden çıkışı sağlıyor. döngü 1 { döngü 2 { // code if(c=='q') break; //code } // Yukarıdaki break sonrası bu satırdan devam eder. yani ait olduğu döngüden çıkar. // code } ■ goto statement ════════════════════════ döngü 1 { döngü 2 { döngü 3 { if(expr) goto LabelA; } } } LabelA: //Codes █ örnek: Ekrana merhaba yazan bir C programı yaz, ancak kaynak kodda ; kullanmayacaksın cevap 1: int main(void) { if(printf("merhaba")) {} //; kullanımı yok, null statement yerine {} kullandık. Note that printf returns some number } cevap 2: int main(void) { while(!printf("merhaba")) {} //; kullanımı yok, null statement yerine {} kullandık. Note that printf returns some number } cevap 3: int main(void) { switch(printf("merhaba")) {} //; kullanımı yok, Note that printf returns some number } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 15.Ders C [Loop Statements cont'd] ■ continue statement ════════════════════════ Syntax continue; Döngü deyimi gövdesi içinde kullanılması gerekiyor. Aksi halde syntax hatası. while(expr) { statement1; statement2; statement3; continue; //Kendinden sonraki statementlerin-4 ve 5'in yürütülmesini bypass ederek tekrar döngü başına - şart sınanmasına - atlayatacak. statement4; statement5; } ■ do-while döngüsü [while(declaration yapılamaz!)] ════════════════════════ Syntax do { //codes }while(expr); █ örnek: ndigit fonksiyonu yazalım, do-while kullanarak int ndigit(int x) { int digit_count = 0; do { ++digit_count; x /= 10; }while(x) return digit_count; } ■ for statement ════════════════════════ Syntax : ┌ Döngüye başlamadan önce bir kez yürütülür [expr1 içinde declaration okay, enabled by C99] │ for(expr1 ; expr2; expr3) expr1-2-3 olabilir de olmayabilirde. Syntaxın parçası değil. Syntax for(;;) │ │ │ └ Döngü gövdesinde statementlerin her yürütülmesinden sonra expr3 yürütülür. │ └ Control expression Logic yorumlamaya tabii, yani void türden bir ifade yazamazsın syntax hatası olur. Nonzero ise döngü gövdesindeki statementlar yürütülür Zero ise döngüden çıkılır. Eğer expr2 yoksa , buraya logic True yazmış gibi oluyorsun. Yani boş demek 1 yazmışsın anlamında. █ örnek: Aşağıdaki program çalıştırıldığında ekrana ne yazar? int i; for (i=0; i<5 ; ++i); printf("%d",i); cevap : 5 yazar. for döngü göndesi null statement. printf çeldiricilik olsun diye indented yazılmış. █ örnek: Aşağıdaki programı yorumlayın for (int i=0; i<5 ; ++i) { int i = 7; printf("%d",i) } cevap: C++ da syntax hatası. Aynı kapsamda , aynı ismi birden fazla değişkene veremeyiz. Ancak C de syntax hatası değil. Siz döngü gövdesini bölgesni bloklasanızda, adeta zahiri bir blok var. Yani derleyici açısından for (int i=0; i<5 ; ++i) { zahiri { int i = 7; printf("%d",i) } } zahiri Dolayısıyla program ekrana 77777 yazar ▪ N kez dönen döngü idiomu for (i=0; i 1 ? (x=15) : y ; gibi düşünmeliyiz. ▪ Tipik olarak nerelerde karşımıza çıkar? ▪ Ürettiği değeri kullanmıyorsak, koşul anlamında kullanma. ▪ Ürettiği değeri şuralarda kullanıyoruz ▪ Değişkene koşula bağlı olarak o değer yada bu değer gibi. x = x>=0 ? x: -x; //abs(x) yaptık x= a10 ? 5 : 13; //ilk değer verme de kullandım for (int i = a>b ? a: b; ;) //for da expr1 de ▪ Bir fonksiyona koşula bağlı olarak farklı değer gönderebilmek için func(x>y ? x:y) printf("%s \n", x==y ? "dogru":"yanlis"); ▪ Fonksiyonun ifadeli return deyiminde return x>y ? x:y; gibi ▪ else if merdivenine ihtiyaç duyduğunda ama one liner bir expression yapman gerekiyorsa int a = x == 5 ? 131: x == 19? 713:777; █ örnek: x>y ? x : y bu ifade x ve y den büyük olanını üretiyor, sanki max (x,y) gibi Bu örneği okumayı kolaylaştırmak için (x>y) ? x :y şeklinde yazabiliriz. Burada parantez sentaksın parçası değildir. █ örnek: x > y ? x : y > 100 ifadesi x > y ? x : (y>100) şeklinde gruplanır █ örnek: x = 20 ; x++ > 10 ? x*5 : x-5 ifadesinin değeri nedir? Cevap : 21*5 = 105 █ örnek: Aşağıdaki kodda redundancy var mı? if(x != 0) y = x; else y = 0; Cevap : evet, y=x yazmaya çalışmış bunu yazan :) ▪ Ternary Operatörün syntaxında C ve C++ arasında bazı farklılıklar var. Bunlardan en önemlisi ise şu: C++ da expr2/3 L value expression ise , ternary oeratör de L value expression döndürüyor. Halbuki C 'de, L value olsalar bile, döndürdüğü ifade R value oluyor. if(ival > 10) x = ival; else y = ival; kodu yerine ival > 10 ? x : y = ival; yazılabilir mi? C için Error, C++ için Okay. Bunun C de gerçeklenmesine giden yol, henüz görmediğimiz ptr yapısı kullanarak olabilir. *(ival > 10 ? &x : &y) = ival; //Okay ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 16.Ders C [Function Bildirimleri/Prototipleri Preprocessor Directives] Linker programının kaynak kodla hiç alakası yok. Linker in çalışma birimi obj kodlar/dosyalar. Yani compiler çıktıları. Derleyicinin bir fonksiyon çağrısı karşılığı bir obje kodu üretebilmesi için ilgili fonksiyonun tanımını görmeye ihtiyacı yok. Sadece bildirimi yapılmış bir fonksiyon ve ona çağrı yapıldığı bir kod dosyasını düşünelim. Derleyici fonksiyonun bildiriminden elde ettiği bilgilerle, çağrının geçerli olup olmadığını denetler. Çağrıda kullanılan arguman sayisi ile bildirimde kullanılan parametre sayısı eşleşiyor mu? Fonksiyonun kodunu görmese de, hata vermez.(Linker hata verecektir.) Çünkü fonksiyona giriş ve fonksiyondan çıkış kodlarını üretiyor.Fonksiyonda çalıştırılacak kodları üretmiyor. Arguman tipleri ile parametre tipleri uyuşuyor mu? (Burada bir örtülü tip dönüşümü gerçekleştirebilir) Veri kaybına sebep olabilecek bir tip dönüştürme gerçekleştirecekse, buna ilişkin bir uyarı mesajı verir. Derleyicinin fonksiyon bildirimine ihtiyacı var: Yanlışlıkla yazılan kodlara karşı bizi koruyor. ▪ arguman-parametre sayısı uyumu ▪ arguman-parmetre dönüşümleri (tür dönüşümleri) ▪ fonksiyon geri dönüş değeri, aslında çağrılan fonskyon tarafından bellekte bir yere yazılıyor. Derleyicinin ürerteceği kodun, bu geri dönüş değerini bellekten okuyabilecek bir kod üretebilmesi için fonksiyonun geri dönüş türünü bilmesi gerekiyor. Fonksiyon bildiriminde, parametre değişkenleri, isim verilebilir/verilmeyebilir. Her ikisi de legaldir. int foo(int,int); //okay int foo(int x , int y); //okay İsim verilirse, burada verilen isimler farklı bir scope kuralına - Function Prototype Scope - tabidir. Scope ları bir hatırlayalım ────────────────────────────────────────────────────────────────────────────────────────────────── ▪ File Scope : Global isim alanında bildirilen isimlerdi ▪ Block Scope : Block içinde bildirilen isimler ▪ Function Prototype Scope : Fonksiyon bildirimlerinde (declaration) parametrelere verilen isimler - Burada dikkat edilmesi gereken husus şudur : Farklı parametrelere aynı ismi verirsen Syntax hatası olur. Öte yandan, az evvel yukarıda belirttiğimiz gibi, parametrelere isim vermemek de legaldir. ▪ Function Scope : fonksiyon tanımlarında (definition) fonksiyonların parametre değişkenleri ─────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Parametre Parantezinin içini boş bırakmak, int foo(); gibi bir bildirimden bahsediyoruz. Dikkat : Önemli bir kural farklılığı between C and C++ C++ : Hiçbirşey yazmamak ile (void) yazmak aynı anlamdadır. Yani int foo() ile int foo(void) aynı anlamdadır. C : int foo() ile int foo(void) aynı anlamdadır. Geçmişe doğru uyumluluk ile ilgili. │ │ │ └ Bu bildirimde, parametre değişkeni olmadığını söylüyoruz. │ └ Bu bildirimde , foo nun parametre değişkeni olmadığını değil, bu konuda bilgi vermediğimizi söylüyoruz. Asla böylesi bir bildirimde de bulunma. Varlık sebebi, Sadece geçmiş kodların yeni derleyiciler de problem çıkarmaması için. ▪ C dilinde, fonksiyonlarla ilgili olarak ▪ Function Overloading YOK ▪ Function Default Argument YOK ▪ Nested function (Fonksiyon içinde fonksiyon tanımlama) YOK ▪ Function Re-Declaration : Derleyicinin bir fonksiyona ilişkin (özdeş) birden fazla bildirim görmesi bir sentaks hatası oluşturmaz. ▪ Default/Implicit Function Declaration : C99 ile artık değişmiş/kaldırılmış olan, nahoş bir kuraldı. Derleyici bir ismi arayıp bulamadığında sentaks hatası vermesi gerekiyor. Fakat aradığı isim, fonksiyon çağrı operatörünün operandı olmuşsa- yani fonksiyon çağrısı kullanımı durumunda - i.e func(12,24); gibi bir statementda - derleyici diyor ki ben bunu bulamadım, bildirilmemiş ama bunu bir fonksiyon ismi olarak kabul edeceğim. Derleyicinin fonksiyonun bildirimini görmemesine rağmen, bir bildirim varmış gibi - ancak parametrik yapısı hk. bilgi verilmeyen cinsten - davranmasına default Function Declaration deniyor. Yani func(12,24); satırı ile karşılaştığında, func a ilişkin bir bildirim bulamazsa int func(); //Adeta Böyle bir bildirim varmış gibi davranıyor ve sentaks hatası üretmiyor. Bu çok tehlikelidir. Zira Geri dönüş değeri kullanılırsa ve int den farklı ise UB ler oluşacak. Ayrıca argümanlardan parametre değişkenlerine kopyalamada, default argument conversion denilen kural geçerli oluyor.[int altı türler int'e, float double'a,diğer türler aynen kalacak] Zaten C99 ile de bu kural dilden çıkarılmıştır. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Seperate Compilation Model Lojik açıdan tek ama fiziksel olarak iki birim [Lojik birliktelik için isimleri aynı mesela ali demişiz iki dosya ismine de] ali.c source file code file implementation file olarak anılır ali.h header file -> ali.c den hizmet almak isteyen kodlar, bu hizmeti alabilmek için ihtiyaç duyacakları bildirimler ali.h 'dadır. Tür eş isim(typedef int Word; gibi), Fonksiyon, user-define tür bildirimleri vs... ▪ What is Inline Expansion [Derleyicilerin kullandığı en önemli optimizasyon tekniklerinden biri] Derleyici bir fonksiyon çağrısı ile karşılaştığında, derleyici bunun bildirimini görmek şartı ile, fonksiyona giriş ve çıkış ile ilgili bir kod üretir, bu kodda, linker ile önceden anlaşılmış bir şablona göre dekore eder ve fonksiyona ilişkin referansı bu şablonun içine kor. Giriş-Çıkış kodlarının bir ilave maliyeti var ve bu maliyet küçük kodlu-sık çağrılan fonksiyonlarda önemli bir maliyet kalemi haline gelebilir. Inline expansion yapabilmesi için ▪ Çağrılan fonksiyonun kodunu (definition) derleyicinin görmesi gerekiyor ▪ Bundan bir fayda sağlayacağına karar vermesi gerek. ▪ Ancak görse dahi, bunu yapacak diye bir zorunluluk yok. ▪ Derleyicinin switchleri de bunda etkili. ▪ C99 ile dile eklenen inline anahtar sözcüğü - bundan daha sonra bahsedeceğiz. ■ Preprocessor (Önişlemci programı ve komutları) ▪ Derleyiciden önce çalışır, C bilmez, çıktısı Translation Unit, TU derleyici girdisidir. ▪ Kendi komutlarını yürütür. ▪ #include ▪ #define - #undef ▪ #if - #else - #elif - #endif ▪ #ifdef - #ifndef ▪ #line ▪ #error ▪ #pragma ▪ # null directive ▪ Komutları sonunda ; yok. ▪ # ile başlayan komut satırları yerine getirildikten sonra silinir. ▪ Makro, önişlemci programın, #define komutu ile önişlemci programa tanıtılan varlıklar. ■ #include ═══════════ Basitçe, bildirdiğimiz dosya ismindeki içeriği, #include 'un bulunduğu yere yapıştır. #include -> Anlamsal açıdan, ya C'nin standard başlık dosyalarından biri, ya da derleyici tarafından verilen dosyalardan biri demektir. - default directory - #include "necati.h" -> Bildirdiğimiz dosyada (başlık dosyası) içeri ▪ önişlemci komutları ▪ derleyiciyi ilgilendiren kodlar(bildirimler) Dolayısıyla onun içine girince, onun içindeki önişlemci komutlarını da yürütüyor. ■ Multiple Inclusion Guard - daha sonra bakacağız - Ders 19 da ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 17.Ders C [Preprocessor Directives] ■ static ve extern anahtar sözcükleri hakkındda [Sorduğum soru üzerine kısa bir anlatım] Elimizde murat.c ve murat.h olsun. Bundan anlamamız gereken şey şu: murat.c yi kullanacak ve hizmet alacak kodlar, murat.h 'yı include etmeliler. Yani murat.h, murat.c yi kullanacaklar için gerekli bildirimleri içerir. Tabiki bundan başka , hizmet alacakları ilgilendirmeyen, ancak murat.c nin kullanımı için gereken başka bildirimlerde olacaktır. Bunları direkt murat.c içinde yapalım - ve de dosya ya özel oladuğunu belirtelim -. Senaryo şu olsun: ▪ murat.c nin sunduğu hizmetler(fonksiyonlar ve değişkenler) ival tamsayısı ve func fonksiyonu olsun. Kendi kullanımında ise x tamsayısı ile foo fonksiyonu olsun. Bu durumda: ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ murat.h ▪ murat.c │ extern int ival; │ int ival = 10; extern void func(int); //Aslında fonksiyonlar │ static int x = 10; //için başına extern yazılmasa da okay │ void func(int a) { .. } │ static void foo(int a) { .. } ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Conditional Compiling #if 1 void foo(int); #else void foo(double); #endif ■ #define ═════════ Birden çok kullanım amacı olabiliyor. Tamamı büyük harflerle yazılması, yaygın bir konvansiyondur. Bununla tanıtılan isimlere Macro deniyor. object-like ve function-like Tanımladığı yerden aşağıya doğru giderek replacement(bu değiştirme yazı olarak yapılır, değer hesaplanmaz) yapar. Yukarıdakilere dokunmaz. ▪ Bir sabiti doğrudan kullanmak yerine bir makro kullanmak , okuma ve yazmada kolaylık ▪ Taşınabilirlik sağlamak ▪ Birden fazla yerde değiştirme imkanı ▪ Örn; Conditional Compile i yönlendirme ▪ Object-like Makro #define SIZE (100) symbolic constant deniyor. #define ERRMSG "insufficient memory" --> printf(ERRMSG); KURAL : Bir makroyu birden çok kez tanımlamak UB. #define SIZE (100) -- #define SIZE (500) -- Bu ikisini bu şekilde birden fazla defa kullanmaya çalışmak , korumasızca, UB dir. Koruma için #undef SIZE yazabilirsin - yukarıdaki iki satır arasına #define NEC (SIZE*2) Okay int arr[SIZE] = {0}; Okay ▪ Function-like Makro [Öncelik problemlerini bertaraf etmek için daima hem parametreleri hem de makroyu dıştan da parantez içine alalım] Kodu küçük ve sık çağrılan kodların, fonksiyon çağırma maliyetinden kurtulmak. Makro ismi ile açılan parantez atomu arasında boşluk karakteri olmamalı. Aksi takdirde, istemsizce, object-like makro yapmış olursun. Makro argumanı olmak zorunda değil. Nested/Mixed şekilde kullanım mümkün. #define MAX(x,y) ( (x) > (y) ? (x) : (y) ) isimden sonra boşluk karakteri olmadan () yazıldığına dikkat etmeliyiz. #define SQUARE(x) ( (x) * (x) ) #define ISUPPER(c) ( (c) >='A' && (c) <='Z' ) #define ISLOWER(c) ( (c) >='a' && (c) <='z' ) #define ISLEAP(y) ( (y) % 4 == 0 && ( (y)%100 != 0 || (y)%400 == 0 ) ) #define RANDOMIZE() srand( (unsigned)time(0) ) #define ISALPHA(c) ( ISUPPER(c) || ISLOWER(c) ) #define ASIZE(x) ( sizeof(x) / (sizeof(x[0]) ) dizinin boyutunu - kaç elemanlı olduğu - soyluyor #define RAN_ELEM(x) ( x[rand() % ASIZE(x)] ) dizinin rastgele bir elemanını döndürür ▪ Bir çok önlem alsak da bazı durumlarda makrolar güvenli olmayacak. Örneğin #define SQUARE(x) ( (x) * (x) ) makrosunu, SQUARE(y++) olarak kullanılmaya çalışıldığını düşünelim. Önişlemci bu makroyu açtığında ( (x++) * (x++) ) şeklinde yazacak, arada Sequence Point olmadan birden çok kullanım olduğundan UB oluşacak Buna bir başka örnek, SQUARE( foo() ) gibi kullanmaya çalışıldığını düşünelim. Önişlemci bu makroyu açtığında ( (foo()) * (foo()) ) şeklinde yazacak, foo nun iki kez çağrılması gerekecek, belkide maliyetli, ya da içindeki bir statik değişken değer değiştirecek, yada ekrana 2 kez yazacak vs.. ▪ Enterasan bir durum şu olabilir: Bir programda aynı isimli hem makro hem fonksiyon var. Şimdi ne olacak? Note that, bir çok kütüphane hem makroyu hem fonksiyonu sunuyor. [Fonksiyon ismini parantez içinde yazarlar, böylelikle aynı isimli bir makronun fonksiyonun tanımını değiştirmesine izin verilmez] █ Makro çağrılır █ Fonksiyon çağrılır int square(int a) int square(int a) { { printf("fonk cagrildi"); printf("fonk cagrildi"); return a*a; return a*a; } } #define square(x) ( (x) * (x) ) #define square(x) ( (x) * (x) ) int main(void) int main(void) { { int ival = 5; int ival = 5; int result = square(ival); int result = (square)(ival); } } Çünkü önişlemci derleyiciden önce Çünkü, makro olarak değerlendirilebilmesi için isimin bitişiğinde ( aç parantez görmeliydi. Halbuki ) var. çalışıyor. Burada diğer mekanizma şu: Fonksiyon çağrı operatörü Fonksiyon ismini fonksiyonun adresine dönüştürür. Öncelik parantez içine alınması bu gerçeğe aykırı düşmez. █ Şimdi syntax hatası veriyor - Neden ? #define square(x) ( (x) * (x) ) int square(int a) { printf("fonk cagrildi"); return a*a; } int main(void) { int ival = 5; int result = square(ival); } Cevap: Çünkü önişlemci derleyiciden önce çalışıyor. square(int a) yerine ( (int a)*(int a) ) koyuyor da ondan. Soru : Bu yanlışlığa düşülmemesi için peki ne yapabiliriz? Cevap: fonksiyon tanımında, ismi yine parantez içine yazabilirsin. int (square)(int a) { printf("fonk cagrildi"); return a*a; } █ Puzzle - Ekrana ne yazar? █ Puzzle - Ekrana sürekli nec yazan aşağıdaki kodda, tek bir karakter sil-ekle-ya da değiştirerek ekrana 5 kez nec yazsın int main(void) int main(void) { { int i= 1; int n= 5; do for(int i=0; i #define printf(x) printf("%d\n",x) int main(void) { int ival = 10; printf(ival); } Cevap: Önişlemci printf makrosunu açacak, printf("%d\n",ival); elde edecek, sonra ? KURAL: Bir makronun açılımı sonrası tekrar kendisi çıkarsa ikinci kez makro açılımı uygulanmıyor. Dolayıysla , printf("%d\n",ival); satırındaki printf tekrar açılmaya çalışılmayacak. Prg çalışınca ekrana 10 yazar. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 18.Ders C [Preprocessor Directives] ▪ Fonksiyonel makrolar ile gerçek fonksiyonların karşılaştırılması ▪ Makro, önişlemci programa verilen bir yerdeğiştirme talimatıdır. Gerçek bir fonksiyon sözkonusu değil. Dolayısyla fonksiyona giriş-çıkış maliyetleri yok. ▪ Makro olan isim önişlemci tarafından bilinen bir isim, derleyicinin bildiği bir isim değildir. ▪ Makrolar kaynak kodu büyütme eğilimindedir. Dolayısyla derlenmiş kodun büyüklüğünü de artırabilir. ▪ Makrolar, fonksiyonlar gibi kapsamlı bir Debugger desteği yok. i.e Fonksiyonlara breakpoint koyabiliyoruz, yerel değişkenlerin değerlerini görebiliyoruz. ▪ Fonksiyonların adresleri olduğu için function pointer kullanımı imkanı var. Makrolarda bu imkanımız yok. ▪ Fonksiyonlar türe bağlı, makrolar ise generic, türden bağımsız. ▪ Fonksiyonlar, makrolara göre çok daha güvenli. █ Çok satırlı bir önişlemci makrosu - multiline macro - #include #define make_swap_fn(t) void swap(t *p1, t *p2) { \ t temp = *p1; \ *p1 = *p2; \ *p2 = temp;} make_swap_fn(int) int main() { int a = 10, b= 34; swap(&a,&b); printf("a=%d b=%d",a,b); } ▪ Derleyicinin bilmediği, önişlemcinin bildiği operatörler - Preprocessor Operators: 1▪ Makroda kullanılır, # stringification operator , unary #a --> "a" çift tırnak içine alır. ═══════════════════════════ #define mgun(x) #x printf(mgun(hello)); //Önişlemci sonrası bu satır printf("hello"); haline dönüştürülür Bunun pratik bir kullanımı: #define iprint(a) printf(#a "= %d\n",a) int x = 10, y=20; iprint(x+y); x+y = 30 yazacak ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Vesileyle dilin enterasan bir kuralından bahsedelim: printf("ali" "veli" "murat") ifadesi compile time da printf("alivelimurat") haline getirilir. Çünkü kural: Aralarında boşluk karakteri dışında hiçbirşey olmayan string literalleri compile time da birleştirilir. Bu kuralın pratikte sevilen bir tatbikatı: printf( "[1] Kayit ekle\n" "[2] Kayit sil\n" "[3] Programdan Cik\n"); ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2▪ Makroda kullanılır, ## token-pasting operator, binary a##b --> ab haline getiriyor ═════════════════════════ #define merge(a,b) (a##b) int prime_count = 0; ++merge(prime_,count); printf("%d\n",prime_count); //ekrana 1 yazar printf("%d\n",merge(10,20)); //ekrana 1020 yazar █ Bu operatörü kullanarak, öyle bir fonksiyonel makro yazalım ki, önişlemci bu makroyu açtığında derleyicinin göreceği bir fonksiyon tanımı ortaya çıksın: #include #define make_swap_fn(t) void swap_##t(t *p1, t *p2) { \ t temp = *p1; \ *p1 = *p2; \ *p2 = temp;} make_swap_fn(int) make_swap_fn(double) int main() { int a = 10, b= 34; swap_int(&a,&b); // printf("a=%d b=%d",a,b); } Yada daha bir başka örnek #define SWAP(x,y) {int temp = x; x=y; y = temp;} int main() { int a = 10, b= 34; if(a>b) SWAP(x,y); else printf("Necati"); printf("a=%d b=%d",a,b); } şeklinde kullanmaya çalıştığımızda, syntax error - else without if - hatası veriyor. Çünkü blok sonundaki ; null statement Bunu bertaraf edebiliriz. #define SWAP(x,y) do{int temp = x; x=y; y = temp;}while(0) Burada SWAP(x,y); sonundaki ; aslında do-while 'in syntaxini tamamlamış oldu. 3▪ Conditional compiling de kullanılır, defined operatörü ════════ Conditional Compiling Derleyicinin görmesini istediğimiz kodları derleyiciye vereceğiz. #undef, #if , #endif , #else , #elif , #ifdef , #ifndef , #line , #error , #pragma bu önişlemci komutlarının büyük kısmı conditional compiling ile ilgili. Kullanım yerleri: Farklı işlemci/donanımsal yapılar hedeflendiğinde (aynı işi yapan, pic16 için farklı pic32 için farklı kodlar Farklı işletim sistemleri hedeflendiğinde Farklı programlama dilleri için (C C++ farkı, c derleyicisi derlerse şu kodu görsün, C++ derleyicisi derlerse şu kodu görsün) Farklı kütüphane versiyonları için Farklı ürün versiyonları için Lokalizasyonlar için Farklı derleyiciler için Bunlardan başka hemen her C projesinde kullanılagelen bir senaryo var: Debug versiyondan release versiyona geçerken, çünkü debug hata yakalamaya yönelik, ancak reel iş yapmayan bir çok kod yükü içeriyor. Programming error: Programcı kodu yanlış yazmış, bunları bulup düzeltebilirsin. Sonra istediğin gibi çalışabilir. Runtime error : Yazdığın kodda bir hata yok, ancak çalışırken oluşan koşullar nedeniyle oluşan hatalar örneğin programın bir veritabanı ile bağlantı kurması gerekiyor, ve bu bağlantı string inin string operasyonları ile elde edilmesi gerekiyor. Ama bu esnada bir hata yapılmış ve bağlantı ifadesinde mesela bir nokta karakteri yanlış yere yazılmış. İşte bu programming Error. Örneğin veritabanı bağlantısı için bir klasörde yer alan veritabanı dosyasını birisi silmiş. Dolayısıyla bağlantı kurulmuyor. İşte bu Runtime Error. Assertion (Doğrulama/Güvence altına alma)- Hatamızı bulmaya yönelik araç Runtime Assertion : Çalışma zamanında yapılan doğrulama █ Diyelim ki, bir fonksiyona göndereceğimiz argumanın 0 olmaması gereksin. 0 gönderiyorsak yanlış kodlama var demektir. void func(int a) { assert (a != 0); //... } Compile Time Assertion : Derleme zamanında yapılan doğrulama █ _Static_assert(sizeof(int)>4, "int turu yeterince buyuk degil"); ▪ Kullanım: #if preprocessor_expression ... ... #endif preprocessor_expression true ise aradaki kodları derleyiciye verir, aksi takdirde derleyiciye vermez. preprocessor_expression ifadeleri için bazı kısıtlar var. Kullanılabilecekler şu şekilde: ▪ macro lar kullanılabilir ▪ gerçek sayı aritmetiği kullanılamaz, yalnızca tamsayı aritmetiği kullanılabilir ▪ + * - / % >> << ve karşılaştırma koşul ve logic operatörleri kullanılabilir ▪ ternary operatör kullanılabilir █ Mesela, SIZE a göre ilave bildirimleri derleyiciye verelim. #define SIZE 10 #if SIZE>20 void func(int); void foo(int); #endif █ Mesela, #define SIZE 10 #define MAX 100 #define NEC 45.987612 //Okay //Error - Gerçek sayı aritmetiği yasak #if SIZE>20 && MAX <10 #if NEC>1.2 void func(int); void func(int); void foo(int); void foo(int); #endif #endif //Okay #if SIZE>20 && MAX <10 void func(int); void foo(int); #else void foo(int,int); #endif ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 19.Ders C [Preprocessor Directives] ▪ KURAL : Tanımlanmamış makro 0 kabul edilir. █ Mesela, #define NEC tanımlanmamış olsun. Bu durumda aşağıdaki kod hata vermez, NEC 0 kabul edilir , #else - #endif aralığındaki kod derleyiciye verilir. #if NEC > 8 void func(int); void foo(int); #else void foo(int,int); #endif ▪ #ifdef - Makroyu arguman olarak alır. Makronun o nokta itibariyle tanımlanmış ise True ▪ #ifndef - Makroyu arguman olarak alır. Makronun o nokta itibariyle tanımlanmamış ise True Bunların #else ile kullanımı mümkün. █ Mesela, #define NEC tanımlanmamış olsun. Dolayısıyla #else - #endif aralığındaki kod derleyiciye verilir. #ifdef NEC void func(int); void foo(int); #else void foo(int,int); #endif ▪ KURAL : Eğer bir makroyu tanımlamışsan ama bir yer değiştirme önermediysen, önişlemci kodda o makroyu siler. █ Mesela, buna ilişkin tipik bir kullanım: (Bir kelimeyi anahtar sözcük gibi kullanım , ancak aslında dilde öyle bir sözcük yok) #define PUBLIC PUBLIC void func(int); █ Mesela, buna ilişkin tipik bir kullanım: (Bir kelimeyi anahtar sözcük gibi kullanım , ancak aslında dilde öyle bir sözcük yok) #define MYDEBUG int main() { MYDEBUG } Sinek Karo Maça Kupa typedef enum {Club,Diamond,Spade,Heart} Suit; Oyunda kartların suitinin önemi var, buna göre // card.h #ifdef POKER typedef enum {Club,Diamond,Spade,Heart} Suit; #else typedef enum {Club,Diamond,Heart,Spade} Suit; #endif Geçen derste buraya kadar gelmiştik. 3▪ defined operatörü - unary bir operator, bool değer üretiyor , şimdi bunu mercek altına alalım Buna bir ismi operand olarak veriyoruz, operandı () içinde de verebiliriz. Aşağıdaki iki yapı aynı anlamda #ifdef NEC #if defined NEC void func(int); void func(int); #endif #endif Aşağıdaki iki yapı aynı anlamda #ifndef NEC #if !defined NEC void func(int); void func(int); #endif #endif Tarihsel gelişimde , C dilinde önce #ifdef vardır, sonradan defined eklendi. Bunun sebebi Birçok durumda #if nested olmak zorunda, bu durumlarda kullanım (kod okuma ve yazma) daha meşakkatli. Örn istediğim koşul şu olsun: SIZE define edilmiş ancak NEC define edilmemiş Bunu şöyle yazabiliyorduk Ancak bunu artık şöyle yazabiliyoruz #ifdef SIZE #if defined SIZE && !defined NEC #ifndef NEC ... ... #endif #endif #endif ▪ #elif - nested if amaçlı yapıyı tamamlar ═══════ #define EUR 1 #define USD 2 #define GBP 3 #define CHF 4 #define CUR USD #if CUR == EUR ... #elif CUR == USD ... #elif CUR == GBP ... #elif CUR == CHF ... #endif ■ Multiple Inclusion Guard [Çoklu dahil etmeye karşı koruma]- ══════════════════════════ daha sonra bakacağız demiştik. Koşullu derlemenin kullanımının çok önemli olduğu bir konu. Note that, C dilinde functionların bildirimlerinin - özdeş olmak kaydıyla - birden çok defa varolması hataya yol açmaz - function redeclaration. Ancak değişkenlerin bildirimlerinin birden çok kez yeralması hataya sebep olur. Bir başlık dosyasında tipik olarak değişkenlerde yer aldığından, bir başlık dosyasının birden çok kez include edilmesini istemeyiz. #include "myheader1.h" #include "myheader2.h" düşünelim. Hem myheader1 hem myheader2 nin de myheader.h yi include ettiklerini düşünelim. Bu durumda myheader.h iki defa include edilmiş olur. Bunun önüne geçmek isteriz. İşte Multiple Inclusion Guard bunu bize sağlayan araç. myheader1.h ve myheader2.h dosyalarına #ifndef MYHEADER_H #define MYHEADER_H #endif ifadesini eklediğimizde myheader.h dosyasının çoklu dahil edilmesine karşı önlem almış oluruz. ▪ #undef ═════════ #define NEC 100 #define square(x) ((x)*(x)) #undef NEC //NEC makrosu bu noktadan itibaren artık tanımlı olmayacak #undef square //square makrosu bu noktadan itibaren artık tanımlı olmayacak Peki zaten tanımlı olmayan bir makroyu undef etmek hataya sebep olur mu? - Cevap : Hayır ▪ Bir makroyu farklı değerlerle belli kısımlarda kullanmak için işine yarayabilir. #define SIZE 100 //... #undef SIZE #define SIZE 500 //.. ▪ int main() { .. #define SIZE 100 .. } Böylesi bir kodda SIZE makrosunun scopu main midir? Hayır. Önişlemcinin scope kavramı yok. ▪ #error - Derlemeyi daha önişlemci fazında durdurur. Sanki önişlemci zamanındaki abort gibi. ════════ Örneğin kodumuzun asla C++ da derlenmesini istemiyoruz. Yalnızca C derleyicisinde derlenmesini istiyoruz. #if expr #error THIS FILE MUST BE COMPILED BY C COMPILER #endif ▪ Predefined Symbolic Constants [ön tanımlı sembolik sabitler] ═══════════════════════════════════════════════════════════════ Predefined Symbolic Constants, bir #define komutuna sahip olmayan, dilin kuralları ile tanımlı kabul ediliyor. Ayrıca compiler'ın, dilin kurallarından başka kendisi de bazı predefined symbolic constant lar sunabiliyor.(Compiler extension) Predefined Symbolic Constants, #undef ile tanımsızlaştırılamıyor. Yani bizim kontrolümüzde değiller. Sayıları çok, ancak bazıları çok yaygın. c nin ilk zamanlarından olan 5 tane: ════════════════════════════════════ __LINE__ Örn:printf("kaynak dosyanin %d inci satiri",__LINE__); __FILE__ Dosyanin tam ismi(uzantısı dahil) Örn:printf("%s dosyasinin %d inci satirinda hata olustu",__FILE__,__LINE__); __DATE__ Derlemenin yapildiği Tarih Örn : printf("Compile Date %s",__DATE__); __TIME__ Derlemenin yapildiği Zaman Örn : printf("Compile Time %s",__TIME__); __STDC__ This macro is intended to indicate a conforming implementation , expands to integer constant 1 C versiyonunu kontrol etmek için sık kullanılan bir makro (C95) ile geldi. __STDC_VERSION__ C version. ════════════════ 199409L (C95 için) 199901L (C99 için) 201112L (C11 için) 201710L (C17 için) C dilinde; __ ile başlayan tüm isimler reserve edilmiştir. _BÜYÜKHARF ile başlayan tüm isimler reserve edilmiştir. __func__ (C99 ile eklendi). ═════════ Bu aslında Predefined symbolic constant olmamasına rağmen, onun gibi kullanılageliyor. void foo(int x) { printf("%s fonksiyonunda hata",__func__); } ▪ #line - daha çok kod generate edecek programlarda kullanım içindir. Normalde buna işimiz düşmez. ▪ #line 500 yazdığımızda, __LINE__ in değerini ilk kullanımda 500'e set etmiş oluyoruz. ▪ #line 500 "nec.c" yazdığımızda, hem __LINE__ değerini 500'e , hem de __FILE__ değerini nec.c'ye set etmiş oluyoruz. ▪ Yorum satırları - comment lines - Yorumların koddan çıkarılması, önişlemci tarafının yerine getirilmesinden önce gerçekleştirilir. C89 a göre /* arası yorum */ Translation Unit haline geldiğinde , yorum koddan çıkarılır. C99 ile birlikte // yorum imkanı geldi. // dahil satırın sonuna kadar ki kısım koddan çıkarılır Yorum satırları nested olamıyor. [Ancak IDE bunu fix edecek bir extension opsiyonu sunabilir.] /* /* */ */ Neden nested comment lines gereksin ki? Silmek istemediğim ancak derlemeden çıkarmak istediğim bir kısım olsun. Pratikte buna comment out deniyor. Sonra bunu da içeren daha geniş bir alanı comment out etmek istediğim de :) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 20.Ders C [ switch ve goto deyimi ] 14 Haziran 2022 Salı ■ switch deyimi Belli elseif merdiven biçimlerine [tamamına değil, sayıların == ile sınanması] alternatif. Kodun okunması ve yazımını kolaylaştırıyor, ve bazı koşullarda da daha iyi optimizasyon imkanı olabiliyor. Case deyimleri zorunlu değil. Break deyimleri zorunlu değil. case kullanılmışsa, Case lerin ensonundakinde bir statement - en azından null statement - zorunlu. Default case en sonda olmak zorunda değil. Aynıdeğere sahip birden fazla case labeli syntax hatası oluyor. switch (integralexpr) parantez içi tamsayı türlerinden olmak zorunda { case constexpr: └────────────────────────────────────────────────────────── Örnekler: case -5 : // okay statement1; case 10*20 : // okay statement2; case 'A' : // Karakter sabiti = Tamsayı sabitidir. .. case "A" : // Error - actually this is an array statementN; case eRED : // enum okay, enumaration tamsayı türlerinin bir alt kategorisidir break; //Aşağıya doğru işletmeye devam etmemesi için case SIZE : // SIZE makro (symbolic constant) default: yanına ifade istemeyen bir case label dir aslında } █ Örnek int main() { int x = 10; // Bloklamadan kullanımda tek bir statement, gövdeyi oluşturur. switch(x) case 2: printf("This is part of switch body"); switch(x) case 2: printf("This is part of switch body"); printf("This is NOT part of switch body, sasirtma icin devamina yazilmis olsa bile"); printf("This is NOT a part of switch body too, sasirtma icin iceriden yazilmis olsa bile"); // Bloklu kullanım switch(x) { // case anahtar sözcüğü bulunması da zorunlu değil, bu kod legal. } switch(printf("Hello World")) { // ; kullanmadan ekrana Hello World yazabilirsin } switch(x) { case 1: case 2: case 3: printf("Okay, All these cases result with same action.") } } █ Örnek #include #include int main(void) int main(void) { { int wday; int wday; printf("Haftanin kacinci gunu: "); printf("Haftanin kacinci gunu: "); scanf("%d",&wday); scanf("%d",&wday); switch(wday) switch(wday) { { case 1:printf("Pazartesi"); case 1:printf("Pazartesi");break; case 2:printf("Sali"); case 2:printf("Sali");break; case 3:printf("Carsamba"); case 3:printf("Carsamba");break; case 4:printf("Persembe"); case 4:printf("Persembe");break; case 5:printf("Cuma"); case 5:printf("Cuma");break; case 6:printf("Cumartesi"); case 6:printf("Cumartesi");break; case 7:printf("Pazar"); case 7:printf("Pazar");break; } } printf("switch deyiminden sonra"); printf("switch deyiminden sonra"); } } Output: Haftanin kacinci gunu : 6 Output: Haftanin kacinci gunu : 6 Cumartesi Pazar switch deyiminden sonra Cumartesi switch deyiminden sonra Not: break deyimi kullanmadığımız icin Not: break deyimi döngü ve switch deyimleriyle kullanılıp, döngüden yada switch ana bloğundan çıkışı sağlar idi. Output: Haftanin kacinci gunu : 34 switch deyiminden sonra Note that 'A' --> C de int türü, C++ da char türüdür "A" --> arraydir, char * 'e dönüşüyor. Aklına takılanlar icin, haftanın günlerine bakmanın diğer bir yolu lookup table kullanmak olabilir tabiki. static const char* const pdays[] = { "Pazartesi", "Salı", "Carsamba", "Persembe", "Cuma", "Cumartesi, "Pazar" } █ Güzel bir Örnek : Bir tarihin yılın kaçıncı günü olduğunu hesaplayalım - Lookup kullanmadan switch ile yapacağız, örnek olsun diye. int isleap(int year) { return (year%4 == 0) && (year%100 || year %400 ==0); } int day_of_year(int day,int mon,int year) { int sum = day; switch(mon-1) { case 11: sum += 30; //falltrough case 10: sum += 31; //falltrough case 9: sum += 30; //falltrough case 8: sum += 31; //falltrough case 7: sum += 30; //falltrough case 6: sum += 31; //falltrough case 5: sum += 30; //falltrough case 4: sum += 31; //falltrough case 3: sum += 30; //falltrough case 2: sum += isleap(year) ? 29:28; //falltrough case 1: sum += 31; } return sum; } █ Örnek : [Kraps oyunu] Oyuncu kasaya karşı 2 tane zar atıyor. Oyuncunun attığı zarların toplamı 7 veya 11 ise oyuncu kazanır. Eğer oyuncunun attığı zarların toplamı 2 3 12 ise youncu kaybeder. 4 5 6 8 9 10 zarlarından birini atarsa, farklı bir faza giriyor: Attığı ilk zarı tekrar atarsa oyuncu kazanır. Oyuncu 7 atarsa kaybedecek. Mesela, bu faza girdikten sonra attığı zarların toplamı sırasıyla 9 12 3 8 4 9 olunca oyuncu kazanır, çünkü 9 attı. Yada mesela 10 12 4 11 7 attı, oyuncu kaybetti. Soru, oyuncunun kazanma olasılığını hesapla. Bir rastgele sayı üreticisi ile oyunu defalarca oynatıp hesaplayacağım. Note that uniform bir dağılım elde etmek için mod operatörünü kullanmak kesinlikle tavsiye edilmez, ancak biz şimdilik burada bunu kullanacağız. C++ da uniform dağılıma ilişkin bir kütüphane fonksiyonu var. C de kendin birşeyler yazmalisin. #include #include #define USER_WIN 1 #define USER_LOOSE 0 #define NGAMES 10000 int roll_dice(void) { int dice_1 = rand() % 6 +1; // 0 ve 5 aralığına 1 ekledim. - int rand(void) 0 RAND_MAX arası rastgele bir sayı üretir. int dice_2 = rand() % 6 +1; return dice_1 + dice_2 ; } int game(void) { int dice = roll_dice(); switch(dice) { case 7: case 11: return USER_WIN; case 2: case 3: case 12: return USER_LOOSE; default: return game_(dice); } } int game_(int diceOrg) { for(;;) { int new_dice = roll_dice(); if(new_dice==diceOrg) return USER_WIN; if(new_dice==7) return USER_LOOSE; } } int main(void) { int win_count = 0; for(int i=0;i 10 ? a: b ▪ Bitfield memberlarda örn struct Data{ int a:3; //yapinin a elemanı 3 bit yer kaplasin int b:5; } ▪ Daha sonra göreceğiz ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 21.Ders C [Type Conversions] 16 Haziran 2022 ■ Tür dönüşümleri ══════════════════ Bu konu iyi anlaşılmalı. Yoksa değer kaybından,lojik hatalar ve hatta UB'ye uzanan tehlikelere maruz kalırız. Ancak bu konudaki kurallar oldukça karmaşık, anlaşılması biraz zor. Tür dönüşümlerini ikiye ayırmak mümkün : ▪ Implicit (Örtülü/Otomatik) Type Conversion ════════════════════════════════════════════ Derleyici dilin kurallarına dayanarak, durumdan vazife çıkarıyor ve bir tür dönüşümü gerçekleştiriyor. The following operators require their operands to be of the same type: ▪ The binary arithmetic operators: +, -, *, /, % ▪ The binary relational operators: <, >, <=, >=, ==, != ▪ The binary bitwise arithmetic operators: &, ^, | ▪ The conditional operator ?: ▪ Explicit Type Conversion ══════════════════════════ Operatör (type-cast operator) kullanarak nasıl bir tür dönüşümü istediğimizi söylüyoruz. Tür dönüşümlerini bir başka açıdan da ikiye ayırmak mümkün : ▪ Atama-kopyalama ══════════════════ T a = expr; // T türünden a ya ilk değerini veriyoruz. x = expr ; // x e değer ataması yapıyoruz Burada expr in türüne bakıyor, sol taraf ile aynı türden değilse, expr i sol tarafın türüne dönüştürüyor sonra atamayı gerçekleştiriyor. Bunlar 4 maddede incelenebilir: ▪ ilk değer vermede (initialization) T a = expr; ▪ atama x = expr ; ▪ fonksiyon çağrıları void func(long x); olsun func(expr); //çağrısında expr i tür dönüşümüne tabi tutar ▪ fonksiyonların geri dönüş değerinde double foo(void){ .. return ival;} Burada ival tür dönüşümüne tabii tutulur. Dolayısıyla, atanacak tür, hedef türde ifade edilebiliyorsa, veri kaybı olmaz. edilemiyorsa, veri kaybı olacaktır. Bunun nasıl yapılacağı Implementation Defined [Derleyiciye bağlı.] Öte yandan, büyük türden küçük türe dönüşümde - kullandığım değerlerden çıkarım sonucunda - eminim ki veri kaybı oluşmayacaksa mesela; long long x = 1 //... int ival = x; şeklinde yazarsam , kodu okuyan burda bir kodlama hatası olduğunu düşünür. int ival = (int) x; şeklinde yazarsan bunu senin bilerek isteyerek yaptığını anlarlar. Type-cast Operator , 2. öncelik seviyesinde. (Tür)Değişken mesela (int)dval gibi syntaxi var. Burada dval'in türü değişmiyor. Ona birşey olmuyor. Operatörün ürettiği değer - temporary bir object - türü değişiyor. Operatörün ürettiği değer R value expression dir. Tipik kullanım alanları: Pointer larda - Daha sonra değineceğiz. İşlemin benim istediğim türde yapılmasını sağlamak. (Mesela tamsayıların bölünmesini double türde yapılmasını istiyorsam) Veri kaybı riski içeren dönüşümlerde niyet beyanı için - kontrol bende, bilerek yapıyorum diyorsun Gerçek sayı türünden tamsayı türüne dönüşme gerekince öncelikle ondalıklı kısım kaybedilecek. TRUNCATION. Sonrasında hedef türün sınırlarına girmeyebilir.Bu durumda UB oluyor. Bu veri kaybı değil. UB. ▪ [Usual Arithmetik Conversions] ════════════════════════════════ Atama-kopyalama dışında, Diğer operatörlerin kullanımında sözkonusu olan dönüşümler var Sözgelimi a + b ifadesinde a ve b aynı türde değilse, ortak bir tür bulup ona dönüştürür, şaşırtıcı olarak ne a nın ne b nin türünde değil ,ikisinden de farklı bir türe dönüştürebilirler. a == b ifadesinde ■ Rank - İşaretli olup olmama ile ilgisi yok.yani unsigned int ile int aynı rank dedir. ═══════ C deki temel türlerin rank skalası: long double > double > float > long long > long > int > short > char > _Bool ═══════════════════════════════════ ──────────────────── └ int altı türler Bunlara Integral Promotion tatbik edilir ■ Conversion için Dilin kuralının belirlediği bir algoritma var: ═══════════════════════════════════════════════════════════════ ▪ Operandlardan biri long double ise long double türüne dönüşüm yapılarak işlem gerçekleştirilir. ▪ elseif double ise double ▪ elseif float ise float ▪ elseif Integral Promotion kuralını uygular └─ ▪ Operandlardan biri int altı türden ise (short, char, _Bool) → int 'e dönüştürülür. Binary operatörler yanısıra, unary olmasına rağmen işaret operatörleri içinde Integral Promotion uygulanır. Bu noktada artık int altı tür kalmadı. Şimdi operandlara bakılır: ▪ Aynı türden ise → o türde işlem yapılır. ▪ Değilse (yani operandlar farklı tür, demekki ya rank ya da işaret farklı ya da her ikiside farklı) ▪ Bunlar farklı rank lerde ise ▪ yüksek rank de olan unsigned → unsigned türüne dönüşüm yapılarak işlem gerçekleştirilir. ▪ yüksek rank de olan signed → Yüksek rank li işaretli tür, diğerinin tüm değerlerini tutabiliyorsa → signed olan ın türünde tutamıyorsa → yüksek rank de olanın unsigned türüne dönüştürülürler ▪ Bunlar aynı rank lerde ise ▪ signed/unsigned farkı var ise → unsigned türüne dönüşüm yapılarak işlem gerçekleştirilir. █ Örnekler : Aşağıdaki ifadelerdeki tür dönüşümlerini yazınız: int + double → double float + double → double long double + double → long double char + char → int -- int altı türler int'e terfi ettirilirler short + char → int -- int altı türler int'e terfi ettirilirler char + long → long int + int → int -- aynı türden olduklarından yine o türde işlem yapılır long + unsigned long → unsigned long -- aynı rank'deler, sadece işaret farkı var, unsigned'a dönüştürülür int + unsigned int → unsigned int -- aynı rank'deler, sadece işaret farkı var, unsigned'a dönüştürülür int + unsigned long → unsigned long -- farklı rankdeler, yüksek rank olan unsigned olduğuna göre onun türü unsigned int + long → unsigned long -- farklı rank'ler, yüksek rank'deki işaretli diğerini tutabiliyor mu? sorusunu kendimize sorarız unsigned int , benim derleyicide, 4 byte long int , benim derleyicide, 4 byte yani long türü unsigned int türünün tüm değerlerini tutamaz --> yüksek rank de olanın unsigned türüne dönüştürülürler ■ İşaret, yani + - operatörleri için unutulmaması gereken 2 etki: ══════════════════════════════════════════════════════════════════ ▪ İfadeyi R value expression haline getirir ▪ int altı türler için türü değiştirebiliyor, Integral Promotion yapar. char c = 21; c ifadesinin türü → char +c ifadesinin türü → int -c ifadesinin türü → int Yine bir hatırlatma yapalım : İşaretli türlerde taşma UB. İşaretsiz türlerde taşmada modüler aritmetik devreye girer. UB değil. Konuya yeni başlayanlar için Şaşırtıcı örnekler: █ Örnek : Her iki operand aynı ise, bir tür dönüşümü yapılmaz int x = 10, int y = 3; double val = x/y; // val --> 3.0 Açıklama: x/y işlemi int türünde gerçekleşir, çünkü ikiside int türden, sonrasında double val 'e atama yapılırken double dönüşümü yapılır. █ Örnek : En tehlikeli durumlardan biri işaretli ve işaretsiz tamsayıları işleme sokmaktır !!! int x = -1, unsigned int y= 3; if(x>y) printf("Dogru") else printf("Yanlis") Output: Dogru Açıklama: x>y ifadesinde int ve unsigned int → unsigned -- aynı rank'deler, sadece işaret varkı var -1 tamsayısı (bitleri aynı kalacak ama) unsigned olarak dönüştüğünde işaret biti sebebiyle büyükçe bir sayı olarak yorumlanacak. 4milyar küsür olarak yorumlanıyor █ Örnek : char c = 18; │ char c = 187; │ if(c==18) printf("Dogru") │ if(c==187) printf("Dogru") else printf("Yanlis") │ else printf("Yanlis") │ Output: Dogru │ Output: Yanlis Açıklama: c int'e donusturulmeli çünkü ==18 int.│ Açıklama: c int'e donusturulmeli çünkü ==187 int. c 0001 0010 , bitleri aynı kalacak şekilde │ c 1011 1011, bitleri aynı kalacak şekilde int olarak yorumlandığında yine 18 olur. │ int olarak yorumlandığında -69 olur. Nasıl Mı? See How to Convert Two's Complement To Decimal! Dolayısıyla Output Dogru yazar. │ Dolayısıyla Output Yanlis yazar. ▪ One's Complement : Bitleri toggle et ▪ Add 1 █ Örnek : ▪ Convert to decimal and Prepend - sign char c1 = 187, c2=65; c1+c2 ifadesinin türü nedir? Cevap int █ Örnek : Lahana gibi kuralların sarmalandığı, Tipik bir mülakat sorusu int x = 10, double dval = 4.5; double val = (x>5 ? x:d)/3; // dval --> 3.333333 Açıklama: KURAL : Ternary operatorün 2. ve 3. operandı arasında da bir tür dönüşümü var , compile time da belirleniyor Burada 2. operand x int, 3.operand d double dolayısıyla dönüşüm double yani Ternary operatörün ürettiği değer bu durumda double olur. █ Örnek : signed char c1 = -5; unsigned char c2 = c1; printf("c2 = %u\n,c1); //Output c2=251 Açıklama : hedef unsigned, signed tür c1 dönüşür, -5 + 255 + 1 -> 251 (KURAL : hedef türün MAX değeri + 1) └ MAX └ kuraldan █ Örnek : int x = 76234; signed char c = x; Açıklama : Burada veri kaybı var, ve bu işlemin nasıl gerçekletirileceği derleyiciye bağlı. Tipik olarak derleyiciler yüksek anlamlı byteları kaybedecek şekilde implement ediyorlar. ■ Endianness - Platforma/CPU ya bağlı int türünün 4 byte ile varolduğu bir sistemde int x = 0xABCD; değeri Little Endian Big Endian Bellek adresi Hex Value Hex Value 8000 D A 8001 C B 8002 B C 8003 A D Yani Little Endian sistemlerde (tipik olarak intel), düşük anlamlı byte, düşük adresli bellek alanında tutulur. █ Örnek : Sistemin Endianness ini tespit edecek bir program yazınız int main() { int x = 1; if(*(char *) &x) printf("Little Endian"); else printf("Big Endian"); } ■ Rastgele Sayı üretimi [Random number generation] ══════════════════════════════════════════════════ Bir sayının rastgeleliğinden bahsedebilmek için, elimizde bir sayı sekansı olmalı. Rastgelelik öngörülebilirle ilgili. Öngörülebilirlik yüksekse, rastgelelilik düşük demek . Birçok alanda kullanılıyor. ▪ True Random Number Generation : Doğa tabanlı, Dışsal aygıt(lara) ihtiyaç var. ▪ Pseudo Random Number Generation : Deterministik süreç, algoritma kullanarak yapılıyor. Baştan hangi sayıların üretileceği aslında belli. Algoritmayı tetikleyen input a Tohum değeri [Seed Value] deniyor. Mesela seed value 2 byte ise bu durumda 0 65535 . Yani 65535 tane farklı sayı zinciri üretebilir demek. Sözgelimi seed value=15 için üretilecek sayılar bellidir, yani zincir belli. Rastgele üretilen sayılardan faydalanarak , bir dağılım yaptırmak başka bir şey (tavla zarı 1..6 ve gelme ihtimalleri eşit - uniform distribution) C dilinin retgele sayı üretimine vediği destek basit amaçlıdır. iki fonksiyon veriyor. Bunlar başlık dosyasında ▪void srand(unsigned int) // Seed value yi set eder, default tohum değeri 1 dir. örnek : srand(67u); //Seed Value yu 67 olarak set ediyor. ▪int rand(void) // Döndürdüğü rastgele sayı Dağılım için [0 RAND_MAX ] aralığını kullanır. Burada uniform dağılım veriyor. Fakat uniformity kalitesi de düşük. █ Örnek : Her rastgele sayı istediğinde, tohum değerini değiştirerek sayı istemek Oyun her başladığında farklı bir sayı zincirini kullansın niyetiyle srand( rand() ); // Böyle yazıyorlar, ama bu hep tohum değerini 41 'e set ediyor. Gülünç oluyor. Buna mesela, zaman gibi bir şey enjekte edilmeli. (Tabiki elinde gerçek rastgele sayı üreticisi varsa kullan, ancak C de std da böyle bir olanak yok.) Sistemin bir zaman orijininden geçen -epoche değeri - (tipik olarak sistemler 01 01 1970 00:00) gecen saniye sayısı Prototipi time_t time(time_t *) bir fonksiyon var. Burada time_t tür eş ismi. (Benim derleiyicide long long). Bu fonk time.h başlık dosyasında. #include int main() { time_t timer; time(&timer) printf("Gecen saniye = %lld\n",timer); } Output : Gecen saniye = 1655830054 Bunu bir degiskende tutmak istemiyorsan, bir defalığına değeri sadece almak istersen, fonksiyona null pointer göndererek çağrı yapabilirsin. Bunun için Parantez içine NULL veya 0 yazabilirsin. #include int main() { printf("Gecen saniye = %lld\n",time(NULL)); } Output : Gecen saniye = 1655830058 ■ Randomize Idiomu Bu bilgiler ışığında, oyun başlangıcında tohum değerini üretmek için srand( (unsigned) time(NULL) ); ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 22.Ders C [Dizilere başlıyoruz] 21 Haziran 2022 █ Örnek : Aşağıdaki program çalıştırıldığında üretilen 10 parola birbirinden farklı #define MIN_PWD_LEN 5 #define MAX_PWD_LEN 11 void print_random_pwd(void) { int len; len = ( rand() % (MAX_PWD_LEN - MIN_PWD_LEN +1) ) + MIN_PWD_LEN ; // 5 - 11 aralığında bir sayı üretiyorum for(int i=0; i #include #define NPOINT 10000 int main() { std:: mt19937 eng; std:: uniform_real_distribution dist{0,1.}; inside_count = 0; for(int i=0; i < NPOINT; ++i) { dist(eng); //[0 1) arası rastgele değer üretimi double x = dist(eng); double y = dist(eng); if(x*x + y*y <= 1.) ++inside_count; } printf("pi ~ %.12f\n", 4. * inside_count/NPOINT); } ▪ Ödev Sorusu: Oyuncunun kazanma olasılığını bulun. (Böyle bir soruyu matematiikle çözmek uzun zaman alır ama kodlamayla daha hızla sonucu öngörülebilir.) Oyun başladığındı oyuncu ve kasanın 100 Lirası var. Oyuna girmek - Yazı/tura atmak için her atış öncesi- oyuncu kasaya 10 Lira verir. Oyuncu arka arkaya 2 kez Yazı atarsa kasa -> oyuncuya 35 Lira verir. Oyuncu arka arkaya 3 kez Tura atarsa kasa -> oyuncuya 60 Lira verir Kazanma sonrası atış bufferi resetlenir. (İki kez YY attıktan sonra Y atınca tekrar kazanmaz) Oyuncu Oyuna girmediği zaman ya da kasa ödeme yapamazsa oyun biter. ■ Diziler C'de hazır olarak sunulan tek veri yapısı dizi. Syntax T isim[boyut] Örnek int a[size_expr]; └ Boyutunu bildiren ifade. Bazı kısıtlamalara tabii.Bunlar: ▪ integral type ve constant expression olmalı Yani compile time da bakıp bunun ne olduğunu bilmesi gerekiyor Dizi boyutu 0 olamaz.1 olabilir. Negatif tamsayı olamaz. int ar[foo()]; // Error int ar[var]; // Error, var is a variable int ar[SIZE]; // okay, if SIZE is makro int ar[10*20]; // okay, compile time da derleyici bunun 200 oldyğunu görür int ar[10]; ar'nın türü nedir sorusunun cevabı: int[10] ar[5]'in türü ne sorusunun cevabı int ar diyorki, hey x, bende senden 10 tane var. int x; int x, ar[30]; //okay , int hem x'i hem ar[30]'u niteliyor C99 ile gelen, C11 ile opsiyonel [derleyici sunmak zorunda değil] hale getirilen, VLA [Variable Length Array] bir kavram var. Bu ayrı bir konu. Kursun sonunda ayrı bir derste göreceğiz. Zaten çok sık kullanılan bir araç değil. ▪Fixed array veri yapısı : C nin bize hazır sunduğu diziler fixed array. Bunlar eleman ekleme-çıkarmaya kapalı (imkanı yok). Sorsan, Abi biz ne uzarız ne kısalırız der. Ama indexle , constant time da ögelerine erişirsin. ▪Dynamic array veri yapısı :En sık kullanılan yapı. Veriler Bellekte ardışıl (contiguous ) olarak , bir blok halinde yer alır. ▪ Verileri bellekte bu şekilde asker gibi tutmanın avantajı nedir? ▪ Birincinin konumunu biliyorsam, herhangi elemanın konumunu hesaplar ve döngüye ihtiyaç duymadan O(1) Constant Time'da erişebilirim. ▪ Yine böyle bir dizide, belli bir değeri arıyorsam, dizi elemanlarını tek tek dolaşmalıyım - O(n) Linear Time Complexity ile amacıma ulaşabilirim - ▪ Dinamik dizilerde öge ekleme - çıkarma (insertion) yapabiliyoruz. ▪ Ortadan ekleme-silme O(n) karmaşıklıkta ▪ Son öge ekleme-silme [sondan ekleme = Push back] yine O(1) Constant Time'da yapılabiliyor. ▪Computational Complexity : Diğer faktörler sabitken, Algoritmanın daha az işlemle gerçekleştirilmesi diyebiliriz. Bunun için bir ölçüt, veri yapısındaki öge sayısının artışıyla, yapılması gereken işlem miktarı arasındaki ilişki. ───────────────────────────────────────────────────────────────────────────────────────────────────── Big-O Complexity Chart (▪ Operations) as elements increase ▪ O(1) [Constant time Complexity] Öge sayısı artsa da , işlem miktarı değişmiyorsa ▪▪ O(logn) [Logarithmic time Complexity] - Meşhur Binary Search Algorithm ▪▪▪ O(n) [Linear time Complexity] ▪▪▪▪ O(nlogn) ▪▪▪▪▪▪▪▪▪ O(n^2) [Quadratik time Complexity] ▪▪▪▪▪▪▪▪▪▪▪ O(2^n) ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪ O(n!) ───────────────────────────────────────────────────────────────────────────────────────────────────── Algorithm Complexity Comparison : Algoritmaların hepsi olmasada , bazı koşullara bağlı olarak farklı karmaşıklıklara sahip olabiliyor. ┌──────────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ │ Algorithm │ Best Time │ Avg Time │ Worst Time │ Worst Space │ Complexity ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Linear Search │ O(1) │ O(n) │ O(n) │ O(1) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Binary Search │ O(1) │ O(logn) │ O(logn) │ O(1) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Bubble Sort │ O(n) │ O(n^2) │ O(n^2) │ O(1) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Selection Sort │ O(n^2) │ O(n^2) │ O(n^2) │ O(1) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Insertion Sort │ O(n) │ O(n^2) │ O(n^2) │ O(1) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Merge Sort │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(n) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Quick Sort │ O(nlogn) │ O(nlogn) │ O(n^2) │ O(logn) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Heap Sort │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(n) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Bucket Sort │ O(n+k) │ O(n+k) │ O(n^2) │ O(n) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Radix Sort │ O(nk) │ O(nk) │ O(nk) │ O(n+k) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Trim Sort │ O(n) │ O(nlogn) │ O(nlogn) │ O(n) │ ├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │ Shell Sort │ O(n) │ O((nlogn)^2) │ O((nlogn)^2) │ O(1) │ └──────────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ ───────────────────────────────────────────────────────────────────────────────────────────────────── Bunların sabit çarpanıyla ilgilenmiyoruz. Aksi belirtilmedikçe bıradaki logn in tabanı 2 dir. Öge sayısı düşükse, complecity ler arasında büyük farklar olmayacak. TSP (Travelling Salesman Problem) : Satıcının uğraması gereken şehirler var. Her iki şehir arasında ulaşım için yol olduğunu düşünelim. Satıcı için öyle bir rota oluşturmak istiyoruzki, en kısa olsun. Bunu kesin çözen algoritmanın karmaşıklığı O(n!) Ben bir algoritma yaptığımda bunun karmaşıklığını söyleyebilir miyim? It depends. Bazen kolay bazen çok zor olabilir. █ Örnek : Küçükten büyüğe sıralanmış iki diziyi, üçüncü bir dizide birleştirin, ancak sırlama- küçükten büyüğe sıralama bozulmasın. Bunun çözüm O(n) de gerçekleşir. Bir öğrenci üçüncü diziye, bu dizileri yerleştirip, üçüncü dizide bubble sort - O(n^2) -çekmiş. Sınavdan 0 almış :) | dizi1 2 7 9 12 89 dizi2 3 6 45 67 | Çözüm, dizileri gez, karşılıklı elemanları karşılaştırıp küçüğünü üçüncü diziye yaz. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 23.Ders C [Arrays cont'd + sizeof operatörü] 23 Haziran Perşembe ■ Diziler ▪ Yerel diziler olabilir, bunlar Otomatik ömürlü - Static ömürlü olabilirler. ▪ Bir fonksiyonun parametresi bir dizi olamaz! ▪ Fonksiyonlar dizi döndüremez. Geri dönüş değeri türü dizi olamaz. ▪ Dizileri initialize ederken int ar[3] = {1,2,3,4} ; // error, dizinin sahip olduğundan daha fazla elemana sahip bir liste ile initialize edilemez int ar[3] = {1,2,3} ; // okay int ar[3] = {1} ; // okay, ar[0] = 1 , diğer elemanlar 0 olacak yani ar[1] ve ar[2]=0 olacak. int ar[3] = {} ; // error, C de geçerli değil, C++ da geçerli int ar[3] ; // ilk değer verilmediğinde statik ömürlü ise 0 ile initialize edilir. otomatik ömürlü ise garbage value ile initialize olmuş olur int x = 1, y = 2; int ar[3] = {x,y,x+y); // okay ▪ Diziyi liste ile initialize edersek, dizi boyutunu belirtmek zorunda değiliz. Compile time da derleyici dizi boyutunu belirler. int ar[] = {1,2,3,4} ; // okay, Derleyici dizi boyutunu 4 olarak set eder ▪ Dizinin boyutunu-eleman sayısını- nasıl elde edebilirim? (Mesela bir for döngüsünde kullanmak üzere) ▪ Designated initializer (C99) int ar[100] = { [15]= 75, [13]=26}; 15 indeksindeki elemanına 75 13 indeksindeki elemanına 26 ilk değerini vermiş oluyoruz. Belirtilmeyenler yine 0 ile hayata başlayacak. Designated initializer kullanırken de , dizi boyutunu belirtmeye ihtiyaç yok. Bu durumda derleyici, en büyük indeksliye bakıp dizi boyutunu belirler. int ar[ ] = { [15]= 75, [13]=26}; //Derleyici diziyi 16 elemanlı olarak kabul eder. ▪ Array Decay : Dizi ismi bir ifade içinde kullanıldığında - 2 istisna hariç - , derleyici dizinin ismini , dizinin ilk elemanının adresi olarak dönüştürür. Yani ar demek &ar[0] demek └─ ▪İstisna 1 : sizeof operatörü ▪İstisna 2: & (address of operatörü) İşte bu yüzden iki dizi, isimleriyle birbirlerine atanamazlar(i.e ar = br; //Error) ▪ [] subscript operator, syntax : address[int] şeklinde olmalı, x[y] ifadesini *(x+y) haline getiriyor. int a[10]; ▪ Geçersiz index kullanmak UB. i.e a[10] ▪ traverse/iterate : dizinin elemanlarını dolaşmak anlamında kullanılıyor ■ sizeof operatörü - 2. öncelik seviyesi ▪ sizeof hem operatör hem keyword, diğer operatörler bir-iki karakterden oluşurken, bu sözcük şeklinde. Bundan başka bu şekilde olan bir operatör daha var. ▪ Bir compile-time operatörü, yani ürettiği değer derleyici tarafından compile time da bir sabit olarak görülüyor. Yani bir constant expression. ▪ Ürettiği değer size_t türünde [örneğin sizeof x ifadesinin türü size_t dir], operandın bellekte kaç byte yer kapladığını söyler, burada size_t, işaretsiz bir tamsayı olmak zorunda (standard type alias - size_t , bildirimi derleyicilere bırakılmış) . ▪ kullanım biçimi ▪ sizeof(Tür) Tür ismi ile kullanım. Buradaki parantez sentaksın bileşenidir, öncelik parantezi değildir. i.e printf("sizeof(long) = %zu bytes\n", sizeof(long)); ▪ sizeof var ve sizeof(expr) Ifade ile kullanım. Burada parantez kullanımı gerekmemekte, eğer yazarsan öncelik parantezi yazmış olursun. sizeof un CompileTime operatör olmasından mütevellit, expr ifadesini işletmez, türünü tespit edip kapladığı alanı söyle. Unevaluated Context (işlem kodu üretilmeyen bağlam). Bu gerçekten hareketle , sınavlarda çok çeldirme yapmaya çalışıyorlar. ▪ sizeof diziismi Dizi ismi ile kullanım -> array decay uygulanmıyor. int arr[100]; printf("sizeof arr = %zu \n", sizeof arr); //Outputs 400 Yani dizi ismi array decay ile ilk elemanın adresine dönüşmez. Hakikaten dizinin bellekte kapladığı toplam uzunluk byte olarak değerlendirilir, döndürülür. Bundan hareketle dizinin kaç elemanlı olduğunu (boyutunu) sizeof(arr)/sizeof(arr[0]) ifadesi ile hesaplayabiliriz. int x; sizeof x // bu ifade okay sizeof(x) // bu ifade okay sizeof x + 5 // (sizeof x) + 5 olarak gruplanır/yorumlanır sizeof(x+.5) // double türünün kapladığı alanı söyleyecek sizeof 'A' // C de karakter sabitleri int, dolayısyla int türünün bellekte kapladığı alanı söyleyecek █ Örnek : (Mülakatlarda sorulur) Ekrana ne yazar? #define asize(x) (sizeof(x)/sizeof(x[0])) int main() { int a[]= {2,6,3,1}; for(int i=-2 ; i < asize(a)-2 ; ++i) printf("%d ",a[i+2]); } Cevap : Hiçbirşey yazmaz, çünkü döngüye girmez, çünkü Tür dönüşümü hikayesi, asize makrosu unsigned int/unsigned int,sonrasında unsigned int - int ifadesinin türü - otomatik tür dönüşümü ile birlikte - unsigned int olur. Dolayısya i (int ) < unsigned int iadesinde , karşılaştırma operatörü yine aynı türleri talep ettiğinden otomatik tür dönüşümü ile birlikte unsigned int < unsigned int haline dönüşecek ve daha ilk sorguda i= -2 unsigned int e dönüşünce işaret bitinden dolayı çok büyük bir sayı olacak dolayısıyla koşul sağlanmadığından döngüye hiç girmeyecek. █ Örnek : #define asize(x) (sizeof(x)/sizeof(x[0])) int a[] = {1,4,7,2} int b[asize(a)] = {0}; // a ile aynı uzunlukta mıdır? ■ nutility.h Daha fazla örnek yapabilmek için Pointers ile tanışmamıza da az kaldı, ama nutility içinde kullanacağız. . . . █ Örnek : Dizinin elemanları toplamı, ve elemanların ortalamasını bulduran bir prg yaz. Yazdığın algoritmanın Karmaşıklığı nedir? O(n) video @1:19 █ Örnek : Dizinin tek sayı değerli elemanlarının toplamı ve ortalamasını bulduran? (a[1] a[3] değil, değeri tek olanlardan bahsediyoruz) - Note that , tekliği sınamanın bir yolu bitsel VE kullanıp yani x & 1 işlemi LSB yi döndürecektir. - Dikkat , belkide dizide hiç tek değere sahip eleman yoktur, averaj alırken 0 a bölme yaparsan UB. █ Örnek : Dizinin standard sapmasını bulunuz. (Her elemanın Ortalamaya mesafelerinin karelerini toplayıp, bu toplamı eleman sayısının 1 eksiğine bölüp, ardından karekökünü alırsan, std sapmayı bulursan) █ Örnek : Dizinin en büyük değerli elemanının değeri ve elemanın indisini bulunuz. Yazdığın algoritmanın Karmaşıklığı nedir? O(n) █ Örnek : Dizinin en küçük değerli elemanının değeri ve elemanın indisini bulunuz. █ Örnek : Yarışın ikincisine runner up denir. Dizilerde en büyük ikinci elemanı. Dizinin runner up elemanı nı bulduran prg. █ Örnek : Kullanıcıdan bir değer al, dizide bu değer var mı yok mu bulduran prg. █ Örnek : D.Knuth sorusu imiş. Dizide bir değer(sval) aranacak, for(i=0;ia[j+1]) { int temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } print_array(a,SIZE); } █ Örnek :Yukarıdaki örneği büyükten küçüğe sıralama yaptıracak şekilde değiştirmek istersem nereyi değiştirmeliyim? if(a[j]a[j+1] ) ) Bubble sort gibi 3-4 algoritmayı ezbere kodlayabilecek durumda ol. ▪ Bir kod parçasının işletme zamanını ölçmek #include clock_t startTime = clock(); // clock() returns the number of clock ticks elapsed since the program was launched //code clock_t endTime = clock(); printf("%2.f saniye", (double)(end-start)/CLOCKS_PER_SEC) --------------- Ödev sorusu (meşhurmuş) En az 1 elemanı negatif değerli bir dizi var. Maximum sub-sequence problem Dizinin Subsequence i ne demek? Dizinin ardışık n elemanının oluşturduğu grup Dizinin max toplama sahip subsequence i için max toplam ne ? --------------- Ödev Sorusu Partition Sort Algorithm Koşula göre veri yapısını ikiye ayırma, mesela sağlayanlar başa ve sağlamayanlar sona gibi, bir de partition point = sağlamayan ilk öğenin indexi, hiç yoksa SIZE --------------- ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 25.Ders C [char diziler ve yazı işlemleri] 30 Haziran 2022 Perşembe ■ char diziler ve yazı işlemleri ▪ C de string türü yok. Yazı tutmak için char türden dizilerde tutuluyor. char str[100]; // Yazı tutmak için böyle bir diziye ihtiyacın var Burada tutulan yazının uzunluğu nasıl biliniyor? Null Terminated Byte Stream , son karakterden Null Character \0 konuyor. Tek başına direkt yazıldığında '\0' , tamsayı değeri ise 0. (Mesela 'A' nın tamsayi değeri 65 idi.) Mesela printf("%d \n",'A'); //A karakteri karşılığı Output 65 printf("%d \n",'0'); //0 karakteri karşılığı Output 48 printf("%d \n",'\0'); //null karakteri karşılığı Output 0 BABA yazısı bir char türden bir dizide tutuluyor olsaydı, elemanların değerleri 65 66 65 66 0 şeklinde olacaktı. ▪ Dolayısıyla char str[SIZE]; //En fazla SIZE-1 uzunluğunda bir yazıyı tutabilir. - UB ye mahal bırakmayacak şekilde - ▪ Initialize edelim char str[100]; ▪ str[100] = {'B','A','B','A'}; //well it depends, str zero initialize edilmiş ise UB olmaz, çünkü belirtilmeyen elemanlar 0 - null character olacaktı. Aksi takdirde UB oluşur. str[100] = {'B','A','B','A','\0'}; //okay ▪ str[] = {'B','A','B','A','\0'}; //okay, dizilerde boyut belirtmezsen initialize list ten çıkarım yapıyordu. ▪ Elemanları tek tek initialize edebilirsin. str[0] = 'B'; str[1] = 'A'; str[2] = 'B'; str[3] = 'A'; str[4] = '\0'; ▪ str[100] = "murat"; //okay Dizinin elemanlarına bu yazının karakter kodlarıyla ilk değer verir ve sonuna null karakteri de ekler. Dizi boyutu belirtmeden bunu yaparsam yine sonuna null karakteri ekler Yani str[] = "murat"; //okay, str yi 6 elemanlı yapar, yani sonuna sonuna null karakteri de ekler. ▪ Peki ya str[5] = "murat" ; yaparsam ne olur? Yani sonuna null eklemesine fırsat bırakmayacak şekilde muzip bir initialization yapmaya çalışırsam? C ve C++ da bu farklı kurallara tabii. C++ da geçersiz. Çünkü sonuna null karakter ekleyemediğinden. Ancak C dilinde , bir syntax hatası yok, geçerli, ama sonuna null karakter ekleyemiyor. ▪ "murat" aslından 6 elemanlı bir dizi. █ Örnek : int main() { char str[SIZE]; str[0] = 'B'; str[1] = 'A'; str[2] = 'B'; str[3] = 'A'; şeklinde bir atama yapsam, bu dizide BABA yazısı tutuluyor diyemem, çünkü str dizisi çöp değerlerle init edildiğinden yazının sonunda -str[4] elemanının değerinin- null karakter olma garantisi yok. Bunu ekrana yazmak isteyen biri belkide şöyle bir kod yazdı: for(int i=0; str[i] != '\0';++i) putchar(str[i]); } Çöp değerle initialize edilmiş elemanlara erişmeye çalışacağından, UB oluşacak. Ama ekranda BABA yazdı. Olabilir ama UB de ne olacağı belli olmaz. Bir de debug da çalıştır bakalım ne oluyor? █ Örnek : Aynı kodu aşağıdaki şekilde yazsam UB olmayacak. Çünkü dizi global olacak ve globaller ilk değer verilmemişse 0 ile initialize edilecek. char str[SIZE]; int main() { str[0] = 'B'; str[1] = 'A'; str[2] = 'B'; str[3] = 'A'; for(int i=0; str[i] != '\0';++i) putchar(str[i]); } Burada str[i] != '\0' yazmak yerine str[i] != 0 yazsaydım da fark olmazdı, ama null karakteri ile yazmak, yazı ile ilgili olduğu fikrini/algısını kolaylaştırır. Keza str[i] != '\0' yazmak yerine str[i] yazsaydım da fark olmazdı - lojik yorumlamaya tabii, 0 false . ▪ Diziyi ekrana yazdırmak ───────────────────────────────── ▪ Karakterleri tek tek yazdırma for(int i=0; str[i] != '\0';++i) putchar(str[i]); ▪ Puts kullanarak : char türden dizinin adresini arguman olarak alır - array decay - , dizide null karakter görene dek ekrana yazdırıyor, sonrasında bir de new line gönderiyor. puts(str); // Bunu puts(&str[0]) şeklinde yazmakla fark yok - array decay - ▪ Printf kullanarak , printf variadic bir fonksiyon idi. Formatlı yazdırma içindi. printf("isim = %s",str); //okay ▪ String literal yazdırmak printf("murat"); //okay, burada aslında 6 elemanlı bir dizinin adresini göndermiş oluyoruz. ▪ Ekrandan Diziye yazı almak ───────────────────────────────── ▪ scanf kullanarak : Dikkat, boşluk karakterlerini diziye almış olmayız - Ayrıca Taşmalara dikkat. char str[SIZE]; printf("bir isim girin:"); scanf("%s",str); // Dikkat, boşluk karakterlerini diziye almış olmayız Mesela girişte omer faruk yazarsak , str dizisinde omer alınmış olur. Ancak -scanset denen -formatlama özelliğini değiştirerek bunu başarmak mümkün scanf("%[^\n]s",str); // New line karakterine kadar bütün karakterleri al demek. // reads a line from stdin into the buffer pointed to by str until either a terminating newline or EOF found █ Örnek : Öte yandan scanf in boşlukları ayırdedici olarak kullanmasının avantajı olan durumlarda var: int main() { char name[SIZE]; char surname[SIZE]; int age; printf("isim soyisim ve yas giriniz:"); scanf("%s%s%d",&name,&surname,&age); printf("%s %s %d yasinda",name,surname,age); } ▪ gets kullanarak: C99 ile deprecate edilen, C11 ile dilden çıkarılan gets fonksiyonu var. Boşluk karakterlerini de alabiliyordu. ▪ nutility içine void sgets(char* p) diye kendimiz bir fonksiyon yazabiliriz. void sgets(char *p) { while( (c=getchar())!='\n' ) *p++ = (char) c; *p = '\0'; } █ Örnek : Girilen yazıyı tersten ekrana yazan bir prg yaz. █ Örnek : Girilen yazının palindrom olup olmadığını ekrana söyleyen [evet - hayır] prg yaz. İkinci bir dizi kullanmak yok, yazıyı değiştirmek yok. █ Örnek : Girilen iki kelimenin yerini degistirip ekrana yazdıran prg. Prg şablonun #define SIZE 100 int main() { char str[SIZE]; printf("iki kelime girin:"); sgets(str); printf("|%s|\n",str); //your code here printf("|%s|\n",str); } █ Örnek : Girilen iki kelimenin anagram olup olmadığını ekrana söyleyen [evet - hayır] prg yaz. İlave dizi kullanarak da, kullanmadan da iki çözümü de yapmaya çalış Anagram, uzunlukları eşit, ancak harflerinin yeri farklı olmakla birlikte aynı sayıda harften oluşacak, i.e kalem emlak #define SIZE 100 int main() { char str1[SIZE]; char str2[SIZE]; printf("iki kelime girin:"); scanf("%s%s",str1,str2); //your code here } █ Örnek : Girilen bir yazının ve sonrasında girilen bir karakterin, yazının içinde kaç defa geçtiğini söyleyen bir prg yaz. #define SIZE 100 int main() { char str[SIZE]; printf("bir yazi girin:"); sgets(str); printf("sayilacak karakteri girin:"); int c = getchar(); int cnt = 0; for(int i = 0; str[i]!= '\0';++i) { if(str[i]==c) ++cnt; } printf("%d tane %c karakteri var\n",cnt,c); } █ Örnek : Yukarıdaki, örnekteki yazıdaki ascii karakterlerini sayacak prg yaz. - Tipik bir mülakat sorusu #define SIZE 100 int main() { char str[SIZE]; printf("bir yazi girin:"); sgets(str); int cnts[26]={0}; //26 tane ascii karakter var. 26 sayacli bir sizi yapalım öyleki //bu dizinin [0] A nin sayaci ... [25] Z nin sayaci olsun for(int i = 0; str[i]!='\0'; ++i) { if(isalpha(str[i])) { ++cnts[ toupper(str[i]) - 'A']; } } // Hangi karakterden kaç tane varmış ekrana dökeyim, hiç olmayanları yazmaya gerek yok for(int i = 0; i<26; ++i) { if(cnts[i]) { printf("%c %d\n",'A'+i,cnts[i]); } } } Remove ve remove copy algoritmaları: Remove : Bir yazıyı bir yerden bir yere kopyalamak #define SIZE 100 int main() { char str[SIZE]; char tmp[SIZE]; printf("bir yazi girin:"); sgets(str); //null karakter görene dek dolaş ve kopyalama int i=0; for(i=0; str[i]!='\0' ; ++i) { tmp[i] = str[i]; } tmp[i]='\0'; printf("|&s|",str); printf("|&s|",tmp); } Yukarıdaki örnekte, sözgelimi a ları silerek kopyalasaydık buna Remove Copy deniyor. Bu amaçla hedef index - write_idx - tutman gerekiyor. #define SIZE 100 int main() { char str[SIZE]; char tmp[SIZE]; printf("bir yazi girin:"); sgets(str); //null karakter görene dek dolaş ve kopyalama int write_idx=0; for(int i=0; str[i]!='\0' ; ++i) { if(str[i]!='a') tmp[write_idx++] = str[i]; } tmp[write_idx]='\0'; printf("|&s|",str); printf("|&s|",tmp); } █ Örnek : Yazıdaki a harflerini silmek █ Örnek : Yazıdaki rakam karakterlerini silmek #define SIZE 100 #define SIZE 100 int main() int main() { { char str[SIZE]; char str[SIZE]; printf("bir yazi girin:"); printf("bir yazi girin:"); sgets(str); sgets(str); printf("|&s|",str); printf("|&s|",str); int write_idx=0; int write_idx=0; for(i=0; str[i]!='\0' ; ++i) for(i=0; str[i]!='\0' ; ++i) { { if(str[i]!='a') if(!isdigit(str[i])) str[write_idx++] = str[i]; str[write_idx++] = str[i]; } } str[write_idx]='\0'; str[write_idx]='\0'; printf("|&s|",str); printf("|&s|",str); } } █ Örnek : iki yazının eşitligini sınayalım #define SIZE 100 int main() { char s1[SIZE]; char s2[SIZE]; printf("iki kelime girin:"); scanf("%s%s",s1,s2); int i=0; int isEofArr = 0; while(s1[i] == s2[i]) { if(s1[i] == '\0') { isEofArr = 1; break; } ++i; } //Döngüden nasıl çıkmış, dizi sonu sayesinde mi yoksa elemanların eşitsizliği yüzünden mi? Bunu flag im ile anlıyorum. if(isEofArr) printf("Esit"); //Demekki döngüden break ile çıkmış else printf("Esit degil"); //Demekki döngüden elemanların eşitsizliği yüzünden çıkmış } █ Örnek : Yazıdaki kelimeleri sayalım, kelimeler arası birden fazla boşluk olabilir #define SIZE 100 #define INWORD 1 #define OUTWORD 0 int main() { char str[SIZE]; printf("bir yazi girin:"); sgets(str); printf("|&s|",str); //code - Bir sonlu durum makinası yazacağız. int flag = OUTWORD; int cnt = 0; for(int i = 0; str[i] != '\0'; ++i) { if(isspace(str[i])) {flag = OUTWORD;} else if (flag==OUTWORD) {++cnt; flag=INWORD;} } printf("Toplam %d kelime",cnt); } Yazılardan sayılara dönüşüm Sayılardan yazıya dönüşüm Bunlarla ilgili dilin sağladığı araçlar var, şimdilik biz mantığını anlamak için primitive şeyler yazmaya çalışalım. Bunlarla ilgili örnekler yapıldı. Mesela girilen yazı 5712 , bunu sayıya dönüştürdük, girilen yazı hex olarak yorumlayıp sayıya dönüştürdük. Mesela girilen sayı 1763 bunu yazıya dönüştürdük gibi. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 26.Ders C [Pointers] Pointer address anlaminda kullanıliyor. Adres tutan değişkenlere pointer diyoruz. Expression için data type ve value category den bahsetmiştik. Şimdi artık ifadelerimizin adres anlamına da geleceğini öğreneceğiz. ▪ Pointerların türü : Bazı ifadeler adress anlamına gelecekse, adress e ilişkin bir tür de olması gerekir. Adress o adresteki değişkenin türü ile de ilişkilendirilen bir tür. Yani , tek başına, adres diye ayrı bir tür yok. Mesela T x ; // T türünden x Bir expr, x in adresi ise, bu expr in türü T* deniyor. Bunu sorgulamayalım ve bu şekilde kabul edelim. Dolayısıyla şimdiye dek kaç tür le karşılaşmışsak , bunlara ilişkin bir de adres türleri var. örn double* int* vs... ▪ C dili aslında pointerları iki şekilde grupluyor object pointers function pointers Dikkat: object pointerlar ile function pointerların storage ihtiyacı aynı olmak zorunda değil. Böyle bir garanti yok. ▪ pointer lar da, diğer değişkenlerde olduğu gibi, global --> statik ömürlü, file scopeda, ilk değer verilmezse zero initialize edilir - null pointer local --> otomatik ömürlü, blok scopeda, ilk değer verilmezse çöp-indeterminite value/garbage value ile hayata başlar değişkenlerde olduğu gibi static keyword ile statik ömürlü hale getirilebilir. static ömürlü olunca tabi zero initialize edilir. Buralarda değişen bir şey yok. ▪ Tüm object pointerlar bellekte aynı miktarda yer kaplar. miktar derleyiciye bağlı. Mesela VS de 4 byte. printf("sizeof char is %zu", sizeof(char )); //1 byte printf("sizeof char* is %zu", sizeof(char*)); //4 byte ▪ Declaration int* ptr; // Bu şekilde tanımlıyoruz. Şöyle okuyoruz pointer to int. ptr nin türü int* dir. Buradaki * bir operatör değil. Bir deklaratör. int* ptrx, ptry; // No, ptry is not of type int*. // Declarator * does not apply on comma seperated list - * deklaratörü sadece önüne gelene etki ediyor, dağılma özelliği yok. If you want ptry to be a pointer type, you shoud declare int* ptrx, * ptry ; Bu dağılma ile ilgili hususa örnek olarak int ival; int* p; int a[10]; Bunları tek satırda bildirmek için int ival,*p,a[10]; şeklinde yazabilirsin. ▪ Pointer türlerle atama/ilk değer verme işlemleri - adres değeri hangi kılıklarla karşımıza çıkabilir? ─────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Bir pointer değişkeninin kendisi bir adrestir. Dolayıysla bir başka pointer'a atama/ilk değer vermede kullanılabilir. int x= 190; int* p1 = &x; // Pointer a & operatörü ile değer atamak/ilk değer vermek okay. int* p2 = p1; // Yani aynı türden bir başka ptr in değerini atamak/ilk değer vermek. ▪ & address of operatörünün ürettiği değer ile bir pointer'a atama/ilk değer vermede kullanılabilir. int x= 190; int* p1 = &x; // Pointer'a & operatörü ile değer atamak/ilk değer vermek okay. ▪ Array Decay ile (Array to Pointer Conversion) int a[10] = {0}; int* ptr = a; //Bir ifade de a ismini kullanıdığımızda &a[0] a dönüşür. Yani int* ptr = &a[0]; yazmak aynı şey ptr = a ; //okay ▪ Array Decay - Adres değerinin bir başka kılığı ───────────────────────────────────────────────── Eğer bir dizinin ismini - 2 istisna hariç - bir ifade de kullandığımızda, dizinin ismi, dizinin ilk elemanının adresine dönüşür. └─ ▪İstisna 1 : sizeof operatörü ▪İstisna 2: & (address of operatörü) Örnek: int a[10] = {0}; int* ptr = a; //Bir ifade de a ismini kullanıdığımızda &a[0] a dönüşür. Yani int* ptr = &a[0]; yazmak aynı şey ptr = a ; //okay printf("a =%p\n",a); // Output 006FFD1C printf("&a[0]=%p\n",&a[0]); // Output 006FFD1C printf("ptr =%p\n",ptr); // Output 006FFD1C ▪ Direkt değer verme; int * ptr = (int*) 0x1ACF; //okay Şunu yorumlayın: int* ptr; ptr = 345; // okay in C, Syntax error in C++ Yorum : Bir pointer'a (adres türü tutan bir değişkene), Bir int türü ile değer vermeye çalışılmış. C++ dilinde aritmetik türlerden adres türlerine otomatik dönüşüm olmadığından bu ifade C++ dilinde olsaydı, syntax error olurdu. C dilinde, tamsayı türlerinden adres türlerine örtülü dönüşüm var. Yani geçerli bir kod. Bir UB değildir. Ancak pratikte Böyle bir kod yazmayız. Derleyici böyle bir şey gördüğünde bir lojik uyarı verir. int x = 10; int* ptr = x; //Pointera int türü ile değer vermeye çalışılmış.C dilinde, tamsayı türlerinden adres türlerine örtülü dönüşüm var. Yani geçerli bir kod. Ancak bunu yapmak mecburiyetindeysek, niyetimizi bildirmek adına int* ptr = (int*) x; demek uygun olur. ▪ Null pointer conversion : Ancak bunu yapmak mecburiyetindeysek demiştik, - tamsayı 0 sabiti - buna bir örnek; int* ptr = 0; // Null pointer conversion : 0 tamsayı sabiti derleyici tarafından null pointer'a dönüştürür. Bunun çevresinden dolaşmaya çalışmanın null pointer conversion gerçekleşeceğinin garantisi yok. Çünkü 0 tamsayı sabiti ile null pointer conversion olur dedik. int x = 0; int* ptr; ptr = x; // Burada null pointer conversion gerçekleşir diyemeyiz, bu yüzden böyle bir kod yazmamalıyız. ▪ Adres(Pointer) döndüren bir fonksiyon ile değer verme; int* ptr = foo(); // Burada int* foo(void) ile bildirilmiş bir fonksiyon olduğunu varsayalım ▪ Hatırlayalım: ──────────────────────────────────────────────────────────────────────────────── int x , a[10]; int foo(void), Şu ifadelerin türleri ne? Bunların değer kategorisini ne? x int L a[3] int L 26 int R foo() int R x+4 int R İşte adres türleri içinde böylesi bir durum - farklı kılıklarla karşımıza çıkabiliyor - var. int x; int* ptr; int a[10]; Şu ifadelerin türleri ne? Bunların değer kategorisini ne? &x int* R ptr int* L a int* L a+3 int* R ▪ Pointer Operators ─────────────────────────────────────── 1 [] -> ─────────────────────────────────────── ◄2 & * ─────────────────────────────────────── Unary & addressof operator ( Binary infix kullanımda BitwiseAND ile karıştırma ) Unary * derefencing operator ( Binary infix kullanımda Multiplication ile karıştırma) [] Subscript operator -> operator ■ & addressof operator - ifadenin L value mu R value mu olduğunu test etmek için daha önce öğrenmiştik En önemli kısıt, operandı olarak L value expression olmak zorunda. Ürettiği değer : Operandı olan nesnenin Addressidir ve değer kategorisi R value dur. Dolayısıyla, atama,++ ve -- operatörlerinin sol operandı olamazlar. Şu ifadelere gözatalım &7 // Error, expression must be L value &x // Okay &(x+4) //Error &+x //Error, işaret opratörü bunu R value haline getirir int x; &x ifadesinin türü nedir? int* int* ptr; ptr ifadesinin türü nedir? int* int* ptr = &x; // okay, ptr yi deklare edip initializ ediyorum ptr = &x ; // okay, ptr ye atama yapıyorum Bir de adresi ekrana formatlı yazdırırken - printf - %p yi ptr ile eşleyerek yazdır, hex formatted yazdırır. int x = 345; int* ptr = &x; printf("ptr nin tuttugu adres yani ptr nin degeri = %p *n",ptr); // Ouput: ptr nin tuttugu adres yani ptr nin degeri = = 0098FB24 Yorumlayınız: double* dval = 1.1; char* ptr = &dval; C++ dilinde sayı türlerinden adres türlerine örtülü dönüşüm olmadığı gibi, farklı adres türleri arasında da örtülü dönüşüm yoktur. Syntax hatasıdır. Ancak C dilinde, geçerli, ancak farklı türden atama yapılmamalı. Bunu bilerek isteyerek yapıyorsak tür dönüştürme ifadesini açıkça yazarak niyetimizi belli etmeliyiz. ■ * dereferencing operator - En önemli kısıt, operandı olarak adres olmak zorunda. Bu adresteki nesneye - pointee'ye - erişmek için kullanılıyor. Şu ifadelere gözatalım *5 // syntax error int ival = 45; *ival // syntax error *&ival // okay *&*&*&*&ival=77; // okay ++*&*&*&*&ival // okay int* ptr = &ival; *ptr // okay int ar[] = {3,5,9,10}; *ar // okay, remember array decay, it becomes *&ar[0] *ar=88; // okay ++*a; // okay pointer : gösterici pointee : gösterilen (point edilen) ▪ Pointerların en sık kullanım yerlerinden biri fonksiyonların parametre değişkeni olması - call by reference void func(int* p) { } func fonksiyonuna arguman olarak int türden bir nesne adresi vermemiz gerekiyor. Dolayısyla , func a şu şekilde çağrılar yapılabilir int ival = 0; int* ptr = &ival; int ar[] = {3,5,9,10}; func(&ival); //okay func(ptr); //okay func(ar); //okay iki int değişkeni swap edecek bir fonksiyon yazdık. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 27.Ders C [Pointers cont'd] 7 Temmuz 2022 Perşembe ▪ call by reference neden önemli ▪ fonksiyon hesapladığı değeri, kendini çağırana geri gönderme için alternatif bir yol ▪ birden fazla değer döndürmek için imkan ▪ maliyeti düşük - geri dönüş değeri ile iletmeye göre Çünkü x = get_value(); ifadesinde 2 kez blok kopyalama yapılması demek 1- fonksiyonun içindeki return expr ; gibi bir ifadeye sahip olması gerektiğini gözönüne alırsak, burada expr'in bir geçici nesne oluşturup - bellek alanı ayırıp - buraya kopyalanması gerek 2- x = geçici nesne ataması esnasındaki kopyalama Not: özellikle user defined types da göreceğimiz üzere, bunlar kapladığı alan açısından hacimli türler olabilirler, dolayısıyla bunlar için blok kopyalama önemli maliyet oluşturabilir. ▪ fonksiyona değer aktarmak için düşük maliyetli bir imkan Yine user defined types türünden hacimli Parametre değişkenlerini düşünelim. Bunları fonksiyona iletirken bunların birer kopyalarının oluşturulması gerekecek. Halbuki fonksiyona nesnelerin adreslerini ilettiğimizde maliyet avantajı sağlayacaktır. Bu durumda, eğer fonksiyon adresi gönderilen nesneyi değiştirmeyecekse - in parameter - bu niyeti/kısıtın beyanı const anahtar sözcüğü ile yapılır. Örneğin void add_matrix(Matrix *presult, const Matrix *m1, const Matrix m2); Hazır yeri gelmişken, pointer türünden fonksiyon parametrelerini şu açıdan da incelemek ▪ in parameter ▪ out parameter ▪ in-out parameter (geleni bilgi için alacak hem de üzerine yazarak geri iletmek) ▪ Dizilerin zaten call by value ile fonksiyonlara gönderilmesi mümkün değil. [C dilinde bir fonksiyonun parametre değişkeni bir dizi olamaz.] void func(int a[]) yada void func(int a[10]) görüyorum, Hani gönderilmezdi? Bu aslında void func(int* a) olarak ele alınıyor. [] içindeki sabiti filan dikkate almaz. Özellikle kendi kendine dili öğrenmeye çalışanlar, bu hususu yanlış anlıyorlar. Projede özellikle istenmedikçe , void func(int* a) olarak kullanalım. ▪ Pointer Aritmetiği [Toplama Çıkarma] ifade işlem sonucu Bir adres ile bir tamsayı toplanabilir. adres + tamsayı adres // okay Bir adresden bir tamsayi çıkarılabilir. adres - tamsayi adres // okay Bir tamsayı ile bir adres toplanabilir. tamsayi + adres adres // okay adres - adres tamsayı // okay Neler yasak ? tamsayi - adres // Error adres + adres // Error Adresler üzerinde yapılan toplama/çıkarma işlemleri bildiğimiz aritmetik gibi değil. Farklı kurallara tabii. Bunun en önemli tatbikatını diziler de görmekteyiz. Bir nesne adresine - nesnenin türü ne olursa olsun - 1 topladığınızda , bellekteki aynı türden bir sonraki nesnenin adresi elde edilir. int a[10] = {0,11,22,33,44,55,66,77,88,99}; for (int i=0; i<10; ++i) { printf("%p %p %p %d %d \n", &a[i], &a[0]+i, a+i, a[i], *(a+i)); //a+i Means &a[i], *(a+i) means a[i] } ▪ ptrdiff_t türü : adres -adres ifadesi sonucunun türü ptrdiff_t, mesela &a[5]-&a[2] --> 3 dür. bunun türü ptrdiff_t dir. Tabi burada aynı diziye ilişkin adreslerin birbirinden çıkarılması bir anlam ifade ediyor. int x,y; &x- &y //geçerli, okay, ancak ne anlamı var? Örnek : ptr, ismi a olan bir dizinin bir elemanını göstermektedir. ptr'nin gösterdiği dizi elemanının indeksi nedir? Cevap : ptr-a Bundan hareketle; ▪ eleman indeksi i ise adresi a+i ▪ adresi ptr ise indeksi ptr-a ▪ idiomatic bir yapı ++ptr bir sonraki elemanı göster anlamına geliyor. int a[10] = {0,11,22,33,44,55,66,77,88,99}; int* ptr = a; for (int i=0; i<10; ++i) { printf("%d %d %d \n", a[i], *(a+i), *ptr ); ++ptr; } ■ [ ] subscript operator x[y] ifadesini *(x+y) haline getiriyor. Burada x dizi olmak zorunda değil. aslında özünde adres olmalı. Dolayısyla adres + tamsayi -> adres int x = 6; (&x)[0] = 98; //okay, çünkü derleyici şu kodu üretir *(&x+0)=98 , bu da x=98 yazmakla aynı kapıya çıkar. 0[&x]++; //okay, çünkü derleyici şu kodu üretir *(0+&x)++ , bu da x++ yazmakla aynı kapıya çıkar. int a[10] = {0,11,22,33,44,55,66,77,88,99}; int* ptr = a+5; printf("%d\n", *ptr); // outputs 55 printf("%d\n", ptr[0]); // outputs 55 printf("%d\n", ptr[3]); // outputs 88 printf("%d\n", ptr[-3]); // outputs 22 ▪ Diziler üstünde işlem yapan fonksiyonlar void array_func(int* ptr, size_t size) { // method 1 : size defa dönen for ile elemanları gezmek - şimdiye dek tipik kullanageldiğimiz yapı for(int i=0 ; i const T* Bu otomatik dönüşüm okay, no problem, tür dönüşüm operatörü bile kullanmana gerek yok. ▪ const T* --> T* Bu otomatik dönüşüm C++ dilinde zaten yok, C dilinde var birkaç istisna dışında her zaman yanlış/uygunsuz. void func(int*) int main() { const int x = 5; func(&x); // C için okay, C++ için syntax hatası } Acaba const T* --> T* dönüşüm ün zaruri/kaçınılmaz olduğu bazı haller var mı?. Evet. Bu hallerde implicit dönüşüm yerine tür dönüştürme ile niyetimizi ortaya koyarak bu işi gerçekleştirmeliyiz. void func(const char* p) { char* ptr = (char*) p; ─────── └─ explicit tür dönüştürme ile niyetimizi belli ettik. buna const cast deniyor. } ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪ Koruma amaçlı ve fiziksel const kavramı: ▪ Koruma amaçlı const int x = 10; const int* p = &x; // Koruma amaçlı const *p = 45; //Syntax error Bunu yorumlayalım: Burada pointer p koruma amaçlı const. Yani x değişkenine, p üzerinden yazma yapılmasını istemiyorum. ▪ Fiziksel const , bu zırh C de derleyici hata vermeden, "sözde" delinebiliyor - ancak bunu delmek GameOver! KURAL : Fiziksel const olan bir nesneyi değiştirme girişimi, derleyicinin syntax hatası aşılsa bile UB'dir. İşte bu örnek çok ilginç. Fiziksel const bir değişkenin zırhını pointer ile deliyoruz. Ancak sonuç UB oluyor. Yani game over oluyor. const int x = 10; // Fiziksel const, bunu delmek için iki pointer ile saldıralım int* p = &x; *p = 45; // C dili sadece uyarı veriyor, C++ syntax hatası veriyor. C dilinde bu UB. int* ptr = (int*) &x; // Hem C hem C++ dilinde de uyarı yok. *ptr = 45; // UB ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ pointer idioms pointer ile ilgili ifadeleri yazarken ++ ,-- , * operatörleriyle karşımıza sık çıkan yapılar var. Önce Hatırlatmalar : ▪ ++ ve -- operand olarak L value expression istiyorlardı. Topyekün ifade ise R value oluyordu. ++ -- ifade C C++ ────── ────── ── ─── ++x R L operand L value --x R L x++ R R x-- R R ▪ & operatörünün operandı olan ifade L value olmak zorunda. Topyekün ifade R value oluyordu. & ifade C C++ ────── ────── ── ─── operand L value &x R R ▪ * operatörünün operandı olan ifade adres olmak zorunda. Topyekün ifade L value oluyordu. * ifade C C++ ────── ────── ── ─── operand Adres *x L L █ Örnek : int x = 10; &x++ // Error, x++ R value, halbuki & operandı L value olmalı. &++x // Error, ++x R value, halbuki & operandı L value olmalı. ++&x // Error, &x R value, halbuki ++ operandı L value olmalı. ■ *p++ idiomu ────────────── *p++ // okay, p nin işaret ettiği indeksteki elemanın değerini döndürür, sonra p nin bir sonraki elemanı işaret etmesi için p yi 1 artırır. int a[] = {10,20,30,40,50}; int* p = a; *p++ = -1; // okay, dizinin ilk elemanına -1 değerini atamış olurum. Sonrasında p nin bir sonraki elemanı işaret etmesi için p yi 1 artırır. *p = -2; //okay, dizinin ikinci elamanına -2 değerini atamaış olurum. Çünkü önceki satırdaki yan etkiden dolayı bu satır başında p ikinci elemanı gösteriyor. █ Örnek : void set_array_random(int *p, int size) { while(size--) { *p++ = rand() % 1000; // p nin gösterdiği elemana bir değer atanıyor, sonrasında p 1 artırılarak bir sonraki dizi elemanını işaret etmesi sağlanıyor. } } █ Örnek : void copy_array(int* pdest, const int* psource, int size) { while(size--) { *pdest++ = *psource++; } } ■ *++p idiomu ────────────── *++p // okay, p nin işaret ettiği indeksteki elemandan bir sonraki elemanının değerini döndürür, sonra p nin bir sonraki elemanı işaret etmesi için p yi 1 artırır. int a[] = {10,20,30,40,50}; int* p = a; *++p = -5; // Dizinin ikinci elemanına -5 değerini atadı, sonra p nin bir sonraki elemanı işaret etmesi için p yi 1 artırır. *p = -2; // Dizinin ikinci elmanına -2 değerini atadı. ■ ++*p idiomu ────────────── int a[] = {10,20,30,40,50}; int* p = a; ++*p; // Dizinin ilk elemanını 1 artırdı. █ Örnek : int a[] = {10,20,30,40,50}; *a++ = -1 // Syntax error, Burada ++ nın operandı a, yani dizi ismi, yani array decay ile bir adrese &a[0] dönüşüyor, dolayısıyla *(&a[0]++) haline geliyor, ki burada ++ ifadesi L value değeri ister halbuki & operatörü R value üretir. Buradan şunu söylemek mümkün : C ve C++ dillerinde bir dizi ismi atama operatörünün sol operandı olamaz (atama operatörünün sol operandı L value olmalıdır). ++ ve -- operatörlerinin operandı olamaz (++ ve -- operatörlerinin operandı L value olmalıdır). ++*a; //okay, dizinin ilk elemanının değerini 1 artırır. array decay ile ++ *(&a[0]) █ Örnek : Bir diziyi tersten bir başka diziye kopyalayan fonksiyonu yazınız void reverse_copy(int* pdest, const int* psource, int n) { psource += n; //Dizinin son elemanından sonraki yerin adresi, burayı dereference etme UB olur, çünkü burada dizinin bir elemanı yok. while(n--) { *pdest++ = *--psource; } } █ Örnek : Program sonunda a dizisinin elemanlarının değerlerini yazınız. void func(int* p , int size) { while(size--) { ++*p++; } } int main() { int a[] = {10,20,30,40,50}; func(a,5); } Cevap {11,21,31,41,51} ■ Valid and invalid state pointers - geçerli ve geçersiz durumdaki adresler - syntax anlamında değil, state anlamında Adres tutan bir değişken - pointer - geçersiz durumdaysa, o pointer değişkeni, ona geçerli bir atamak dışında hiçbir işlemde kullanamazsanız. Kullanırsanız UB. Ancak geçerli durumdaysa, bazı işlemlerde mantıklı faydalı şekilde kullanma imkanı var. ─────────────── └─ ++ -- ve karşılaştırma operatörlerinin operandı yapabilirsin Not: Bilhassa dizinin bittiği yerin adresinden çok faydalanıyor olacağız. Peki bir pointer'a dair geçersiz durumlar nelerdir? ▪ Otomatik ömürlü ve ilk değer verilmemiş bir pointer çöp değere sahiptir, Geçersiz durumdadır. Wild Pointer diye de anılıyor. ▪ Gösterdiği nesnenin hayatı bitmiş pointer geçersiz durumdadır. Dangling Pointer diye de anılıyor. Peki bir pointer ne zaman geçerli durumdadır? ▪ null pointer ise ▪ Hayatı devam etmekte olan bir nesneyi gösteriyorsa ▪ Bir dizinin bittiği yerin adresini tutan - dikkat , dizinin sonuncu elemanını demiyoruz - bir pointer geçerli durumdadır. int a[5] = {10,20,30,40,50}; int* p = a+5; // p, dizinin bittiği yerin adresini gösteriyor. Dereference etmemelisin, ama mesela bunu ++ -- ve karşılaştırma operatörlerinin operandı yapabilirsin. Dizinin bittiği adres, Dizinin ilk elemanının adresine, dizinin boyutunun eklenmesiyle elde edilen adres. ▪ Dizi olmayan değişkenler - tekil nesneler - adres açısından, 1 elemanlı dizi gibi ele alınıyor, yani bunları gösteren pointer i 1 artırdığımızda, bir sonraki aynı tür nesnenin contigous olarak yerleştirilebileceği adresin değerine sahip oluyor. int x = 21; int * p = &x; ++p; //okay p is @ valid state- p geçerli bir durumdadır. int a[1]= {10}; int* p = a+1;//okay p is @ valid state- p geçerli bir durumdadır. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 29.Ders C [Pointers cont'd - Adres Döndüren Fonksiyonlar] 21 Temmuz 2022 Perşembe ■ pointer larda karşılaştırma işlemleri int x=10, y=20; int* p1 = &x; int* p2 = &y; int* p3 = &x; int a[5] = {10,20,30,40,50}; int* p4 = a; int* p5 = a+5; Adresleride karşılaştırma işlemlerine sokabiliyoruz. Şu durumları irdeleyelim: ▪ == iki pointer aynı nesnenin adresi ise 1 üretir. if(p1==p2) // p1 v ep2 farklı nesneleri gösteriyor, 0 False üretilir. if(p1==p3) // p1 v ep2 aynı nesneleri gösteriyor, 1 True üretilir. ▪ == iki pointer, aynı dizinin bittiği yeri gösteriyorsa, eşittirler. while(p4 != p5) { printf("%d\n",*p4++); } Range [ptr1 ptr2) kavramı: ptr1 artırıldıkça, bir zaman ptr2 ye eşit hale geliyorsa, bu bir range'dir. Diziler üzerinde işlem yapan bir çok fonksiyon, range bilgileri parametre olarak alıp işlem yapmakta. Daha evvel kullanageldiğimiz print_array fonksiyonunu bu teknikle tekrar yazalım: void print_array(const int* p, const int* pe, int* ps) { while(ps != pe) { printf("%3d ",*ps++); } printf("\n"); } ▪ Bir pointerin değerinin null olup olmadığı (null pointer ile) karşılaştırılabilir. Benzer şekilde iki pointer null ise, == ile karşılaştırıldıklarında işlem doğru değerini üretir. Adresleride < <= > >= karşılaştırma işlemlerine sokabiliyoruz. ▪ Bu işlemler Aynı diziye ilişkin pointerlar üzerinde anlamlıdır. Aynı diziye ait olmayanlar için bu işlemler mantıksal bir anlam ifade etmeyeceği gibi unspecified behaviour - derleyiciye bağlı -. Matematikte olduğu gibi şşöyle bir durum var: Bir dizinin düşük indeksli elemanının adresinin değeri , büyük indeksli elemanının adresinden daha küçüktür. - diziler contigous - █ Örnek : Bir diziyi tersine çeviren bir fonksiyon yazın. void reverse_array(int* p, int size) { int *pe = p+size; while(p T* dönüşüm ün zaruri/kaçınılmaz olduğu bazı haller var mı? diye sormuş, yanıt olarak evet ancak Bu hallerde implicit dönüşüm yerine tür dönüştürme ile niyetimizi ortaya koyarak bu işi gerçekleştirmeliyiz. Şimdi buna bir örnek yapacağız. int* get_array_max(const int* p, int size) { const int* pmax = p; //dizinin ilk elemanını max varsayarak başlayalım for(int i=0;i *pmax) pmax = p+i; } return (int*) pmax; // const cast yapıyoruz // Note that , burada lokal bir değişkeni döndürüyoruz. // lokal - otomatik ömürlü değişkenin adresini değil! } Burada, diziyi değiştirmeyeceğimizin sözünü, fonksiyon parametresinde const ile niteleyerek veriyoruz. int main() { int a[5] = {10,20,30,40,50}; printf("max val=%d", *get_array_max(a,5) ); } █ Örnek : Selection sort algoritması yazalım, diziyi küçükten büyüğe doğru sıralasın Önce Bir dizinin en küçük elemanı ile ilk elemanını takas eden fonk yaz. Bu foksiyonu daha sonra küçükten büyüğe doğru sıralama için kullanabiliriz. Mesela verilen bir dizi için bunu çalıştırırız, sonra ilk eleman hariç dizinin ikinci elemanı ve kalan kısmı için çalıştırırız so on... int* get_array_min(const int* p, int size) { const int* pmin = p; for(int i=0; i*pmin) pmin= p+i } return (int*) pmin; } void selection_sort(int* p, int size) { for(int i=0; i (1) int* const p = &x; (2) const int* p=&x; CEVAP (1) } TOP-LEVEL const LOW LEVEL const KURAL : Bir pointer türüne eş isim verip, eş ismi const ile nitelersen, her zaman nesnenin kendisinin constluğu (TOP LEVEL const)olacaktır. █ Örnekler : int[10] için INTA10 tür eş ismini verelim --> typedef int INTA10[10] , bu bildirimle birlikte INTA10 x demek int x[10] demek oldu. const int* için CIPTR --> typedef const int* CIPTR , bu bildirimle birlikte CIPTR x demek const int* x demek oldu. #define IPTR int* // Derleyici IPTR 'yi hiç görmez. Önişlemci replace eder. Scope yok. typedef int* iptr; // Derleyici tarafından ele alınır. Burada IPTR p1,p2,p3; yazsan sadece p1 int* türünden , p2 ve p3 int türünden olurdu. Halbuki iptr p1,p2,p3; yazsan p1, p2,p3 ün tamamı int* türünden olur. int a[10][20] -> a dizi dizisidir. a'nın elemanları 20 elemanlı int diziler INTA10 a[20]; -> a nın INTA10 türünden 20 tane elemanı var. Bir türe neden alternatif bir isim (alias) verelim ki? ▪ O türü genel kullanım alanı dışında daraltılmış özel bir anlamda kullanmak örn: typedef double Dollar_t; typedef int Counter_t; ▪ Karmaşık bildirimlerin yapılmasında kolaylık sağlamak örn: ▪ Taşınabilirlik amaçlı - Farklı derleyiciler/hedef sistemlere uyum sağlanması için ▪ size_t : Std kütüphanede de en sık kullanılan tür eş ismi. sizeof operatörünün ürettiği değer türü ▪fonk param biri bir türün sizeof değerini istiyor ▪fonk param biri dizi boyutu istiyor. Bunlarda size_t türünden oluyor. ▪Yazı uzunluğu ▪tane/adet türü - Bazı fonklar belirli bir parametresine quantity istiyor ve bunu size_t türünden olmasını istiyor. ▪ ptrdiff_t : Pointer difference type ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 31.Ders C [ string.h ] 28 Temmuz 2022 Perşembe ▪ string.h kütüphanesi - yazılarla ilgili işlemlere destek verir string.h kütüphanesinde fonkların hepsi olmasa da , birçoğu, yazının NTBS olması şartıyla hizmet verir. Bunlara yapılan aksi çağrılar UB oluşturabilir. See its docu. Kabaca , string.h daki fonksiyonları ikiye ayırabiliriz: Generic functions - mem..... (Yazılarla da ilgili işlemler bellekle ilgili işlemler) Bir fonksiyon ne zaman dizinin boyutunu isteyebilir: Bu diziye bir yazı yerleştirmekse Bu dizideki yazıyı büyütebilecek bir iş yapacaksa öncesinde kontrol edip taşmaya sebep olmamak için dizinin boyutunu da isteyebilir. Bu amaçları gütse bile, yinede dizinin boyutunu istemeyebilir. Bu durumda dizinin taşırılma riskini, çağırana bırakıyor. strlen(str) NOT a constant expression, evaluated at runtime, ürettiği değer yazının uzunluğu, türü size_t sizeof(str) constant expression, evaluated at compile time, ürettiği değer dizinin boyutu, türü size_t NULL ve '\0' birlikte kullanıldığı bir C idiomu: özünde if( p! = NULL && *p != '\0' ) // ptr null ptr değilse ve aynı zamanda gösterdiği karakter- null karakter değilse kısaca if( p && *p ) buna eşlik eden/tamamlayan bir başka idiomatic özünde if( p == NULL || *p == '\0' ) // ptr null ptr ise veya agösterdiği karakter- null karakter ise kısaca if( !p || !*p ) █ Örnek:Ekrana ne yazdırılır int main() { char str[100] = "MURAT"; char *p = str; printf("%zu \n", sizeof str); // 100 printf("%zu \n", sizeof *str); // 1 printf("%zu \n", sizeof p); // 4 - Şu anki derleyicimizde 4 byte printf("%zu \n", sizeof *p); // 1 printf("%zu \n", sizeof p++); // 4 - unevaluated context , p nin değeri artırılmaz printf("%zu \n", strlen(p)); // 5 } ■ strchr - Yazının içinde karakter arar - ilk bulunuşu ▪ char *strchr(const char *str, int c) Searches for the first occurrence of the character c (an unsigned char) in the string pointed to, by the argument str. █ Örnek:strchr fonksiyonunu kendimiz implemente etmeye çalışalım char* mystrchr(const char* p, int c) { while(*p != c) { if(*p == c) return (char*) p; ++p; } if(c == '\0') return (char*) p; return NULL; } ─────────────────────────────────────────────────────────────────────────────────────────────────────── ■ C Library - ■ Tür eş isimleri ▪ size_t Unsigned integral type, and is the result of sizeof operator ■ Macros ▪ NULL This macro is the value of a null pointer constant. ■ Functions ▪ void *memchr(const void *str, int c, size_t n) Searches for the first occurrence of the character c (an unsigned char) in the first n bytes of the string pointed to, by the argument str. ▪ int memcmp(const void *str1, const void *str2, size_t n) Compares the first n bytes of str1 and str2. ▪ void *memcpy(void *dest, const void *src, size_t n) Copies n characters from src to dest. ▪ void *memmove(void *dest, const void *src, size_t n) Another function to copy n characters from str2 to str1. ▪ void *memset(void *str, int c, size_t n) Copies the character c (an unsigned char) to the first n characters of the string pointed to, by the argument str. ▪ char *strcat(char *dest, const char *src) Appends the string pointed to, by src to the end of the string pointed to by dest. ▪ char *strncat(char *dest, const char *src, size_t n) Appends the string pointed to, by src to the end of the string pointed to, by dest up to n characters long. ▪ char *strchr(const char *str, int c) Searches for the first occurrence of the character c (an unsigned char) in the string pointed to, by the argument str. ▪ int strcmp(const char *str1, const char *str2) Compares the string pointed to, by str1 to the string pointed to by str2. ▪ int strncmp(const char *str1, const char *str2, size_t n) compares up to the first n bytes of two strings lexicographically ▪ int strcoll(const char *str1, const char *str2) Compares string str1 to str2. The result is dependent on the LC_COLLATE setting of the location. ▪ char *strcpy(char *dest, const char *src) Copies the string pointed to, by src to dest. ▪ char *strncpy(char *dest, const char *src, size_t n); write exactly n bytes to dest, copying from src or add 0's ▪ size_t strspn(const char *str1, const char *str2) Calculates the length of the initial segment of str1 which consists entirely of characters in str2. ▪ size_t strcspn(const char *str1, const char *str2) Calculates the length of the initial segment of str1 which consists entirely of characters not in str2. ▪ char *strerror(int errnum) Searches an internal array for the error number errnum and returns a pointer to an error message string. ▪ size_t strlen(const char *str) Computes the length of the string str up to but not including the terminating null character. ▪ char *strpbrk(const char *str1, const char *str2) Finds the first character in the string str1 that matches any character specified in str2. ▪ char *strrchr(const char *str, int c) Searches for the last occurrence of the character c (an unsigned char) in the string pointed to by the argument str. ▪ char *strstr(const char *haystack, const char *needle) Finds the first occurrence of the entire string needle (not including the terminating null character) which appears in the string haystack. ▪ char *strtok(char *str, const char *delim) Breaks string str into a series of tokens separated by delim. ▪ size_t strxfrm(char *dest, const char *src, size_t n) Transforms the first n characters of the string src into current locale and places them in the string dest. ─────────────────────────────────────────────────────────────────────────────────────────────────────── ■ C Library - ■ Tür eş isimleri ▪ size_t Unsigned integral type, and is the result of sizeof operator ▪ clock_t This is a type suitable for storing the processor time. ▪ time_t is This is a type suitable for storing the calendar time. ▪ struct tm This is a structure used to hold the time and date. struct tm { int tm_sec; /* seconds, range 0 to 59 */ int tm_min; /* minutes, range 0 to 59 */ int tm_hour; /* hours, range 0 to 23 */ int tm_mday; /* day of the month, range 1 to 31 */ int tm_mon; /* month, range 0 to 11 */ int tm_year; /* The number of years since 1900 */ int tm_wday; /* day of the week, range 0 to 6 */ int tm_yday; /* day in the year, range 0 to 365 */ int tm_isdst; /* daylight saving time */ }; ■ Macros ▪ NULL This macro is the value of a null pointer constant. ▪ CLOCKS_PER_SEC This macro represents the number of processor clocks per second. ■ Functions ▪ char *asctime(const struct tm *timeptr) Returns a pointer to a string which represents the day and time of the structure timeptr. ▪ clock_t clock(void) Returns the processor clock time used since the beginning of an implementation defined era (normally the beginning of the program). ▪ char *ctime(const time_t *timer) Returns a string representing the localtime based on the argument timer. ▪ double difftime(time_t time1, time_t time2) Returns the difference of seconds between time1 and time2 (time1-time2). ▪ struct tm *gmtime(const time_t *timer) The value of timer is broken up into the structure tm and expressed in Coordinated Universal Time (UTC) also known as Greenwich Mean Time (GMT). ▪ struct tm *localtime(const time_t *timer) The value of timer is broken up into the structure tm and expressed in the local time zone. ▪ time_t mktime(struct tm *timeptr) Converts the structure pointed to by timeptr into a time_t value according to the local time zone. ▪ size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr) Formats the time represented in the structure timeptr according to the formatting rules defined in format and stored into str. ▪ time_t time(time_t *timer) Calculates the current calender time and encodes it into time_t format. ─────────────────────────────────────────────────────────────────────────────────────────────────────── ■ C Library - ■ Tür eş isimleri ▪ size_t Unsigned integral type, and is the result of sizeof operator ▪ wchar_t This is an integer type of the size of a wide character constant. ▪ div_t This is the structure returned by the div function. ▪ ldiv_t This is the structure returned by the ldiv function. ■ Macros ▪ NULL This macro is the value of a null pointer constant. ▪ EXIT_FAILURE This is the value for the exit function to return in case of failure. ▪ EXIT_SUCCESS This is the value for the exit function to return in case of success. ▪ RAND_MAX This macro is the maximum value returned by the rand function. ▪ MB_CUR_MAX This macro is the maximum number of bytes in a multi-byte character set which cannot be larger than MB_LEN_MAX. ■ Functions ▪ double atof(const char *str) Converts the string pointed to, by the argument str to a floating-point number (type double). ▪ int atoi(const char *str) Converts the string pointed to, by the argument str to an integer (type int). ▪ long int atol(const char *str) Converts the string pointed to, by the argument str to a long integer (type long int). ▪ double strtod(const char *str, char **endptr) Converts the string pointed to, by the argument str to a floating-point number (type double). ▪ long int strtol(const char *str, char **endptr, int base) Converts the string pointed to, by the argument str to a long integer (type long int). ▪ unsigned long int strtoul(const char *str, char **endptr, int base) Converts the string pointed to, by the argument str to an unsigned long integer (type unsigned long int). ▪ void *calloc(size_t nitems, size_t size) Allocates the requested memory and returns a pointer to it. ▪ void free(void *ptr) Deallocates the memory previously allocated by a call to calloc, malloc, or realloc. ▪ void *malloc(size_t size) Allocates the requested memory and returns a pointer to it. ▪ void *realloc(void *ptr, size_t size) Attempts to resize the memory block pointed to by ptr that was previously allocated with a call to malloc or calloc. ▪ void abort(void) Causes an abnormal program termination. ▪ int atexit(void (*func)(void)) Causes the specified function func to be called when the program terminates normally. ▪ void exit(int status) Causes the program to terminate normally. ▪ char *getenv(const char *name) Searches for the environment string pointed to by name and returns the associated value to the string. ▪ int system(const char *string) The command specified by string is passed to the host environment to be executed by the command processor. ▪ void *bsearch(const void *key, const void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *)) Performs a binary search. ▪ void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) Sorts an array. ▪ int abs(int x) Returns the absolute value of x. ▪ div_t div(int numer, int denom) Divides numer (numerator) by denom (denominator). ▪ long int labs(long int x) Returns the absolute value of x. ▪ ldiv_t ldiv(long int numer, long int denom) Divides numer (numerator) by denom (denominator). ▪ int rand(void) Returns a pseudo-random number in the range of 0 to RAND_MAX ▪ void srand(unsigned int seed) This function seeds the random number generator used by the function rand. ▪ int mblen(const char *str, size_t n) Returns the length of a multibyte character pointed to by the argument str. ▪ size_t mbstowcs(schar_t *pwcs, const char *str, size_t n) Converts the string of multibyte characters pointed to by the argument str to the array pointed to by pwcs. ▪ int mbtowc(whcar_t *pwc, const char *str, size_t n) Examines the multibyte character pointed to by the argument str. ▪ size_t wcstombs(char *str, const wchar_t *pwcs, size_t n) Converts the codes stored in the array pwcs to multibyte characters and stores them in the string str. ▪ int wctomb(char *str, wchar_t wchar) Examines the code which corresponds to a multibyte character given by the argument wchar. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 32.Ders C [ string fonksiyonlarını tanımaya devam ediyoruz ] 02 Ağustos 2022 Salı █ Örnek : Dosya ismindeki . karakterini arayan - sondan geriye doğru ilk karşılaştığımız - #include #include #define SIZE 100 int main() { char str[SIZE]; printf("Bir yazi girin:") sgets(str); // sgets nutility içinde printf("aranacak karakteri girin:") int ch = getchar(); char *p = strrchr(str,ch); if(!p) printf("bulunamadi\n"); else printf("bulundu, yazinin %d indisli karakteri %s",p-str,p); } █ Örnek : Elimizde bir yazi dizisi (NTBS) olsun, bir pointeri yazı sonundaki \0 null karakterini gösterecek şekilde pozisyonlayalım Buna ilişkin idiomatic yapı: while(*p) ++p; Peki bunu şöyle yazabilir miydik? while(*p++) ; Hayır, çünkü döngüyü böyle yazarsak, null karakterin bulunduğu yerden bir sonraki yerin adresi olur. Bu ptr dizinin bittiği yerin adres olur. Bu ptr valid state dedir. Tabiki dereference edersen UB olur o ayrı. Peki döngüyü şöyle kursak, pointeri yazı sonundaki \0 null karakterini gösterecek şekilde pozisyonlayabilir miyiz? while(*++p); Yazı boş değilse bu çalışır, ancak Yazı boşsa - tek elemana sahip o da null karakter ise - problem olur. Peki bu nasıl olur? p += strlen(p); Okay, pointera yazının uzunluğunu eklersek, p yazıdaki null karakteri gösterir. Peki bu nasıl olur? p = strchr(p,'\0'); Okay, pointera yazıdaki null karakterin bulunduğu adresi atadık. ■ strcpy - Hedef adresteki dizi taşarsa , UB oluşur! - Kaynak ve Hedef adres blokları overlapped blocks -çakışıyorsa- ise UB! Yani src ve dest pointerları aynı nesneyi göstermeyecek, farklı nesneleri göstermeli. ▪ char *strcpy(char *dest, const char *src) Copies the string pointed to, by src to dest. Neden önemli? C de diziler assignable varlıklar değil. Dizilerde içeriği atama sematiği ile yapamıyoruz. Yani str="Volkan"; //Syntax Error C de stringlere atama yapmamızı sağlayan komut bu. str1 = str2; // NO!, Dizi isimleri atama operatörünün sol operandı olamaz!! İşte bu noktada, yani str2 nin içindeki karakterleri, str1 e kopyalamak için ya döngü yazacaksın yada strcpy kullanacaksın. Bu fonksiyonun ilk parametresi, yani yazıyı yerleştirdiği yerin adresi, aynı zamanda fonksiyonun geri dönüş değeri. Peki o zaman şu soru akla geliyor: İlk parametreyi veren de benim, niye benim verdiğim şeyi bir de fonksiyonun geri dönüş değeri olarak bana döndürüyor? Cevap : Chaining yapmaya imkan tanımak için. i.e foo(strcpy(pdest,psource)) gibi kullanabilmen için. ▪ strcpy nin implementasyonunu yapmaya çalışalım, char *My_strcpy(char *dest, const char *src) { char* ptemp = dest; while(*src != '\0') { *dest = *src; ++dest; ++src; } //Yazının sonuna null karakteri de koymalıyız *dest = '\0'; //Fonksiyonun geri dönüş değeri return ptemp; } char *My_strcpy(char *dest, const char *src) { char* ptemp = dest; while(*src != '\0') { *dest++ = *src++; } //Yazının sonuna null karakteri de koymalıyız *dest = '\0'; //Fonksiyonun geri dönüş değeri return ptemp; } char *My_strcpy(char *dest, const char *src) { char* ptemp = dest; while(*dest++ = *src++;) // Atama operatörünün ürettiği değer, nesneye atanan değerdir. ; //null statement // Dolayısıyla *src null karakter değerine sahip olduğunda , bu atama işlemi yapılacak ve sonrasında döngüden çıkılacak. // atanan değer 0 olacak - yani null karakteri de kopyalamış olacağız- //Fonksiyonun geri dönüş değeri // Döngüden çıktığında src, ++ nın yan etkisinden dolayı 1 artacak, dolayısyla dizinin bittiği yeri gösteriyor olacak. return ptemp; } strlen, strchr,strrchr,strcpy fonksiyonlarını şimdiye dek gördük. ■ strstr - Programlama mülakatlarında sık sorulduğu için bunu ödev olarak yapalım. ▪ char *strstr(const char *haystack, const char *needle) Finds the first occurrence of the entire string needle (not including the terminating null character) which appears in the string haystack. Bir arama fonksiyonu, bir yazı için yazı arıyor. haystack = sap yığını[yazı çuvalı] , needle=iğne/ibre[aranacak key yazı] █ Ödev : strstr işlevselliğini gerçekleştiren bir fonksiyon yazınız. char *My_strstr(const char *haystack, const char *needle) { //Your code } █ Örnek : Bir yazı içinde yazıyı arasın eğer varsa, oradaki karakterler * haline gelsin. Bu işlevselliği gerçekleştiren bir fonksiyon yazınız. char *func(char *haystack, const char *key) { //Your code } ■ strcat - dest dizisinin sonuna src dizisini ekler - Kaynak ve Hedef adres blokları overlapped blocks -çakışıyorsa- ise UB! Yani src ve dest pointerları aynı nesneyi göstermeyecek, farklı nesneleri göstermeli. ▪ char *strcat(char *dest, const char *src) Appends the string pointed to, by src to the end of the string pointed to by dest. char *My_strcat(char *dest, const char *src) { char* pTemp = dest; while(*dest++) ; //null statement - null karakterin olduğu yeri bul,göster while(*dest++ = *src++) ; //null statement return pTemp; } char *My_strcat(char *dest, const char *src) { strcpy(dest + strlen(dest),src); return dest; } char *My_strcat(char *dest, const char *src) { strcpy(strchr(dest,'\0'),src); return dest; } ■ strcmp - iki yazının Lexicographical karşılaştırılmasının kesin sonucunu iletiyor. ■ stricmp - Küçük-Büyük harf ayrımı duyarsız-invariant karşılaştıran versiyonu return value > 0 , str1 is greater than str2 return value = 0 , str1 is equal to str2 return value < 0 , str1 is less than str2 ┌─ ▪ int strcmp(const char *str1, const char *str2) Compares the string pointed to, by str1 to the string pointed to by str2. Eşit olması için öge sayısı eşit olacak ve karşilikli ögeler eşit. Karşilikli elemanlar çift çift karşılaştırıldığında, büyük olana sahip olan büyüktür. █ Örnek : int a[2]={5,9} int b[1000]={1,12,...} Cevap : a büyük, 5 ve 1 karşılaştırıldığında 5 büyük, artık karşılaştırma işlemine devam edilmez. int a[2]={5,9} int b[1000]={5,9,...} Cevap : b büyük, tüm elemanlar eşitse, uzun dizi büyük. masa ankara MASA antalya Cevap :burada m>M Cavap: k ankara remember ascii table old. için masa > MASA Buradaki idiomatic yapılar: ▪ İki yazı birbirinden farklıysa if( strcmp(str1,str2)!= 0) if( strcmp(str1,str2) ) ▪ İki yazı birbirine eşitse if( strcmp(str1,str2)== 0) if( ! strcmp(str1,str2) ) █ Örnek : strcmp yi implement edelim. int My_strcmp(const char *str1, const char *str2) { while(*p1==*p2) { if(*p1=='\0') return 0; ++p1; ++p2; } return *p1-*p2; } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 33.Ders C [ String Literals , Pointer Arrays ] 04 Ağustos 2022 Perşembe ■ String Literal - "This is a string Literal" - string literallerini değiştirme girişimi UB, String Literalleri statik ömürlüdür ┌ char diziye ilk değer verme sentaksı Derleyici - 1 istisna dışında - bir string literal görünce bir char dizi(sonuna null karakter ekleyerek) oluşturuyor ve bunu salt okunur bellek alanına koyuyor. Burada oluşturduğu dizinin niteleyecisi const değil, ancak koyduğu yer salt okunur bölge olduğundan, bunu değiştirmeye yönelik bir girişimler UB. Sözgelimi "eray" yazısını görünce char[5] türünde bir dizi oluşturup salt okunur belleğe koyuyor. String Literalleri statik ömürlüdür. ▪ Empty String Literal is "" and türü char[1], içeriği '\0' dır. ▪ Bir string literalini birden fazla satıra bölerek yazabilirmiyim ? Evet. Bunun 2 yolu var. ▪ \ karakteri kullanmak ki bu pek seyrek kullanılır. \ karakteri literale dahil olmaz, ancak alt satırda boşluk bırakırsan onlar dahil olur. ▪ daha evvel gördüğümüz bir kural: ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Vesileyle dilin enterasan bir kuralından bahsedelim: printf("ali" "veli" "murat") ifadesi compile time da printf("alivelimurat") haline getirilir. Çünkü kural: Aralarında boşluk karakteri dışında hiçbirşey olmayan string literalleri compile time da birleştirilir. Bu kuralın pratikte sevilen bir tatbikatı: printf( "[1] Kayit ekle\n" "[2] Kayit sil\n" "[3] Programdan Cik\n"); ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── █ Örnek : Tüm zamanların en sık sorulan mülakat sorusu. İkisi arasındaki fark nedir? char s[]= "alim"; // okay, burada alim stringi ilk değer verme syntaxinin bir parçası, ayrıca bir dizi olarak ele alınmıyor. Zaten diziye ilk değer veriliyor. char* p = "alim"; // okay, burada p ye, bellekte oluşturulan alim string literalinin adresi atanıyor. Dolayısyla p nin gösterdiği yeri yani alim yazısını // değiştirmeye yönelik girişimler UB. Böyle bir bildirimin düzgün yapılması için const char* p şeklinde yapılması yerinde olacaktır. █ Örnekler : *ptr = 'k'; // UB ptr[2] = 'k'; // UB void do_sth(char* p); int main() { do_sth("murat"); // UB, because do_sth seems to edit char array, but we send him string literal in read only memory } // Be careful on sending String Literals to functions that have pointer parameter putchar(*"alim"); // okay, output e putchar("eray"[3]); // okay, output y printf("%p","eray"); // okay, eray yazısının yerleştirildiği bellek adresini ekrana yazar printf("%zu",strlen("eray")); // okay, output 4 printf("%s","hello"); // okay, output hello printf("hello"); // okay, output hello printf("%zu",strlen("")); // okay, output 0 printf("%zu",sizeof("")); // okay, output 1 - char türü 1 elemanlı bu dizinin storage ihtiyacı 1 byte int a[sizeof ""]; // okay, means int a[1] const char* p = "\x42URS\x41\xA"; // okay, - String Literal yazarken karakter sabitleride kullanılabilir. puts(p); // okay, output BURSA printf("%zy\n",strlen(p)); // okay, output 5 const char* p = "\x42ABAdede"; // Syntax error, too big for character diyor, çünkü hepsi hex olarak geçerli, sınırını bulamıyor printf("\102102\101101"); // output B102A101 çünkü octal olarak 3 karakter alıyor char* p1 = "murat"; char* p2 = "murat"; // Soru , p1 ve p2 eşit mi? Cevap: Unspecified Behaviour, yani derleyiciye bağlı Derleyici aynı kaynak dosyada özdeş string literali ile karşılaştığında bunu tek bir dizi olarak da ele alabilir, ayrı ayrı diziler olarak da ele alabilir. String literali yazarken - özellikle sıklıkla karşımıza gelecek iki karakter - bazı karakterler için escape kullanmamız gerekiyor. \ karakterini string literalinin içine koymak istersen \\ şeklinde yazman gerekiyor. puts("\\alim\\"); outputs \alim\ Dosya yolu belirtirken \ karakterini kullanıyoruz o yüzden sıklıkla diyoruz. " karakterini string literalinin içine koymak istersen \" şeklinde yazman gerekiyor. puts("\"alim\""); outputs "alim" json html gibi şeylerde " karakteri sıklıkla karşımıza çıkıyor. C++ da Raw String Literali diye bir kavram var. Bununla birlikte yazım oldukça rahat hale geliyor. Örn R"(mus\a"p")" converts everything inside directly , this case mus\a"p" █ Örnek : Aşağıdaki kodda bir yanlışlık var mı? Yorumlayın. char* weekday_str(int day_no) { switch(day_no) { case 1: return "Pazartesi"; case 2: return "Sali"; case 3: return "Carsamba"; case 4: return "Persembe"; case 5: return "Cuma"; case 6: return "Cumartesi"; case 7: return "Pazar"; } return NULL; } Cevap: Yanlışlık yok. Fonksiyonun geri dönüş değeri char*. Fonksiyonlarda geri dönüş değeri pointer ise asla lokal değişken adresi döndürmeyin, olsa olsa statik ömürlü yerel değişken adresi döndürülebilir diye bir hatırlatma notumuz vardı. Öte yandan burada döndürdüklerimiz String Literal , String Literalleride statik ömürlü oldukları için bir problem yok. █ Örnek : Döngünün her turunda yazacağı adres a) Hep aynı adresi yazar , bu garanti altındadır b) Farklı yazar c) Belli olmaz int main() { for(int i=0; i<10; ++i) printf("%p","murat"); } Cevap: a -> Hep aynı adresi yazar , bu garanti altındadır. Derleyici kaynak kodu derlerken "murat" i bir yere yerleştiriyor. Run time ile ilgisi yok. █ Örnek : Döngünün her turunda a) aynı şeyi yazar b) Farklı şey yazar void func(void) { char* p = "murat"; printf("%p\n",p); } int main() { for(int i=0; i<10; ++i) func(); } Cevap: a -> Hep aynı şeyi - p nin değerini yazdırıyoruz, ki bu "murat" string literalinin adresidir. Evet p otomatik ömürlü ancak her defasında aynı değerle initialize ediliyor. █ Örnek : Programcıların çok kez karıştırdığı , str karşılaştırılması ile adreslerinin karşılaştırılması Aşağıdaki kodu yorumlayınız. int main() { char str[100]; // code, str değerini alıyor if(str=="murat") { //... } } Cevap: Böyle bir kod bilinçli yazılmış olamaz. Bu kod always FALSE döner. İçerikleri karşılaştırmak istiyor ancak str dizisinin adresi ile "murat" string literalinin adresini karşılaştırıyor. █ Örnek : Aşağıdaki kodu yorumlayınız. int main() { char *p = "murat" // code, if( p == "murat") { //... } } Cevap: Böyle bir kod bilinçli yazılmış olamaz. İçerikleri karşılaştırmak istiyor ancak p pointerini değeri ile "murat" string literalinin adresini karşılaştırıyor. Derleyici aynı kaynak dosyada özdeş string literali ile karşılaştığında bunu tek bir dizi olarak da ele alabilir, ayrı ayrı diziler olarak da ele alabilir. ■ Pointer Arrays - elemanları ptr olan dizi int ival = 5; int* foo(void); int* ptr = &ival; int ar[3] = {1,2,3,4}; int *p; Öyle bir dizi olsun ki, p gibi elemanları olsun. Bende senin gibi 10 tane var diyebilsin. Cevap: int* a[10] // Elemanları int* türünden , 10 elemanlı bir dizi a[0] = &ival; //okay a[3] = foo(); //okay a[4] = ptr; //okay a[5] = NULL; //okay a[6] = ar; //okay int* b[4] = {&x,&ival}; //okay, kalan iki elemanı NULL pointer ile initialize edilir *b[1] = 56; //okay, ival e 56 atadık ++*b[1]; //okay, ival in değeri 1 artırdık █ Örnek: int a[] = {10,20,30,40} int* p[4] = {&x,a,&y,&z}; p[1][2] = 99; //okay, [] operatörü 1. öncelik seviyesinde ve yönü ► , dolayısıyla p[1] demek a demek, burada a[2] ye 99 atamış oluruz. // Sonuçta a dizisinin elemanları {10,20,99,40} haline gelir. p[1][2]++; //okay, a[2] yi bir artırmış oluruz //Sonuçta a dizisinin elemanları {10,20,100,40} haline gelir. █ Örnek: int a[] = {10,20,30,40}; int b[] = {-10,-20,-30,-40}; int c[] = {1,2,3,4}; int* p[] = {a,b,c}; ++p[1]; // p dizisinin ikinci elemanının değerini 1 artırıyoruz, ikinci eleman b dizisinin ilk elemanının adresi idi, // dolayısyla p dizisinin ikinci elemanının değeri, 1 artırılırsa, b dizisinin ikinci elemanının adresi olur ++*p[1]; // Üstteki satır sonrası, b dizisinin ikinci elemanının değeri 1 artmış olur // dolayısyla {-10,-19,-30,-40} haline gelmiş olur. █ Örnek: int x,y,z; int* p[]={&x,&y,&z}; int* const p1 = {&x,&y,&z}; // p1 in elemanlarını değiştirmeme taahhütü, p1 in elemanları const, yani p1 daima x y ve z yi gösterecek demek. p1 in başka şeyleri göstermesi girişimi UB. int ival = 456; p1[1] = &ival ; //syntax error *p1[1] = 999 ; // okay const int* p2 = {&x,&y,&z}; // p2 nin elemanlarının gösterdiği nesnelerin değerleri değiştirilemez. int ival = 456; p2[1] = &ival; //okay *p2[1] = 999; //syntax error const int* const p3 = {&x,&y,&z}; int ival = 456; p3[1] = &ival; //syntax error *p3[1] = 999; //syntax error █ Örnek: Lookup olarak kullanma teması, indeksten yazıya geçiş imkanını gösteriyor int main() { const char* pMonths[] = { "", "ocak", "subat", "mart" } for(int i=0;i<3,++i) { puts(pMonths[i]); // Output ayları ekrana yazar printf("%s\n",pMonths[i]); // Output ayları ekrana yazar printf("%p\n",pMonths[i]); // Output ayların tutulduğu dizilerin adreslerini ekrana yazar printf("%zu\n",strlen(pMonths[i])); // Output ayların karakter olarak uzunluklarını yazardı 4 5 4 } } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 34.Ders C [ Pointer To Pointers ] 9 Ağustos 2022 Salı const char* p[] ={"ali",veli","murat"}; putchar(**p); // Outputs a . Note that *p[0] ile **p aynı. █ Örnek: Lookup olarak kullanma teması ancak değerden indekse geçiş int main() { const char* pMonths[] = { "", "ocak", "subat", "mart" }; for(int i=0;i<3,++i) { puts(pMonths[i]); // Output ayları ekrana yazar printf("%s\n",pMonths[i]); // Output ayları ekrana yazar printf("%p\n",pMonths[i]); // Output ayların tutulduğu dizilerin adreslerini ekrana yazar printf("%zu\n",strlen(pMonths[i])); // Output ayların karakter olarak uzunluklarını yazardı 4 5 4 } char entry[SIZE]; printf("Bir ay ismi girin:); scanf(%s,entry); //1.yazim bçimi //──────────────────────────────────── size_t i; for(i=0;i<3;++i) { if(!strcmp(entry),pMonths[i]) break, } if(i==3) printf("%s gecerli bir ay ismi degil\n",entry); else printf("%s yilin %zu inci ayidir\n",i+1); //2.yazim bçimi //──────────────────────────────────── size_t i; for(i=0;i<3 && strcmp(entry),pMonths[i]);++i) //Kısa devre davranışından faydalandık ; //null statement if(i==3) printf("%s gecerli bir ay ismi degil\n",entry); else printf("%s yilin %zu inci ayidir\n",i+1); } █ Örnek: const char* p[SIZE] = {"ali","abdullah","adem", adnan","afacan","agah","ahmet",alev","ali","alican" ....}; int main() { for(size_t i = 0;i0) { const char* ptemp = p[k]; p[k] = p[k+1]; p[k+1] = ptemp; } } } //───────────────────────────────────────────────────────────────────────────────────── // Bu da çok meşhur mülakat sorulardan biriymiş, çoğunluk yanlış cevap veriyormuş // Diziyi öyle sıralaki dizinin başında en az harfe sahip elemanlar, dizi sonuna doğru artan harf sayısına göre. Öte yandan aynı harf sayısına sahip olanlar ise lexicographical sıralı olacak. for(size_t i = 0;istrlen(p[k+1])) || ( strlen(p[k])==strlen(p[k+1]) && (strcmp(p[k],p[k+1])>0) ) ) { const char* ptemp = p[k]; p[k] = p[k+1]; p[k+1] = ptemp; } } } } ■ NULL elemanın, pointer arraylerde son eleman olarak kullanımı: const char* p[] = {"ali","abdullah","adem", adnan","afacan","agah","ahmet",alev","ali","alican",NULL}; Bu dizide son eleman NULL pointer. Böylelikle bu diziyi dolaşmak istersek bunu bir sentinel olarak kullanabiliriz. Dolayısyla döngüyü, NULL pointer görene dek mantığıyla kurabiliriz. while(p[i] != NULL) { printf("%s",p[i++]); } Ancak bu noktada dikkatli olunması gereken bir husus ortaya çıkıyor: Dizinin bu son elemanını - yani NULL pointeri - dereference edersen UB oluşur. ■ Pointer To Pointers int x = 20; // int* ptr = &x; // p -> ptr -> x int** p = &ptr; // int** int* int ┌──────────────────┬──────────────┬───────────────┐ │ expression │ datatype │ value category│ ├──────────────────┼──────────────┼───────────────┤ │ x │ int │ L │ ├──────────────────┼──────────────┼───────────────┤ │ &x │ int* │ R │ ├──────────────────┼──────────────┼───────────────┤ │ ptr │ int* │ L │ ├──────────────────┼──────────────┼───────────────┤ │ *ptr │ int │ L │ *ptr demek x demek ├──────────────────┼──────────────┼───────────────┤ │ &ptr │ int** │ R │ ├──────────────────┼──────────────┼───────────────┤ │ p │ int** │ L │ ├──────────────────┼──────────────┼───────────────┤ │ *p │ int* │ L │ *p demek ptr demek ├──────────────────┼──────────────┼───────────────┤ │ **p │ int │ L │ **p demek x demek ├──────────────────┼──────────────┼───────────────┤ │ &p │ int*** │ R │ └──────────────────┴──────────────┴───────────────┘ ++**p; // x i 1 artırır printf("x=%d\n",x); // output 21 Şunların farkını gör printf("&x =%p\n", &x); // x in adresi printf("ptr =%p\n", ptr); // ptr nin değeri printf("&ptr=%p\n", &ptr); // ptr nin adresi printf("p =%p\n", p); // p nin değeri printf("&p =%p\n", &p); // p nin adresi █ Örnek: int a[]={1,2,3}; int b[]={10,20,30}; int c[]={100,200,300}; int* p = a; int** ptr = &p; ++*ptr; // ++p demek, yani p nin değeri a+1 adresi olacak ++**ptr; // ptr nin gösterdiği yerdeki değeri 1 artırdık, a[1]'in değeri 2 iken, bir artırılacak ve 3 değerine sahip olacak ▪ Hatırlayalım, void foo(int* ptr); { *ptr = 99; } int main() { int x = 10; foo(&x); } Burada, call by reference ile, adresini yolladığımız değişkene bizi eriştiren foo tanımının ve ona çağrı yapma örneğini görüyoruz. ▪ Pointer To Pointer kullanımının en yaygın teması : Bir pointer değişkenin değerini set eden fonksiyonlar █ Örnek: int x = 56; int y = 98; int* p1 = &x; int* p2 = &y; p1 ve p2 nin değerlerini takas etsek ne olur? Cevap : p1 artık y yi gösteriyor, p2 ise x i gösteriyor olurdu. İşte böyle bir takası - yani int* türden değişkenleri takas eden - yapacak bir fonksiyon yazalım. Kullanırken pswap(&p1,&p2) diye kullanmamız gerekecek. void pswap(int** ptr1, int** ptr2) { int* ptemp = *ptr1; *ptr1 = *ptr2; *ptr2 = ptemp; } █ Örnek: Bir çocuk böyle bir şey yazmış. func fonksiyonunun ptr ye bir değer yazabileceğini sanmış. Niye olmuyor diye soruyor. Bunu analiz edelim. void func(int* p, int n) { p = (int*) malloc(n * sizeof(int)); } int main(void) { int* ptr; func(ptr,100); } Yorum: Burada ptr nin değeri çöp değer :) bu çağrıda ptr nin değeri değiştirilemez , çünkü call by value. func a ptr nin değerini göndermiş! Bu amacını gerçekleştirmek için void func(int** p, int) gibi olması, çağrı ifadesinin ise func(&ptr,100) gibi olması gerekirdi. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 35.Ders C [ Pointer To Pointers cont'd , void Pointers] 11 Ağustos 2022 Perşembe Lokal pointerimizi set etmek amacıyla pointer to pointer kullanmak durumundayız demiştik. █ Örnek: int main(void) { int x = 567; int* p = &x; func(p); // func fonksiyonu p ye bir değer atayabilir mi? Yanıt: Hayır. // peki func ne yapıyor olabilir? Yanıt: p nin gösterdiği yere - yani x e - bir değer atıyor olabilir. func(&p); // evet şimdi func fonksiyonu p ye bir değer atayabilir. Peki bu durumda func bildirimi neye benzer? Yanıt: void func(int** ptr) } █ Örnek: int main(void) { int a[10] = {1,3,5,78,89,2,7,3,3}; } a dizisinin elemanları üzerinde işlem yapacak bir fonksiyonun bildirimi nasıl olurdu ve bu fonksiyona nasıl çağrı yapardım? void foo(int*, size_t) gibi bir bildirimi olurdu foo(a,10) Dizinin adresi ve boyutu ile çağrı yapardım Peki, elimde int* a[10] gibi bir pointer dizisi olsaydı? void foo(int**, size_t) gibi bir bildirimi olurdu foo(a,10) yine Dizinin adresi ve boyutu ile çağrı yapardım █ Örnek:Bir int dizinin en buyuk elemanının adresini döndüren fonksiyon yazmıştık. Tanımı şöyle idi: int* get_array_max(const int* pa, size_t size) Şimdi ise, Adresini ve boyutunu aldığı dizinin hem en büyük, hem en küçük elemanının adresini bildiren bir fonksiyon yazmaya çalışalım. void get_array_min_max(const int* pa, size_t size, int** ptr_min, int** ptr_max) { *ptr_min = *ptr_max = (int*) pa; // const correctness , bilerek isteyerek yaptığımı göstermek için cast ediyoruz, alternatif olarak = &pa[0] şeklinde de yazılabilirdi. for(int i=1 ; i **ptr_max) *ptr_max = (int*) (pa+i); else if(pa[i]<**ptr_min) *ptr_min = (int*) (pa+i); } int main(void) { int a[SIZE]; randomize(); set_array_random(a,SIZE); print_array(a,SIZE); int* pmin; int* pmax; get_array_min_max(a,SIZE,&pmin,&pmax); printf("max= &d ve dizinin %d indisli elemanı\n",*pmax,pmax-a); printf("min= &d ve dizinin %d indisli elemanı\n",*pmin,pmin-a); swap(pmin,pmax); print_array(a,SIZE); } █ Örnek: Elimizde bir pointer array var. int main(void) { char* pArr[] = {"ali","abdullah","adem",adnan","afacan","agah","ahmet",alev","ali","alican" ....}; Bunları ekrana bastırmak isteseydim for(size_t i=0; i< asize(pArr) ; ++i) { printf("%s",pArr[i]); } } Ekrana yazdırma işini yapan bir fonksiyon yazmaya çalışalım. void print_names(char** namesArr, size_t size) { // way1 for(size_t i=0; i< size ; ++i) { printf("%s", namesArr[i]); // namesArr[i] yerine tabiki *(namesArr+i) de yazılabilirdi. } // way2 while(size--) printf("%s",*namesArr++) } ■ Pointer To Pointers ifadelerinde const anahtar sözcüğü Hatırlatma: int x = 5; int* const p = &x; // Top level const, yani p pointeri x i gösterir, başka bir şeyi gösteremez, öte yandan x in değerini değiştirebilir. const int* p = &x; // Low level const, yani p pointeri x in değerini değiştirmeme sözü veriyor, öte yandan p pointer i bir başka değişkeni gösterebilir. Bununla ilgili pratik bir kural vardı, hatırlayalım: const neyden önce geliyorsa, const olan odur. int **p ifadelerinde const nasıl kullanılıyor? - Yukarıda bahsettiğimiz kural bu ifadeleri yorumlamada da geçerlidir. int x = 10; int y = 33; int* p1 = &x; int* p1 = &y; int** ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //okay *ptr = &y; //okay **ptr = 888; //okay int** const ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //Syntax error - const anahtar sözcüğü ptr ye etki ediyor *ptr = &y; //okay **ptr = 888; //okay int*const* ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //okay *ptr = &y; //Syntax error - const anahtar sözcüğü *ptr ye etki ediyor **ptr = 888; //okay int const** ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //okay *ptr = &y; //okay **ptr = 888; //Syntax error - const anahtar sözcüğü **ptr ye etki ediyor const int ** ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //okay *ptr = &y; //okay **ptr = 888; //Syntax error- const anahtar sözcüğü **ptr ye etki ediyor const int ** const ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //Syntax error - const anahtar sözcüğü ptr ye de etki ediyor *ptr = &y; //okay **ptr = 888; //Syntax error - const anahtar sözcüğü **ptr ye de etki ediyor const int *const * const ptr = &p1; // bu ifadeye ilişkin aşağıdaki atamalardan hangileri uygun, hangileri sentaks hatası? ptr = &p2; //Syntax error - const anahtar sözcüğü ptr ye de etki ediyor *ptr = &y; //Syntax error - const anahtar sözcüğü *ptr ye de etki ediyor **ptr = 888; //Syntax error - const anahtar sözcüğü **ptr ye de etki ediyor Şimdi az önceki örneğimizdeki printnames isimli fonksiyona tekrar dönelim. Bunun bildiriminde const anahtar sözcüğü kullanmak gerek. Nerede kullanmalıyız? ┌─ Burada kullanmalıyız, böylelikle dizinin elemanlarını değiştirmeyeceğimizi taahhüt ediyoruz. void print_names(char* const * namesArr, size_t size) { // way1 for(size_t i=0; i< size ; ++i) { printf("%s", namesArr[i]); // namesArr[i] yerine tabiki *(namesArr+i) de yazılabilirdi. } // way2 while(size--) printf("%s",*namesArr++) } Elemanları char pointer olan bir dizide , isimleri- elemanları - sıralayacak bir fonksiyon yazsaydım, bunun bildirimini void sort_names(const char** namesArr, size_t size) diye bildirirdim, böylelikle isimlerin kendilerini değil dizideki yerlerini değiştireceğimi taahhüt ederdim. Tabi burada elemanlar string literallerinden oluştuğundan, yani elemanları değiştirme girişimi zaten abes olduğundan void sort_names( char** namesArr, size_t size) diye de bildirilebilir. { for(size_t i=0; i0) { char* ptemp = namesArr[k]; namesArr[k] = namesArr[k+1]; namesArr[k+1] = ptemp; } } } } Şimdi, pek sık tartışılan konuyu bir inceleyelim. Soru şu: void func(int x) fonksiyonunun bildiriminde const anahtar sözcüğüne ihtiyaç var mı? yani void func(const int x) şeklinde mi olmalı? | C/C++ dilinin kuralları açısından, burada const anahtar sözcüğünün olması yada olmamasının bildirimsel açıdan bir farkı yok. Bilhassa C++ da meşhur sorudur, function overloading midir diye sorarlar, değildir, function redeclaration dır. void func(const int x) call by value olduğundan, client açısından hiç önemi yok. Ancak fonksiyonun implementasyonunda, logic açıdan değerinin değiştirilmemesi önemli diyebiliriz. Bu yüzden nadirde olsa böyle bildirimler yapabiliriz. Benzer şekilde, bir fonksiyonun parametresinde yer alan pointer in top level const luğunu düşünelim. void func(int* const ptr) gibi bir bildirimi düşünelim. Client nesnesinin değişip değişmeyeceği ile ilgilenir. Yani fonsiyon parametresinin top level const luğu , client i ilgilendirmiyor. █ Örnek: Aşağıdaki kod için şu cümlelerden hangisi(leri) doğrudur? i. Bu kod Tanımsız davranış- UB ii. Bu kodun UB olup olmaması //code un ne olduğuna bağlıdır. iii.UB değil int** baz(void) { int* ptr = NULL; //code return &ptr; } Cevap: i Çünkü otomatik ömürlü değişken adresi döndürüyor. █ Örnek: Aşağıdaki kod derlendiğinde hata/uyarı verir mi? Vermezse ne iş yapar? void foo(int** p1,int** p2) { int x = **p1; **p1 = **p2; **p2 = x; } int main(void) { int x = 34; int y = 67; int* p1 = &x; int* p2 = &y; foo(&p1,&p2); } Cevap: Hata/uyarı vermez, x ve y nin değeri takas edilmiş olur. ■ void - void type - ▪ bir türdür - tıpkı int gibi-. ancak bazı kısıtlamalara tabii. ▪void is an Incomplete Type . Bu yüzden, ▪Türü void olan bir nesne olamaz. Böyle bir nesne tanımlama girişimi syntax hatası oluşturur. ▪sizeof() operatörünün operandı olamaz. ▪Elemanları void türden olan bir dizi olamaz. ▪Bir ifadenin türü void olabilir. Hangi ifadelerin türü void türüdür? ▪void türden geri dönüş değeri olan fonksiyonlara yapılan çağrı ifadelerinin türü void dur. ▪tür dönüştürme operatörünün hedef türünü void yaparak, bir ifadenin türünü void yapabiliriz. ▪Fonksiyon bildirimlerinde void kullanımı ▪ Geri dönüş değeri olmayan fonksiyonların başında void yazılır, örn void foo(int); Burada void yazılmadan foo(int) yazarsak implicit int function kuralı - ki bu kural da C99 da kalktı- geri dönüş değeri int kabul eder. ▪ Fonksiyon parametre almayacağında bildirimde parantez içine void yazılarak bildirilir, örn int bar(void) Burada parantez içine void yazılmadan int bar() şeklinde bildirirsek, bu parametre olmadığı anlamına gelmez, parametre hakkında bilgi vermediğimiz anlamına gelecektir. ■ void* - void Pointers type - Herhangi türden bir nesnenin adresini tutabilir. int* p demek, int türden bir nesnenin adresini tutar. ▪diğer object pointerları ile aynı storage ihtiyacındadır. ▪sizeof(void*) okay, yani sizeof() operatörünün operandı olabilir. Çünkü kendisi bir adrestir günün sonunda. ▪void* p ise yukarıdaki gibi değil, adeta diyor ki, ben adresini tutacağım nesnenin türüyle ilgilenmiyorum, bellekteki adresini tutarım diyor. ▪Yani herhangi türden bir nesnenin adresini tutabileceğin bir pointer. Örneğin: vptr ye herhangi türden nesnenin adresini atayabiliriz. int ival = 5; double dval= 4.5; void* vptr = NULL; //okay vptr = &x; //okay vptr = &y; //okay ▪void foo(void* p) fonksiyonunu düşünelim. Herhangi türden bir nesnenin adresi ile çağrılabilir. Bu cins fonksiyonlar, diğer fonksiyonlardan , call by reference ancak herhangi türden nesne adresini kabul edecekler - farklıdır, generic programlama paradigm - türden bağımsız programlama. Bu yapı bildiğimiz const semantiğini etkilemiyor, void bar(const void* p) gibi tanımlanan bir fonksiyonda olabilir. Bu, bildiğimiz, "nesnenin değerini değiştirmeyeceğim" taahhütüdür. ▪void* türünden bir ifade, bir adres bilgisi olmasına rağmen, her türlü derefencing mümkün değildir , syntax hatasıdır. Örneğin: *vptr //Syntax Error vptr[2] //Syntax Error vprt+i //Syntax Error ▪Onu yapamam bunu yapamam, ne yapabilirim? ▪ İlk değer verebilir, değer ataması yapabilirsin - tabiki adres değerleri ile çalışmalısın. Örneğin, void* p nin bir başka vptr nin adresini verebilirsin. void* p = &vp1; //okay ▪ NULL değerini kullanabilirsin. - null pointer kastedliyor. ▪ NULL ile kıyaslama yapabilirsin. ▪ Bir başka pointera eşitliğini sınayabilirsin ▪ Tür eş ismi verebilirsin, typedef void* ListHandle; ▪void* türünden diğer pointer türlere dönüşüm [C dilinde okay, ancak C++ da yok!] void* dan diğer* türlerine otomatik(implicit) dönüşüm var. Only in C. Hem C hem C++ da kullanılabilen bir kod olsun istersen explicit cast kullanarak yaz ┌────────OK────────→ void* diğer* türler like int* etc.. ←───────OK────────┘ tüm adres türlerinden void* türüne otomatik(implicit) dönüşüm var. Both in C/C++ Örnek: int x = 10; void* p = &x; //okay both in C / C++ int* iptr = vptr; //okay in C, Syntax error in C++ , C++ da explicit cast yapmak zorundasın. Bu satırın hem C hem C++ da kullanılabilir olması için int* iptr = (int*) vptr; şeklinde yazabilirsin. Yeri gelmişken hatırlayalım █ Örnek/KURAL: Bir pointer türüne eş isim verip, eş ismi const ile nitelersen, her zaman nesnenin kendisinin constluğu (TOP LEVEL const)olacaktır. typedef int* IPTR; int main() { int x = 10; const IPTR p = &x; // Bu hangisi? 1 mi 2 mi? --> (1) int* const p = &x; (2) const int* p=&x; CEVAP (1) } TOP-LEVEL const LOW LEVEL const Türden bağımsız hizmet verebilen fonksiyon yazma hedefimize doğru ilerleyelim. Böyle fonksiyonlara generic fonksiyonlar deniyordu. Hatırlayalım, bir nesnenin bellekte contigous olarak tutulma garantisi var. Dolayısıyla bir nesneyi byte lardan tutulan bir bir blok olarak ele alınabilir. █ Örnek: nutility de int türden iki değişkenin değerini takas eden bir fonksiyonumuz vardı. Bunu diğer türlerden nesnelerin değerlerini takas etmek için nasıl implement ederiz? Bu fonksiyona , nesnenin uzunluğunu(sizeof) değerini de gönderecek şekilde modifiye edebiliriz. void gswap(void* vp1, void* vp2, size_t size) { char* p1 = (char*)vp1; char* p2 = (char*)vp2; while(size--) { char temp = *p1; *p1++ = *p2; *p2++ = temp; } } int main(void) { int x = 4565; int y = 690; gswap(&x,&y,sizeof(int)); double dx = 4565.; double dy = 690.; gswap(&x,&y,sizeof(double)); int a1[]={1,2,3,4}; int a2[]={-1,-2,-3,-4}; gswap(a1,a2,sizeof(a1)); //a1 in ilk 3 elemanı ile a2 inin son üç elemanını takas edelim ┌ a2 dizisinin boyutu - son üç elemanın başladığı adresi bulmak için kullanıyorum gswap(a1, a2+4-3, 3*sizeof(int)); } C nin standard kütüphanesinde bellek blokları ile ilgili işlerde yardımcı olacak fonksiyonlar var. Her nekadar string.h da - yazıişleri gibi gözükse de - bildirilse de , aslında bellek blokları üzerinde işlem yapar. Aslında bunların bildirimlerine baktığımızda da , türden bağımsız yani generic olduğunu anlayabiliyoruz. ▪ void *memchr(const void *str, int c, size_t n) Searches for the first occurrence of the character c (an unsigned char) in the first n bytes of the string pointed to, by the argument str. ▪ int memcmp(const void *str1, const void *str2, size_t n) Compares the first n bytes of str1 and str2. ▪ void *memcpy(void *dest, const void *src, size_t n) Copies n characters from src to dest. ▪ void *memmove(void *dest, const void *src, size_t n) Another function to copy n characters from str2 to str1. ▪ void *memset(void *str, int c, size_t n) Copies the character c (an unsigned char) to the first n characters of the string pointed to, by the argument str. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 36.Ders C [ void Pointers cont'd ] 16 Ağustos 2022 Salı ▪ void *memset(void *str, int c, size_t n) Copies the character c (an unsigned char) to the first n characters of the string pointed to, by the argument str. █ Örnek: int a[SIZE] dizisinin tüm elemanlarını 0 yapmak istediğim bir noktada memset(a,0,sizeof(a)); diyerek bunu başarabiliriz. Elbette bunu bir döngüylede yapabilirdik.Ancak elimizde buna ilişkin bir std kütüphane fonksiyonu varken bunu tercih etmeliyiz. Böyle durumlara avoid Raw Loops deniyor. Yani elde buna ilişkin bir fonksiyon varken fonksiyonu kullanmaya çalışmalıyız. █ Örnek: char str[]="murat gunay" dizinde rat harfleri yerine ! karakteri koymak istesem memset(str+2,'!',3); diyerek bunu gerçekleştirebiliriz. █ Örnek: char str[]="murat gunay" dizinde rat harflerinin aratalım, bulursak bu harfler yerine ! karakteri koymak istesem char* p = strstr(str,"rat"); if(p) memset(p,'!',3); █ Örnek: Çeşitli taskleri gerçekleştirelim #define SIZE 100 int main(void) { char old_file_name[SIZE]; char new_file_name[SIZE]; printf("eski dosya ismi:"); scanf(%s,old_file_name); // ▪ Eski dosya ismini yeni dosya ismine kopyalayalım strcpy(new_file_name, old_file_name); // ▪ Eğer dosyanin uzantisi yoksa new file name deki dosya uzantisi .txt olacak // ▪ Eğer dosyanin uzantisi jpeg ise new file name deki dosya uzantisi .png olacak // ▪ Eğer dosyanin uzantisi xls ise new file name deki dosya uzantisi silinecek char* p = strrchr(new_file_name,'.'); if(!p) { strcat(new_file_name,".txt"); } else if(!strcmp(p,".jpeg")) { strcpy(p,".png"); } else if(!strcmp(p,".xls")) { *p = '\0'; // strcpy(p,"") ifadesi de olurdu. } // printf("(%s)==>(%s)",old_file_name,new_file_name); } █ Örnek: Kendimiz bir memset yazalım void* mymemset(void* vp, int c , size_t n) { while(n--) { *p++ = (char) c; } return vp; } █ Örnek: Ekrana ne yazar? int main(void) { int x; memset(&x,0,sizeof(x)); printf("x=%d",x); //Output 0 memset(&x,255,sizeof(x)); printf("x=%d",x); //Output -1 memset(&x,1,sizeof(x)); printf("x=%d",x); //Output 16843009 } ▪ void *memcpy(void *dest, const void *src, size_t n) Copies n characters from src to dest. Overlapping olmama garantisi isteniyor, aksi halde UB. █ Örnek: Kendimiz bir memcpyyazalım void* mymemset(void *dest, const void *src, size_t n) { char* ptemp_dest = (char*) dest; const char* ptemp_source = (const char*) src; while(n--) { *ptemp_dest++ = *ptemp_source++; } return dest; } █ Örnek: Bir diziyi bir başka diziye kopyalamak için kullanabiliriz. int main(void) { int a[SIZE]; int b[SIZE]; //code, burada diziler değerini alsınlar memcpy(b,a,sizeof a); } █ Örnek: Bir dizideki belli bir indeksten başlayarak n tane elemanı bir başka dizide bir başka indeksten başlamak suratiyle kopyalamak için kullanabiliriz. int main(void) { int a[SIZE]; int b[SIZE]; //code, burada diziler değerini alsınlar memcpy(b+idx_b,a+idx_a,n*sizeof(int) ); } █ Örnek: int main(void) { char s[]="murat gunay" memcpy(s+3, s, 6 ); // UB due to overlapping of source and destination memmove(s+3, s, 6 ); // okay, memmove does not require non-overlap warranty } prepend --> başa ekleme append --> sona ekleme ▪ void *memchr(const void *str, int c, size_t n) Searches for the first occurrence of the character c (an unsigned char) in the first n bytes of the string pointed to, by the argument str. █ Örnek: Elimde bir yazı olsun, ilk 5 karakterine dek olan kısımda bir karakter arayalım int main(void) { char str[SIZE]; //code, burada dizi değerini alsın char *p = (char*) memchr(str,'a',5); if(!p) printf("bulunamadi\n"); else printf("bulundu yazinin %d indeksli karakteri",p-str); } ▪ int memcmp(const void *str1, const void *str2, size_t n) Compares the first n bytes of str1 and str2. iki bellek bloğu, byte byte unsigned char türünde karşılaştırılıyor. Eşitsizlik durumunda karşılaştırma sonlanıyor. Eşitsizlikte byte üzerinden küçük büyük neticesini üretiyor. █ Örnek: Elimizde iki dizi olsun. int main(void) { int a[SIZE]; int b[SIZE]; //code, burada diziler değerini alsınlar if( memcpy(a,b, sizeof(a)) ) printf("esit"); else printf("esit degil"); } █ Örnek: Bir diziyi reverse edecek bir fonksiyonu (generic olarak) tanımlayabilir miyiz? int a[SIZE] dizisi için bu fonksiyona yapılacak çağrı : greverse(a,SIZE,sizeof(int)) double a[SIZE] dizisi için bu fonksiyona yapılacak çağrı : greverse(a,SIZE,sizeof(double)) Bir diziyi reverse etmek için dizinin boyutunun yarısı kadar dönen bir döngüyle, ilk ve son elemanları swap etmiştik and so on... void* greverse(void* vp, size_t arrSize, size_t elemSize) { char *p = (char*) vp; // way 1 for(size_t i=0; i void func(int a[100]) yazarsan, derleyici buradaki parametreyi int* a olarak değerlendirir. Bir Fonksiyonun parametresi bir fonksiyon olamaz! --> void func(int x(int)) yazarsan, derleyici buradaki parametreyi bir function pointer olarak yani int (*)(int) şeklinde değerlendirir. Fonksiyonların geri dönüş türü fonksiyon olamaz! ▪ Fonksiyonun adresi kavramı: Fonksiyonların adresleri var, bu sayede onları bir başka fonksiyona yollamak mümkün. int func(int) gibi bir fonksiyon bildirilmiş olsun. Bunun adresi ne? Cevap: &func double baz(double) gibi bir fonksiyon bildirilmiş olsun. Bunun adresi ne? Cevap: &baz Bir fonksiyonun ismi bir ifade içinde kullanıldığında, derleyici tarafından fonksiyon ismi, ilgili fonksiyonun adresine dönüştürülür. Buna "function to pointer conversion" denir. Yani bir ifade içinde &func yada sadece func yazmak aynı kapıya çıkıyor. ▪ Fonksiyon adresinin türü kavramı: double baz(double) gibi bir fonksiyon bildirilmiş olsun. Bunun adresinin türü, yani &func ifadesinin türü ne? Cevap: double (*)(double) int func(int,int) gibi bir fonksiyon bildirilmiş olsun. Bunun adresinin türü, yani &func ifadesinin türü ne? Cevap: int (*)(int,int) func in adresini bir değişkende tutmak istesem bu değişkeni nasıl tanımlayabilirim? Cevap: int (*myfptr)(int,int) = func; // = &func ; da yazabilirdik. Aynı şey. myfptr = bar; //okay █ Örnek: strlen işlevinin adresi ile ilk değer verdiğiniz bir pointer değişken tanımlayınız size_t (*fp)(const char*) = strlen; █ Örnek: strcmp işlevinin adresi ile ilk değer verdiğiniz bir pointer değişken tanımlayınız int (*fp)(const char*,const char*) = strcmp; Peki, bu fp değişkeninin adresi ile bir pointer değişkene ilk değer verin. int (**fpp)(const char*,const char*) = &fp; Peki, öyle bir dizi tanımlayınki, dizinin elemanları, strcmp gibi fonksiyonların adreslerini tutsun int (*fpArr[10])(const char*,const char*) = {&strcmp,&strcoll}; █ Örnek: Öyle fonksiyon bildirinki, iki parametresi olsun, bu parametreler strcmp türünden fonksiyon pointer olsun, fonksiyonun geri dönüş değeri ise strcmp fonksiyonunun adresi türünden bir adres döndürsün int (* foo(int (*) (const char*, const char*) , int (*) (const char*, const char*)) ) (const char*, const char*); Görüldüğü gibi fonksiyon türleri ve bunlara ilişkin adres türlerinin yazımı oldukça karmaşık. Bunu basitleştirmek için Typedef bildirimlerinden yararlanabiliriz. ▪ Hatırlatma : typedef bildirimi syntaxi için pratik kural ▪ o türden bir değişken tanımlayın ▪ başına typedef yaz ▪ değişken ismini sil, yerine tür eş ismini yaz. █ Örnek:int func(int,int) gibi bir fonksiyon bildirilmiş olsun. Bu fonksiyonun adres türüne FPTR tür eş ismini verelim. ▪ o türden bir değişken tanımlayın int (*x)(int,int) ▪ başına typedef yaz typedef int (*x)(int,int) ▪ değişken ismini sil, yerine tür eş ismini yaz. typedef int (*FPTR)(int,int) █ Örnek:strcmp fonksiyonunun adres türüne FCMP tür eş ismini verelim. typedef int (*FCMP)(const char *, const char *) █ Örnek: Öyle fonksiyon bildirinki, iki parametresi olsun, bu parametreler strcmp türünden fonksiyon pointer olsun, fonksiyonun geri dönüş değeri ise strcmp fonksiyonunun adresi türünden bir adres döndürsün FCMP foo(FCMP,FCMP) FCMP türünden bir değişken tanımlayın ismi fp olsun FCMP fp; ========================= Operatör Öncelik Tablosu ========================= ┌────┬──────────────────────────────────────────────────────────────────────────── │ No │ Operator / Description ├────┼──────────────────────────────────────────────────────────────────────────── │ 1 │ [] () . -> Köşeli Parantez, Fonksiyon Çağrı, Nokta, Ok operatörleri ├────┼──────────────────────────────────────────────────────────────────────────── () Function call operator, bunun operandı fonksiyon adresi, yani fonk_adresi() şeklinde, yani fonksiyon ismi decay oluyor. Buna göre int foo(int) olarak bildirilmiş bir fonksiyon olsun. Bunu çağırırken foo(10) şeklinde çağırıyorum. (&foo)(10) şeklinde de çağırabilirim fcptr(10) şeklinde de çağırabilirim. Burada fcptr foo nun adresi türünden bir pointer olmak kaydıyla. █ Örnek: void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; int main(void) { void(*fp)(void); // Declare a function pointer fp = f1; fp(); //Output f1 called fp = f2; fp(); //Output f2 called fp = f3; (*fp)(); //Output f3 called f1(); //Output f1 called (&f1)(); //Output f1 called (*f1)(); //Output f1 called - Dilin enterasan kuralı, burada içerik operatörü sonucunda yine fonksiyonun adresine döndürülür } Function Pointer ların en sık kullanım teması callback yapısıdır. ═════════════════════════════════════════════════════════════════ Aşağıda func işini görmek için foo dan hizmet alıyor. Bu şimdiye dek kullanageldiğimiz yapı. void func(void) { // code foo(); //code } Ancak callback yapısında func in kimden hizmet alacağını, bir function pointer ile çağıran kod ona yolluyor void func(void (*fp)(void)) { // code fp(); //code } █ Örnek: void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; void func(void (*fp)(void)) { // code fp(); //code } int main(void) { func(f1); //Output f1 called func(f2); //Output f2 called func(f3); //Output f3 called } Burada şu iki şeyi karıştırmayalım func(foo()) foo nun geri dönüş değeri, func a arguman olarak geçiliyor func(foo) foo nun adresi func a arguman olarak geçiliyor █ Örnek: Daha evvel ctype ile ilgili isupper - islower gibi örneklerle çalışmıştık. Bunu function pointer kullanarak generalize edelim. void print_chars(const char* pfname, int (*fptest)(int)) { puts(pfname); for(int i=0; i<128; i++) { if(fptest(i)) printf("%c",i); } printf("\n") } int main() { print_chars("upper",&isupper); print_chars("lower",&islower); print_chars("digit",&isdigit); print_chars("punct",&ispunct); print_chars("alnum",&isalnum); } ▪ qsort - nlogn karmaşıklıkta, türden bağımsız, quick sort olmak zorunda değil. ════════ qsort sıralama -nlogn karmaşıklıkta, türden bağımsız- fonksiyonunun, her C programcısı tarafından bilinmesi elzemdir. qsort fonksiyonu bildirimi stdlib.h başlık dosyasındadır. Bildirimi void qsort(void* vpa,size_t arrSize,size_t elemSize, int (*fcmp)(const void*, const void*) ) Sıralanacak ┘ │ │ │ │ └ Kıyaslanacak eleman adresi dizinin adresi │ │ │ └ Kıyaslanacak eleman adresi Dizinin boyutu┘ Dizi ┘ │ eleman boyutu └ Dizi elemanlarını kıyaslayan fonk adresi █ Örnek: q sort ile küçükten büyüğe sıralama yapalım #include #define SIZE 100 int icmp(const void* vp1, const void* vp2) { //icmp dizinin elemanlarının türünün int olduğunu biliyor if ( *(const int*)vp1 > *(const int*)vp2 ) return 1; if ( *(const int*)vp1 < *(const int*)vp2 ) return -1; return 0; //Note that aşağıdaki implementasyonda taşma riski oluşabilir. return *(const int*)vp1 - *(const int*)vp2; //Some other implementation using ternary operator could be int left = *(const int*)vp1; int right = *(const int*)vp2; left > right ? 1: left *(const double*)vp2 ) return 1; if ( *(const double*)vp1 < *(const double*)vp2 ) return -1; return 0; } int main() { int a[SIZE]; randomize(); set_array_random(a,SIZE); print_array(a,SIZE); //qsort dizinin elemanlarının türünün int olduğunu bilmiyor qsort(a, SIZE, sizeof(*a), &icmp); double b[] = {1.1, 1.2, 1.4, 5.0, 4.4, 7.7}; qsort(b,asize(b),sizeof(double),dcmp); } █ Örnek: bubble sort algoritmasını generic olarak implement eden gbsort fonksiyonu tanımlayınız ve test ediniz void gbsort(void* vpa, size_t arrSize, size_t elemSize, int (*fcmp) (const void*, const void*) ) { char* p = (char*)vpa; for (size_t i=0; i< arrSize-1; ++i) { for (size_t k=0; k0) gswap(p+k*elemSize, p+(k+1)*elemSize, elemSize); } } } int icmp(const void* vp1, const void* vp2) { //icmp dizinin elemanlarının türünün int olduğunu biliyor if ( *(const int*)vp1 > *(const int*)vp2 ) return 1; if ( *(const int*)vp1 < *(const int*)vp2 ) return -1; return 0; } int main() { int a[SIZE]; randomize(); set_array_random(a,SIZE); print_array(a,SIZE); //gbsort dizinin elemanlarının türünün int olduğunu bilmiyor gbsort(a, SIZE, sizeof(*a), &icmp); print_array(a,SIZE); } █ Ödev: generic partition fonksiyonunu yazınız #define SIZE 100 void* gpartition(void* vpa, size_t size, size_t sz, int (*fp)(const void*)) { // Ödev: Your code } //sarmalayan bir fonksiyon yapıverelim int f_isprime(const void *p) { return isprime(*(const int*)p); } int main() { int a[SIZE]; randomize(); set_array_random(a,SIZE); print_array(a,SIZE); int *partitionPoint = gpartition(a, SIZE, sizeof(*a), &f_isprime); printf("partisyon indeksi: &d", partitionPoint-a) } █ Ödev: qsort fonksiyonuna çağrı yaparak diziyi lexicographical olarak sıralayınız - hiç error/warning olmasın. callback olarak strcmp yi kullanınız. char* namesArr[] = {"ali","abdullah","adem",adnan","afacan","agah","ahmet",alev","ali","alican" ....}; int scmp(const void vp1, const void* vp2) { // Cevap? - check it! return strcmp(*(char**)vp1, *(char**)vp2); } int main() { for(size_t i=0; i *(const int*)vp2 ) return 1; if ( *(const int*)vp1 < *(const int*)vp2 ) return -1; return 0; } int main() { int a[SIZE]; randomize(); set_array_random(a,SIZE); qsort(a, SIZE, sizeof(int), &icmp); print_array(a,SIZE); int sval; printf("aranacak deger:"); scanf("%d",&sval); int* p = bsearch(&sval, a, SIZE, sizeof(int), &icmp); if(p) { printf("Bulundu, dizinin %d indisli elemanı", p-a); } else { printf("Bulunamadı"); } } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 38.Ders C [ Function Pointers cont'd, Multi-Dimensional Arrays] 23 Ağustos 2022 Salı ■ function pointer arrays ═══════════════════════════ Neden böyle bir diziye ihtiyacım olsun? Elemanları function pointer olan bir dizi, elemanları mantıksal bir ilişkiye sahip. Örneğin, bir oyun programında savaşçının eylemlerini gerçekleştiren fonksiyonların adreslerini tutan bir dizimiz olabilir, bundan elde edeceğimiz fayda ise, bir jump table ile bir fonksiyona erişip onu çağırabiliriz. Hatırlayalım bir menu de sıralanmış seçeneklerden bir sıra nosu alarak ona erişiyorum. int (*fp)(int); // Burada fp, Geri dönüş değeri int , parametresi int olan bir fonksiyon adresi tutan bir değişken int (*fpArr[10])(int); // fp ye diyorki, ben de senin gibi 10 tane var int (*fpArr[])(int) = {&isupper, &islower, &isxdigit, &isalnum }; // 4 elemanlı bir function pointer array bildirdik ve initilize ettik Yazım kolaylığı açısından, bir typedef bildirimi yaparsak; typedef int (*FP)(int); FP fpArr[] = {&isupper, &islower, &isxdigit, &isalnum }; function pointer arrays de const olabilir. const FP fpArr[] = {&isupper, &islower, &isxdigit, &isalnum }; // dizinin const olması demek, elemanları değişmeyecek dizi demek. █ Örnek: void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; int main(void) { void(*fpArr[])(void) = {&f1, &f2, &f3); // & lar olmasa da olurdu. Function to pointer conversion // Dizideki tüm fonk lara çağrıda bulunalım. for(size_t i = 0; asize(fpArr); ++i) { fpArr[i](); //okay } } █ Örnek: Bir karakteri ctype.h da ki karakter test fonksiyonları ile test edelim. #include typedef int (*FCTEST)(int); int main(void) { const FCTEST fpArr[] = {&isupper, &islower, &isalpha, &isdigit, &isxdigit, &isalnum, &isspace, &ispunct,&isprint, &isgraph, &isblank, &isctrl }; int ch; printf("Bir karakter girin:"); ch = getchar(); // Dizideki tüm fonk lara çağrıda bulunalım. for(size_t i = 0; asize(fpArr); ++i) { if( fpArr[i](ch) ) { printf("ok \n"); } else { printf("NOK \n"); } } } █ Örnek: Yukarıdaki örneği, çıktıda hangi fonksiyon için OK NOK olduğunu belirtecek şekilde düzenleyelim. Bunun için mapping/eşleme mantığından faydalanalım. #include typedef int (*FCTEST)(int); int main(void) { const FCTEST fpArr[] = {&isupper, &islower, &isalpha, &isdigit, &isxdigit, &isalnum, &isspace, &ispunct, &isprint, &isgraph, &isblank, &isctrl }; const char* const fnames= {"isupper","islower","isalpha","isdigit","isxdigit","isalnum","isspace","ispunct","isprint","isgraph","isblank","isctrl" }; int ch; printf("Bir karakter girin:"); ch = getchar(); // Dizideki tüm fonk lara çağrıda bulunalım. for(size_t i = 0; asize(fpArr); ++i) { if( fpArr[i](ch) ) { printf("%s testi icin %c karakteri ok \n",fnames[i],ch); } else { printf("%s testi icin %c karakteri NOK \n",fnames[i],ch); } } } █ Örnek: Yukarıdaki örneği, fonksiyon ismini bir yazıdan alacak şekilde düzenleyelim. #include typedef int (*FCTEST)(int); int main(void) { const FCTEST fpArr[] = {&isupper, &islower, &isalpha, &isdigit, &isxdigit, &isalnum, &isspace, &ispunct, &isprint, &isgraph, &isblank, &isctrl }; const char* const fnames= {"isupper","islower","isalpha","isdigit","isxdigit","isalnum","isspace","ispunct","isprint","isgraph","isblank","isctrl" }; int ch; printf("Bir karakter girin:"); ch = getchar(); char name_entry[SIZE]; printf("Hangi karakter testi yapılacak:"); scanf("%s",name_entry); size_t i; for( i= 0; i void bar(void) { printf(bar cagrildi"); } int main(void) { func(); //func burada foo yu çağıracak FPTR fprev = set_func(bar); //bu noktadan sonra eğer func çağrılırsa, artık bar fonksiyonunu çağıracak , //ancak fp bir önceki set edilmişin adresi yani foo yu göstericek - geriye dönmek için bu şekilde bir davranışı olsun func(); //func burada bar ı çağıracak set_func(fprev); //bu noktadan sonra eğer func çağrılırsa, artık foo fonksiyonunu çağıracak func(); //func burada foo yu çağıracak } ▪ exit ve atexit ikilisine gözatalım. atexit ile register edilen fonksiyonlar , exit çağrısına müteakip sondan geriye doğru çağrılıp akabinde program sonlandırılır. void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; int main(void) { printf("main basladi"); //register clean up functions atexit(f1); atexit(f2); atexit(f3); //exit program exit(1); } Sırasıyla f3 sonra f2 sonra f1 i çağırıp programı sonlandıracak █ Örnek: Biz de böyle bir yapıyı gerçekleyelim. myutil.h myutil.c #define SIZE 20 typedef void(*FPTR)(void); static FPTR gfa[SIZE]; void func(void); static int g_idx = 0; void f_register(FPTR); void f_register(FPTR f) { gfa[g_idx++] = f; } void func(void) { for(int i=0; i< g_idx; ++i) { gfa[i](); } } main.c #include "myutil.h" #include void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; int main(void) { f_register(f1); f_register(f2); f_register(f3); func(); } Şimdilik function pointer arrays e ara vereceğiz. User defined types konusunda tekrar değineceğiz ■ Multi-Dimensional Arrays - Çok boyutlu diziler ═════════════════════════════════════════════════ ▪ C'de tüm diziler aslında tek boyutludur. Peki int a[10][20]; bildirimi yapıyoruz, bu ne oluyor? Bu, elemanları 20 elemanlı dizi olan, boyutu 10 olan bir dizi. - Spiral kuralı ile okuma yap - a'nın elemanlarının türü int[20] ▪ Dizilere ilk değer verme: int a[5][3] = { {1,1,1}, {2,2,2}, {3,3,3} }; //ilk üç elemana değer vermişiz, belirtilmeyenler 0 int a[5][3] = { {1}, {2,2,}, {3,3,3} }; //ilk üç elemana değer vermişiz - ama bunlardada eksik bırakmışız- , belirtilmeyenler 0 int a[5][3] = { 1,2,3,4,5,6,7,8 }; //also okay, üçlü üçlü gruplayarak ele alacak, burada ki yazım { {1,2,3}, {4,5,6},{7,8} } gibi ele alınır. Designated initializer kullanımı geçerli: int a[10][5] = { [7]={2,1,5,9}, [5]={[3]=3} } //okay int a[ ][5] = { [7]={2,1,5,9}, [5]={[3]=3} } //okay, derleyici bu durumda a[8][5] olarak düşünür █ Örnek mülakat sorusu: Hangileri Geçerli ilk değer verme? Cevap : Yalnızca b a int a[ ][ ] = { 1,2,3,4,5,6,7,8 }; Çünkü dizinin boyutunu belirtmek zorunda değilim, ki bu da ilk köşeli parantez. b int a[ ][3] = { 1,2,3,4,5,6,7,8 }; ikinci köşeli parantez dizinin elemanının türüyle ilgili. Bu ikinci parantez içi boş bırakılamaz! c int a[5][ ] = { 1,2,3,4,5,6,7,8 }; asize makromuz iki boyutlu dizi içinde doğru çalışır mı? Cevap : Evet int main(void) { int a[10][20]; printf("sizeof(a) = %zu\n",sizeof(a) ); // Output 800 - assuming int is 4 bytes printf("sizeof(a[0]) = %zu\n",sizeof(a[0]) ); // Output 80 - assuming int is 4 bytes printf("sizeof(a[0][0]) = %zu\n",sizeof(a[0][0]) ); // Output 4 - assuming int is 4 bytes for(int i=0; i < 10; ++i) { printf("%p %p\n",a+i,&a[i]); //Yazdırılan adresler arasında 20*4=80 byte fark olduğunu görelim. a'nın her elemanı arasında 20*4 byte lık fark var. //Çünkü a'nın her elemanı 20 elemanlı int dizi. } //a'nın ilk elemanını bir pointerda saklamak isteyeyim int(*p)[20] = a; //p yi 1 artırırsam a'nın bir sonraki elemanını gösterir. } ▪ 2 boyutlu diziyi , 1 boyutlu dizi gibi ele almak istiyor olalım. int* p = a; // C'de uyarı, C++ da syntax Error └ Warning C4047: initializing : int* differs in levels of indirection from int(*)[20] Yani ilk değer vermedeki tür uyuşmazlığı konusunda uyarıyor. Çünkü dediğimiz gibi a'nın elemanlarının türü int[20] Yani bu elemanları gösterecek pointer int(*)[20] türünde olmalı. a nın adresi aslında a[0][0]'ın adresidir. Yani sayısal değer anlamında bir yanlışlık yok. Derleyici tür uyumsuzluğuna sitem ediyor. Bu sitemi gidermek için tür dönüşümü uygulayabiliriz. int* p = (int*) a; int* p = &a[0][0]; //okay int* p = a[0]; //okay, a[0] dediğimiz 20 elemanlı bir dizidir.Yine Array decay ile bu dizinin ilk elemanının adresine dönüşür. int* p = &**a; //okay █ Örnek: Bir matrisin elemanlarını ekrana bastıralım int a[5][3] = { {1,1,1}, {2,2,2}, {3,3,3}, {4,4,4}, {5,5,5} }; for (int i=0; i<5; ++i) { for (int k=0; k<3; ++k) { printf("%d",a[i][k]); } printf("\n"); } █ Örnek: Yukarıdaki örnekteki diziyi tek boyutlu gibi ele alıp elemanları ekrana bastıralım int a[5][3] = { {1,1,1}, {2,2,2}, {3,3,3}, {4,4,4}, {5,5,5} }; int* p = &a[0][0]; for (int i=0; i<15; ++i) { printf("%d",*p++); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 39.Ders C [ Multi-Dimensional Arrays cont'd, ] 25 Ağustos 2022 Perşembe int a[10] = {1,2,3,4,5,6,7,8,9,10}; tek boyutlu dizisini ele alalım. &a[0] türü int* a türü int* -array decay- &a türü int(*)[10] Öte yandan, int *p1 = a; -------------------------> *p1 demek a[0] demektir. int (*p2)[10] = &a; -------------------> *p2 demek a dizisi demektir. Mesela şu şekilde kullanım okay for(int i=0; i <10; ++i) { printf("%d",(*p2)[i]); //(*p2)[i] a[i] demektir. Çünkü *p2 demek a demektir. } int a[5][3] = { {1,1,1}, {2,2,2}, {3,3,3}, {4,4,4}, {5,5,5} }; // Bu dizinin elemanlarının türü int[3] int[3] türüne bir tür eş ismi verelim typedef int INTA3 [3]; O halde a dizisini şu şekilde yazabiliriz. INTA3 a[5]; █ Örnek: 2d bir dizi yapalım, rastgele değerlerle dolduralım, değerleri ekrana yazalım int main(void) { // 2d bir dizi yapalım int a[10][20]; randomize(); // rastgele değerlerle dolduralım for(int i=0; i<10; ++i) { for(int k=0; k<20; ++k) { printf("%d", a[i][k] = rand()%10); } printf("\n"); } ────────────────────────────────────────────────────────────────────────────────────────── // alternatif printing for(int i=0; i<10; ++i) { print_array(a[i],20); //dizinin adresi ve kaç elemana sahip olduğunu söyledim, print_array fonksiyonu nutility içinde. printf("\n"); } ────────────────────────────────────────────────────────────────────────────────────────── // alternatif printing int (*p)[20] = a; int n=10; while(n--) { print_array(*p++,20); } ────────────────────────────────────────────────────────────────────────────────────────── // alternatif printing int (*p)[20] = a; for(int i=0; i<10; ++i) { for(int k=0; k<20; ++k) { printf("%d", (*p)[k] ); // Interesting way to access , notice the operator priority! } ++p; printf("\n"); } ────────────────────────────────────────────────────────────────────────────────────────── } █ Örnek: Mesela farklı 2d diziler-Matrisler- üzerinde işlem yapan bir fonksiyon yapabilir miyiz? Cevap : direkt olarak yapamayız, endirekt yollara başvurabiliriz, çünkü int a[10][20] dizisinin elemanlarının türü int(*)[20] int b[5][8] dizisinin elemanlarının türü int(*)[8] int c[6][4] dizisinin elemanlarının türü int(*)[4] int d[20][40] dizisinin elemanlarının türü int(*)[40] yani bu diziler farklı türden elemanlara sahip. Dolayısıyla, soruyu biraz daha daraltıp şu hale getirelim: Elemanları 20 elemanlı int dizi olan bir diziyi rastgele değerler ile set eden, bir fonksiyon yazalım: void set_array_20(int(*p)[20], size_t arrSize) { // way 1 // way 2 for(size_t i=0; i0) { char temp[20]; strcpy(temp,p[k]); strcpy(p[k],p[k+1]); strcpy(p[k+1],temp); } } } █ Örnek: Yukarıdaki örnekte dizinin elemanlarını ekrana yazdırmıştık. Şimdi buna ilişkin bir fonksiyon yazalım ve fonksiyon üzerinden yapalım. void print_names(const char(*ptr)[20], size_t arrSize) { for(size_t i=0; i0) { int scmp(const void* vp1, const void* vp2) char* ptemp = sorted_names[k]; { sorted_names[k] = sorted_names[k+1]; return strcmp(*(char**)vp1, *(char**)vp2); sorted_names[k+1] = ptemp; } } } } //Orijinal diziyi hali ekrana bastır for(size_t i=0; i < asize(sorted_names); ++i) { printf("%s ",names[i]); } //Sıralanmış hali ekrana bastır for(size_t i=0; i < asize(sorted_names); ++i) { printf("%s ",sorted_names[i]); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 40.Ders C [ yazı-sayı dönüşüm işlemleri, Std C fonksiyonlarına cont'd ] 30 Ağustos 2022 Salı ▪ yazı-sayı dönüşüm işlemleri için başlık dosyasında sunulan işlevselliklerden faydalanıyor olacağız ───────────────────────────── ▪int atoi(const char*) ▪double atof(const char*) ▪long atol(const char*) ▪long long atoll(const char*) char str[SIZE] = "84735"; //Bunu bir int değişkene koymak istiyorum Bunu çeviren algoritmanın ilkel halini yazmaya çalışalım: (negatif sayılar /karakterler olsa patlar) char str[SIZE]; printf("Bir yazi girin :"); sgets(str); int result = 0; for(int i=0; str[i] != '\0'; ++i) { result = result*10 + str[i]-'0'; } printf("%d",result); ▪ int atoi(const char *str) Converts the string pointed to, by the argument str to an integer (type int). İlkelini yazmaya çalıştığımız şeyi dört başı mağmur ve garantilerle yerine getiren atoi kullanırsak char str[SIZE]; printf("Bir yazi girin :"); sgets(str); printf("|%s| |%d| ",str,atoi(str)); █ Örnek: dd-aa-yyyy formatında girilen tarihteki bileşenleri tamsayı olarak elde edelim. Doğru formatta giriş yapıldığını kabul edeceğiz. char str[SIZE]; printf("Bir tarih girin dd-aa-yyyy "); sgets(str); printf("%s",str); int day,month,year; day = atoi(str); month=atoi(str+3); year =atoi(str+6); printf("%02d/02d/%02d",day,month,year); --> strto ile başlayan fonksiyonlarda, formata uymayan ilk karakterin indeksi ile ilgilenmiyorsak, ikinci parametreye NULL geçebiliyoruz. ▪ long int strtol(const char *str, char **endptr, int base) Converts the string pointed to, by the argument str to a long integer (type long int). atoi den farklı olarak, yazıda formata uymayan ilk karakterin yerinide bize bildiriyor - char* 'a yazarak - char str[SIZE]; printf("Bir yazi girin :"); sgets(str); char* p ; long result = strtol(str,&p,10); printf("|%s| |%ld| index=&d \n",str,result,p-str); ▪ long long strtoll( const char *restrict str, char **restrict str_end, int base ); ▪ unsigned long int strtoul(const char *str, char **endptr, int base) Converts the string pointed to, by the argument str to an unsigned long integer (type unsigned long int). ▪ double strtod(const char *str, char **endptr) Converts the string pointed to, by the argument str to a floating-point number (type double). ▪ sayı-yazı dönüşüm işlemleri ───────────────────────────── int ival = 84735; //Bunu bir char diziye koymak istiyorum char str[SIZE]; ▪ char* _itoa(int val,char* str,int base) Std da itoa fonksiyonu yok. Ama derleyiciler bunu sağlıyorlar. Orijinaline geçmeden bunun da bir ilkelini yazalım önce. int main(void) { int ival; printf("Bir sayı girin:"); scanf("%d",&ival); char str[SIZE]; int temp = ival; char* p = str; while(temp) { *p++ = temp %10+'0'; temp /=10; } *p = '\0'; _strrev(str); printf("%d |%s| \n", ival, str); } Şimdi _itoa kullanarak, yukarıda yaptığımız örneği tekrar edelim. int main(void) { int ival; printf("Bir sayı girin:"); scanf("%d",&ival); char str[SIZE]; _itoa(ival,str,10); printf("%d |%s| \n", ival, str); } ▪ printf ---- Yazıyı ekrana yazıyor ▪ sprintf ---- Yazıyı verdiğimiz değişkene yazıyor ▪ fprintf ---- Yazıyı dosyaya yazıyor ▪ int sprintf( char *restrict buffer, const char *restrict format, ... ); (since C99) Loads the data from the given locations, converts them to character string equivalents and writes the results to a character string buffer. The behavior is undefined if the string to be written (plus the terminating null character) exceeds the size of the array pointed to by buffer. ▪ Diziyi taşırma tehlikesinden bizi uzak tutan birde snprintf var! incele... █ Örnek: Dosya ismi ve uzanti nosu alıp murat_678.jpg olarak bir diziye bunu koyacak bir prg yazalım int main(void) { int ival; printf("Bir sayı girin:"); scanf("%d",&ival); char name[SIZE]; printf("Bir isim girin:"); scanf("%s",&name); char filename[SIZE]; sprintf(filename,"%s_%d.jpg", name, ival); printf("Dosya ismi: %s",filename); } █ Örnek: Bir gerçek sayıyı yazıya dönüştürelim int main(void) { double dval; printf("Bir gercek sayı girin:"); scanf("%d",&dval); char str[SIZE]; sprintf(str,"%f", dval); printf("|%s|",str); } █ Örnek: murat001..123.txt yaz ekrana. int main(void) { char filename[SIZE]; for(int i=1; i<123; ++i) { sprintf(filename,"murat%03d.txt",i); printf("%s",filename); } } Formatlı yazıdan Değişkeni set ediyor ▪ scanf ---- yazı klavyeden alınır ▪ sscanf ---- yazı verdiğimiz bir diziden alınır ▪ fscanf ---- yazıyı dosyadan alıyor ▪ int sscanf( const char* buffer, const char* format, ... ); Reads the data from null-terminated character string buffer, interprets it according to format and stores the results into given locations. █ Örnek: Bir dizideki yazıdan , bir tamsayıyı set edelim. int main(void) { char buffer[] = "87324787ali"; // Bu diziden yazıyı alacağız int ival; sscanf(buffer,"%d",&ival); printf("ival = &d",ival); } █ Örnek: Bir dizideki yazıdan , 4 tamsayıyı set edelim. int main(void) { char buffer[] = "87 912 74347 6"; // Bu diziden yazıyı alacağız int x,y,z,t; sscanf(buffer,"%d%d%d%d",&x,&y,&z,&t); printf("x = %d",x); printf("y = %d",y); printf("z = %d",z); printf("t = %d",t); } █ Örnek: Bir dizideki yazıdan , hem yazı hem sayı al int main(void) { char buffer[] = "87ahmet"; // Bu diziden yazıyı alacağız int ival; char name[20]; sscanf(buffer,"%d%s",&ival,name); printf("ival = %d",ival); puts(name); } Programın Sonlandırılması: ────────────────────────── 1. normal termination : belirli garantilerle sonlanıyor. atexit ile kayıda alınmış işlemlerin kayıt sırasıyla ters - LIFO - işletilerek sonlandırma garantisi var. 2. abnormal termination : ani sonlandırma ▪ exit prosesi normal sonlandırır void exit(int) Parametre int başarı bilgisi. 0 başarı, 1 fail. stdlib başlık dosyasında iki tane makro var. EXIT_SUCCESS ve EXIT_FAILURE Bizim main deki return 0 yazdığımız, derleyici tarafından exit(0) a dönüştürülüyor. ▪ atexit sonlanma öncesi çalıştırılacak fonksiyonların kaydını sağlar. int atexit(void (*) (void)) Parametre olarak geri dönüş ve parametresi olmayan bir function pointer istiyor. Geri dönüş değeri başarı bilgisi. Yani fonksiyonu kaydetmişse 0, başarısız olmuşsa 1 dönecek. ▪ abort prosesi abnormal sonlandırır. Daha çok hata tespit mekanizması amaçlı kullanılıyor. void abort(void) Hata akımına, programın kendisi tarafından sonlandığına dair bilgi veriyor. █ Örnek: void f1(void) {printf("f1 called")}; void f2(void) {printf("f2 called")}; void f3(void) {printf("f3 called")}; int main(void) { printf("main basladi"); //register clean up functions atexit(f1); atexit(f2); atexit(f3); //exit program exit(1); } ▪ string.h kütüphanesinde eksik bıraktığımız fonksiyonlar : n adet için iş yapan versiyonlar: ▪ char *strncpy(char *dest, const char *src, size_t n); write exactly n bytes to dest, copying from src. or add 0's. n <= LEN(src) ise sonuna \0 eklemez. Çünkü aslında bir replacement amaçlı fonksiyon. n > LEN(src) ise \0 ile doldurur. int main(void) { char dest[SIZE]; char src[SIZE]="Eray Goksu"; size_t n; printf("Kac karakter kopyalanacak:"); scanf("%zu",&n); strncpy(dest,src,n); // Sonuna null koymayı garanti etmek için strncpy(s1,s2,n)[n] = '\0'; şeklinde de yazabiliriz.Şık? puts(dest); // Girilen n değerine göre UB oluşabilir! n <= LEN(src) ise sonuna \0 eklemez } int main(void) { char str[SIZE] = "Ertan Suluagac"; strncpy(str+2,"kut",3); //Yazıdaki tan, kut ile yerdeğiştirsin } ▪ char *strncat(char *dest, const char *src, size_t n) Appends the string pointed to, by src to the end of the string pointed to, by dest up to n characters long. int main(void) { char str1[SIZE] = "murat"; char str2[SIZE] = "aksakal"; strncat(str1,str2,5); printf("%s",str1); //Output murataksak } ▪ int strncmp(const char *str1, const char *str2, size_t n) Compares at most the first n bytes of str1 and str2. int main(void) { char str1[SIZE] = "ankara"; char str2[SIZE] = "karaman"; if(! strncmp(str1+2,str2,3) ) printf("evet esit"); else printf("hayir esit degil"); } ▪ char *strtok(char *str, const char *delim) Breaks string str into a series of tokens separated by delim. ilk çağrıda tokenize edilen yazının adresini geçiyoruz. sonraki çağrılarda null ptr geçiyoruz. Yani döngüsel olarak çağırıyoruz. Tokenize edecek bir kalmayınca, fonksiyon bize null ptr döndürüyor. Bu fonk, tokenize edilecek yazıyı değiştirip, delim olan yerlere \0 yerleştiriyor. int main(void) { char str[SIZE] = "cinar gursoy ve eray goksu, bugun yine derse katildilar!"; char* p = strtok(str," ,.!"); //ilk çağrı while(p) { printf("%s \n",p); p = strtok(NULL," ,.!"); //dongusel çağrılar için nullptr } } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 41.Ders C [ Dinamik Bellek Yönetimi ] 1 Eylül 2022 Perşembe Dynamic Memory Management Şimdiye dek, kullandığımız değişkenler/nesnelerin bellekte tutulacağı yer, derleyici tarafından ayrıldı/ayarlandı. Fakat şimdi, programın çalışma zamanında bir bellek alanı kullanılacak. Bu bellek alanının edinilmesi, programın çalışma zamanında koşan bir kodla elde edilecek. Storage'ı programın çalışma zamanında elde edilen, programın çalışma zamanında edinilen bellek alanlarında hayatını devam ettiren yeni değişkenlerimiz olacak. Peki, statik bellek yönetimi mi yoksa dinamik bellek yönetimi mi daha fazla maliyete sahip? Kesinlikle dinamik bellek yönetimi çok daha pahalı. Aklımızda takribi 40-50 kat pahalı -belki daha fazla- gibi bir yaklaşıklık bilgisi kalsın. Örneğin statik ömürlü nesneler -tipik olarak-segmente edilmiş özel bir bellek alanında [data segment]tutuluyor. Otomatik ömürlü nesneler, stack dediğimiz tipik bellek alanında tutuluyor. Her fonksiyon çağrısı ile stack te yeni bir alan [stack frame] açılıyor. Ayrıca kodlaması da , hata yapmaya çok açık. Dinamik nesnelerin hayatlarının sonlandırılarak, işgal ettikleri bellek alanının iadesi , bazı dillerin içine gömülü çöp toplama mekanizması sayesinde otomatik gerçekleştiriliyor. Yani programcı bu iadeden sorumlu değil. Ancak C/C++ dillerinde çöp toplama mekanizması yok. Dolayısyla, iadeden programcı sorumlu. Programcının bunun iadesini unutması vs. gibi hatalara Memory-Leak deniyor. C/C++ efficiency üzerine kurulu, dolayısyla böyle bir mekanizması yok. Peki hem daha pahalı olmasına hem de daha zor/meşakkatli kodlamaya rağmen neden tercih etmek durumunda kalabiliyoruz? ▪ Şimdiye dek statik ve otomatik ömürlü nesnelerimiz vardı. Ancak öyle işler var ki bu iki nesne modeli bize yetmiyor. Sözgelimi, fabrika fonksiyonları denen fonksiyonlar, her kendini çağırana bir nesne hayata getirip veriyor, ancak nesnenin ne zaman hayata veda edeceği çağıranın sorumluluğunda oluyor. ▪ Tipik olarak, Nesnenin türünün çalışma zamanında belli olduğu durumlarda dinamik ömürlü nesnelere ihtiyaç duyuyoruz. Buna ilişkin paradigmaya Runtime Polymorphism deniyor. ▪ İhtiyacımız olan bellek alanının , çalışma zamanında belli olduğu durumlarda dinamik ömürlü nesnelere ihtiyaç duyuyoruz. Mesela dizinin boyutunun çalışma zamanında belli olması gibi. Heap : Programın çalışma zamanında elde edilecek bellek alanlarının bulunduğu genel bellek alanına heap deniyor. Linear olmayan bir kullanım/tahsisat sözkonusu. Programlamada iki anlamda kullanılıyor. 1. Bir data structure. Bu pool alanını yöneten veri yapısı/algoritma. 2. [Pool] Dinamik bellek yönetimine ayrılmış genel bellek alanına heap deniyor. Karışmasın diye pool terimi de kullanılıyor. Fragmentation: Farklı farklı kodlar, pooldan sürekli al-ver ilişkisi içerisinde iken, pool alanına şöyle bir baksak parça parça bir işgal görürdük. Dolayıysla contigous bir bellek alanı talep eden birine, gerçekte parça pinçik toplam o kadar alan mevcut olmasına rağmen, tahsisat sağlanamayabilir. Bu probleme fragmentation problem diyorlar. Dangling Pointer : Bir bellek alanını gösteren birden fazla pointer olsun. Bu bellek alanı iade edildikten sonra bu pointerlar dereference edlirse UB olur. ■ da bildirilen şu fonksiyonları öğrenmeliyiz: ▪ malloc : Tahsisat yaptığı bellek bloğunu indeterminite -çöp değerle, içine hiç dokunmadan - değerle veriyor. ▪ calloc : Tahsisat yaptığı bellek bloğunu cleared değerle - sıfırla doldurarak - veriyor. ▪ realloc : Daha evvel edinilmiş bir bellek bloğunun azaltmak/artırmak için. ▪ free : Edinilmiş bir bellek bloğunun sisteme iadesi ■ void* malloc(size_t nBytes) ══════════════════════════════ This function returns a pointer to the allocated memory, or NULL if the request fails. malloc ile yapılan tahsisat sonucu bize tahsis edilen kısımdan başka, sistemin bu tahsisat ile ilgili ihtiyacı olan, tahsisata eşlik eden bir başlık alanıda gerçekleşir. Yani hidden bir cost var. Sözgelimi 10000 bytelık 5 ayrı malloc çağrısı ile yapılan tahsisat, 50 bytelık 1000 ayrı malloc çağrısı ile yapılan tahsisata göre, fiziksel bellekte daha az yer kaplar.Daha ucuz olur. Yapılacak bir pointer hatası ile - sözgelimi yapılmaması gerektiği halde p++ vs.. uygunsuz hareketler yaparak - bu header alana erişip buranın içeriğini değiştirirsek, artık o blok corrupt edilmiş olur. Böyle bir corruption ile dynamic memory management imiz çöker, kötü sonuçlar doğabilir. If nBytes is zero, the behavior of malloc is implementation-defined. For example, a null pointer may be returned. Alternatively, a non-null pointer may be returned; but such a pointer should not be dereferenced, and should be passed to free to avoid memory leaks. █ Örnek: Bir tamsayı dizi için yer tahsisatı yapalım. int main(void) { int n; printf("Kac tamsayi lazim?"); scanf("%d",&n); int* ptr = (int*) malloc(n*sizeof(int)); if(!ptr) { fprintf(stderr,"malloc basarisiz!"); // Mesajı std Hata akımına yönlendiriyoruz exit(EXIT_FAILURE); } // Kod buraya gelmişse zaten alles gut! Bellek tahsisatı Success! //Code //.. free(ptr); // Tahsis edilen bellekle işim bitti, iade ediyorum //Code //... } malloc ile tahsisatın başarımını kontrol etmek kritik. Ve her tahsisatta bu kontrol için aynı kodu yazmaktansa bunu bir fonksiyonla sarmalayıp/gerçekleyip kod tekrarından kaçınabiliriz. void* checked_malloc(size_t nBytes) { void* vp = malloc(nBytes); if(!vp) { fprintf(stderr,"malloc basarisiz!"); // Mesajı std Hata akımına yönlendiriyoruz exit(EXIT_FAILURE); } return vp; } ■ void free(void*) ═════════════════════ Edinilmiş bir bellek bloğunu, sisteme iade ediyor. Peki bu bloğun kapladığı alanı nerden biliyor? Orada header vs de tuttuğu bilgiler var demiştik ya. Biliyor işte. ▪ free fonksiyonu çağrıldığında, o bellek adresini gösteren pointer artık dangling hale geliyor. Bu pointerı dereference etmek artık UB sebep olur.. ▪ free fonksiyonu ile edinilmiş bir bellek bloğunun bir kısmını iade etmek mümkün değil. ya hep ya hiç! free(ptr+100); // UB, programcı ilk 100 byte ı kendime saklamaya devam edeyim, geri kalanını iade edeyim kafasında. Ama bu mümkün değil! ▪ dinamik olarak elde edilmemiş bir bellek bloğunu, free çağrısı ile iade etmek de mümkün değil. int main(void) { int a[100]; //.. int* ptr = a; //.. free(ptr); // UB . Bu tanımsız davranış, çünkü ptr nin gösterdiği bellek bloğu dinamik olarak elde edilmemiş ki! } ▪ [Double Deletion] Zaten iade edilmiş bir bellek bloğunun adresiyle free ye çağrı yapılmasıda UB oluşturur. int main(void) { int* ptr1 = (int*) malloc(nBytes); //.. int* ptr2 = ptr1; // Bir başka pointer ı daha , dinamik ayrılmış bellek bloğunu göstermek için görevlendirdim. // free(ptr1); // okay, bellek bloğumu iade ediyorum, ptr1 üzerinden // free(ptr2); // UB , aklıma geldi demin iade ettiğim bloğu, bir de ptr2 üzerinden free etmye çalışıyorum. UB ! // invalid bir pointer i, ancak yeni bir adres atamak için kullanabilirim. } ▪ Edindiğin bir bellek bloğunu, ileride iade etmek için kullanacağın pointeri , başka işlerde kullanırsan, o bellek bloğunu iade etme imkanı ortadan kalkar. İhtiyaç olmadığı halde meşgul edilen bellek alanı olayına Memory Leak deniyor. int* ptr = (int*) malloc(nBytes); //.. ptr = &x; // Eyvah, edindiğin bellek bloğunun bilgisini kaybettin! //.. ▪ free fonksiyonunun NULL pointer ile çağrılması UB değil. No operation'dır. free(NULL); //okay, not UB, but no operation ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 42.Ders C [ Dinamik Bellek Yönetimi cont'd] 6 Eylül 2022 Salı ▪Fonksiyonların geri dönüş değeri pointer ise ▪statik ömürlü nesne adresi ▪global nesne adresi ▪static yerel nesne adresi ▪string literali formundaki bir dizi adresi ▪dinamik ömürlü nesne adresi ▪çağıran kodun kendisine gönderdiği adresi döndürebilir. █ Örnek: Fonksiyonların geri dönüş değeri pointer ise, fonksiyonun dökümantasyonuna bir bakmak gerek. Neden mi? İşte örnek: #define SIZE 40 int* get_random_pwd(void) { static char pwd[SIZE+1]; //code , generating a random pwd size_t len = rand()%8 + 5; // En az 5 karakterli olsun for(size_t i=0; i Returns a pointer to a null-terminated byte string, which is a duplicate of the string pointed to by str1. The returned pointer must be passed to free to avoid a memory leak. If an error occurs, a null pointer is returned and errno may be set. The function is identical to the POSIX strdup. Bunun kullanımına bir örnek verelim. strdup ile döndürülen nesneyle işimiz bitince, onu free ettiğimize dikkat edelim. int main(void) { char str[SIZE]; printf("bir yazi girin"); sgets(str); char* p = strdup(str); printf("|%s| |%s|", str, p); free(p); // Unutulmaması gerek } Şimdi de kendi strdup umuzu yazmaya çalışalım. #include char* mystrdup(const char *str) { char * dup = (char*) malloc( (strlen(str)+1)*sizeof(char) ); //nullchar ilavesi yüzünden +1 ekledik if(!dup) { errno = ...; return NULL; //Mesela böyle olabilir dedik } return strcpy(dup, str); // Zaten strcpy nin dönüş değeri 1. parametresine geçtiğim adres } █ Örnek: Bize T* foo(void) olarak bildirilmiş bir fonksiyon verilmiş. Ama döndürdüğü nesne, dinamik ömürlü mü, statik ömürlü mü belirtilmemiş. Ne yaparız? Cevap: Birden çok kez çağrı yapıp, döndürdüğü adresleri kıyaslarız. Dinamik ömürlü ise döndürdüğü adresler farklı olur. Aslında yine kesin olmayabilir. Çok boyutlu bir diziden bir eleman adresi döndürüyorsa, yine ofsayt ihtimali var. █ Örnek: Bize void foo(T* ptr) olarak bildirilmiş bir fonksiyon verilmiş. Dikkat edelim, const T* denmemiş. Demekki set niyeti var. Acaba buna null ptr göndermek UB mi? Yoksa tanınmış bir opsiyon mu? Öte yandan, acaba T* ptr dediği parametre, out parametre mi? yoksa in-out parametre mi? out parametre diyor ki, bana nesne adresini ver, ben onu set edeyim. Bu tiptense, çöp değerli bir nesne gönderebilirsin. in-out parametre diyor ki, bana nesne adresini ver, ben onu okuyup sonra set edeyim. Bu tipse, çöp değerli bir nesne göndermemek lazım.UB olur. █ Örnek: Bize T* foo(T* ptr) olarak bildirilmiş bir fonksiyon verilmiş. Acaba benim gönderdiğimi bana mı döndürüyor? Acaba fonksiyon işini yapamadığında NULL pointer mi döndürüyor? Böyle mi kurgulanmış? █ Örnek: Sıkça ihtiyaç duyulan bir operasyon yazıları birleştirmek.strcat yazıyı değiştiriyor(tıpkı s1 +=s2 gibi). Ben ise s1 ve s2 yi değiştirmeden, yeni bir nesnede birleşmiş hallerini istiyorum. Burada iki yaklaşım olabilir. i. s3 gibi bir dizi ile yeri kendimiz temin ederiz. s3 de birleşmiş hali oluştururuz.(Eğer s1 ve s2 uzunluğu çalışma zamanında belli oluyorsa, bu seçenek de masadan kalkar.) ii. Dinamik ömürlü bir elemana koyarız. ii için örnek yapalım. char* mystrconcat(const char* s1, const char* s2) { char* temp = malloc( ( strlen(s1)+strlen(s2)+1 )* sizeof(char) ); if(!temp) exit(EXIT_FAILURE); strcpy(temp,s1); strcat(temp, s2); return temp; } int main(void) { char s1[SIZE]; char s2[SIZE]; printf("birinci yazıyı girin:"); sgets(s1); printf("ikinci yazıyı girin:"); sgets(s2); char* p = mystrconcat(s1,s2); //Do what ever with p free(p); } Yukarıdaki örnek için, farklı bir kullanımdaki, gözden kaçacak olan bir Memory Leak nasıl olur görelim. int main(void) { char s1[SIZE]; char s2[SIZE]; printf("birinci yazıyı girin:"); sgets(s1); printf("ikinci yazıyı girin:"); sgets(s2); char* p = mystrconcat( mystrconcat(s1,s2) , "murat" ); //iki yazıyı birleştirip sonuna murat da eklemek niyetiyle böyle yazdım diyelim. Bunda eleştirilecek ne var? //Do what ever with p free(p); } Burada char* p = mystrconcat( mystrconcat(s1,s2) , "murat" ); satırındaki sorun ne? Cevap: içteki mystrconcat in döndürdüğü dinamik ömürlü nesnenin adresine sahip olamadığım için onu free edemeyeceğim, Memory Leak e sebep olacağım. █ Örnek: Çalışma zamanında belli olan N adet elemana sahip, int gösteren bir pointer array ini oluşturalım. int** ptr = (int**) malloc( N * sizeof(int*) ); Buna ilişkin - dinamik ömürlü bir ptr dizisi - bir örnek yapalım. Bir matrise ihtiyacım var. Fakat matrisin boyutları çalışma zamanında belli. Rastgele değerlerle doldurulmuş matris tedarik eden bir fonksiyon yapalım. Burada duruma göre iki şekilde ele alınabilir: ▪ Matrisi için gereken alanı bir defada , tek boyutlu row*col uzunlukta bir tahsisat talep edebiliriz. Bu yaklaşımda bir avantaj, tüm dizinin contigous oluşudur. int* ptr □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□....□□□□□□□□□ : row*col │ └ i,k elemanına ulaşım ptr[i*col+k] şeklinde olacaktır. ▪ Eğer fragmentation yüzünden tek seferde böyle bir tahsisat uygun değilse, row tane tahsisat talep edebiliriz. Her biri col elamanlı olacak şekilde , ki bu durumda her bir şeridin (col elemanlı tahsisatların) farklı adresleri olacaktır. Bu adresleride bir başka dizide - row elemanlı - muhafaza ederek, bunu çağırana döndürebiliriz. Tabi burada çağıran, bunları kullandıktan sonra, öncelikle şeritleri sonrasında ise row elemanlı adres dizisini free etmelidir. int** ptrM □ → □□□□□□□□□ : col □ → □□□□□□□□□ : col □ → □□□□□□□□□ : col ... └ i,k elemanına ulaşım ptrM[i][k] şeklinde olacaktır. ... □ → □□□□□□□□□ : col +_____ row adet Bu yaklaşımda aklımızda tutmamız gereken bir husus, matirisin topyekün contigous olmaması.(Şerit içi contigous ama şeritler arası contigous değil.) int main(void) { size_t row, col; printf("matrisin row ve col sayılarını giriniz:") scanf("%zu%zu",&row,&col); int** ptrM = (int**) malloc(row * sizeof(int*)); //row elemanlı ptr dizisi (şeritleri gösterecek) if(!ptrM) {fprintf,stderr,"tahsisat yapilamadi"); exit(EXIT_FAILURE);} for(size_t i=0 ; i int x = 10; −−−−−−−−−−−−−−→ extern int x; x = 11; // okay, can access static int z = 10; −−−−−−−→ - void foo(int x) −−−−−−−−−−→ extern void foo(int); foo(6); // okay , can call foo {...} │ └ Burada extern sözcüğünü kullanmakla kullanmamak arasında fark yok. static void bar(void) −−−→ - {...} Pratikte, kaynak dosyaya bakınca, linkage'ı ne durumda anlamayı kolaylaştırmak için; #define PRIVATE static #define PUBLIC yapıları da sık kullanılıyor. ▪ Storage Class Specifiers : ═══════════════════════════ ▪ auto : Storage'in otomatik olduğunu söylüyor. C'nin erken zamanlarına ilişkin bir kelime/kavram. Ancak zaten default durum bu olduğu için artık kullanımı yokolmuştur. Zaten, Mesela global değişkenleri auto ile nitelemek syntax hatasıdır! (C++ daki auto ile karıştırmayalım. C++ da type deduction için çok sık kullanılan ve önemli bir anahtar kelimedir. ) Tek enterasan tarafı, implicit int kuralının auto için de geçerli olması.(Yerelde bir auto x = 5 ; için x int olarak kabul edilir.), ki bu implicit int C99 ile de dilden kaldırıldı. Özetle C dilinde, auto'yu hayatından çıkart! ▪ register : Derleyiciye, değişkenin bir registerda tutulması talebidir/ricasıdır. Ancak Derleyici buna uymak zorunda değildir. Günümüzde derleyicilerin çok akıllı hale gelişiyle birlikte, pratikte kullanımı artık yokolmaya yüz tutmuştur. Gereksizdir - unless antik bir derleyici ile çalışmıyorsan - . Tek enterasan tarafı, register ile nitelenen bir değişkenin adresine ulaşmak için &var ifadesi UB. ▪ static : Linkage başlığında izah edildi. ▪ extern : Linkage başlığında izah edildi. ▪ typedef : Aslında konumuzla bir ilgisi yok ama gramerle ilgili olarak standardlar bu başlığa koymayı uygun görmüşler. ▪ _Thread_local : Bu dersin kapsamında değil! ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 45.Ders C [ Type Qualifiers ] 15 Eylül 2022 Perşembe ■ Type Qualifiers : ═══════════════════ ▪ volatile : Tıpkı const gibi bir türü niteliyor. ════════════ volatile int x = 10; Derleyiciye diyorki: x prg dışı kaynaklar tarafından değiştirebilir! Değişkenin aktuel işletilen kod dışında da değiştirilebileceğini anlatmak için kullanıyoruz. Bu değişkeni her kullandığımızda, işlemci bunun en son atanan değerini ezbere değilde, gidip bellekten güncel/hakiki değerini - belli bir maliyet de oluşturur - okuyup gelir. Pointerlar la da kullanılıyor. #define PRXT (int*)0x1ACF int main(void) { volatile int* ptr = PRXT; // Burada volatile olan ptr nin gösterdiği int nesne. Tıpkı const ta yorumladığımız gibi yorumluyoruz. // *p ifadeleri gördüğünde, bellekten hakiki değer okuyup getirecek. int* volatile p = PRXT; // Burada p nin kendisi volatile olur. // p ifadesi gördüğünde, bellekten hakiki değeri okuyup getirecek. const volatile int* p = PRXT; // Bu nasıl bildirim böyle? // *p nin volatile olması, bellekten hakiki değerle // öte yandan const luk *p ye atama yapılmayacağını niyetini beyan ediyor. Tamam, ben kodda değiştirmeyeceğim, ama dış kaynak sürekli bununla kedi-fare gibi oynuyor değiştiriyor olabilir. const volatile int* const p = PRXT; // Bu bildirimde mantıklı olabilir. p nin hep aynı adresi göstermesini istiyor da olabilirim. } ▪ restrict : Yalnızca Pointerların kendisini niteleyen bir kelimedir. Non-Overlapping bellek blokları olması gerektiğini belirtir. C99 ile dile geldi. ════════════ Sadece pointerin kendisini restrict yapabilirsin. Derleyicinin daha iyi optimizasyon yapmasını destekler. restrict int* p = &x; //Syntax Error int restrict* p = &x; // Syntax Error int* restrict p = &x; // okay En önemli kullanım teması: void func(int* restrict p) -> Bunu şöyle anlamalıyız. p nin kullanıldığı scope ta , p nin gösterdiği nesneyi gösteren tek pointer p dir/olmalıdır. { } void foo(int* restrict p1, int* restrict p2) --> Bunu şöyle anlamalıyız. Ey p1 ve p2, bana non-overlap (no aliasing) garantisi ile bana gelin. { } ▪ const : Değişkenin değerinin değişmeyeceğini beyan ediyorduk. Zaten kullanageliyorduk. Aşağıdaki örneklerle bir hatırlayalım. ════════ const neyin önündeyse , const olan odur! - asterisk önü arkasına dikkat - █ const ile ilgili Örnekler: const int x = 10; // Fiziksel bir const tanımladık, global bir değişken olsun. C dilinde , x external linkage da. C++ da internal! - const sözcüğü yüzünden - ++x; // Syntax hatası, değişkenin değerini değiştirmeye çalışıyorum, derleyici bunu kontrol etmek zorunda int* p = (int*) &x; *p = 99; // Derleyici hata vermiyor, ancak bu bir UB! Fiziksel zırhın delmeye çalışmak! Peki int a[x], case x: gibi sabit ifadesi gereken yerlerde kullanabilir miyim? Cevap : Hayır Derleyici aşağıdaki iki bildirimi görürse ne tepki verir? Cevap: Parametrenin kendisinin constluğu, bildirimsel açıdan fark oluşturmuyor. Redeclaration olur. Herhangi hata/problem yok. void func(int); void func(const int); Derleyici aşağıdaki iki bildirimi görürse ne tepki verir? Cevap: Parametrenin kendisinin constluğu, bildirimsel açıdan fark oluşturmuyor. Redeclaration olur. Herhangi hata/problem yok. void func(int*); void func(int* const p); int x = 10; int y = 67; const int* ptr = &x; // ptr ile x in değerini değiştirmeme sözü veriyoruz. Buna - Contractual Constness da deniyor - ptr = %y; // okay, ptr nin değerini değiştirebiliriz, buna ilişkin bir taahhütümüz yok. *ptr = 77; // Syntax error int* const ptr = &x; // ptr nin değerini değiştirmeme sözü veriyoruz. ptr = %y; // Syntax error, ptr nin değerini değiştirmeyeceğimiz sözünü vermiştik. *ptr = 77; // okay, ptr nin gösterdiği nesnenin değerini değiştirebiliriz, buna ilişkin bir taahhütümüz yok. const int* const ptr = &x; // hem ptr nin hem de ptr nin gösterdiği nesnenin değerini değiştirmeyeceğimiz sözünü veriyoruz. ptr = %y; // Syntax error, ptr nin değerini değiştirmeyeceğimiz sözünü vermiştik. *ptr = 77; // Syntax error, ptr nin gösterdiği nesnenin değerini de değiştirmeyeceğimiz sözünü vermiştik. typedef int* iptr; // bir pointera tür eş ismi verelim const iptr p = &x; // tür eş ismini const ile niteleyerek bir değişken oluşturalım p = &y; // Syntax error, see kural below! *p = 56; // okay Burada şu kuralı tekrar hatırlayalım: pointerlara typedef ile tür eş ismi verildiğinde, tür eş isimlerinin const ile nitelenmesi sonucu, pointer in kendisi const olur. int x = 10; const int* p = &x; // Contractual Constness, p ile x in değiştirmeme sözünü veriyoruz. int* ptr = (int*) p; // ptr nin de x i göstermesini sağlayalım. note that ptr herhangi söz/beyan vermiyor! *ptr = 99; // okay, çünkü x fiziksel const değil!, zaten ptr nin de herhangi değiştirmeyeceğim diye bir beyanı yok. void func(const int* p) { *p const. Ancak *p nin fiziksel bir const olup olmadığını bilemeyiz. } int x = 10; int y = 10; int* p = &x; int* q = &y; int** const ptr = &p; için hangileri syntax hatası olur i. ptr = &y; //syntax error ii. *ptr = &y; //okay iii.**ptr = 90; //okay int*const* ptr = &p; için hangileri syntax hatası olur i. ptr = &y; //okay ii. *ptr = &y; //syntax error iii.**ptr = 90; //okay const int** ptr = &p; için hangileri syntax hatası olur i. ptr = &y; //okay ii. *ptr = &y; //okay iii.**ptr = 90; //syntax error const int* foo(void); Böyle bir bildirim görsen ne anlarsın? Cevap: Adres döndürüyor ama kendisini çapıran koda, benim sana döndürdüğüm adresteki nesneyi değiştirmeyeceksin şartıyla bu hizmeti veriyor. Yani tutup *foo(void) = 12 ; // Syntax error olur. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 46.Ders C [ User Defined Types - struct ] 20 Eylül 2022 Salı ■ User Defined Types : Problem domainindeki varlıkları temsil edip, daha kolay kod yazmamızı sağlıyan tür oluşturmamıza sağlayan araçlar. ══════════════════════ ▪ struct ▪ union ▪ enum ■ struct : Bu bildirim ile derleyiciyi bir türün varlığından haberdar etmiş oluyoruz. ═════════ struct optional_structureTag struct Data int main(void) { { { members int x,y,z; Data YourData; // Syntax error }; │ }; struct Data MyData; // Şimdi bellekte yer ayrılıyor. │ } └ fonksiyon hariç hemen herşey │ olabilir. │ en az 1 member olmalı! └ Her seferinde struct Data diye uzun yazmaktan Bu bildirime karşılık bir bir typedef bildirimi ile kaçınabiliriz. yer ayrılmıyor! Bu tür hakkında bilgi veriyor. Memberlara erişim kontrolü yok. (Diğer dillerdeki public vs gibi..) Memberları bildirim içi - inclass initializer - ilk değer verme mümkün değil. Memberlar, function olamaz. Ancak function pointer olabilir. Memberlar arası, kullanılmayan, alignment amacıyla, Padding adı verilen boşluklar olabilir. - ileride göreceğiz - Ancak bildirim sırasına göre yerleşim garanti altında. daha önce bildirilen bir elemanının adresi, ondan daha sonra bildirilen bir elemanın adresinden daha küçük olmak zorunda. Bu garanti altında. yapı nesnesinin kendi adresi ile yapı nesnesinin ilk ögesinin adresi, değer olarak, aynı olmak zorunda. Ve topu, dıştan sınırlandırılmış "contigious" monoblok bir bellek alanında. printf("sizeof(struct Data) = %zu\n",sizeof(struct Data)); // ■ optional olan structureTag'in kullanılması ═════════════════════════════════════════════ ▪ Sadece yapı türünü bildirebilirim. Daha sonra programımda struct stData x; diye nesne bildirebilirim. struct stData { int a,b,c; double d; } ; ▪ Hem yapı türünü bildirip hem bu yapı türünden global nesneleri oluşturabilirim. struct stData { int a,b,c; double d; } x, y; └ Global Değişkenler ■ typedef ile birlikte kullanım da mümkün ═════════════════════════════════════════════ Yapı türünü , structureTag , direkt olarak temsil etmediği için, typedef bildirimi kullanma imkanımız var. Hem yapı türünü bildirip hem hem de typedef ilave ederek buna bir tür eş ismi vermek amacıyla kullanabiliriz. typedef struct stData { int a,b,c; double d; } Data; └ Tür eş ismi, Dolayısıyla nesne tanımlamasını struct kelimesi geçmeden Data x; // şeklinde yapmak mümkün olur. ■ optional olan structureTag'in kullanılmadığı temalar ══════════════════════════════════════════════════════ ▪ Bu yapı türünden global nesneleri oluşturabilirim. Bu yapı türünün ismi olmadığından, bir başkasının, bu yapı türünden nesneler oluşturmasını engellemiş oluyorum. struct { int a,b,c; double d; } x, y; └ Global Değişkenler ■ typedef ile birlikte kullanım da mümkün ═════════════════════════════════════════════ Yapı türünü , structureTag kullanmadan, typedef bildirimi kullanma imkanımız var. typedef ilave ederek buna bir tür eş ismi vermek amacıyla. typedef struct { int a,b,c; double d; } Data; └ Tür eş ismi, Dolayısıyla nesne tanımlamasını struct kelimesi geçmeden Data x; // şeklinde yapmak mümkün olur. █ Örnek: Enterasan bir örnek: Derleyici aşağıdaki bildirimleri görse, ne olur? struct { int x,y } a; struct { int x,y } b; Cevap : Geçerli, okay Peki, a = b ; ataması geçerli olur mu? Cevap : Hayır, Error! Çünkü bunlar, her ne kadar içerikleri aynı olsa da , farklı (distinct) türler gibi muameler görür. ■ Bir nesne hayata getirmek ═══════════════════════════ struct stData │ typedef struct { │ { int a,b,c; │ int a,b,c; double d; │ double d; }; │ } Data; │ └ Tür eş ismi, Dolayısıyla nesne tanımlamasını struct kelimesi geçmeden yapmak mümkün olur. int main(void) │ int main(void) { │ { struct stData myData; //okay │ Data myData; //okay stData YourData; //syntax err │ } │ } Tıpkı diğer değişken türlerinde olduğu gibi global ve statik yerel nesneler, ilk değer verilmezse, zero initialize edilirler. Yerel değişkenler çöp değerle hayata gelirler. Yapı nesnesini const-volatile ile niteleyebilirsin. const struct stData myData; //okay Öte yandan, yapı nesnesi öğelerini const ile niteleyebilirsin. struct stData { int a,b,c; const double d; }; Burada dikkat edilecek husus, bu türden bir yapı nesnesi hayata getirirken, ilk değer verme gerekecek. struct stData myData = {1,2,3,4.}; Ve tabiki d isimli ögeyi değiştirme girişimi syntax hatası olacaktır. myData.d = 4.4; //syntax error Ve tabiki fiziksel zırhı , bir pointer ile aldatıp delmeye çalışmak yine UB. Tıpkı, int x = 10; const int* ptr = &x; *(int*) ptr = 45; // okay const int x = 10; const int* ptr = &x; *(int*) ptr = 45; // syntax error vermiyor ancak yine de UB! ■ Yapı nesnelerinin elemanlarına (öğelerine) erişim: (Nokta ve ok operatörleri) ═════════════════════════════════════════════════════════════════════════════════ ├────┼─────────────────────────────────────────────────────────────────────── ────────────────────── │ 1 │ [] () . -> Köşeli Parantez, Fonksiyon Çağrı, Nokta, Ok operatörleri ├────┼─────────────────────────────────────────────────────────────────────── ────────────────────── └ Member Selection Operators Dot operator : Birnary bir operatör. x.elem (Nesne ile erişim) Sol operandı olan ifade bir structure/union nesnesi değilse syntax error. structure/union nesnesi ise, sağ operandı structure scope da arar. Arrow operator :(*ptr).elem yazmak yerine ptr -> elem (Pointer ile erişim) yazmayı tercih ediyoruz. struct stData { int a,b,c; double d; int arr[10]; }; struct stData myData; struct stData* p = &myData; myData.a = 5; // okay p->a = 6; // okay (*p).a = 7; // okay // . operatörü, * operatöründen daha yüksek öncelik seviyesine sahip olduğundan parantez kullandım int* ptr = &mydata.a; //okay, . operatörü, & operatöründen daha yüksek öncelik seviyesine sahip olduğundan parantez kullanmaya gerek yok ++mydata.a; //okay, . operatörü, ++ operatöründen daha yüksek öncelik seviyesine sahip olduğundan parantez kullanmaya gerek yok int* pA = myData.arr; //okay, array decay works too. myData.arr is same as = &myData.a[0] ; ■ Bir yapı nesnesinin kendisiyle neler yapabilirim? ══════════════════════════════════════════════════════ struct stData { int a,b,c; double d; int arr[10]; }; struct stData myData; └ Bununla neler yapabilirim? ▪ Sadece şu 4 operatörün operandı yapılabilir. ▪ Nokta . operatörünün ▪ Ok -> operatörünün ▪ sizeof operatörünün ▪ Atama = operatörünün (Dile sonradan eklenen bir sematik, eskiden memcpy ile yapılageliyordu - memcpy(&x,&y,sizeof(struct stData));) └ Bir yapı nesnesine ancak ve ancak aynı türden bir yapı nesnesi atanabilir. Implicit yada explicit dönüşümlerde sözkonusu değildir. Yani aritmetik/pointer/farklı bir yapı türünden dönüşüm mümkün değil! Atama operatörü, hedefteki yapı nesnesinin tüm elemanların değerlerinin, kaynak yapı nesnesindekilerle birebir aynı hale getirme garantisi var. Bunun bir blok kopyalaması olduğu ve dolayısıyla büyük yapılar için bir maliyeti olacağını unutma. Burada şuna dikkat: karşılaştırma, artırma vs diğer operatörlerin operandı olamıyor! Ancak, atama operatörü var. Bunun bize sağlayabileceği şey şu olur: bir diziyi yapı nesnesinin içine yerleştirmek suretiyle dizilerin atamasını yapabiliriz. Array Wrapper struct arr10 │ int main(void) { │ { int a[10]; │ struct arr10 x,y; } ; │ // set val │ x = y; //okay, yapı nesneleri tarafından sarmalanmış içerdeki dizilerde atama gerçekleşir :) } Dinamik bellek te böyle bir nesne yaratıp, adresini geri döndüren bir func yazalım struct stData* create_data(void) { struct stData* p = (struct stData*) malloc(sizeof(struct stData)); //set val return p; } ■ Initialization ══════════════════ Hatırlatma: Initializer list te Trailing comma olabiliyordu. Görürsen şaşırma! struct Point │ struct Point { │ { int x,y,z; │ int x,y,z; } pA = {1,2,3}; │ } pA = {1,2,3}, *p ; │ │ │ Okay,Bir nene tanımladım. │ Okay , Bir de pointer ilave ettim. │ ──────────────────────────────────────────────────────────── struct Point │ int main(void) { │ { int x,y,z; │ struct Point pA = {1,2,3}; // x 1, y 2, z 3 }; │ struct Point pA = {1}; // ilk eleman x 1, diğerleri 0 │ struct Point pA = {0}; // Tüm elemanları 0 │ struct Point pA = {.y=5}; // y 5 , diğerleri 0 │ } ──────────────────────────────────────────────────────────── struct Employee │ int main(void) { │ { int id; │ struct Employee e1 = { 234, char name[40]; │ "murat gunay", char address[60]; │ "tuzla istanbul", double wage; │ 101.22 } │ } │ } ──────────────────────────────────────────────────────────── struct Point { int x,y,z; int arr[5]; }; int main(void) { struct Point pA = {1,2,3,{10,20,30,40,50} }; // x 1, y 2, z 3 ve arrayimizin elemanları 10,20,30,40,50 struct Point pA = { .x = 1, // x 1, y 2, z 3 ve arrayimizin elemanları 10,20,30,40,50 .y = 2, .z = 3, .a = {10,20,30,40,50} } ; struct Point pA = {1,2,3,{10} }; // x 1, y 2, z 3 ve arrayimizin elemanları ilk elemanı 10 diğer elemanları 0 struct Point pA = {1,2,3,{[2]=12, [3]=6} }; // x 1, y 2, z 3 ve arrayimizin elemanları üçüncü elemanı 12 dördüncü elemanı 6, diğer elemanları 0 struct Point pA = {1,2,3,4,5 }; // x 1, y 2, z 3 ve arrayimizin elemanları ilk elemanı 4, ikinci elemanı 5 diğer elemanları 0 - Sıralı doldurma, böyle bir sentaks da var. } ──────────────────────────────────────────────────────────── struct stData { int (*p1)(int,int); int (*p2)(int); } int foo(int,int); int bar(int); int main(void) { struct stData myData = {foo,bar}; //okay } ■ Yapı türleri ve typedef ═══════════════════════════ Yapı türünü , structureTag , direkt olarak temsil etmediği için, typedef bildirimi kullanma imkanımız var. Durum1 : Hizmet aldığın modül zaten başlık dosyasında typedef bildirimi yapar. Sende bunu include ettiğinde o türü tanırsın/bilirsin. FILE * f = fopen("ali.txt","r"); └ FILE diye bir tür var. Makro filan yok. include ettiğimiz başlık dosyasından geliyor/biliyoruz. Durum2 : Hizmet aldığın modül zaten struct türü bildirir, sen kendi kaynak dosyanda typedef ile -işini kolaylaştırmak için - sen tanımlarsın/bildirirsin. structureTag'li yalın struct Türü bildirimi ─────────────────────────────────────────────── struct Image { int a,b,c; } Typedef bildirimini ayrıca kullanmak ────────────────────────────────── typedef struct Image Murat; // Okay typedef struct Image Image; // Okay, Alias ve tür ismi aynı olabilir. Okay! typedef struct Image* ImagePtr; // Okay, bir de buna ilişkin pointera bir eş isim bildirdim typedef struct Image Murat,*ImagePtr ; // Okay, İkisi bir arada :) Typedef entegrasyonu: ───────────────────── typedef struct Image { int a,b,c; }Image; └ Bu artık global bir değişken değil. Artık bir tür eş ismi. Böylesi bir durumda, self-referential structure yapma imkanımız da var. - ileride göreceğiz. ─────────────────────────── typedef struct Image { int a,b,c; struct Image* ptr; //Self-referential }; structureTag olmadan Typedef Tür eş ismi entegrasyonu: ────────────────────────────────────────────────────── Yine en sık kullanılan bir konu, structureTag kullanmadan , typedef ile tür eş ismi bildirmek. Böylelikle herkes onu kullanmak durumunda. typedef struct { int a,b,c; }Image; └ Bu artık global bir değişken değil. Artık bir tür eş ismi.structureTag olmadığından herkes senin verdiğin türeş ismini kullanmak durumunda. Yapıya ilişkin, sadece bir pointer eş ismi sağlamak. ──────────────────────────────────────────────────── typedef struct { int a,b,c; }*ImagePtr; └ Bu artık global bir değişken değil. Bu yapıya ilişkin pointer tür eş ismi. Statik ve Otomatik ömürlü bir nesne oluşturulabilme imkanını ortadan kaldırdık. structureTag de olmadığından herkes senin verdiğin pointer ile işlerini yürütmek durumunda. Dolayısıyla ancak dinamik ömürlü nesneler oluşturulabilir. Peki bu durumda, dinamik nesne nasıl hayata getirilebiliyor? - Problem: Note that malloc ifadesinde sizeof gerekecek, ancak nesneye erişebileceğim tür eş ismi yok! Cevap : ImagePtr p = malloc(sizeof(*p)); Şimdi burayı iyi anlamak gerek. Çünkü, sizeof'u iyi anlamamışsan, tutup, " zaten hayata getirmeye çalıştığımız p pointer'ını dereference ediyoruz? Ancak henüz hayat gelmedi ki? " diye sorarsın. Remember, sizeof(..) için parantez içi Unevaluated Context, yani sizeof Compie time da çalışan bir operatör. içerideki ifadeyi işletmez, bakar türüne göre sizeof unu döner. İşte bu sayede problem olmaz. █ Örnek: struct stData { int a,b,c } şeklinde bir yapı türümüz olsun. Bu türden yapı nesneleri içeren bir dizide sıralama yaptıralım. Nasıl bir sıralama? Mesela a elemanı büyük olan büyük, eşitlerse b, eşitler c elamanı kıyası ile işlem yapsın. struct stData { int a,b,c }; int mycmp (const void* vp1, const void* vp2) { const struct stData* p1 = vp1; const struct stData* p2 = vp2; if(p1->a != p2->a) return p1->a - p2->a; if(p1->b != p2->b) return p1->b - p2->b; return p1->c - p2->c; } int main(void) { struct stData arr[100]; //set val qsort(arr, 100, sizeof(*arr), &mycmp); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 47.Ders C [ User Defined Types - struct cont'd ] 22 Eylül 2022 Perşembe ■ Yapı türünden adresler (pointers) ═══════════════════════════════════ █ Örnek: struct Point { double x,y,z; } struct Point* foo(void); int main(void) { printf("sizeof(struct Point) = %zu\n",sizeof(struct Point)); struct Point p1 = {1.1, 1.2, 1.3}; struct Point p2 = {2.1, 2.2, 2.3}; struct Point* ptr = &p1; //okay *ptr = NULL; //okay *ptr = p2; //okay , aynı türden yapı nesneleri birbirine atanabilir (*ptr).y = 12.12; //okay , elemana erişmek için öncelik parantezi lazım - pek tercih edilen bir şekil bu değil, use -> operator instead ptr->y = 12.12; //okay , nice! p1.y = 12.12; //okay (&p1)->y = 12.12; //okay too! struct Point arr[10] = {0}; arr[0].x = 11.11; //okay , dizinin ilk elemanının x isimli ögesini değiştirmiş olacağım. arr->x = 11.11; //okay, array decay still works too. Yukarıdaki arr[0].x ifadesiyle aynı arr[5].x = 11.11; //okay (a+5)->x = 11.11; //okay, array decay still works too. Yukarıdaki arr[5].x ifadesiyle aynı ptr = arr; //ptr Dizimizi göstersin for(int i =0; i<10; ++i) { // Dizi elemanlarının adreslerini ve ptr nin değerini gösterelim, dikkat printf("%p %p %p\n",&arr[i], a+i, ptr++); // ptr nin her 1 artışı ile birlikte aslında değeri bir sonraki elemanı gösterecek şekilde değişiyor } foo()->x = 11.11; //okay, foo() hangi nesnenin adresini döndürdüyse, o elemanın x isimli ögesini değiştirdim. foo()[5].x = 11.11; //okay, foo() nun adresini döndürdüğü dizinin 5. elemanının x isimli ögesini değiştirdim. struct Point * const ptrA = &p1; //okay, const ptr - ptr'nin kendisi const, yani bir başka nesneyi göstermek üzere değiştirilemez struct Point const * ptrB = null; //okay ptrB= &p1; //okay, *ptrB = p2; //syntax error ptrB->y = 12.12; //syntax error (*ptrB).y = 12.12; //syntax error } ptr -> y ifadesinde , derleyici ptr ye - yani sol operanda - bakıyor. Bu bir yapı nesnesi türünden adres değilse, doğrudan syntax hatası veriyor. Bu bir yapı nesnesi türünden adres ise , sonrasında y - sağ operand - için, struct scope da namelookup'ı yürütüyor. ptr->y ifadesini derleyici (*ptr).y gibi ele alıyor. ■ Incomplete Types (Tamamlanmamış Türler) in User-Defined Types ════════════════════════════════════════════════════════════════ Bir yapı türünün Complete olması demek, derleyici onunla ilgili tüm bilgileri var demenktir. ───────────────────── █ Örnek: Complete type örneği struct Point { double x,y,z; }; Bir yapı türünün Incomplete olması demek, derleyici bunun varlığından haberdar, ancak türün ögeleriyle ilgili bilgileri eksik. ───────────────────────── █ Örnek: InComplete type örneği struct Point; // diye bir bildirim yaparsak, derleyici struct Point diye bir yapı nesnesi türünün varlığından haberdar olacak. Bu bildirimle birlikte yapabileceğim ve yapamayacağım şeyler var : Neleri yapmaya müsaade var? ══════════════════════════ ▪ Fonksiyon bildirimlerinde yer alabilir. struct Point foo(struct Point, struct Point*); // okay struct Point* bar(const struct Point*, const struct Point*); // okay ▪ Tür eş isim bildirimlerinde yer alabilir. typedef struct Point Point; // okay typedef struct Point RobotCoordinate; // okay typedef struct Point* ptr; // okay typedef struct Point* (*Fptr) (struct Point*); // okay ▪ Pointer türünden değişken tanımlanabilir struct Point * ptr = NULL; // okay, Derleyici pointer sizeof u bildiğinden bunu yapabilir. ▪ Bir değişkenin extern bildirimi yapılabilir extern struct Point a ; // okay, Derleyici buna yer ayırmadığından - Non Defining Declaration - olabilir extern struct Point b[100] ; // okay, Derleyici buna yer ayırmadığından - Non Defining Declaration - olabilir extern struct Point c[] ; // okay, Derleyici buna yer ayırmadığından - Non Defining Declaration - olabilir Neleri yapamam? ══════════════════════════ ▪ Bir yapı nesnesini hayata getiremezsin. struct Point myPoint; // Syntax Error, Incomplete type is NOT allowed! ▪ sizeof operatörünün operandı yapamam. size_t n = sizeof(struct Point); // Syntax Error, ▪ Incomplete type bir nesneye ilişkin pointer bildirebiliyordum . Ancak bu pointer ı . ve -> operatörlerinin operandı yapamam. struct Point* bar(void); // okay struct Point * ptr = bar(); // okay ptr. ptr-> nin her türlü kullanımı ve dereferencing syntax error ! ▪ Incomplete type bir nesneye ilişkin pointer bildirebiliyordum . Ancak bu pointerla , pointer aritmetiğinden faydalanamam. struct Point* bar(void); // okay struct Point * ptr = bar(); // okay struct Point *p = ptr+5; // Syntax error, Türün boyutunu bilmeden nasıl 5 sonraki yeri göstertebilir ki? ■ Incomplete Types niye önemli - benimle ne işi var? ════════════════════════════════════════════════════════════════ Bir başlık dosyasının gereksiz şekilde başlık dosyalarının include etmemesi gerektiğini öğrenmiştik. Az önce yukarıdaki öğrendiklerimizi değerlendirdiğimizde, başlık dosyalarında, - eğer pointerla işimizi yapma imkanı varsa - incomplete yapı nesne pointerları bildirerek, yapı nesnelerini bildiren başka başlık dosyalarını include etmekten imtina edebiliyoruz. █ Örnek: Data.h dosyasında Data isimli bir yapı nesne bildirimi olsun. Data.h struct Data { //.... } Bu yapıyı kullanma ihtiyacım olsun. murat.c dosyasında struct Data* ptr; // okay, // Bu sayede #include "Data.h" yapmaya ihtiyacım kalmadı. ■ Yapı türleri ve Fonksiyonlar ════════════════════════════════════════════════════════════════ Fonksiyonlara yapı nesnesi iletmek yada fonksiyon geri dönüşünden yapı nesnesi almak, - Call By Value ile yapıldığında - yapı nesnesinin büyüklüğüne göre oldukça maliyetli hale gelecektir. typedef struct { int id; char name[40]; char address[255]; double salary; } Employee; voif f1(Employee); // Call by value, okay ancak maliyetli void f2(const Employee*); // Call by reference - in param void f3(Employee*); // Call by reference - out or in-out param - see function's documentation Employee f4(void); // Fonksiyonun geri dönüş değeri bellekte geçici bir yere kopyalanıyor, // sonrasında kullanımda da bizim belirttiğimiz nesneye kopyalama olmak üzere 2 kez kopyalama maliyeti ile karşılaşırız. Employee* f5(void); // Nice const Employee* f6(void); // salt-okunur amaçlı kullanım için geri döndürüyor ■ Yapı türleriyle ilgili C-tarzı kütüphanelerin tipik anatomisi ════════════════════════════════════════════════════════════════ │ Sözün özü; struct Person ──└ Beni kullanacaksan, hem beni hem elemanlarımı iyi bileceksin, onlara çalışacaksın ! diyorlar. { Bilgi, vakit, dikkat gerektiriyor. int id; Tüm elemanları kullanımda. char name[40]; Elemanlarda isim değişiklik olursa, kullanan herkesde değiştirilmesi zorunluluğu char address[255]; Yeni eleman eklenmesi durumunda, kullananların tekrar derlenmesi gerekecek. double salary; Tüm bu meşakkatin sebebi verim. } ; Bu meşakkati bir nebze rahatlatmak için araya istediğimiz işleri yapacak fonksiyonlar yazabiliriz: Mesela createPerson(), getName() etc.. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 48.Ders C [ time.h kütüphanesi / Sınıf Çalışması ] 27 Eylül 2022 Salı Tarih zaman işlemleri ile ilgili destek veren modül Unix Epoche = 01 01 1970 00:00:00 ════════════════════════════════════════════════════════════════ epoche ▪ time_t : Tür eş ismi, calendar time, saniye cinsinden bir epoch tan geçen zaman , └──────────────────────────> time ▪ clock_t : Tür eş ismi, part of calendar time, ▪ struct tm : Broken-Down time struct tm { int tm_sec; // seconds, range 0 to 59 int tm_min; // minutes, range 0 to 59 int tm_hour; // hours, range 0 to 23 int tm_mday; // day of the month, range 1 to 31 int tm_mon; // month, range 0 to 11 int tm_year; // The number of years since 1900 int tm_wday; // day of the week, range 0[Pazar] to 6 int tm_yday; // day in the year, range 0 to 365 int tm_isdst; // daylight saving time, Negative: Bilgi tutulmuyor 0:Tasarruf modunda değil, Pozitif:Tasarruf modunda } ▪ CLOCKS_PER_SEC This macro represents the number of processor clocks(Clock ticks) per second. ▪ time_t time(time_t *timer) Calendar time döndürür. Bu döndürdüğü değeri aynı zamanda pointerin gösterdiği nesneye de yazar. Eğer arguman olarak NULL kullanırsan, bir nesneyi set etmez. ▪ clock_t clock( void ) Approximate processor time(Clock ticks) that is consumed since by the program main execution started, and on failure function returns -1. ▪ time_t mktime(struct tm *timeptr) converts the structure pointed to by timeptr into a time_t value according to the local time zone. ▪ struct tm *localtime(const time_t *timer) returns a pointer to a tm structure with the local time information filled in [statik ömürlü nesne adresi] ▪ struct tm *gmtime(const time_t *timer) returns a pointer to a tm structure with the UTC time information filled in [statik ömürlü nesne adresi] ▪ char *ctime(const time *timer) 26 karakterlik dizi, dikkat son iki karakteri \n\0 var. Başarısız ise NULL döndürür. ▪ char *asctime(const struct tm *timeptr) 26 karakterlik dizi, dikkat son iki karakteri \n\0 var. Başarısız ise NULL döndürür. returns a C string containing the date and time information in a human-readable format Www Mmm dd hh:mm:ss yyyy, where Www is the weekday, Mmm the month in letters, dd the day of the month, hh:mm:ss the time, yyyy the year. localtime ───────────────────────────────→ time_t struct tm* │ ←─────────────────────────────── │ │ mktime │ │ │ │ │ char *ctime(const time*) char *asctime(const struct tm*) █ Örnek: time_t sec; struct tm x ; time(&sec); x.tm_year = 1987-1900; struct tm *p = localtime(&sec); x.tm_mon = 5-1; x.tm_mday = 21; x.tm_hour = 0: x.tm_min = 0; x.tm_sec = 1; time_t mydate = mktime(&x); █ Örnek: time_t time(time_t *timer) fonksiyonunun kullanımına ilişkin örnek #include #include #include #include int main(void) int main(void) { { time_t sec; time(&sec); printf("saniye = %lld\n",sec); printf("saniye = %lld\n",time(NULL)); } } █ Örnek: time_t den broken down time a dönüşüm fonksiyonu : struct tm *localtime(const time_t *timer) Kullanım örneği [statik ömürlü nesne adresi] #include #include int main () { time_t sec; time( &sec ); struct tm *p = localtime(&sec); printf("%02d-%02d-%d %02d:%02d:%02d\n", p->tm_mday, p->tm_mon+1, p->tm_year+1900, p->tm_hour, p->tm_min, p->tm_sec); } █ Örnek: time_t den broken down time a kendi özelleştirmemizi yapalım, Ay ve Gün isimlerini yazdıralım Kullanım örneği #include #include int main () { static const char* const pmons[] = { "Ocak","Subat","Mart","Nisan","Mayis",Haziran","Temmuz","Agustos","Eylul","Ekim","Kasim","Aralik", } static const char* const pdays[] = { "Pazar","Pazartesi","Sali","Carsamba","Persembe","Cuma",Cumartesi", } time_t sec; time( &sec ); struct tm *p = localtime(&sec); printf("%02d-%s-%d %s %02d:%02d:%02d\n", p->tm_mday, pmons[p->tm_mon], p->tm_year+1900, pdays[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec); } █ Örnek: Özellikle log dosyalarina isim verirken işimize yarayacak şekilde bir fonk yazalım #include #include #include char* get_log_file_name(void) { static char buffer[BUFFER_SIZE]; time_t sec; time( &sec ); struct tm *p = localtime(&sec); sprintf(buffer, "%d_%02d_%02d_%02d_%02d_%02d.log", p->tm_year+1900, p->tm_mon+1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec); return buffer; } // 10 tane dosya oluşturalım, isimlerini yazdığımız fonksiyondan alalım int main () { for(int i=0; i<10; ++i) { FILE *f = fopen (get_log_file_name(void),"w"); printf("Dosya %d olusturuldu \n",i+1); fclose(f); Sleep(1000); } } █ Örnek: char *ctime(const time *timer) ve char *asctime(const struct tm *timeptr) kullanım örneği #include #include int main () { time_t sec; time( &sec ); char * p1 = ctime(&sec); printf("length = %zu\n",strlen(p1)); //outputs 25, note that sonunda new line var ve en sonda da tabiki null karakteri printf("(%s),p1); //25 karakter ve sonunda newline olduğunun kanıtı, bak bakalım ) nerede print ediliyor :) sec -= 15000; // 15000 sec öncesinin tarihine de bakalım struct tm *p = localtime(&sec); char *p2 = asctime(p); printf("length = %zu\n",strlen(p2)); printf("(%s),p2); } █ Örnek: time_t mktime(struct tm *timeptr) ile 2345 saat öncesinin tarihini bulma örneği #include #include int main () { time_t sec; time( &sec ); struct tm *p = localtime(&sec); p->tm_hour -= 2345; time_t pastDate = mktime(p); } █ Örnek: clock_t clock( void ) fonksiyonu #include #include int main () { clock_t clk_start = clock(); // code.. clock_t clk_end = clock(); double codeExecutionTimeInSec = (double) (clk_end-clk_start)/CLOCKS_PER_SEC; } ■ locale ════════ Yani lokalde farklı tercihler var. Sözgelimi; Türkçede ondalık ayracı virgüldür. Halbuki printf ile bir double yazdırdığında ondalık ayracı nokta. Türkçede binlik ayracı . Halbuki Amerikancada virgül. locale, belirli konudaki kuralların toplamından oluşan küme. Aksi belirtilmedikçe programın koştuğu locale C locali deniyor. İşletim sistemi ve derleyici destekliyorsa, çalışma lokalini değiştirebiliriz. başlık dosyasında └ Buradaki en önemli iki fonksiyon ▪ char *setlocale(int category, const char *locale) : lokali set eder │ └ LC_ALL for all of the below. "en_GB" "de_DE" "turkish" ... LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_MESSAGES char* p = setlocale(LC_ALL,"turkish"); if(p == NULL) printf("locale deisikligi yapilamadi") else printf("now locale is : %s\n", p); locale-dependent fonksiyonlar : Çalışma lokalinden etkilenen fonksiyonlar, i.e strftime,strcoll, printf, scanf ... locale-independent fonksiyonlar : Çalışma lokalini değiştirsek de bundan etkilenmeyen fonksiyonlar, i.e strcmp .. ■ size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr) formats the time represented in the structure timeptr according to the formatting rules defined in format and stored into str. Parameters str − This is the pointer to the destination array where the resulting C string is copied. maxsize − This is the maximum number of characters to be copied to str. format − This is the C string containing any combination of regular characters and special format specifiers. timeptr − This is the pointer to a tm structure that contains a calendar time broken down into its components These format specifiers are replaced by the function to the corresponding values to represent the time specified in tm. The format specifiers are − Specifier Replaced By Example ───────── ────────────────────────────────────────────────────────────────── ──────────────────────── %a Abbreviated weekday name Sun %A Full weekday name Sunday %b Abbreviated month name Mar %B Full month name March %c Date and time representation Sun Aug 19 02:56:02 2012 %d Day of the month (01-31) 19 %H Hour in 24h format (00-23) 14 %I Hour in 12h format (01-12) 05 %j Day of the year (001-366) 231 %m Month as a decimal number (01-12) 08 %M Minute (00-59) 55 %p AM or PM designation PM %S Second (00-61) 02 %U Week number with the first Sunday as the first day of week one (00-53) 33 %w Weekday as a decimal number with Sunday as 0 (0-6) 4 %W Week number with the first Monday as the first day of week one (00-53) 34 %x Date representation 08/19/12 %X Time representation 02:50:06 %y Year, last two digits (00-99) 01 %Y Year 2012 %Z Timezone name or abbreviation CDT %% A % sign % ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── █ SINIF ÇALIŞMASI: Date isimli, nesne yönelimli kullanma tarzına sahip bir kütüphane yapacağız. Date diye bir türümüz olacak ve kullananlara bu türle kolay çalışma imkanı sunacağız. Şimdilik kontrol stratejisi yapmayacağız, simply programı sonlandıralım. date.h ───────────────────────────── #ifndef DATE_H #define DATE_H #include typedef struct { //Karşılıklı iyiniyet çerçevesinde client kodların bunlara erişmemesini rica ediyoruz. int md,mm,my; Ancak istersem gizleyebilirim de. ileride ona da bakacağız. } Date; //Setter, Mutator Functions Date* set_date(Date* p, int day, int mon, int year); Date* set_today(Date* p); Date* set_date_from_str(Date* p, const char* pstr); dd-mm-yyyy formatlı olsun Date* set_date_time_t(Date* p, time_t sec); Date* set_date_random(Date* p) Date* set_year(Date* p, int year); Date* set_month(Date* p, int month); Date* set_month_day(Date* p, int mday); //Getter,accessor Functions int get_year(const Date* p); int get_month(const Date* p); int get_month_day(const Date* p); int get_week_day(const Date* p); //0:Pazar 1:Pzt ... olsun int get_year_day(const Date* p); //Input-Output Functions void print_date(const Date* p) Date* scan_date(Date* p); //utility Functions int date_diff(const Date* p1, const Date* p2); // gün cinsinden int cmp_date(const Date* p1, const Date* p2); // Pozitif: 1. tarih büyük 0: Eşit Negatif : 2. tarih büyük Date* ndays_date(const Date* p, Date* pdest, int n); #endif date.c ───────────────────────────── #include #include #define PRIVATE static #define PUBLIC #define YEARBASE 1900 #define RANDOM_MIN_YEAR 1940 #define RANDOM_MAX_YEAR 2010 #define isleap(y) ( (y) % 4 == 0 && ( (y)%100 != 0 || (y)%400 == 0 ) ) #define mdays(m,y) daytabs[isleap(y)][m] #define rand_interval(low,high) ( rand()% ((high)-(low)+1) + (low) ) PRIVATE const int daytabs[][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // Normal yıllar için ayların gün sayıları - ilk eleman kullanılmayacak, yer tutucu {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // Artık yıllar için ayların gün sayıları - ilk eleman kullanılmayacak, yer tutucu }; // Private fonksiyonlarımızın bildirimleri PRIVATE int is_valid_date(int day, int mon, int year); PRIVATE Date* set(Date* p, int day, int mon, int year); PRIVATE int day_of_week(int d, int m, int y); PRIVATE int totaldays(const Date* p); PRIVATE Date* totaldays_ToDate(Date* ,int tdays) // *********************************************************************************** // // Public Functions of Library // // *********************************************************************************** PUBLIC Date* set_date(Date* p, int day, int mon, int year) { return set(p, day, mon, year); } PUBLIC Date* set_today(Date* p) { time_t sec; time(&sec); return set_date_time_t(p, sec) } PUBLIC Date* set_date_from_str(Date* p, const char* pstr) { int day = atoi(pstr) int mon = atoi(pstr+3) int year = atoi(pstr+6) return set(p,day,mon,year); } PUBLIC Date* set_date_time_t(Date* p, time_t sec) { struct tm* tptr = localtime(&sec); int day = tptr->tm_mday; int mon = tptr->tm_mon+1; int year = tptr->tm_year+1900; return set(p,day,mon,year); } PUBLIC Date* set_date_random(Date* p) { int year = rand_interval(RANDOM_MIN_YEAR,RANDOM_MAX_YEAR); int mon = rand_interval(1,12); int day = rand() % mdays(mon,year) + 1; return set(p,day,mon,year); } PUBLIC Date* set_year(Date* p, int year) { return set(p,get_month_day(p), get_month(p), year); } PUBLIC Date* set_month(Date* p, int month); { return set(p,get_month_day(p), month, get_year(p)); } PUBLIC Date* set_month_day(Date* p, int mday) { return set(p, mday, get_month(p), get_year(p)); } PUBLIC int get_year(const Date* p) { return p->my; } PUBLIC int get_month(const Date* p) { return p->mm; } PUBLIC int get_month_day(const Date* p) { return p->md; } PUBLIC int get_week_day(const Date* p) Tomohiko Sakamuto algoritmasını kullanalım gitsin. { const int day = get_month_day(p); const int mon = get_month(p); const int year = get_year(p); return day_of_week(day, mon, year); } PUBLIC int get_year_day(const Date* p) { int sum = get_month_day(p); const int mon = get_month(p); const int year = get_year(p); for(int i=1, i=YEARBASE && mon>=0 && mon<=12 && day>0 && day>=mdays(m,year) } PRIVATE Date* set(Date* p, int day, int mon, int year) { if(!is_valid_date(day,mon,year)) { fprintf(stderr,"gecersiz tarih \n"); exit(EXIT_FAILURE); } p->md = day; p->mm = month; p->my = year; return p; } PRIVATE int day_of_week(int d, int m, int y) { // array with leading number of days values static const int t[] = {0,3,2,5,0,3,5,1,4,6,2,4}; //if month is less than 3 reduce year by 1 if(m<3) y-=1; return ( (y+y/4-y/100+y/400+t[m-1]+d)%7); } PRIVATE int totaldays(const Date* p) { //Verilen tarihin 1.1.1900 den kaç gün sonra olduğunu hesaplar int sum = get_year_day(p); const int year = get_year(p); for(int i=YEARBASE; i ( isleap(y)?366:365) ) { totaldays -= ( isleap(y)?366:365 ); ++year; } int mon = 1; while( totaldays > ( mdays(mon,year) ) { totaldays -= ( mdays(mon,year) ); ++mon; } int day = totaldays; return set(p,day,mon,year); } // *********************************************************************************** main.c ───────────────────────────── #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include void set_date_array_random(Date* p, size_t size) { while(size--) set_date_random(p++); } void print_date_array(const Date* p, size_t size) { while(size--) print_date(p++); } int dcmp(const void * vp1, const void * vp2) { return cmp_date( (const Date* ) vp1, (const Date* ) vp2 ); } void sort_date_array(Date* p, size_t n) { qsort(p,n,sizeof(*p),&dcmp); } int main(void) { //Test Codes/Scenarios test1(); test2(); test3(); test4(); test5(); } void test1(void) { Date d1; set_date(&d1,3,5,1992); print_date(&d1); Date d2; set_today(&d2); print_date(&d2); Date d3; set_date_from_str(&d3,"24-02-2012"); print_date(&d3); set_year(&d1,2017); set_month(&d2,1); set_month_day(&d3,25); print_date(d1); print_date(d2); print_date(d3); } void test2(void) { randomize(); for(int i=0; i<10; ++i) { Date date; set_array_random(&date); print_date(&date); printf("Yil :%d \n",get_year(&date) ); printf("Ay :%d \n",get_month(&date) ); printf("Ayin Gunu :%d \n",get_month_day(&date) ); printf("Haftanin Gunu :%d \n",get_week_day(&date) ); (void) getchar(); } } void test3(void) { Date birthdate; printf(Dogum Tarihinizi Girin:"); scan_date(&birthdate); print_date(&birthdate); Date today; set_today(today); print_date(&today); const int n = date_diff(&today,&birthdate); printf("Su fani hayatinizin %d gunu:\n",n); } void test4(void) { size_t nArrSize; printf("Kac tarihlik bir dizi yapalim:"); scanf("%zu",&nArrSize); Date* pd = (Date*) malloc(n*sizeof(Date)); if(pd==NULL) { fprintf(stderr,"bellek yetersiz\n"); return 1; } set_date_array_random(pd,nArrSize); printf("Siralama Basladi..."); clock_t start = clock(); sort_date_array(pd,nArrSize); clock_t end = clock(); printf("Siralama Bitti, Suresi %f saniye\n",(double)(end-start)/CLOCKS_PER_SEC); _getch(); print_date_array(pd,nArrSize); } void test5(void) { Date today; set_today(today); printf("Bugun :") print_date(&today); Date futureDate; for(int i=1; i<10000; i*=10) { printf("%d gun sonraki tarih:\n",i); ndays_date(&today,&futureDate,i); print_date(&futureDate); } } ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── end of Sınıf Çalışması ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 49.Ders C [ User Defined Types - cont'd - LinkedList ve Handle kavramları] 29 Eylül Perşembe Bir önceki dersteki sınıf çalışmasına devam edip, .c dosyasının fonksiyonlarını tamamlayıp, main.c de test senaryoları ile sınama yaptık. ■ Bir yapının elemanının , bir başka yapı türünden olması durumu: typedef struct typedef struct { { int md,mm,my; int id; } Date; char name[40]; Date m_bdate; -> hatırlayalım, complete type olmalı Date* mpdate; -> hatırlayalım, incomplete type olabilir } Person; int main(void) { Person Ali = { 1, "Ali", { 14,2,2000} }; //ilk değer verme sentaksı Person* p = &Ali; p->m_bdate.mm = 12; // okay p->mpdate->mm = 12; // okay Person * ptrArr[10] ; //.. p[5]->m_bdate.mm = 12; // okay } ■ Bir yapının elemanı kendi türünden olabilir mi? Yanıt: Hayır struct Nec { int x; struct Nec nc; //syntax error, kendi türünden bir eleman barındıramaz. Bu noktada Nec incomplete type durumunda } ■ Bir yapının elemanı kendi türünden bir pointer olabilir mi? Yanıt: Evet Hemde veri yapıları konusunda çok çok faydalı, i.e Linked List, Biranry tress, graph gibi veri yapıları implementasyonunda çok sık karşımıza çıkacak. Bu tür nesnelere Node dendiğini göreceğiz. struct Node { int m_data; struct Node* pNext; // Böyle yapılara self-referential yapılar diyoruz } ■ Bir yapı içinde bir başka yapı tanımlamak mümkün mü? Yanıt: Evet struct Ertan { int mx,my; struct Volkan { double d1,d2; } v1, v2; // struct Ertan'ın v1 ve v2 isimki, struct Volkan türünde elemanları var } struct Encloser { int x,y; struct Nested { int a,b,c; } n1,n2; } ■ struct Nested türünü, struct Encloser türünün visible olduğu yerlerde kullanablir miyim? C dilinde evet. Çünkü Derleyici bunu struct Nested { int a,b,c; } n1,n2; struct Encloser { int x,y; struct Nested n1,n2; } yazılmışcasına ele alıyor. Dolayısıyla struct Nested türünden nesneler tanımlamaya da izin veriyor. C++ dilinde ise durum farklı.struct Nested türünden nesneler tanımlamaya izin vermiyor. ■ İlginç bir başka özellikten bahsedelim. struct Encloser { int x,y; struct // structureTag vermedim. Bunlara Anonymous structure deniyor. Dolayısyla başkaları bu türden bir nesne oluşturamaz. { int a,b,c; } n1; // Değişkene isim verdim. } int main(void) { struct Encloser enc; //.. enc.n1.a = 5; //okay } ■ İlginç bir başka özellikten daha bahsedelim. 2011 standarları ile dilin kurallarına eklendi. struct Encloser { int x,y; struct // structureTag vermedim. Bunlara Anonymous structure deniyor. { int a,b,c; } ; // Değişkene de isim vermiyorum. [Aslında isimsiz bir değişken gibi oluyor.] } int main(void) { struct Encloser enc; //.. enc.a = 5; //okay } Peki niye böyle bir şey yapalım? structure lar ve union ların birlikte kullanılması için kurgulanmış bir senaryo. ileride yine kullanacağımız bir başka kütüphane yaparak ve örneklendirerek ilerleyelim: employee.h ───────────────────────────── #pragma once #include "date.h" typedef struct { int m_id; char m_name[20]; char m_surname[20]; char m_town[20]; Date m_bdate; } Employee; Employee* set_employee_random(Employee*); void print_Employee(const Employee*); int cmp_employee(const Employee*, const Employee*) employee.c ───────────────────────────── #include "employee.h" #include "nutility.h" #include "string.h" #include "stdlib.h" Employee* set_employee_random(Employee* p) { p->m_id = rand(); strcpy(*->m_name, get_random_name() ); strcpy(*->m_surname, get_random_surname() ); strcpy(*->m_town, get_random_town() ); set_date_random(&p->m_bdate); return p; } void print_Employee(const Employee*) { printf("%-8d %-16s &-24s &-16s", p->m_id, p->m_name, p->m_surname, p->m_town); print_date(&p->m_bdate); } //ilk olarak isimler, sonra soyisimler, sonra şehirler, sonra doğum tarihler, en son id lere baksın int cmp_employee(const Employee* p1, const Employee* p2) { int cmp_result = strcmp(p1->m_name, p2->m_name); if(cmp_result) return cmp_result; cmp_result = strcmp(p1->m_surname, p2->m_surname); if(cmp_result) return cmp_result; cmp_result = strcmp(p1->m_town, p2->m_town); if(cmp_result) return cmp_result; cmp_result = cmp_date(&p1->m_bdate, &p2->m_bdate); if(cmp_result) return cmp_result; return p1->m_id - p2->m_id } main.c ───────────────────────────── Yazdıklarımızı deneme amacıyla şöyle bir test kodu yazalım #include "employee.h" #include "nutility.h" #include "conio.h" #include "stdlib.h" #include "stdio.h" int ecmp(const void* vp1, const void* vp2) { return cmp_employee( (const Employee*)vp1, (const Employee*)vp2 ); } int main(void) { size_t n; printf("kac calisan:"); scanf("%zu", &n); Employee* pd = (Employee*) malloc(n* sizeof(Employee*) ); if(!pd) { fprintf(stderr,"bellek yetersiz\n"); return 1; } for(size_t i =0; i #include void test1(void) { Employee e; for(int i=0; i<10;++i) { set_employee_random(&e); print_employee(&e); push(&e); } printf("\n Toplam %d calisan listeye eklendi\n", get_list_size()); print_employee_list(); clear_list(); } void test2(void) { Employee e; int n; printf("Kac calisan eklenecek: "); scanf(%d",&n); for(int i=0; idata; } void push(const Employee* p) // Listeye baştan ekleme yapacak { Node* p_new_node = create_node(); p_new_node ->pNext = gp_first; p_new_node->data = *p; gp_first = p_new_node; ++gsize; } void pop(void) // Listedeki ilk Employee yi silecek { if(is_empty()) { fprintf(stderr,"bos liste hatasi\n"); exit(EXIT_FAILURE); } Node* pTemp = gp_first; gp_first = gp_first->pNext; free(pTemp); --gsize; } void clear_list(void) // Listedeki tüm ögeleri silecek { while(!is_empty()) { pop(); } } void print_employee_list(void) { for( Node* p = gp_first; p!= NULL; p = p->pNext) { print_Employee( &p->data) } } int is_empty(void) { return gp_first==NULL; } int get_list_size(void) { return gsize; } **** Bu yaptığımız örnekteki kusur şu: Tüm kodlar aynı listeyi kullanıyor! Yani, birbirinden bağımsız, iki tane ayrı Employee listesi yapılamıyor. Yani tek instance üzerinden çalışıyor. Şimdi , yukarıda yaptığımız örneği , handle sistemi çerçevesinde tekrar düzenleyelim. Kütüphanemizi kullanacak olandan 2 ricamız var: create ile bir tane edin. işin bitince destroy ile kullanılan kaynakları iade et. employeeList.h ───────────────────────────── #pragma once struct List; typedef struct List* ListHandle; ListHandle create_list(void); destroy_list(ListHandle h); void top(ListHandle h, Employee*); // Listedeki ilk ögeye eriştirecek void push(ListHandle h, const Employee*); // Listeye baştan ekleme yapacak void pop(ListHandle h); // Listedeki ilk Employee yi silecek void clear_list(ListHandle h); // Listedeki tüm ögeleri silecek void print_employee_list(ListHandle h); int is_empty(ListHandle h); int get_list_size(ListHandle h); employeeList.c ───────────────────────────── #pragma once #include "employee.h" #include "employeeList.h" typedef struct Node { Employee data; struct Node* pNext; } Node; struct List { Node* gp_first; int gsize; }; ListHandle create_list(void) { ListHandle h = (ListHandle) malloc(sizeof(*h)); if(!h) { fprintf(stderr,"bellek yetersiz\n"); exit(EXIT_FAILURE); } h->gp_first = NULL; h->gsize = 0; return h; } destroy_list(ListHandle h) { clear_list(h); free(h); } static Node* create_node(void); { Node* pd = (Node*) malloc(sizeof(Node)); if(!pd) { fprintf(stderr,"bellek yetersiz\n"); exit(EXIT_FAILURE); } return pd; } void top(ListHandle h, Employee* p) // Listedeki ilk ögeye eriştirecek { if(is_empty(h)) { fprintf(stderr,"bos liste hatasi\n"); exit(EXIT_FAILURE); } *p = h->gp_first->data; } void push(const Employee* p) // Listeye baştan ekleme yapacak { Node* p_new_node = create_node(); p_new_node->pNext = h->gp_first; p_new_node->data = *p; h->gp_first = p_new_node; ++h->gsize; } void pop(ListHandle h) // Listedeki ilk Employee yi silecek { if(is_empty(h)) { fprintf(stderr,"bos liste hatasi\n"); exit(EXIT_FAILURE); } Node* pTemp = h->gp_first; h->gp_first = h->gp_first->pNext; free(pTemp); --h->gsize; } void clear_list(ListHandle h) // Listedeki tüm ögeleri silecek { while(!is_empty(h)) { pop(h); } } void print_employee_list(ListHandle h) { for( Node* p = h->gp_first; p!= NULL; p = p->pNext) { print_Employee( &p->data) } } int is_empty(ListHandle h) { return h->gp_first==NULL; } int get_list_size(ListHandle h) { return h->gsize; } main.c de kullanımını gösterelim: ───────────────────────────── #pragma once #include "nutility.h" #include "employeeList.h" #include "employee.h" #include #include int main(void) { ListHandle h = create_list(); for(int i=0; i<10;++i) { Employee e; set_employee_random(&e); print_employee(&e); push(h,&e); } printf("\n Toplam %d calisan listeye eklendi\n", get_list_size(h)); print_employee_list(h); destroy_list(h); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 51.Ders C [ User Defined Types - cont'd - unions] 6 Ekim 2022 Perşembe Şöyle bir tekrar yapalım. Mantıksal ilişki içinde olan func1 ... funcN olsun. Bunlar d1 ... dN datalarına get/set ile kullanıyor olsunlar. Bu veri seti bir tane olsaydı, bunları global yapar, client lara kapatır, işimi görürdüm. Ancak, bu veri setlerinden de çok sayıda olduğun durumu düşündüğümüzde, örn birden fazla pencere/dosya/dinamik dizi gibi,bunlarla çalışan func1 ... funcN fonksiyonlarına, hangi instance ile işlem yapacaksak o instance'ı (yani handle'ı) geçmek gerekecek. Bunu nasıl gerçekleştirmiştik: ▪ Verileri bir yapıda tümleştir. struct xyz { d1, dN; } ▪ Typedef bildirimi ile bu yapıya ilişkin bir pointer ile erişim imkanı sun. typedef struct xyz* handle; ▪ İsmi sözgelimi create olan bir fonksiyon sağla, bu sayede bir nesne oluştursun ve pointerini döndürsün. handle create(void); ▪ Tüm fonksiyonların birer parametresi de bu yapıya ilişkin bir ptr olsun. func1(handle h, varsa başka pars) .. funcN(handle h, varsa başka pars) ▪ Son olarak, sözgelimi ismi destroy olan bir fonksiyon sağla, iş bitince oluşturulan instance'a ilişkin bellek tahsisatını iade etsin. destroy(handle h) █ Örnek: Şimdi, geçen dersteki yaptığımız kütüphaneyi kullanarak, 1000 tane handle olan bir örnek yapalım: #include "employee_list.h" #include "nutility.h" #include "employee.h" #include #define NO_OF_LISTS 1000 int main(void) { ListHandle a[NO_OF_LISTS]; randomize(); for(int i=0; ibar(12); //ok } ■ Data Alignment ══════════════════ İşlemcinin bellekteki veriye erişirken ki maliyetinin en iyilenmesi için bellekte veriler hizalıdır. 32 bit CPU demek, belleğe 32 bitlik bloklar şeklinde erişiyor demek. Bu blokları Çekmece gibi düşünebiliriz. Bir eşyamızı almak için çekmeceyi açıyoruz, içinde bir çok eşya olabiliyor. İşte , eşyamızın bir kısmı bir çekmecede, bir kısmı bir sonraki çekmecede gibi durum varsa, eşyamıza ulaşmak yüksek maliyetli - yani iki çekmeceye açmak - olacak. İşte, derleyiciler de kod üretirken, komutların en iyi şekilde belleğe erişimini sağlamak için, değişkenleri belirli adreslere ve katlarına yerleştiriyor. Böylelikle işlemci bunlara daha düşük maliyetle erişiyor. İşte buna Data Alignment deniyor. ■ Alignment Requirement (Hizalama Gereksinimi) - bir türe ilişkindir. Mesela bir türün alignment requirement i 4 demek, o türün parçalı yerleşimden imtina edilerek , en az maliyetli erişimi sağlayacak şekilde , 4 ün katı olan adreslere yerleştirilmesi anlamına gelir. Böylelikle bu türden bir dizi, ki contigious olmalı- ek maliyet olmadan elemanları erişilebilirdir. Yani her veri türü için bir Alignment Requirement var. ■ _Alignof : Bir türün Alignment Requirement ini nasıl öğrenebilirim? Eskiden extension olarak desteklenen , 2011 standardı ile dile eklenen, size_t _Alignof(tür) ile öğrenebiliriz. Not: başlık dosyasında alignof olarak std hale geldi. int main(void) { printf("%zu\n",_Alignof(int)); } ■ Padding Bytes Dolayısıyla, yapı türleri için de Alignment Requirement sözkonusu. Tipik olarak en fazla yer kaplayan elemanı, birim kap ebadını belirler. Yapıların elemanlarına da erişiyorduk ya, işte cpu nun bu yapı elemanlarına erişimini de kolaylamak için alignment tatbik ediliyor. Tabi bu durumda , bazı elemanlar, bulunduğu kabı dolduramıyor. Dolayısyla kabın içinde, doldurma bitler oluyor. Bunlara Padding Bytes deniyor. ■ Yapı nesnesinin adresi ile ilk elemanının adresi birbirine eşittir. Bu garanti altındadır. ■ Yapı nesnesinin başında - ilk elemanından önce- padding olmaz. Bu garanti altındadır. typedef struct { char c1; int ival; char c2; }Data; Normalde, benim kullandığım derleyici için, Data türünün sizeof değerinin 1+4+1 = 6 byte olmasını beklerim. Ancak hizalama gereksinimleri gözönüne alınınca , derleyici bunun yerleşimini öyle yapımışki, 12 byte oluyor. ──────── c1 -------- padding -------- padding -------- padding ──────── ──────── ival byte -------- ival byte -------- ival byte -------- ival byte ──────── ──────── c2 -------- padding -------- padding -------- padding ──────── Hem ziyan olmasın, hem erişimde ek maliyet çıkmasın istiyorsak, yapı elemanlarının bildirim sırasından biz sorumluyuz. Yukarıdaki örnekte bildirimi şu şekilde değiştirmek, çok daha iyi bir fikir. ■ Yapı nesnelerinin elemanlarını, tipik olarak, büyükten küçüğe doğru sıralayarak bildir. En iyi yerleşime giden tavsiyelerden birisi budur. typedef struct { int ival; char c1; char c2; }Data; Bu bildirimle birlikte Data türünün sizeof değeri 8 olacaktır. ──────── ival byte -------- ival byte -------- ival byte -------- ival byte ──────── ──────── c1 -------- c2 -------- padding -------- padding ──────── ■ offsetof Makrosu - başlık dosyasında tanımlı Yapı nesnesindeki elemanlara, pointer arithmetic yoluyla erişmek istersen, padding(boşlıklar yüzünden) asla kafadan hesap yapıp da erişmeye çalışma. offsetof makrosunu kullan. █ Örnek: offsetof makrosu kullanmadan , kafadan sizeof yapanların sonu ! typedef struct { char c1; int ival; char c2; }Data; int main(void) { Data mydata = {12,13,14}; char *p = &mydata.c1; int* ip = (int*) (p+1); printf("%d\n",*ip); └ Burada 1 yerine offsetof(Data,ival) şeklinde kullanmak gerekirdi! } Outputs: not correct result offsetof makrosunun açılımı çok şıktır. Bir gözatalım: [Mülakatlarda da sıkça sorulur.] #define offsetof(s,m) ((size_t)&(((s*)0)->m)) Bunu şöyle türetiyoruz: (s*)0 // 0 sabitini s* a cast ediyoruz. Burada s bizim yapı nesnesi türümüz olduğundan, sanki 0 adresinde bir yapı nesnemiz varmış gibi oldu ((s*)0)->m // Şimdi bu 0 adresindeki yapı nesnesinin m isimli elemanına erişelim,UB olacak diye endişelenme, kuralı hatırla, Neydi o kural: Eğer tekrar adresini alırsan, derleyici bir derefenceing kodu üretmeyecek! &(((s*)0)->m) // eriştiğimiz m isimli elemanın adresini alalım, (size_t)&(((s*)0)->m) // m isimli elemanın adresini, size_t türüne cast edelim █ Örnek: offsetof makrosunu gördüğümüze göre, mydata'nın adresinden hareketle, dval in adresini hesaplayın typedef struct { char c1; char c2; short s; int x; double dval; char str[20]; }Data; int main(void) { Data mydata = {0}; char*p = (char*)&mydata; // Put your code here } Yanıt: double* ptr = (double*) (p + offsetof(Data,dval)); █ Ödev: Bir yapının elemanının adresinden, yapı nesnesinin adresini elde elde eden bir fonksiyonel makro oluşturun. ■ Yapı nesnelerini kıyaslamak isteyen ve de üşengeçlerin sonu ! █ Örnek: Correct way █ Örnek: InCorrect çünkü padding bytes have indeterminite value! typedef struct { char c1; int ival; char c2; }Data; int main(void) int main(void) { { Data mydata1 = {12,13,14}; Data mydata1 = {12,13,14}; Data mydata2 = {12,13,14}; Data mydata2 = {12,13,14}; if( mydata1.c1 == mydata2.c1 && if( ! memcmp(&mydata1,&mydata2, sizeof(Data)) ) mydata1.ival == mydata2.ival && printf("esit"); mydata.c2 == mydata2.c2 ) else printf("esit"); printf("esit degil"); else } printf("esit degil"); } | | v memcmp ile yapmak isteyen üşengeçler, indeterminite value ya sahip padding leri belirli hale getirirlerse , o zaman ok. Buna ilişkin yaklaşım: int main(void) { Data mydata1; Data mydata2; memset(&mydata1,0,sizeof(Data)); memset(&mydata2,0,sizeof(Data)); mydata1 = (Data){12,13,14}; //Compound Literal C99 ile geldi mydata2 = (Data){12,13,14}; if( ! memcmp(&mydata1,&mydata2, sizeof(Data)) ) printf("esit"); else printf("esit degil"); } ■ Structure Packing Derleyicinin, padding olmadan kod üretmesini sağlayabiliyoruz. Aslında bu bir derleyici extensionı VS için bu #pragma pack(1) Hatırlayalım, zaten önişlemci komutu pragma, derleyicilerin özelleştirmeleri için ayrılmıştır. Bu pragma ile yapı nesneleri arasına padding yapmıyor. ■ unions(birlikler) - Bir verinin farklı biçimlerde temsili - ═════════════════════════════════════════════════════════════ Herşey yapılarla aynı, union Unec typedef union { { int x; int x; }; }Unec; int main(void) int main(void) { { union Unec mynec; Unec mynec; mynec.x = 10; mynec.x=10; } } fakat bazı farklılıklar var ki zaten bunlar birliklerin varlık nedeni. Peki farklılıklar neler? unionlarda tüm elemanlar aynı yerden başlar, dolayıysla aynı alanı kullanırlar. Yani aynı alana, farklı türlerle erişip kullanmak için. Birliklerin sizeof değeri, içindeki elemanların sizeof u en büyük elemana eşittir. Bir union ın , tüm elemanlarının adresleri ile birlik nesnesinin kendi adresi eşittir, aynıdır. Bu durumda, bir elemanın değerini değiştirdiğimizde, diğer elemanların değeride değişecek. Evet, zaten birliklerin varlık nedeni de bu. Bir verinin farklı biçimlerde temsili. Bu Özellik, bitsel işlemlerde çok yararlı oluyor. unionların initialize edilmesinde, ilk elemana değer veriliyor, yada alternatif olarak designated initializer kullanabilirsin. typedef struct { uint16_t low_word; uint16_t high_word; }Data; typedef union { uint32_t uval; Data data; }utype; Yukarıda verdiğimiz tür eş ismi bildirimleri Okay. Ancak, birkaç ders öncesinde yapı nesneleri içinde isimsiz yapı nesnesi bildirimine değinmiştik ve faydasını unions konusunda göreceğimizi söylemiştik. İşte o an şu an. Dolayısıyla , yukarıdaki bildirimleri buna göre değiştirerek, typedef union { uint32_t uval; struct { uint16_t low_word; uint16_t high_word; }; }utype; haline getirdiğimizde, flattened bir şekilde union memberlerine erişim sağlamakla birlikte, tek ve sade bildirimle de yetinebiliyorum. Bakınız int main(void) { utype nec = {.uval=1486}; nec.low_word diyerek sade görünüme kavuştum. } Bir başka kullanım teması ise, aynı anda var olmayan ögeler için: Sözgelimi , kişi yapı nesnesi, kadınlar için kızlık soyismi, erkekler için askerlik rütbesi ve departman bilgisini içermesi mümkün olabilir. typedef struct { int32_t m_id, char name[20]; char surname[20]; union { struct { int rutbe; char departmant[16]; }; char maiden_name[20]; }; }Person; Bir başka kullanım teması ise, variant : farklı türlerden değer alabilen değişken: #define INT 0 #define DOUBLE 1 #define NAME 2 #define DATE 3 typedef struct { union { int ival; double dval; char name[20]; Date date; }; int type; }Data; int main(void) { Data mydata1 = {.type = INT, .ival =11}; Data mydata2 = {.type = DOUBLE, .dval=9.83}; Data mydata[10]; //Elemanları farklı türlerde değer tutabilen bir dizim oldu } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 52.Ders C [ User Defined Types - cont'd - enums, Bitwise Operations] 11 Ekim 2022 Salı ■ enumerations(numaralandırma) ══════════════════════════════ C++ da daha fazla desteğe sahip. Öyle bir değişken olsun ki, önceden belirlenmiş bir veri setindeki değerlerden birini alabilsin. Biz koda baktığımızda da o değeri görebilelim. Bunun en kolay yolu tamsayılarla eşlemek. Sözgelimi haftanın günleri. Makrolarla da benzer bir işi yapabiliriz, ancak makrolarda tür desteği yok. İsim yoksa scope yok. Yazım yardımı yok. Enumaratorler, sabit gereken yerlerde kullanılabilir. Örn, dizi boyutu, case sabiti Derleyici bunlar için int - underlying type - kullanıyor. sizeof(enum ..) ve sizeof(int) in eşit olması garanti altında. Aksi belirtilmediğinde enumeratorler 0 dan başlar ve sırayla birer artırılırlar. Bir user-defined tür oluşturuyoruz ancak bu aslında bir int. ▪ Defining enum enum Suit { Club, // Enumeration Constant - Numaralandırma Sabiti, Enumerator denir değer verilmediği için 0 Diamond, // Enumeration Constant - Numaralandırma Sabiti, Enumerator denir 1 Heart, // Enumeration Constant - Numaralandırma Sabiti, Enumerator denir 2 Spade // Enumeration Constant - Numaralandırma Sabiti, Enumerator denir 3 }; ▪ Declare a variable int main(void) { enum Suit s; //Declare a variable s of type enum Suit } ▪ typedef with enums typedef kullanma imkanımız da var, typedef enum Suit | typedef enum { | { Club, | Club, Diamond, | Diamond, Heart, | Heart, Spade | Spade }Suit ; | }Suit ; ▪ enum members of structs can be incomplete Hangi tür kullanıldığı garanti altında olduğundan, numaralandırma türleri incomplete type olarak kullanılabilirler. Sözgelimi Nec.h başlık dosyasında Nec isimli bir yapı bildirelim. Ve bu yapının içinde enum Color türü olsun. struct Nec { // enum Color mColor; // although enum Color is incomplete in this point, it is okay because compiler knows type and storage requirement - underlying type is int! }; ▪ Enumarators with specific Values enum Color { White = 345, Blue, // Blue 346 Red, // Red 347 Purple = -6, Magenta // Magenta -5, inanmazsan printf("%d\n",Magenta) diye yazdır gör } ▪ Kağıdın cinsinin oyuna göre önemi değişebilir, bunu aşağıdaki gibi halledebilirsin #ifdef POKER typedef enum {Club, Diamond, Spade, Heart } Suit; #else typedef enum {Club, Diamond, Heart, Spade} Suit; #endif ▪ Dönüşümler , aynı tamsayı türünde olduğu gibi. Maalesef diğer sayı tipleri ile örtülü dönüşüm de oluyor. Sözgelimi enum Color c = 34683; //okay, Derleyici şikayet etmedi, halbuki 34683 değeri, veri setinde yok. Ama int gibi atadı geçti. c = Spade; //okay, Derleyici şikayet etmedi, Spade farklı bir enum türünde tanımlı olduğu halde, netice de int ataması gibi atadı geçti. Bundan başka, asıl can sıkıcı olan enum ile double vs.. arasında da örtülü - implicit - dönüşüm var. Buna dikkat etmek gerek! ▪ Maalesef, Numaralandırma sabitlerinin ayrı bir kapsamı yok. enum türünün kapsamı ne ise, numaralandırma sabitlerinin kapsamıda odur. Sözgelimi, global isim alanında bildirimi yapılan enum Color türü enum Color {White,Blue} bildirimi yapıldığı noktadan itibaren filescope ta heryerde kullanabilirim. Blok içinde yapılsaydı, o blok içinde kullanabilirdim. Aynen tekdüze tahmin edeceğin üzere. Dolayısıyla bir başlık dosyasında kullandığında, bu başlık dosyasından gelip yapışan enum u düşünelim. enum Color {White,Blue} ; Sonrasında bir başka başlık dosyasından enum TrafficLights {Red,Blue,Green} ; Burada Blue her iki başlık dosyasından gelen enumda mevcut. Çakıştılar, pişti oldular.İşte bu ciddi bir problem. Aynı scopeda, bir varlığa iki isim verilemez kuralını hatırlayalım. Yani enum ları başlık dosyasına koyduğunda, davet çıkarıyorsun. Ama çoğu kez de bu şekilde kullanmak durumundasın. Yapacak tek şey içindeki enumeratorlere, çakışmaları önleyecek şekilde isimler takmaya çalışmaktan başka şansın yok. Sözgelimi enum Color {ColWhite,ColBlue} ; enum TrafficLights {TrLight_Red,TrLight_Blue,TrLight_Green} ; şeklinde... ▪ Scopelamak amacıyla, Maalesef enumları yapı nesnesi içinde bildirme imkanı yok. struct Screen { // enum Color {ColWhite,ColBlue} ; // Syntax error, Not possible!! } Ancak yapı nesnesinin bu türden bir değişkene sahip olursa o zaman okay: struct Screen { // enum Color {ColWhite,ColBlue} CarColor ; // okay possible, because structure has a member so called CarColor } ▪ Dolayısıyla şöyle bir kullanım temasından bahsedebiliriz enum Suit { Club, Diamond, Heart, Spade}; enum Face { Two, Three, Four, Jack, Queen, King, Ace}; typedef struct { enum Suit suit; enum Face face; } Card; int main(void) { Card mycard = {.suit = Spade, .face = King}; } ▪ enum ların makrolara alternatif , scope lu kullanım teması Makrolarla yapsaydık void func(void) { #define SIZE 100 // #undef SIZE } Enum ile de yapabiliriz. void func(void) { enum {SIZE=100}; //okay, Tag olmak zorunda değil, SIZE sadece bu blok içinde bilinir. // } ■ Bitwise Operations ════════════════════════ ┌────┬─────────────────────────────────────── │ No │ Operator / Description ├────┼─────────────────────────────────────── ├────┼─────────────────────────────────────── │◄2 │ ~ Bitwise NOT ├────┼─────────────────────────────────────── ├────┼─────────────────────────────────────── │ 5 │ >> << Bitwise Shift ├────┼─────────────────────────────────────── ├────┼─────────────────────────────────────── │ 8 │ & Bitwise AND ├────┼─────────────────────────────────────── │ 9 │ ^ Bitwise XOR ├────┼─────────────────────────────────────── │ 10 │ | Bitwise OR ├────┼─────────────────────────────────────── ├────┼─────────────────────────────────────── │◄14 │ >>= <<= &= ^= |= Compound Assignment ├────┼─────────────────────────────────────── Bitwise işlemlerde kullandığımız bu operatörlerin ortak özelliği Operandlarının tamsayı türlerden olması mecburiyeti var Tamsayı türlerden de , işaretsiz olanlar üzerinde kullanım daha yaygın. BU operatorlerin hiçbirinde short circuit filan yok. Aman ha. █ Örnek: Bir tamsayının bitlerini yazdıran bir fonksiyon yazalım. Aslında bunun için bitwise işlemler kullanmamız lazım. Ancak henüz görmediğimizden, etrafından dolaşarak yapacağız. void bitPrint(int x) { char str[40]; _itoa(x,str,2); printf(%032s\n",str); } ■ ~ Bitwise NOT -No Side Effect (Operanda etkimez, sadece operatörün ürettiği değer var) ════════════════════════════════ Integral Promotion bu operatör içinde geçerli tabiki. █ Örnek: Şaşırtıcılar var! int main(void) { int x; int result; printf("Bir tamsayi girin: "); scanf("%d",&x); // 45 girilmiş olsun result = !~x; printf("%d \n",result); // Outputs 0 result = -~x; printf("%d \n",result); // Outputs 46 result = ~-x; printf("%d \n",result); // Outputs 44 } ■ >> << Bitwise Shift -No Side Effect (Operanda etkimez, sadece operatörün ürettiği değer var) ═════════════════════════════════════ Aritmetik Operatörlerinden daha düşük öncelik seviyesinde, ancak Karşılaştırma Operatörlerine daha yüksek öncelik seviyesinde oluşuna dikkat edelim. Yani, a >> b > 10 ifadesi şöyle gruplanır: (a >> b) > 10 -1 >> 1 == -1 ifadesi şöyle gruplanır: (-1 >> 1) == -1 Gerekiyorsa, Sol Operanda Integral Promotion Tatbik edilir. Sağ operanda - kaydırma miktarına - dokunulmaz. Kısıtlamalar : ═════════════════ Her iki kaydırma işleminde de , sağ operand yani kaydırma miktarı ▪ Negatif olması UB ! Yani -3 kaydırma diye bir şey yok. ▪ işlemin yapıldığı tamsayı türünün bit sayısına eşit yada büyük olması UB! Yani 32 bitlik tamsayıyı en fazla 31 kaydırabilirsin. char c olsun. c << 20 ifadesi UB midir? Yanıt: Hayır, Integral Promotion ile sol operand int 'e yukseltilir, 32 bit gibi olur. ■ Left Shift << ═════════════════ Sağ başa gelenler 0 bitleri gelir. Bu garanti altında. Öte yandan işaretli negatif tamsayılarda , tabiki işaret bitini kaybediyor olacağız. O yüzden, left shift , işaretli negatif sayılar için anlamlı bir iş değil. Ama dil tarafından konulmuş bir engelin de yok. █ Örnek: Eğlenceli bir kod, ekrana ne yazar? int main(void) Yanıt: 00000000000000000000000000000001 { 00000000000000000000000000000010 unsigned int x = 1; 00000000000000000000000000000100 while(x) 00000000000000000000000000001000 { bitPrint(x); ... x <<= 1; } } 10000000000000000000000000000000 █ Örnek: Meşhur mülakat sorusudur : Bir tamsayının 2'nin kuvveti olup olmadığını sınayan bir ifade yazın. Sayının sadece 1 bitinin 1 olduğu düşüneceğiz. Yani o biti sıfırlasak, sayı 0 olacak. İşte bu fikirden hareketle, Bir sonraki derste göreceğimiz "sayının sağdan ilk 1 bitini sıfırlama " idiomunu kullanarak, x & (x-1) ifadesinin neticesinin 0 olup olmadığuna bakacağız. Burada gözden kaçırmamamız gereken nokta , sayının 0 olup olmadığıdır. Sayı kendisi 0 ise , 2'nin katı değildir. O halde if( x && !(x & (x-1)) ) // Sayı 2'nin kuvveti else // Sayı 2'nin kuvveti değil Bunu fonksiyonel bir makro gibi de yazabiliriz: #define isPowerOfTwo(x) ( (x) && !((x) & ((x)-1)) ) ■ Right Shift >> ═════════════════ Sol başa gelenler, Sol Operand unsigned ise, soldan beslemenin 0 ile yapılması garanti altındadır. Sol Operand signed ve pozitif ise, soldan beslemenin 0 ile yapılması garanti altındadır. Sol Operand signed ve negatif ise, soldan beslemenin ne ile yapılacağı derleyiciye bağlı - Implementation Defined -. Derleyici tabiki bunu dökümante etmeli. █ Örnek: Mesela, çalışılan derleyicide, sağa kaydırma işinde, soldan beslemenin 1 ile yapılması gereken bir kodun olsun. Eğer platform bunu sağlamıyorsa, derleme hatası oluşsun. static assertion eklenmeden önce yapılan hileler için de bir örnek. Anafikir, 0 elemanlı dizi syntax hatasıdır. typedef int testRightShift_FeedWithOne[ -1>>1 == -1 ] ─────────── İfade yanlışsa, 0 üretilir, buradan da dizi boyutu 0 diye derleme hatası olacak Gerçi C11 ile static assert eklendi, dolayısıyla yukarıdaki ifade şöylede yazılabilir: _Static_assert(-1>>1 == -1, "Bitsel Saga kaydırmada Besleme 1 ile degil 0 ile yapiliyor!"); ■ & Bitwise AND -No Side Effect (Operandlara etkimez, sadece operatörün ürettiği değer var) ═══════════════════════════════ Logic AND && ile çok karıştırılıyor. #define bitAND & gibi kullanım, karışıklığı gidermek için iyi bir fikir. ■ | Bitwise OR -No Side Effect (Operandlara etkimez, sadece operatörün ürettiği değer var) ═══════════════════════════════ Logic OR || ile çok karıştırılıyor. #define bitOR | gibi kullanım, karışıklığı gidermek için iyi bir fikir. ■ ^ Bitwise XOR -No Side Effect (Operandlara etkimez, sadece operatörün ürettiği değer var) ═══════════════════════════════ Truth Table for XOR ─────────────────── 1 XOR 1 --> 0 1 XOR 0 --> 1 0 XOR 0 --> 0 0 XOR 1 --> 1 Hatırlatma: Logic XOR diye bir operator yok. Ancak bunu gerçekleyebiliriz. !!expr1 != !!expr2 şeklinde. Bu da mülakatlarda çok sevilen bir sorudur. █ Örnek: XOR SWAP : Bitwise XOR , üçüncü bir değişken kullanmadan , iki sayının swap edilmesi içinde kullanılıyor. Bu da mülakatlarda çok sevilen bir sorudur. Ancak üretimde kullanmaya yönelik kullanma. Bunlar puzzla vari şeylerdir. int main(void) { int x,y; printf("iki tamsayi giriniz: "); scanf("%d%d",&x,&y); printf("x=%d y=%d\n",x,y); x ^= y; y ^= x; x ^= y; //Hatta yukarıdaki üç satırı, bilhassa makro yaparken, tek satırda da yazıyorlar: x ^= y,y ^= x,x ^= y; printf("x=%d y=%d\n",x,y); } Bir tamsayıyı iki kez aynı tamsayı ile XOR ladığımızda , eski değerine geri dönüyor. a ^=b; a ^=b; Bu noktada , a yine orijinal değerine geri dönecek. Bir tamsayıyı kendisiyle XOR ladığımızda , sayıyı 0 yapmış oluruz. Buna dikkat. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 53.Ders C [ Bitwise Manipulations] 13 Ekim 2022 Perşembe ■ Temel Manipulasyonlar ═══════════════════════ Bir tamsayının i.e x, Belirli bir bitini i.e n ▪ Set etmek - 1 yapmak ══════════════════════ x |= (1<=0 ; --i) { putchar( (x >> i) & 1 ? '1' : '0' ); } putchar('\n'); } void bitPrint_2(int x) { // Yaklaşım : Maskeyi sürekli kaydırarak sayının her bir bitini 1 ile kontrol edelim. unsigned int mask = ~(~0u >> 1); while(mask) { putchar( x & mask ? '1' : '0' ); mask >>= 1; } putchar('\n'); } █ Örnek: Sayı çift mi sınamasını bitsel işlem kullanarak if(x & 1) // Aslında bu LSB nin 1 olup olmadığını sınar. LSB 0 ise sayı çift, 1 ise tektir. Çift else Tek ■ Nice To Have Manipulasyonlar ══════════════════════════════ ▪ Sağdan ilk 1 bitini sıfırlama/resetlemek x & (x-1) ▪ Sağdan ilk 0 bitini birlemek/setlemek x | (x+1) ▪ Sağdaki trailing 1 bitlerini sıfırlama/resetlemek x & (x+1) ▪ Sağdaki trailing 0 bitlerini birlemek/setlemek x | (x-1) ▪ Sağdan ilk 0 bitinin maskesi ~x & (x+1) ▪ Sağdan ilk 1 bitinin maskesinin değili ~x | (x-1) ▪ Sağdan ilk 1 bitinin maskesi ~x & (-x) █ Örnek: Bir tamsayının en düşük anlamlı dört bitini - nibble - get eden bir fonksiyon yazınız. 0000..00000001111 maskesi bize lazım , bu da 15 demek. O halde x &= 15; dersek ilk 4 biti kalır sayıda. █ Örnek: 16 bitlik bir işaretsiz tamsayının ortasındaki sekiz biti get eden ifadeyi yazınız. Way1 : Yine maske yaklaşımını kullanarak 0000 1111 1111 0000 maskem olur. Way2 : Ayrıca bu sayıyı sağa sola kaydırıp dolayısyla başını sonunu 0 larla doldurarak da elde edebilirim. (x<<4) >> 8 ifadesi istenen şeyi döndürecektir. ▪ Sayıdaki set durumdaki bitlerin sayısını bulan fonksiyon - çok çeşitli şekillerde yapılabilir... int sbc(int x) { int cnt = 0; unsigned int mask = ~(~0u >> 1); while(mask) { if(x & mask) ++cnt; mask >>= 1; } return cnt; } sbc işlevini, içinde kontrol deyimi kullanamadan ve daha hızla yapacak bir şekli de tanıyalım. Sayımız 32 bit olsun. Buna göre yaklaşımımız: Sayı 4 byte tan oluşuyor. Yani her bir byte 0-255 aralığında bir değere sahip. Elimizde bir lookup table olsa, 0 ile 255 arası sayıların set durumdaki bit countlarını söyleyen. Örneğin, sbc_lookup[12] , bize 12 sayısındaki set bit count'u dödndürsün. 32 bitlik sayımızın bytelarındaki set olan bitlerin countlarını sırayla tüm bytelar için okuyup sonra toplarız. sayi = byte3 byte2 byte1 byte0 bu byteların decimal değeri [0 255] aralığında byte0 in değeri : sayi & 255 byte0 in içinde sbcount : sbc_lookup[byte0] byte1 in değeri : (sayi >> 8) & 255 byte1 in içinde sbcount : sbc_lookup[byte1] byte2 in değeri : (sayi >> 16) & 255 byte2 in içinde sbcount : sbc_lookup[byte2] byte3 in değeri : (sayi >> 24) & 255 byte3 in içinde sbcount : sbc_lookup[byte3] + ────────────────────────── sayidaki total sbcount Elimde const int sbc_lookup[256] dizisi olsa, bu byteların değerlerini diziye koyduğumda, hey byte da kaç tane setbit var öğrenirim. Neticede sbc = sbc_lookup[sayi & 255] + sbc_lookup[(sayi >> 8) & 255] + sbc_lookup[(sayi >> 16) & 255] + sbc_lookup[(sayi >> 24) & 255] elde edilir. Dolayısyla bunu fonksiyonel makro olarak da ifade edebilirim #define sbc(x) ..... ▪ sbc için Kernighan Algorithm var. [Bit Twiddling Hack dökümanında var] Özü şu: Bir sayıyı, kendisinden 1 eksik sayı ile bAND yapınca, sağdaki ilk 1 biti sıfırlanmış oluyor. Dolayısıyla, bu işi sayının kendisi sıfırlanana dek yaparsak, ve kaç kez bu işi yaptığımızı sayarsak, içinde kaç tane 1 olan bit varmış bulmuş oluruz. while(x) { x &= (x-1); ++sbc; } Bu döngünün bir güzelliğide, sayı içindeki 1 bitleri kadar dönmesi. Yani 32 bit integer için 32 kez fix dönmesi gerekmiyor. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 54.Ders C [ User Defined Types - Bitfields, Komut Satırı Argumanları] 18 Ekim 2022 Salı ■ Bitfields ══════════════════════════════ Bir tamsayının belirli bitlerini ayrı değişkenler olarak kullanabilir miyiz? Ve neden böyle birşey yapalım? Evet, mantıksal ilişki içindeki değişkenleri birarada tutmak, fonksiyonlara arguman olarak geçmek, bellekte tasarruflu olmak için çok kullanışlı bir araç. Bilhassa, embedded tarafta bellek avantajı sağlaması çok önmelidir. ▪ Bir yapı nesnesinin elemanlarının bir yada birden fazlası bitfield member olabilir. ▪ bitfield member bildirimi şu şekilde : ════════════════════════════════════════ struct Nec ┌ işaretli/işaretsiz tamsayı türler olabilir. Ancak nitelemezsen , derleyiciye bağlı olarak işaretli yada işaretsiz olarak alınabilir. { │ │ ┌ Kaç bitlik alan kaplayacak belirten, Constant Expression olmalı unsigned int x : 4 ; ────────→ 4 bitlik alanda, işaretli tür ile [ 0 , 15 ] değer aralığı temsil edilebilir. signed int y : 4 ; ────────→ 4 bitlik alanda, işaretli tür ile [ -8 , 7 ] değer aralığı temsil edilebilir. int z : 2 ; signed/unsigned ile nitelenmediğinde, [normalde signed gibi algılanıyordu ama burada garanti altında değil.], işaretli mi işaretsiz mi olacağı derleyiciye bağlıdır. unsigned int : 1 ; │ └ isimsiz olabilir. Bu durumda placeholder/yer tutucu/padding gibi kullanıyorsun demektir. _Bool b : 1 ; C99 ile bit alanı elemanlarında _Bool türünü de kullanabiliyoruz. Tipk olarak 1 bit uzunluk ile eşleştiriliyor. }; ▪ Bitfield alanı olarak char/long vs standardda yok. Ancak derleyiciler extension olarak destek veriyorlar. Böyle şeyler görürsen şaşırma. ▪ Hem bitfield hem non-bitfield memberlar içeren bir yapı nesnesi - Ne kadar verimli o tartışılır, ancak pekala mümkün/legal struct Nec { int x; unsigned int : 4; } ▪ Bit elemanlarına sahip bir yapının storage ihtiyacı yani size of değeri, storage unitin(yani int) in katları şeklinde oluyor. Daha küçük olsa bile, int alanı kadar yer kaplar. ▪ Yapı nesnesi içinde , bitfield alanların yerleşimi/sırasına dikkat et. Gereksiz yer işgaline sebep olabilir. struct Nec struct Nec { { unsigned int x1 : 1; unsigned int x1 : 1; unsigned int x2 : 32; unsigned int x2 : 1; unsigned int x3 : 1; unsigned int x3 : 32; } } // 3*sizeof(int) yer kaplar //2*sizeof(int) yer kaplar //Araları padding ile dolduracaktır ▪ Yapı nesnesi içinde , bitfield alanların bytelardan taşmasının önüne geçmek için - işlem maliyetinden sakınmak amaçlı - kendimiz padding kullanabiliriz. Padding amaçlı olan bitfield lara, isim vermiyoruz. struct Nec struct Nec { { unsigned int x1 : 5; unsigned int x1 : 5; unsigned int x2 : 4; unsigned int : 3; // Byte'ı tamamlamak için kasıtlı padding koyduk unsigned int x2 : 4; // Böylelikle x2 , ikinci byte içinde } } // x2, hem birinci hem ikinci // byte 'ı kullanıyor ▪ Bazen bir bitfield alanının, bir sonraki bitfield dan başlamasını isteyebiliriz. Bu da dilin standardında tanımlı. Extension değil. struct Nec { unsigned int x1 : 5; unsigned int : 0; // Bundan sonraki bitfield alanının, bir sonraki storage unit ten başlaması için gerekli ifade unsigned int x2 : 4; // Böylelikle x2 , bir sonraki storage unitten itibaren yerleştirilir. }Data; printf("%zu",sizeof(Data)); //Outputs 8, yani iki int storage'ı kadar kullanmış. ▪ bitfield memberlara erişim ════════════════════════════════════════ struct Nec mydata.x1 = 2; // Direkt erişim bu şekilde { ptr->x1 = 3; // Pointer ile erişim bu şekilde unsigned int x1 : 5; unsigned int x2 : 6; ▪ ilk değer verme unsigned int x3 : 4; ════════════════════════════════════════ } Data; Data mydata = {2,3,8}; Data mydata = {.x1=2}; //Designated initializer kullanımı okay ▪ KURAL : Yapıların bitfield memberları, addressof operatörünün operandı olamıyorlar! int* p = &mydata.x1; // Syntax Error! ▪ Bitfield alanlarına bitwise operations ile dolaylı olarak okuma/yazma için kullanmayalım. Çünkü bunların gerçekteki yerleşimi derleyiciye bağlı. Yani kod taşınabilir olmaz. ▪ Common bir practise şudur: Kullanılmayan yer kadar bir eleman daha tanımlanması struct Nec { unsigned int x1 : 5; unsigned int x2 : 6; unsigned int x3 : 4; unsigned int unused : 17; } Data; █ Örnek: Tarih( gün ay yıl) tipi bir yapı nesnesi yapalım, çekirdek isteri 2 byte olsun struct Date { unsigned int mday : 5; // 1..31 aralığını 5 bitle temsil edebiliriz. unsigned int mmon : 4; // 1..12 aralığını 4 bitle temsil edebiliriz unsigned int myear : 7; // 1980 + offset gibi düşünürsek , offset kısmını burada temsil edelim, kalan 7 bit olsun varsın demişler.Eski DOS cular. } █ Örnek: Zaman ( hour min sec) tipi bir yapı nesnesi yapalım, çekirdek isteri 2 byte olsun struct Date { unsigned int hour : 5; // 0..24 aralığını 5 bitle temsil edebiliriz. unsigned int min : 6; // 0..60 aralığını 6 bitle temsil edebiliriz unsigned int sec : 7; // 0..60 için 6 bit lazım, ama elde kalan 5 bit. Bunu da şöyle sıvamışlar: // Sadece çift saniyeleri temsil edelim Dolayıysla 0..31 aralığını ele alalım, gösterimde bunu 2 ile çarpıp gösteririz demişler. } Böylelikle yukarıdaki iki yapıyı birleştirerek 32 bit içinde DateTime yapı nesnesi yapılabilir. typedef union { struct int main(void) { { DateTime dtx = { unsigned int day : 5; .day = 23, unsigned int mon : 4; .mon = 7, unsigned int year: 7; .year = 1998-1980, unsigned int hour: 5; .hour = 16, unsigned int min : 6; .min = 52, unsigned int sec : 5; .sec = 48/2, } }; uint32_t uval; printf("%u \n",dtx.uval); }DateTime; printf("%02u %02u %u %02u:%02u:%02u\n",dtx.day,dtx,mon,dtx.year+1980,dtx.hour,dtx.min,2*dtx.sec); } █ Örnek: 32 bit işaretsiz tamsayının gerek kendisi gerek bitlerinin kullanıma uygun hale getirelim. Üstelik bellek maliyeti olmadan :) typedef union { struct { unsigned int bit0 : 1; unsigned int bit1 : 1; ... unsigned int bit31 : 1; }; uint32_t uval; }; Dikkat : Tanımladığımız bitfield ların gerçekte hangi bitler olduğu (örneğin bit0 ile LSB kastetmişiz ama belkide LSB ye yerleştirmeyecek) derleyiciye bağlı. Yani taşınabilirlik için uygun değil. Bunun önlemini almaktan biz sorumluyuz. Zaten şunuda unutmayalım. 1 byte dan daha büyük olan türlerde , işlemci/derleyiciye göre değişen Little/big endianness kavramıda vardı! Little endian testinin nasıl yapıldığını tekrar hatırlayın: (Bilgi : C++ 2020 de compile time da da test etme imkanı geldi.) int main(void) { int x = 1; if( *(char*)&x ) printf("Little Endian\n"); else printf("Big Endian\n"); } ■ Command Line Arguments - Komut Satırı Argumanları ════════════════════════ Kullanıcının komut satırından programı çalıştırmak istediğinde, programa veri aktarmak istediği veriler olabiliyor. Bunlara Komut Satırı Argumanları deniyor. ┌ OS, main fonksiyonumuza, Argument Count u buradan aktaracak │ │ ┌──── Argument vector, Pointer Array where each element points to a char array that is linked with an command line argument int main(int argc, char** argv) { } ▪ argc, Argument Count, komut satırının tamamındaki yazının, ayraç olarak boşluk karakterini esas olup, buna göre arguman sayısını bildirir. Yani bir diğer deyişle, çalıştırılabilir programın ismide argc ye dahildir. Örneğin wday 23 7 1988 için argc = 4 copy ali.c veli.c için argc = 3 myprog omer faruk ersoy için argc = 4 myprog "omer faruk ersoy" için argc = 2 Eğer boşluk karakterini de göndermek istiyorsak çift tırnak içinde yazıyoruz! myprog "ali veli" "omer faruk ersoy" için argc = 3 ▪ argv, Argument Vector, türü char**, ki bazen yazımda char*[] olarak da yazılıyor, char dizilerin adreslerini tutan bir array aslında argv[argc] nin NULL POINTER olma garantisi var. Bu özellikten faydalanarak bir while döngüsü ile argv nin elemanları gezilebilir. int i = 0; while( argv[i] ) puts( argv[i++] ); // Argv deki argumanları yazdırırsın mesela ▪ argc den hareketle, programın intended sayıda argumanla çağrılıp çağrılmadığını başlangıçta kontrol edebilir, uygunsuzlukta, uygun kullanıma ilişkin bir bildirim yaparak sonrasında programı sonlandırabilirim. Bu çok tipik bir durumdur. int main(int argc, char** argv) { printf("argc = &d\n",argc); for(int i =0; i< argc; ++i) { printf("argv[%d] = &s\n",i, argv[i] ); } if(argc !=3) { fprintf(stderr,"Kullanim: \n"); return 1; } // code printf("&s isimli dosyanin %s isimli kopyasi olusturuldu\n",argv[1], argv[2]); if( argv[argc] == NULL) printf("Dogrudur, argv, sonunda NULL pointeri ile gelir\n"); } █ Örnek: Basit bir hesap makinası programı yapalım. adı topla olsun, çalıştırırken mesela topla 2 3 5 yazıp enter a bastığımızda , program işlemi hesaplayıp sonucu ekrana yazdırsın. int main(int argc, char** argv) { printf("argc = &d\n",argc); for(int i =0; i< argc; ++i) { printf("argv[%d] = &s\n",i, argv[i] ); } int sum = 0; for(int i =0; i< argc; ++i) { sum += atoi( argv[i] ); } printf("Toplam = %d \n",sum); } █ Örnek: Basit bir gün bulma programı yapalım. Mesela çalıştırırken wday 23 7 1988 yazıp enter a bastığımızda , program haftanın hangi günü olduğunu bulup sonucu ekrana yazdırsın. #include #include #include #include "date.h" int main(int argc, char** argv) { printf("argc = &d\n",argc); for(int i =0; i< argc; ++i) { printf("argv[%d] = &s\n",i, argv[i] ); } if(argc !=4) { fprintf(stderr,"Kullanim: \n"); return 1; } Date result; set_date(&result,atoi( argv[1] ),atoi( argv[2] ),atoi( argv[3] ) ); print_date(&result); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 55.Ders C [ File Operations] 20 Ekim 2022 Perşembe ■ File Operations - Dosya Operasyonları ═════════════════ Dosya, içeriği 100101011010010101010101.. şeklinde. Ne anlama geliyor? Bunu belirleyen file format. File format ile kastedilen, içerikteki 1 ve 0 larla ne kastediliyor bilgisidir. Mesela programı yazdığımız dosyanın formatı ASCII Text file Birçok dosya formatında bir header oluyor, bu header kısmında dosya ile ilgili bazı bilgiler veriliyor. Dosya işlemlerini yürütmekle yükümlü makam OS dir. C Std kütüphanesindeki fonksiyonlar , aslında OS deki hizmetleri kullanır. Std C ile tüm dosya işlemlerini yapabilir miyim? %90-96 oranında OS-independent şekilde dosya işlemlerini Std C kütüphanesi gerçekleştirebilir, ancak bazı işler için OS nin sunduğu sistem fonksiyonlarına başvurmak zorunda kalabilirsin. başlık dosyasında , file fonksiyonları bildiriliyor. ▪ File Handle Sistemi fopen-Operations-fclose ═══════════════════════════════════════════════════ Std C de dosya işlemleri ile ilgili olarak, tipik bir Handle sistemi var. fopen bize bir handle(Yapı nesnesi adresi) veriyor. Sonrasında bu handle'i diğer fonksiyonlara arguman olarak göndererek işlerimizi yapacağız. işlerimiz sonrası handle'ı kapatmak/release için fclose kullanacağız. File Pointer- Dosya Konum Göstericisi [FILE* değildir!] ▪ FILE* fopen( const char* pFilename, char* pFileOpenMode) ═══════════════════════════════════════════════════════════ Fonksiyonun başarılı olma garantisi yok. Başarısız olursa NULL pointer döndürüyor. Dolayısıyla bu geri dönüşü bir kontrol edilmeli. Başarılı olursa Handle döndürür. Dosya açış modu kavramının kökü OS den gelir. Hangi işlemi yapacaksak ona göre bir mode söylemeliyiz. Açış modu şunları belirler: ▪ Dosya varsa - yoksa ne olacak? ▪ Dosya açılması durumunda hangi işlemleri yapma (read/write) hakkım var? Dosya Açılış modları ════════════════════ Tipik olarak Read - Write - Append şeklindedir. Derleyici belki başka modlarıda destekliyor olabilir. ┌──────────────┐ ┌──────────────┐ │ Text Mode │ │ Binary Mode │ │ Default │ │ │ └──────────────┘ └──────────────┘ Notlar ──────────────────────────────────────────────── ────────────────────────────────────────────────────────────── Dosya varsa açılır, yoksa açılmaz. Read "r" "rb" Açılırsa, okuma yapabilirim yazma işlemi YAPAMAM Read+ "r+" "r+b" or "rb+" [+Mode] Açılırsa, hem okuma hem yazma işlemi yapabilirim. ──────────────────────────────────────────────── ────────────────────────────────────────────────────────────── Dosya varsa DOSYA SIFIRLANIR(Truncate)! Aman dikkat eldekini yitirirsin!, dosya yoksa oluşturulur. Write "w" "wb" Dosyaya yazma işlemi yapabilirim, okuma YAPAMAM! Write+ "w+" "w+b" or "wb+" [+Mode] Hem okuma hem yazma işlemi yapabilirim. ──────────────────────────────────────────────── ────────────────────────────────────────────────────────────── Dosya varsa açılır, yoksa oluşturulur. Append "a" "ab" Dosyaya sona yazma yapabilirim, okuma YAPAMAM! Append+ "a+" "a+b" or "ab+" [+Mode] Hem okuma hem sona yazma yapabilirim. ──────────────────────────────────────────────── ────────────────────────────────────────────────────────────── Text Mode ve Binary Mode ne demek? ═════════════════════════════════════ ▪ Text Mode demek, anlamsal açıdan, açtığım dosya, bir salt yazı bilgisi tutuyor demek.[Bunun, uzantısının .txt olmasıyla bir ilgisi yok] Dosyanın, diskteki 1 ve 0 içeriği, yada dosyanın görüntülendiğindeki sayılar, karakter olarak anlamlandırılmalı demektir. ▪ Binary Mode demek, artık açtığım dosya bir salt yazı tutuyor anlamına gelmiyor. Anlamlandırılmada bir özel formata sahip demektir. Hangi açılış modunu tercih etmeliyim? ═════════════════════════════════════ ▪ Text Mode Elimizdeki bir text dosyasi ise, ve onu text olarak işleyeceksek, doğal olarak, Text Mode kullanmalı. ▪ Binary Mode ▪ Eğer dosyanın text ile ilgisi yoksa ▪ Yada dosyanın text ile ilgisi var ancak yapacağım işlemin doğrudan text ile ilgisi yoksa (i.e dosyayı kopyalacağım gibi) Bu durumlarda Binary Mode tercih etmeliyiz. Read - Write ne demek? ═════════════════════════════════════ ▪ Read demek, dosyadaki byteların değerlerini elde etmek. Read işlemleri formatlı/formatsız olabilir. Formatsız okuma demek, byte ın değerini direkt/olduğu gibi/"as is" edinmek demek Formatlı okuma demek, byte ları anlamlı gruplayarak bu gruplara bir anlam yükleyerek - mesela 4 byte i bir işaretli tamsayı olrak algılamak gibi - demek ▪ Write demek, dosyadaki byteların değerini değiştirmeye(overwrite) yönelik birişlem demek, veya sonuna ilave bytelar eklemek demek(append) Note that, baştan yahut araya byte eklemek diye bir şey yok. Sadece sona eklemek var. Peki başa/ortaya nasıl ekleniyor? Mesela sona ekleniyor, sonra uygun overwrite lar yapılıyor gibi. Mesela int x = 762344 sayısını dosya yaz dediler? Sorman lazım nasıl yazalım? ▪ insanın anlayacağı bir şekilde karakter kodlarıyla mi? Bu durumda, sayıyı oluşturan her bir rakamın ascii kodu karşılığını yazmak lazım 762344 │││ .. ││50 ascii ││ karakter │54 kodları │ 55 ▪ yoksa x in bellekte tutulduğu byteları aynen mi? ( x değerimiz bellekte 4 byte ta tutuluyor olsun, as is bu bellek değerlerini mi aynen yazalım?) █ Örnek: ilayda.txt dosyasini okuma ve yazma modunda açmaya çalisalim. #include #include int main(void) int main(void) { { FILE* fHandle = fopen("ilayda.txt","r"); FILE* fHandle = fopen("ilayda.txt","w"); // Dikkat! Dosya mevcutsa Budanacak(Truncate) if(!fHandle) if(!fHandle) { { fprintf(stderr, "Dosya acilamadi\n"); fprintf(stderr, "Dosya olusturulamadi\n"); return 1; return 1; } } printf("dosya acildi\n"); printf("dosya olusturuldu\n"); fclose(fHandle); fclose(fHandle); } } unix/linux tarzı programlamada şöyle kodlar - atama operatörünün ürettiği değerden faydalanarak - alternatif yazımlar çok görürsün: FILE* fHandle = ; if((fHandle = fopen("ilayda.txt","r"))==NULL) { fprintf(stderr, "Dosya acilamadi\n"); return 1; } ▪ int fclose( FILE* fHandle) ═══════════════════════════════ Açtığın dosyayı kapamalısın. Kapamazsan ne olur? Okuyana, yardımcı toolları, yani milleti şaşırtırsın, işlerini faydalı yapamazlar. Normal program sonlanmasında tipik olarak kayıp yaşamazsın. Ancak, Anormal program sonlanmasında veri kaybı yaşayabilirsin. Geri dönüş bilgisi Başarı bilgisidir. başlık dosyasındaki int ile başarı bilgisi dönenler, 0 : Success Non-Zero: Failure döndürüyorlar. Bu da aynı şekilde. flose'un geri dönüş değerini test edelim mi? Gerek yok! Çok nadir karşılaşılır yada debug da kullanılır. ▪ int fcloseall( void) Std değil ancak çoğu derleyici bunu veriyor. ══════════════════════ Açtığın tüm dosyaları kapıyor. Geri dönüş değeri, kapatılan dosya sayısı ▪ path meselesi ═══════════════ Eğer path belirtilmemişse, exe dosyanın bulunduğu directory , dosya işleminde varsayılan directory olarak alınır. Peki path nasıl belirtiliyor? █ Örnek: exts dene.txt gibi çalıştırılacak ve dosyanin varligini kontrol edecek bir program yazalım. #define int main(int argc, char** argv) { if(argc != 2) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle; if( (fHandle = fopen(argv[1],"r")) == NULL ) { printf("File does not exists\n"); } else { printf("File exists\n"); fclose(fHandle) } } Programı çalıştırırken, varlığını kontrol etmek istediğimiz dosyaya path de verebiliriz. Ancak path in nasıl verildiği OS'ye bağlıdır. Dikkat edilecek bir husus: Dizin geçiş karakteri \ ise, path verirken bu karakteri belirtmenin yolu \\ şeklinde olmalıdır. C böyle yorumlayacağından! Mesela FILE* f = fopen("dizin1\dizin2\dizin3\ali.txt" filan diyorlar, tabi dosya açılamıyor! Doğrusu FILE* f = fopen("dizin1\\dizin2\\dizin3\\ali.txt" şeklinde olmalı Absolute Path Relative Path nasıl veriliyor, bunlara internetten bak. ▪ FilePointer Operations (Dosya Konum Gösterici İşlemleri) ════════════════════════ İlginçtir ki, okuma yazma yapan fonksiyonlar, dosyanın hangi byte ına işlem yapayım diye bir argumana sahip değiller! Sistem tarafından tutulan , Handle içine gömülü - Bizim doğrudan erişimimize kapalı- , bir tamsayı değişken var. Buna FilePointer deniyor.Dosya Konum Göstericisi. Dosya Konum Göstericisi , Mesela değeri 8 olsun ↓ Dosyadaki bytelar □□□□□□■□□□□□□...□□□□□□□□ indexi 0 8 Durum böyleyken bir işlem (okuma/yazma) yapacak olsak, indexi 8 olan byte'dan başlayarak işlemi gerçekleştirecek. İşlem sonunda, okunan/yazılan byte sayısı kadar FilePointer'i artırır. Mesela 1 byte okusaydık, işlem sonunda FilePointer 9 değerine sahip olurdu. Ben bir dosyayı okuma modunda açtığımda, açar açmaz, bu Handle'a ilişkin FilePointer değeri 0 oluyor. Yani dosya başını gösteriyor. Dolayısıyla, tüm dosyayı okumak istesem, bir döngü içinde sürekli byteları oku diyeceğim. Ben okudukça, FilePointer otomatikman artacak. Benim endişelenmeme gerek yok. Keza yazma işlemide böyle. Yazdıkça, FilePointer otomatikman artacak. Benim endişelenmeme gerek yok. Bu tip kavramlara - sırayla ilerlemeye - Sequential Access deniyor. Dosyanın belirli bir yerinden başlayabilmeye, konumlanabilmeye, Random Access deniyor.Peki bu nasıl geçekleştiriliyor? FilePointer'ı get ve set eden fonksiyonlar var. ▪ int fgetc(FILE* fHandle) ═══════════════════════════ Formatsız 1 byte okuma. Handle ile ilişkili dosyadan, FilePointer in gösterdiği 1 byte'i okur, değerini int olarak döner. Başarısızlık durumunda (Mesela Okunacak başka birşey kalmadığında)-1 değerini döndürüyor. Sık yapılan yanlışlardan biri, fgetc nin geri dönüş değerini char türden bir değişkende saklamak. Böyle yapıldığında geri dönüş değerinin -1 olması durumunda, bu değer char a aktarıldığında 255 haline gelecek, ve 255 dönüşü ile -1 dönüşü olması arasında fark kalmayacak. Veri kaybetmiş olacağız. Dosyaların en sonunda EOF vardır demek doğru değil! EOF, da tanımlı , -1'e define edilmiş bir makrodur. fgetc EOF döndürürse, -başka bir başarısızlık neden yoksa - bu okuyacak başka şey kalmadığından, yani sona geldiğindendir. Ama bu demek değildir ki, dosya sonunda EOF denen bir varlık mevcut! █ Örnek: [Text Mode da] main.c dosyasını okusun, okuduklarını ekrana yazsın. #include int main(void) { FILE* fHandle = fopen("main.c","r") if(fHandle==NULL) { fprintf(stderr, "Dosya acilamadi\n"); return 1; } int c; while( (c= fgetc(fHandle) )!=EOF ) { //printf("%d",c); //Okunan byteları formatsız görmek istersem //printf("%c",c); //Okunan byteları karakter olarak görmek istersem //printf("%c ",c); //Okunan byteları karakter olarak görmek istersem ve karakterler arası birer boşlukla ekrana yazdırayım putchar(c); //Okunan byteları karakter olarak görmek istersem } fclose(fHandle); } █ Örnek: [Text Mode da] dosyadanoku myfile.txt komutu ile dosyadan okusun, okuduklarını ekrana yazsın. #include int main(int argc, char** argv) { if(argc != 2) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle = fopen(argv[1],"r") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } int c; while( (c= fgetc(fHandle) )!=EOF ) { putchar(c); //Okunan byteları karakter olarak görmek istersem } fclose(fHandle); } █ Örnek: [Text Mode da] Bir dosyadaki belirli bir karakteri sayan bir prg yazalım, crcount myfile.txt a [ a karakterini saymak isterse] #include int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle = fopen(argv[1],"r") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } int c; int cnt = 0; const int chr = *argv[2]; // Aranacak olan karakter int byteCount = 0; while( (c= fgetc(fHandle) )!=EOF ) { ++byteCount; if(c == chr) ++cnt; } fclose(fHandle); printf("Character Count: %d\n", cnt); printf("Byte Count: %d\n", byteCount); } Dosyadan okuduğumuz byteCount ile işletim sisteminin bize gösterdiği/bildirdiği dosya uzunluğu[bytes] tutmuyor! Neden? Karışıklığa sebep olan şey NewLine karakteri. ASCII de 10 nolu karakter NewLine. Windows da, [Text Mode da] dosyaya yazma işleminde, bu karakter 1 byte değil 2 byte olarak (13 10) yazılıyor. (Unix sistemlerde 1 byte - normal yazılıyor) Mesela, içeriği BABAnewLine DEDE olan text dosyasından okunan bu şekilde. 66 65 66 65 13 10 .... Ve, fgetc, [Text Mode da], ardışık 13 10 geldiğinde, bunu okurkende , sadece 10 - tek byte - okumuş gibi geri dönüyor. Dolayısıyla, OS nin bildirdiği byte uzunluğu ile tutacak şekilde byte sayabilmek için, ben, fgetc her 10 döndürdüğünde, byte sayacını 2 artırmam gerek. Şimdi dosyayı [Binary Mode da] açsak, bu kez hakikaten, 13 10 çiftini ayrı ayrı okuyup bize döner. #include int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle = fopen(argv[1],"rb") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } int c; int cnt = 0; const int chr = *argv[2]; // Aranacak olan karakter int byteCount = 0; while( (c= fgetc(fHandle) )!=EOF ) { ++byteCount; if(c == chr) ++cnt; } fclose(fHandle); printf("Character Count: %d\n", cnt); printf("Byte Count: %d\n", byteCount); } Bu örneği çalıştırdığımızda, OS nin bildirdiği byte uzunluğu ile bizim saydığımız byte uzunluğu birbirini tutuyor. ▪ int fputc(int c, FILE* fHandle) ═════════════════════════════════ Formatsız 1 byte yazma. Handle ile ilişkili dosyadan, FilePointer in gösterdiği yere, ilk parametresinde bildirilen karakteri yazar, yazılan karakter kodunu int olarak döner. Başarısızlık durumunda -1 değerini döndürüyor. Tabiki fputc yi kullanabilmek için dosyayı yazma modunda açmış olmak gerekli. #include int main(void) { FILE* fHandle = fopen("murat.txt","w") if(fHandle==NULL) { fprintf(stderr, "Dosya olusturulamadı\n"); return 1; } // A dan Z ye karakterleri dosyay yazdıralım while( int i='A'; i<='Z'; ++i ) { fputc(i,fHandle); } fclose(fHandle); } █ Örnek: Tipik mülakat sorularından biri. Bir text dosyası var. Bundaki tüm harf karakterlerini - büyük/küçük ayrımı olmadan - dosyada kaçar tane olduklarını ekrana yazdır. Prgramın çalıştırılması, komut satırından cntall murat.txt gibi olsun. #include #include int main(int argc, char** argv) { if(argc != 2) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle = fopen(argv[1],"r") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } int letterCounts[26] = {0}; int c; while( (c= fgetc(fHandle) )!=EOF ) { if( isalpha(c) ) { // c is karakter ++letterCounts[ toupper(c)-'A' ]; } } fclose(fHandle); // Harfleri ve tekrar miktarlarını, sırasıyla, Ekrana yazdır for(int = 0; i<26, ++i) { if(letterCounts[i]) printf("%c Count is %d\n", i+'A', letterCounts[i] ); } } █ Örnek: Tipik mülakat sorularından biri. Yukarıdaki soruyu, Harfleri ve tekrar miktarlarını, miktarı büyükten küçüğe olacak şekilde sıralanmış şekilde yazdır. #include #include int ecmp(const void * vp1, const void * vp2 ) { int left = *( (const elem*)vp1->count ); int right = *( (const elem*)vp2->count ); left > right ? 1: left \n"); return 1; } FILE* fHandle = fopen(argv[1],"r") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } typedef struct { int ch; int count; }elem; elem letters[26] = { {'A', 0 }, {'B', 0 }, initialize etmek için böyle uzun uzun yazmalısın, yada kodla initialize etmelisin }; int c; while( (c= fgetc(fHandle) )!=EOF ) { if( isalpha(c) ) { // c is karakter ++letters[ toupper(c)-'A' ].count; } } fclose(fHandle); // Dizimizi sıralayacağız ┌ Sıralanacak dizinin adresi │ ┌ Dizinin boyutu │ │ ┌ Dizinin bir elemanının boyutu │ │ │ ┌ Dizinin iki elemanını karşılaştıracak fonksiyonun adresi │ │ │ │ qsort(letters, 26, sizeof(elem), &ecmp); for(int = 0; i<26, ++i) { if(letters[i].count) printf("%c Count is %d\n", i+'A', letters[i] ); } } █ Örnek: Rastgele bir dosya oluşturan bir program yazalım. createRandomFile ali.txt 4500 10 100 şeklinde çalıştırılsın 4500 satır, Bir satırdaki minimum 10 karakter maximum 100 karakter olabilir demek. #include #include #include #include int get_random_char(void) { int ch ; while(!isalnum(ch=rand()%128)) ; //null statement return ch; } int main(int argc, char** argv) { if(argc != 5) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandle = fopen(argv[1],"w") if(fHandle==NULL) { fprintf(stderr, "%s Dosyasi olusturulamadi\n",argv[1]); return 1; } int no_of_lines = atoi(argv[2]); int min_line_length = atoi(argv[3]); int max_line_length = atoi(argv[4]); randomize(); for( int i= 0; i #include int main(void) { FILE* fHandle = fopen(muratText,"w") if(fHandle==NULL) { fprintf(stderr, "Dosya olusturulamadi\n"); return 1; } for(int = 0; i<100, ++i) { fputc('\n',fHandle); } fclose(fHandle); } Bu dosyaya OS de baksam, dosya uzunluğunu 200 byte olarak görürüm. Halbuki içine 100 byte yazdım. Keza bu dosyayi text modunda açıp byte byte okuduğumda da , Windows, ardışık gelen bu iki byte'ı, tek byte okumuş gibi dönecek. Öte yandan, yukarıdaki dosyayi Binary Mode da yapalim ve 100 adet NewLine yazalim. #include #include int main(void) { FILE* fHandle = fopen(muratBinary,"wb") if(fHandle==NULL) { fprintf(stderr, "Dosya olusturulamadi\n"); return 1; } for(int = 0; i<100, ++i) { fputc('\n',fHandle); } fclose(fHandle); } Bu dosyaya OS de baksam, dosya uzunluğunu 100 byte olarak görürüm. Gerçektende içine 100 byte yazdım. Düşünsenize, mesela bir resim dosyam var, bununla işlem yapacağım diyelim, Binary Mode da açmak yerine Text Mode da açarsak, okuma yaptığımda, tesadüfen yan yana gelmiş 10 13 bytelarını , sadece 10 gibi algılayacağım! , yazma yaptığımda, mesela 10 yazarken, tutup 10 13 yazıyor olacak! Daha kötüsü de var: Eskiden, dosyaların sonuna, dosya sonu olduğunu göstermek için özel bir byte yazılıyormuş. Bu ascii deki 26 nodlu karakter. Ctrl Z karakteri. Günümüzde hiç ihtiyaç olmamasına rağmen, Windows OS ler, Text Modunda okumada buna riayet ediyor. Dolayısıyla, dosyayi text modunda açarsan, byte byte okuma yaptığında, 26 değerine sahip byte'ı okuduğunda , Dosya Sonu zannediyor! ve fgetc -1 döndürecek. █ Örnek: Bir dosyanin kopyasini çikaran bir prg yazalım. Şöyle çalışsın , kopyala ali.txt veli.txt, alinin veli isimli kopyasini oluşturacak. Binary Mode da , ali yi okuma modunda açacağım, veli yi yazma modunda açacağım. Text Mode da yapamaya çalışırsam, başıma belalar gelebilir. 10 13 ve 26 belaları. #include #include int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandleSource = fopen(argv[1],"rb") if(fHandleSource==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } FILE* fHandleTarget = fopen(argv[2],"wb") if(fHandleTarget==NULL) { fprintf(stderr, "%s Dosyasi olusturulamadi\n",argv[2]); return 1; } int c; int byteCount = 0; while( (c= fgetc(fHandleSource) )!=EOF ) { ++byteCount; fputc(c, fHandleTarget); } fclose(fHandleSource); fclose(fHandleTarget); printf("%d byte kopyalanarak, dosyanin kopyasi olustu\n",byteCount); } ▪ int remove(const char *pFilename) -- Dosya silen Std C fonksiyonu ═══════════════════════════════════ Silinecek dosyanın ismini parametre olarak alıyor. Geri dönüş değeri başarı. Silerse 0, silemezse Non-Zero döndürüyor. Tipik olarak, OS ler, dosyanin silinebilmesi için , dosyanin kapatilmiş olmasını şart koşuyor. Buna dikkat etmeliyiz. █ Örnek: ... if( (remove("murat.exe") ) printf("Dosya Silinemedi"); else printf("Dosya Silindi"); ... ▪ int rename(const char *pFilename, const char* pNewFileName) -- Dosya ismini değiştiren Std C fonksiyonu ═════════════════════════════════════════════════════════════ Geri dönüş değeri başarı. Başarırsa 0, başaramazsa Non-Zero döndürüyor. Tipik olarak, OS ler, dosyanin isminin değiştirilebilmesi için , dosyanin kapatilmiş olmasını şart koşuyor. Buna dikkat etmeliyiz. █ Örnek: ... if( (rename("murat.exe","newMurat.txt) ) printf("Dosya ismi degistirilemedi"); else printf("Dosya ismi degistirildi"); ... █ Örnek: İki prg yazalım. İlki Bir dosyayi parcalara bolen bir program olsun. Şöyle çalışsın , bol ali 1000 yani ali dosyasini 1000 byte lık parçalara bolecek, parcalara parca001.par .. diye isimlendirecek. Sonra bir başka program daha yapacağız. Şöyle çalışsın , birlestir ali yani parca001.par ... dosyalarını okuyup ali adı altında birleştirip tek dosya yapacak. Binary Mode kullanacağım tabiki. Bolen Programımız ═════════════════════════════════════ #include #include #include #define MAX_FILE_NAME_LEN 80 int main(int argc, char** argv) { char sourcefilename[MAX_FILE_NAME_LEN+1]; char destfilename[MAX_FILE_NAME_LEN+1]; int chunk; FILE* fHandleSource = NULL; FILE* fHandleDest = NULL; int sourceByteCount = 0; int fileCount = 0; if(argc != 3) { printf("Bolunecek Dosya ismi:"); scanf("%s", sourcefilename); printf("Kac bytelik parcalara bolunsun:"); scanf("%d", &chunk); } else { strcpy(sourcefilename,argv[1]); chunk = atoi(argv[2]); } fHandleSource = fopen(sourcefilename,"rb") if(fHandleSource==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",sourcefilename); return 1; } int c; while( (c= fgetc(fHandleSource) )!=EOF ) { if(fHandleDest == NULL) { sprintf(destfilename, "parca%03d.par", fileCount+1); if( (fHandleDest = fopen(destfilename,"wb") ) ==NULL ) { fprintf(stderr, "%s Dosyasi acilamadi\n",sourcefilename); fclose(fHandleSource); return 2; } ++fileCount; } fputc(c, fHandleDest); ++sourceByteCount; if (sourceByteCount % chunk == 0) { fclose(fHandleDest); fHandleDest = NULL; } } fclose(fHandleSource); if(fHandleDest) // Zaten kapatılmış olan dosyayi kapatmaya çalışırsam dangling ptr ! Bu yüzden kontrol edelim. fclose(fHandleDest); printf("%d bytelik &s dosyasi, %d bytelik %d parcaya bolundu\n",sourceByteCount, sourcefilename, chunk, fileCount); } Birleştiren Programımız ═════════════════════════════════════ #include #include #include #define MAX_FILE_NAME_LEN 80 int main(int argc, char** argv) { char sourcefilename[MAX_FILE_NAME_LEN+1]; char destfilename[MAX_FILE_NAME_LEN+1]; FILE* fHandleSource = NULL; FILE* fHandleDest = NULL; int destByteCount = 0; int fileCount = 0; if(argc != 2) { printf("Olusturulacak Dosya ismi:"); scanf("%s", destfilename); } else { strcpy(destfilename,argv[1]); } fHandleDest = fopen(destfilename,"wb") if(fHandleDest==NULL) { fprintf(stderr, "%s Dosyasi olusturulamadi\n",destfilename); return 1; } int c; while( 1 ) { sprintf(sourcefilename, "parca%03d.par", fileCount+1); if( (fHandleSource = fopen(sourcefilename, "rb")) == NULL ) break; ++fileCount; while( (c= fgetc(fHandleSource) )!=EOF ) { fputc(c, fHandleDest); ++destByteCount; } fclose(fHandleSource); } fclose(fHandleDest); char deleteFilename[MAX_FILE_NAME_LEN+1] ={0}; // Bir hoşluk yapalım, directory deki .par dosyalari silelim. Bir kez daha prg mımız çalışınca ortalık karışmasın. for (int i=0; i< fileCount; ++i) { sprintf(deleteFilename, "parca%03d.par",i); if(remove(deleteFilename)) { fprintf(stderr, "%s Dosyasi silinemedi\n",deleteFilename); return 1; } printf("%s isimli dosya silindi\n",deleteFilename) } printf("%d adet %d bytelik %s isimli dosya olarak birlestirildi\n", fileCount, destByteCount, destfilename ); } ▪ char* tmpnam(char *filename) ═════════════════════════════ OS'lerin sunduğu, bir directory de, benzersiz dosya ismi oluşturabilen bir sistem fonksiyonunu sarmalayan Std C fonksiyonudur. unique file name generator deniyor. Şu garantiyi veriyor. Bu isimli dosyayı yazma amaçlı oluşturursan, o directory deki hiç bir dosyayı ezmemiş olursun. Ancak oluşturulan ismi uzunluğu problemi var. Bu da şu şekilde çözülmüş. Dizi uzunluğu, deki L_tmpnam makrosuyla tanımlanırsa, dizi taşmaz garantisi var. char filename[L_tmpnam]; Return Value : filename if filename was not a null pointer. Otherwise a pointer to an internal static buffer is returned. If no suitable filename can be generated, null pointer is returned. █ Örnek: Basit bir encryption yapan prg yazalım. Şöyle çalışsın enc ali hello1234 ───────── Bundaki hello nin karakter kodlarını diğer 1234 ile toplayıp seed i oluşturacağız. Hatırlatma : Bir tamsayıyı iki kez aynı tamsayı ile XOR ladığımızda , eski değerine geri dönüyor. a ^=b; a ^=b; Bu noktada , a yine orijinal değerine geri dönecek. Bir tamsayı üretici seed seçip - bunun ürettiği sayılar hep aynı sıradadır, bu kavrama Pseudo Random Generation diyorduk - byte ları bunlarla exorlasak Data Bytes Raw □□□□□□... Pseudo Numbers of a Selected Seed ▼░►▒▓▲... XOR ────────────── Encrypted Data encrypted... Pseudo Numbers of a Selected Seed ▼░►▒▓▲... XOR ────────────── Data Bytes -decrypted- □□□□□□... ali dosyasin okuma modunda açacağım. Öte yandan bir başka dosyayi - xxx.dat gibi - yazma modunda açacağım. Encryption işlemimi gerçekleştireceğim. Sonra her iki dosyayi kapatacağım. ali Dosyasini sileceğim. sonra xxx.dat dosyasinin ismini ali olarak değiştireceğim. Bu hokus pokus ile kullanan açısından bakıldığında, dosyanın değiştiğini sanacak. Burada bir problem var, ya programın çalıştırıldığı yerde xxx.dat diye bir dosya varsa?? Bizim işimizin olmadığı , kullanıcının dosyasını heba ederiz. Bunun önüne geçmek için, OS'lerin sunduğu, benzersiz dosya ismi oluşturabilen bir sistem fonksiyonundan faydalanacağız. unique file name generator deniyor. Şu garantiyi veriyor. Bu isimli dosyayı yazma amaçlı oluşturursan, o directory deki hiç bir dosyayı ezmemiş olursun. İşte , OS nin o fonksiyonunu sarmalayan Std C fonksiyonu: char* tmpnam(char *filename) #include #include #include unsigned int get_seed_value(const char* p) { unsigned seed = 0; unsigned sum = 0; for(int i=0; p[i] !='\0'; ++i) { if(isalpha(p[i]) seed += p[i]; else if (isdigit(p[i])) sum = sum*10 + p[i]-'0' } return seed+sum; } int main(int argc, char** argv) { if(argc !=3 ) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandleSource = NULL; FILE* fHandleDest = NULL; char tempFileName[L_tmpnam]; tmpnam(tempFileName); if( (fHandleSource = fopen(argv[1],"rb")) == NULL ) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } if( (fHandleDest = fopen(tempFileName,"wb")) == NULL ) { fprintf(stderr, "can not create temporary file Dosyasi\n"); return 2; } unsigned seed = get_seed_value(argv[2]); srand(seed); int c ; while( (c=fgetc(fHandleSource)) != EOF ) { fputc(c ^ rand() ,fHandleDest); } fclose(fHandleSource); fclose(fHandleDest); if(remove(argv[1])) { fprintf(stderr, "file %s can not be removed\n", argv[1]); return 4; } if(rename(tempFileName, argv[1])) { fprintf(stderr, "file can not be renamed\n"); return 5; } } ▪ Dosya üzerinde yapılan formatlı okuma ve yazma işlemleri ═══════════════════════════════════════════════════════════ ▪ int fprintf(FILE* fHandle, const char pfm,...) ═══════════════════════════════════════════════════════════ Önşart : Dosya Text Mode unda açılmalı. █ Örnek: FILE * fHandle = fopen ("output.txt","w"); for(int i=0; i<100; ++i) { fprintf(fHandle, "%2d %#x\n",i,i); } fprintf(fHandle,"Murat Gunay\n"); fprintf(fHandle,"Necati Ergin\n"); fclose(fHandle); █ Örnek: Kullanıcının istediği kadar asal sayıyı dosyaya yazdıralım. primes50.txt gibi, 50 adet prime içeren dosya #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { int no_of_primes; int primes_per_line; char filename[SIZE]; FILE* fHandle; int prime_count = 0; int x = 2; printf("ilk kac asal sayi:"); scanf("%d",&no_of_primes); printf("bir satira kac sayi yazilsin:"); scanf("%d",&primes_per_line); sprintf(filename,"primes%d.txt",no_of_primes); if((fHandle = fopen(filename,"w")==NULL) { fprintf(stderr, "can not create file\n"); return 1; } while(prime_count < no_of_primes) { if(isprime(x)) { if(prime_count && prime_count&primes_per_line == 0) fprintf(fHandle, "\n"); fprintf(fHandle,"%-12d",x); ++prime_count; } ++x; } fclose(fHandle); } █ Örnek: ileriki konularımızda da kullanacağımız bir dosya yapalım. recs.txt #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { FILE* fHandle; if((fHandle = fopen("recs.txt","w")==NULL) { fprintf(stderr, "can not create file\n"); return 1; } randomize(); for(int i=0; i<800; ++i) { fprintf(fHandle, "%-14d %-20s %-20s %-16s %d\n", rand()*10, // ogrenci numarası get_random_name(), // name get_random_surname(), // surname get_random_town(), // town rand()%101); // notu } fclose(fHandle); } ▪ int fscanf(FILE* fHandle, const char pfm,...) ═══════════════════════════════════════════════ Önşart : Dosya Text Mode unda açılmalı. Tıpkı scanf de olduğu gibi, boşluklarla ayrılmış veriyi, formatlı okur. Set ettiği alan sayısını döndürür. Stream de Okuma yapacak bir byte kalmayınca EOF döndürür. 0 döndürmesi demek, stream de bir şeyler var ama formatı uygun olmadığı için set edememiş. █ Örnek: primes50.txt dosyasindaki sayıları okuyup, ekrana yazdıralım #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { FILE* fHandle = fopen("primes50.txt","r"); if( fHandle == NULL ) { fprintf(stderr, "can not open file"); return 1; } int ival; while(fscanf(fHandle,"%d",&ival) != EOF) { printf("%d",ival); _getch(); } fclose(fHandle); } █ Örnek: primes50.txt dosyasini notepad de aç, içine bir yere murat yaz, primes50Murat.txt diye kaydet ve bu dosyadaki sayıları okuyup, ekrana yazdıralım #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { FILE* fHandle = fopen("primes50Murat.txt","r"); if( fHandle == NULL ) { fprintf(stderr, "can not open file"); return 1; } int ival; int retval; char str[1000]; while(1) { retval = fscanf(fHandle,"%d",&ival) if(retval == 1) { printf("%d\n",ival); // düzgün bir okuma } else if(retval==EOF) { break; // dosya sonu } else { fscanf(fHandle,"%s",str); // yazıyı bufferdan cikarmak icin yazı şeklinde okuma yapiyoruz printf("%s gecerli bir sayi degil\n",str) } } fclose(fHandle); } █ Örnek: recs.txt dosyasindan, yalnızca istenen memleketli kayıtları ekrana yazdıralım #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { FILE* fHandle; if((fHandle = fopen("recs.txt","r")==NULL) { fprintf(stderr, "can not open file\n"); return 1; } char town_entry[SIZE]; printf("vilayet girin: "); scanf("%s",town_entry); int no; char name[SIZE]; char surname[SIZE]; char town[SIZE]; int grade; while( fscanf(fHandle, "%d%s%s%s%d", no, name, surname, town, &grade) != EOF ) { if(!strcmp(town, town_entry)) printf("%-14d %-20s %-20s %-16s %d\n", no, name, surname, town, grade); } fclose(fHandle); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 57.Ders C [ File Operations] 27 Ekim 2022 Perşembe ▪ int fgets(char *str, int count, FILE* fHandle) ═══════════════════════════════════════════════ Dosyadan satır satır okuma. Dosya konum göstericisinin göstediği yerden satırın sonuna dek - newLine görene dek ve newLine DAHİL- okur, str ye yazar, sonuna da null char koyar. count : Tipik olarak str dizisinin boyutu.-Maximum number of characters to write - . Belirtilen host edecek diziyi Taşırmamayı gözetmelisin. Okuduklarını yazıp sonuna null karakteri koyar. Geri dönüşü : str on success, null pointer on failure. █ Örnek: recs.txt dosyasini satır satır okuyup ekrana yazalim. #define _CRT_SECURE_NO_WARNINGS #include #include #include #include "nutility.h" #define SIZE 100 int main(void) { char str[SIZE]; FILE* fHandle; if((fHandle = fopen("recs.txt","r")==NULL) { fprintf(stderr, "can not open file\n"); return 1; } while(fgets(str,SIZE,fHandle) != NULL) { printf("%s",str); _getch(); } fclose(fHandle); } Bu örneği SIZE 10 yapıp tekrar çalıştır ve gözle. █ Örnek: ders 57 videosu 19.dk da anlatılıyor ▪ int fputs(char *str, FILE* fHandle) ═══════════════════════════════════════════════ Dosyaya bir satır yazıyor.Ancak newline vermiyor buna dikkat edelim. Geri dönüş değeri, başarı int. Satırı yazabilirse 0, başarısız olursa EOF döndürür. Tipik olarak bunun yaptığını, fprintf ile de yapabilirsin. ▪ Dosya üzerinde yapılan formatsız okuma ve yazma işlemleri ═══════════════════════════════════════════════════════════ Önşart: Dosyayı binary modda eriş. ┌ Yazılacak olan verinin bulunduğu dizi adresi │ ┌ Dizinin bir elemanının boyutu │ │ ┌ Dizinin kaç elemanının yazılacağı bilgisi │ │ │ ┌ Verinin yazılacağı Dosya handle │ │ │ │ ▪ size_t fwrite(const void* vp, size_t elemSize, size_t elemQty, FILE* fHandle) ═══════════════════════════════════════════════════════════════════════════════ Bellekte bir adresten başlayarak, istediğiniz kadar bytelık bir bellek bloğunu dosyaya byte byte yazıyor. Bu byteların neyi temsil ettiği ile ilgilenmez. Geri dönüş değeri, yazdığı block/elem sayısı, byte sayısı değil! ┌ Dosyadan okunacak verinin bellekte aktarılacağı dizinin adresi │ ┌ Dizinin bir elemanının boyutu │ │ ┌ Dosyadan kaç elemanın diziye aktarılacağı │ │ │ ┌ Verinin okunacağı Dosya handle │ │ │ │ ▪ size_t fread(void* vp, size_t elemSize, size_t qty, FILE* fHandle) ═══════════════════════════════════════════════════════════════════════════════ Dosyadaki byteları, olduğu gibi bir bellek alanına aktarmak için. Bu byteların neyi temsil ettiği ile ilgilenmez. Geri dönüş değeri, okuduğu block/elem sayısı, byte sayısı değil! █ Örnek: Bir dosyaya formatsız olarak 1000 tane rastgele tamsayı yazın Eğer bir hata yapmadıysak, dosyanın boyutunun 1000*sizeof(int) olması gerektiğine dikkat ediniz. #include "nutility.h" #include #include int main(void) { FILE* fHandle = fopen("muratNumbers.dat","wb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } randomize(); for(int i=0; i<1000; ++i) { int x = rand(); fwrite(&x, sizeof(int),1,fHandle); } fclose(fHandle); } █ Örnek: Yukarıdaki örnekteki dosyadan - muratNumbers.dat - , sayilari birer birer okuyup, ekrana yazdırınız. #include "nutility.h" #include #include #include int main(void) { FILE* fHandle = fopen("muratNumbers.dat","rb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } int x; for( fread(&x,sizeof(x),1,fHandle) != 0 ) { printf("%d, x); _getch(); } fclose(fHandle); } █ Örnek: Yukarıdaki örnekteki dosyadan - muratNumbers.dat -, sayilari 10'ar 10'ar okuyup, ekrana yazdırınız. #include "nutility.h" #include #include #include #define SIZE 10 int main(void) { FILE* fHandle = fopen("muratNumbers.dat","rb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } int a[SIZE] ={0}; size_t n; for( (n= fread(a,sizeof(a),SIZE,fHandle) ) != 0 ) { printarray(a,n) } fclose(fHandle); } █ Örnek: Bir dosyaya formatsız olarak ilk 1000 tane asal sayıyı yazın Eğer bir hata yapmadıysak, dosyanın boyutunun 1000*sizeof(int) olması gerektiğine dikkat ediniz. #include "nutility.h" #include #include int main(void) { FILE* fHandle = fopen("muratPrimeNumbers.dat","wb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } int prime_count = 0; int x = 2; while(prime_count<1000) { if(isprime(x)) { fwrite(&x, sizeof(int),1,fHandle); ++prime_count; } ++x; } fclose(fHandle); } █ Örnek: Bir dosyaya formatsız olarak 1000 Employee yazın Eğer bir hata yapmadıysak, dosyanın boyutunun 1000*sizeof(Employee) olması gerektiğine dikkat ediniz. #include "nutility.h" #include #include #include "employee.h" int main(void) { FILE* fHandle = fopen("muratEmployees.dat","wb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } randomize(); Employee x ; for(int i=0; i<1000; ++i) { set_employee_random(&x) fwrite(&x, sizeof(Employee),1,fHandle); } fclose(fHandle); } █ Örnek: Daha önceki derslerde, dosya kopyalamayı fgetc ile byte byte okuyup yapmıştık. Şimdi ise , mesela 1024 byte lık bloklar halinde okuyup yazarak bu işi gerçekleştirelim. #include #include #include #include #define SIZE 1024 char buff[SIZE]; int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "Usage: \n"); return 1; } FILE* fHandleSource = fopen(argv[1],"rb") if(fHandleSource==NULL) { fprintf(stderr, "%s Dosyasi acilamadi\n",argv[1]); return 1; } FILE* fHandleTarget = fopen(argv[2],"wb") if(fHandleTarget==NULL) { fprintf(stderr, "%s Dosyasi olusturulamadi\n",argv[2]); return 1; } size_t nReadQty = 0; while( (nReadQty= fread(buff,sizeof(buff),SIZE,fHandleSource) ) != 0 ) { fwrite(buff,sizeof(buff),nReadQty,fHandleTarget); byteCount += nReadQty*sizeof(buff); } fclose(fHandleSource); fclose(fHandleTarget); printf("%zu bytelık %s dosyasi kopyalanarak, %s isimli kopyasi olustu\n",byteCount,argv[1], argv[2]); } █ Ödev :Bir dosyaya formatsız olarak 1000 Employee yazmıştık. Bu dosyayi okui sonrasinda memleketlerine göre ayrı dosyalara yaz. Mesela izmir.dat dosyasında , memleketi izmirliler olsun. Dolayısıyla 81 tane dat dosyasi oluşabilir. ▪ Dosya Konum Gösterici - File Pointer Fonksiyonları ═══════════════════════════════════════════════════════════ ▪ fseek : FilePointer a konum set etmek için ▪ rewind: FilePointer'ı dosya başına getirmek için ▪ ftell : FilePointer'ın değerini öğrenmek için ▪ fsetpos : FilePointer'ın değerini set etmek için ▪ fgetpos : FilePointer'ın değerini get etmek için ▪ void frewind(FILE* fHandle) ═════════════════════════════ FilePointer i dosya başını gösterecek şekilde konumlar. ┌ Dosya handle │ ┌ Byte cinsinden mesafe stdio.h daki makrolar: │ │ ┌ Konumlama referansı ▪ SEEK_SET : Dosyanın başına göre │ │ │ ▪ SEEK_CUR : Halihazırda bulunduğu konuma göre │ │ │ ▪ SEEK_END : Dosyanın sonuna göre ▪ int fseek(FILE* fHandle, long n, int origin) ═══════════════════════════════════════════════ Dosya Konum Göstericisi ↓ frewind(f) sonrası Dosyadaki bytelar ■□□□□□□□□□□□□...□□□□□□□□ indexi 0 ↓ fseek(f,8L,SEEK_SET) sonrası Dosyadaki bytelar □□□□□□■□□□□□□...□□□□□□□□ indexi 0 8 ↓ fseek(f,2L,SEEK_CUR) sonrası Dosyadaki bytelar □□□□□□□□■□□□□...□□□□□□□□ indexi 0 10 ↓ fseek(f,-1L,SEEK_CUR) sonrası Dosyadaki bytelar □□□□□□□■□□□□□...□□□□□□□□ indexi 0 9 ↓ fseek(f,0L,SEEK_END) sonrası Dosyadaki bytelar □□□□□□□□□□□□□...□□□□□□□□ (bu konumda yazma yaparsan, append etmiş gibi olursun) indexi 0 ↓ fseek(f,-2L,SEEK_END) sonrası Dosyadaki bytelar □□□□□□□□□□□□□...□□□□□□■□ indexi 0 ▪ fseek(f,0L,SEEK_CUR) ══════════════════════ Bu anlamsız gibi gözükse de önemli bir anlamı var aslında. Eğer son yapılan işlem , Dosyadan okuma ise ancak bu noktadan sonra Yazma işlemi yapılacaksa, yada Eğer son yapılan işlem , Dosyaya yazma ise ancak bu noktadan sonra Okuma işlemi yapılacaksa Yani, okumadan-yazmaya veya yazmadan-okumaya geçilecekse, öncesinde bu komut işletilmelidir. Aksi takdirde UB! █ Örnek: muratPrimeNumbers.dat dosyasından kullanıcının istediği bir asal sayıyı, mesela beşinci asal gibi , sağlayan bir prog yazınız #include "nutility.h" #include #include int main(void) { FILE* fHandle = fopen("muratPrimeNumbers.dat","rb") if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } int x,n; printf("kacinci asal sayi: "); scanf("%d", &n); fseek(fHandle, (n-1)* (long)(sizeof(int)), SEEK_SET); fread(&x,sizeof(x),1, fHandle); printf("%d. asal sayi %d\n",n,x) fclose(fHandle); } ▪ long ftell(FILE* fHandle) ═════════════════════════════ FilePointer in değerini döndürür. Başarısızlık durumunda long türünden -1 döndürür. █ Örnek: #include "nutility.h" #include #include int main(void) { FILE* fHandle = fopen("muratPrimeNumbers.dat","rb"); if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } printf(" Dosya Konum Gostericisi Degeri: %ld\n",ftell(fHandle) ); // Dönecek olan değer 0, çünkü dosya ilk açıldığında dosya başı gösterilir. fseek(fHandle,0L, SEEK_END); printf(" Dosya Konum Gostericisi Degeri: %ld\n",ftell(fHandle) ); // Dönecek olan değer dosya uzunluğudur, append için hazır konum fclose(fHandle); } █ Örnek: #include "nutility.h" #include #include #include "employee.h" void print_records(const char* filename) { FILE* fHandle = fopen(filename,"rb"); if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } Employee e; while( fread(&e, sizeof(e), 1, fHandle) ) print_employee(&e); } int replace_names(const char* filename, const char* oldname, const char* newname) { FILE* fHandle = fopen(filename,"rb+"); if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } Employee e; int replace_count=0; while( fread(&e, sizeof(e), 1, fHandle) ) { if(!strcmp(e.m_name, oldname) { ++replace_count; strcpy(e.m_name, newname); fseek(fHandle, -(long)sizeof(e), SEEK_CUR); fwrite(&e, sizeof(e), 1, fHandle); fseek(fHandle, 0L, SEEK_CUR); } } fclose(fHandle); return replace_count; } int delete_records(const char* filename, int month_day) { FILE* fHandleSource = fopen(filename,"rb"); if(fHandleSource==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } char tempfilename[L_tmpnam]; tmpnam(tempfilename); FILE* fHandleDest = fopen(tempfilename,"wb"); if(fHandleDest==NULL) { fprintf(stderr,"Gecici Dosya acilamadi\n"); return 1; } Employee e; int n_Read=0; int n_Write=0; while( fread(&e, sizeof(e), 1, fHandleSource) ) { ++n_Read; if(e.m_bdate != month_day) { fwrite(&e, sizeof(e), 1 , fHandleDest); ++n_Write; } } fclose(fHandleSource); fclose(fHandleDest); remove(filename); rename(tempfilename, filename); return n_Read-n_Write; } void sort_records( const char* filename, int (*fp)(const Employee*, const Employee*) ) { typedef int(*Cmpfunc)(const void*, const void*) ; // Belleğe alıp orada sıralamayı yapacağım FILE* fHandle = fopen(filename,"rb+"); if(fHandle==NULL) { fprintf(stderr,"%s Dosya acilamadi\n", filename); return 1; } //Dosyada kaç kayıt var öğrenmeliyim. fseek(fHandle,0L,SEEK_END); size_t n = ftell(fHandle) / sizeof(Employee); //Tekrar dosyanin başına konumlanalım rewind(fHandle); //Dosyadaki kayıtları, dinamik diziye aktaralım Employee* p = (Employee*) malloc(n * sizeof(Employee) ); if(!p) { fprintf(stderr, "bellek yetersiz\n"); } fread(p, sizeof(Employee), n , fHandle); qsort(p, n, sizeof(*p), (Cmpfunc) fp); //Tekrar dosya başı yapacağım, bu kez yazmaya başlayacağım rewind(fHandle); fwrite(p, sizeof(Employee), n, fHandle); free(p); fclose(fHandle); } int cmp_employee_by_date(const Employee* p1, const Employee* p2) { return cmp_date(&p1->m_bdate, &p2->m_bdate ); } int main(void) { // Test 1 nusret isimli kişilerin isimlerin NUSRETHAN olarak değiştirelim // ─────────────────────────────────────────────────────────────────────── printf(" Mevcut Durum \n") print_records("muratEmployees.dat"); printf("************************\n") int n = replace_names("muratEmployees.dat","nusret","NUSRETHAN"); printf("************************\n") printf("%d kayit degistirildi\n" ,n) printf("************************\n") printf(" Degisim sonrasi Durum \n") print_records("muratEmployees.dat"); // Test 2 mevcut kayıtları, doğum tarihlerine göre sıralayalım // ─────────────────────────────────────────────────────────────────────── sort_records("muratEmployees.dat", &cmp_employee_by_date); printf("Dogum Gunune gore siralandi\n") printf("************************\n") print_records("muratEmployees.dat"); // Test 3 Ayın 10 20 ve 30 unda doğanlar kalsın diğerlerini silelim // ─────────────────────────────────────────────────────────────────────── for(int i=1; i<=31; ++i) { if(i%10 !=0 ) // Ayın 10 20 veya 30 u harici doğanları dosyadan silelim { int y= delete_records("muratEmployees.dat",i); printf(%d gunu dogan %d kayit silindi\n",i,y); } } printf("Ayin 10-20-30 u doganlar\n") printf("************************\n") print_records("muratEmployees.dat"); } ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 58.Ders C [ File Operations ] 1 Kasım 2022 Salı ▪ int fsetpos(FILE* fHandle, const fpos_t *ptr) ════════════════════════════════════════════════ FilePointer in değerini set eder. Set edilen değer fpos_t türü bir nesnededir. ▪ int fgetpos(FILE* fHandle, fpos_t *ptr) ═════════════════════════════════════════ FilePointer in değerini döndürür. █ Örnek: #include "nutility.h" #include #include int main(void) { FILE* fHandle = fopen("muratPrimeNumbers.dat","rb"); if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } for(int i=0; ,i<100; ++i) { fread(&x, sizeof(x), 1, fHandle); } fpost_t pos; fgetpos(fHandle, &pos); printf(" Dosya Konum Gostericisi Degeri: %lld\n",pos ); //... Başka işlemler //sonrasında kaldığım yere konumlayayım fsetpos(fHandle, &pos); fclose(fHandle); } Kalan fonksiyonlarımız: feof ferror fflush clearerr ▪ int feof(FILE* fHandle) ve eof flagi ════════════════════════ Okuma işlemi devam ederken, en sona gelip de okunacak bir şey kalmayınca, okuma fonksiyonu başarısızlık döndürecek. Dosyada okunacak byte kalmayıp da tekrar bir okuma yapıldığında, arka plandaki kodlar bir bitsel flag - eof flag - set ediyorlar. İşte feof fonksiyonu bu flagin durumunu geri döndürüyor. Boolean int, yani 0 - Nonzero döndürür. Okuma fonksiyonlarıda , gereksiz işyüküne girilmesin diye, aslında her çağrıldıklarında bu flag a bakıp ona göre okuma çabasına giriyorlar. Eğer bu flag set li ise , okuma girişiminde bulunmuyor. Böylesi bir duruma girildiğinde, tekrar okuma girişimi öncesi bu flag reset edilmelidir. Bu bayrağın reset edilmesi için std kütüphane şunu belirtiyor: File Pointer set eden bir fonksiyon çağrılmalıdır, ya da clearerr fonksiyonu çağrılmalıdır. █ Örnek: 100k defa sorulan "Satır satır dosyadan okuma yapıyorum, son satırı iki kez okuyor" diyenlerin yazdığı kod int main(void) { char str[5000]; FILE* fHandle = fopen("myfile.txt","r"); if(fHandle==NULL) { fprintf(stderr,"Dosya acilamadi\n"); return 1; } while(!feof(fHandle)) { fgets(str,5000,fHandle); printf("%s",str); } fclose(fHandle); } Çünkü, son satırı okuduğunda, FilePointer dosya sonundadır, ancak henüz flag set edilmedi, çünkü okuma başarısız değil. Dolayısıyla, while döngüsüne bir kez daha girecek, fgets ile bir kez daha okuma girişiminde bulunulacak, bu kez başarısız olacak ve str değiştirlmeyecek ancak başarısız okuma flagi set edilecek, ve sonrasıda printf ile str'nin en son sahip olduğu değer bir kez daha ekrana yazdırılacak. Flag set edildiği içinde döngü sonlanacak. Bu hatadan imtina etmek için döngü şöyle kurulmalıydı while(fgets(str,5000,fHandle)) { printf("%s",str); } ▪ Dosyadan okuma yaptım ve okuma işlemi başarısız oldu.Ama neden? Bununla ilgili temalar şöyle olabilir: ════════════════════════════════════════════════════════════════════════════════════════════════════════ ▪ Okunacak byte kalmamıştır. ve okuma girişimi sonucu okuma işlemi başarısız olur. Doğal neden. bu durumda feof non-zero döndürüyor olacaktır. ▪ fscanf gibi formatlı okuma yaparken, format uyumsuzluğundan okuma başarısız olmuş olabilir. ancak bu durumda feof hala 0 döndürüyor olacaktır. ▪ Sistemden kaynaklanan bir sebepten - mesela dinamik bellek alanı ayrılamaması gibi - olabilir. Ancak u durumda feof hala 0 döndürüyor olacaktır. ▪ int ferror(FILE* fHandle) ve error flagi ════════════════════════ eof flag'inden başka bir flag daha var. error flag. Bu fonksiyon sayesinde error flag inin durumunu öğrenebiliyoruz. ▪ void clearerr(FILE* fHandle) ════════════════════════ Hata bayraklarını clear eder. Ancak hata bayraklarını clear etmenin bir diğer yolunun, Dosya Konum Göstericisini konumlamak olduğunu unutmayalım.( fseek, rewind, fsetpos çağrıları) Hata bayraklarını clear etmeden okuma isteğinde bulunursak, fonksiyon hiç oralı olmaz. Okuma girişiminde bulunmaz. ▪ int fflush(FILE* fHandle) ════════════════════════ Başarı int döndürüyor. Okuma ve yazma işleri arka planda bir buffer (bellek alanı) ile gerçekleşiyor. Mesela dosyaya yazan fonksiyonlar, aslında bir buffera aktarıyor, bufferdan da dosyaya fiili aktarım / yazım yapılıyor. İşte bu işleme dosyanın/buffer in flush edilmesi - fiili yazımı - deniyor. flush edilmeyi sağlayan, bir kısmı std, bir kısmı platforma bağlı bazı event ler var. Bu eventlarden bazıları şunlar : ▪ buffer alanını dolduğunda buffer flush edilir. ▪ dosya kapatıldığında, buffer flush edilmemişse, buffer flush edilir. ▪ program normal terminate edildiğinde dikkat, abort ile abnormal terminate edildiğinde, böyle bir garanti yok. ▪ fflush fonksiyonunu çağırmak ▪ her yazma fonksiyonu ile birlikte gerçekten dosyaya flush edilsin talimatı - bufferlama iptal edilmişse Buna No Buffering deniyor - ▪ standard output ta bir dosya gibi ele alındığından, printf de std outputa yazdığına göre , aslında o yazının da std outta çıkma , ekranda hemen görünme garantisi yok. Dolayısıyla, std output için de flush yapabiliriz. Ancak std input ve std output birbirine tie edilmiştir/bağlanmış durumdadır. Yani, bu şu demek oluyor: std in den okuma yapıldığında - scanf getc vs - önce stdout un buffer i flush ediliyor. Dolayısıyla printf ile prompt sonrası scanf yaparken, acaba printf ile yazdırdığım ekranda görüntülenir mi diye endişelenmene gerek yok. fflush(NULL) ; ile birlikte açık olan tüm dosyaların bufferları flush edilmiş oluyor. ▪ stdin stdout stderr ════════════════════════ OS lerde, bir program çalışmaya başladığında, tipik olarak, 3 tane stream kullanıma hazır hale gelir. stdout , tipik olarak konsola bağlı, istersen başka bir device a yönlendirebilirsin. stdin, tipik olarak klavyeye bağlı, istersen başka bir device a yönlendirebilirsin. stderr, hata mesajlarının yazıldığı stream, tipik olarak konsola yönlendirilmiştir. Bunlar keyword değil. Std kütüphanenin belirlediği identifier lar. Bunlarla aslında FILE* türünden varlıklar. Dolayısıyla, FILE* parametresine bunları da gönderebilirsin. printf("murat"); yazmak ile fprintf(stdout, "murat"); yazmak aynı anlama gelir. Ekrana yazar. putchar('A'); yazmak ile fputc('A', stdout); yazmak aynı anlama gelir. Ekrana yazar. stderr akımında konsola yönlendirildiğinden, fprintf(stderr,"hata hata hata\n"); ekrana yazar. int c = getchar(); yazmak ile, int c = fgetc(stdin); yazmak aynı anlama gelir. Klavyeden bir karakter okur. scanf("%d%d",&x,&y); yazmak ile, fscanf(stdin,"%d%d",&x,&y); yazmak aynı anlama gelir. Klavyeden iki tane sayı okur. Dikkat : Programı kullananı ilgilendiren hata mesajlarını asla stdout'a yazdırmayın. Çünkü stdout başka bir device a yönlendirilmişse, programı kullanan bunları göremeyecektir. Bu bilgilerle birlikte, birtakım dataları ekrana yazdırdığımız fonksiyonlarımızı, daha esnek kurgulama imkanımız var. Şöyleki, print_date(const Date* p) fonksiyonumuzu düşünelim. print_date(FILE* fHandler, const Date* p) olarak değiştirsek ve ekrana yazan printf(..); çağrısını, fprintf(fHandler,..); şekline büründürsek artık istersek bunu ekrana yazdırmak için print_date(stdout, ..) şeklinde bir dosyaya yazdırmak için print_date(fHandler,..) şeklinde kullanabiliyor olabiliriz. char str[10]; dizisine yazı almak istiyor olalım. fgets(str,10,stdin); ile taşma riski olmadan klavyeden alabiliriz. █ Örnek: fgets ile klavyeden yazı almaya karşılık, sorulan tipik mülakat sorusu: int main(void) { char str[10]; printf("isminizi giriniz: "); fgets(str,10,stdin); // add-on buraya if(!strcmp(str,"ali can")) { printf("hosgeldin ali can\n"); } else { printf("Sen ali can degilsin mekana giremezsin\n"); } } Yukarıdaki program çalıştırıllıp , ali can girildiği halde ekrana Sen ali can degilsin mekana giremezsin yazıyor. Neden? Yanıt: fgets newline görene dek alıyor ama newline ı da alıyor. yani ali canNewLine olarak aldı. Bu yüzden ali can ile karşılaştırınca eşit çıkmadı. Peki bunun üstesinden nasıl gelebiliriz? Yanıt: str nin sonunda newline varsa onu silip yerine null char koyabiliriz. //add-on buraya satırının olduğu yere aşağıdaki kodu monte edelim. char* p; if( (p=strchr(str,'\n')) != NULL ) *p= '\0' ▪ FILE* tmpfile() ════════════════════════ Bazen geçici amaçla kullanmak üzere bir dosyaya ihtiyacımız olur. char* tmpnam(char *filename) ile bu ihtiyacı karşılayabiliyorduk. Alternatif olarak, fHandle = tmpfile(); diye kullanarak , Bize, wb+ modunda açılmış, bir dosya handle'ı veriyor. Bizden isim istemiyor. fclose(fHandle); deyince de dosyayı siliniyor. ┌ Dosya handle │ ┌ Buffer² stdio.h daki makrolar: │ │ ┌ buffering mode ˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖˖ ▪ _IOBF : Full Buffering ³¹ [default durum] │ │ │ ▪ _IONBF: No Buffering ³² │ │ │ ┌ Size for buffer⁴ ▪ _IOLBF: Line Buffering ³³ ▪ int setvbuf(FILE* fHandle, char* buf, int bufMode, size_t bufSize) ═════════════════════════════════════════════════════════════ Önşart, dosyanın açılmış olması, ancak henüz hiçbir işlem - okuma/yazma/filepointer set etme gibi - yapılmamış olması gerekiyor.Aksi takdirde UB! Bu fonksiyon, bir dosyanin, bufferlanma stratejisini değiştiriyor/set ediyor. ² Buffer olarak kullanılacak bellek adresi geçiyoruz. NULL geçersek, default sistem/implementasyon ne veriyorsa onu kullanalım demek. Kendi bufferimizi kullandiracaksak, dosya kapatilana dek , buffer alanımızın hayatta olduğundan emin olmalıyız. ³¹ Hep konuşageldiğimiz, önce buffer a yazılacak , oradan flush edilerek dosyaya/stream e fiziksel yazım. ³² Her yazma işiyle birlikte, buffer kullanılmadan, direkt stream e fiziksel yazım. Bu modda iken diğer argumanların bir önemi kalmıyor. ³³ Flush için bizden newLine karakterini beklemesi. Buffer a newLine gelince flush edilir. Her zaman gerçeklenme garantisi yok. OS 'e de bağlı. ⁴ Buffer size ini set ediyoruz. Eğer ikinci parametreye NULL geçmişsek, buradaki parametre, sistemin verdiği buffera size set etmek istiyoruz anlamına gelecek. Eğer bunu değiştirmek istemiyorsak, sistemin verdiği standard buffer size ile ilgili makroyu parametreye geçeriz. BUFSIZ ┌ Dosya handle │ ┌ Buffer ▪ void setbuf(FILE* fHandle, char* buf) ═══════════════════════════════════════ Bazı çağrıları kolaylaştırmak için bu fonksiyonu vermişler. Yani bufferSize ve buffer Mode unu değiştirmeden kullanmak istersek diye verilmiş. Bir diğer deyişle, sadece buffer bellek alanını bildiriyoruz o kadar. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 59.Ders C [ Variadic Functions , Compound Literals , VLA , staticAssert ve assert makrosu] 3 Kasım 2022 Perşembe ■ Variadic Functions ═════════════════════ Giriş-Çıkış fonksiyonlarında karşımıza çıkmıştı. printf, scanf gibi. Belirli kurallara uyarak, bizde variadic fonksiyon yazabiliriz. Sözgelimi n tane sayıdan en büyük olanı bulan gibi.. Dikkatle çağrılması/kullanılması gerekiyor. Typesafe değil.Derleyici diagnostic vermek zorunda değil. Variadic argumanların türlerinin doğru olması mühim. UB ye çok açık. Aman dikkat. Bir fonksiyonun Variadic olduğunu, fonksiyonun son parametresindeki ellipsis atomundan anlıyoruz. Variadic parametreden önce en az bir parametre olmak zorunda. Ve bunlara arguman geçilmesi gerekiyor. Öte yandan variadic arguman geçilmek zorunda değil. Vardiadic parametrelerin türü görülmediğinden, derleyici tür dönüşümüde yapamıyor. Ancak, ezbere uyguladığı, bir istisna var: int altı türleri int'e dönüştürerek, float türünü ise double'a dönüştürerek yolluyor. Variadic fonksiyon tasarlanırken, çağrıda kaç tane variadic parametre geçildiğini anlamak, variadic fonksiyonun sorumluluğunda. Bunun da tipik olarak 3 tane yolu var: ▪ Bir parametrenin arguman sayısı için kullanılması, There is no magic. i.e sum(3, a,b,c) 3 variadic parametre geçiyorum anlamında ▪ printf,scanf (const char*,... da olduğu gibi ilk parametre de istediği string in içinde kullanılan conversion specifier sayısından bu bilgiyi çekiyor. ▪ Bir başka tema, variadic parametrelerden birinin sentinel-ayraç- olarak kullanılması. i.e sum(3,5,6,7,NULL) gibi Variadic fonksiyonların yazılmasında, Std kütüphanenin sağladığı - başlık dosyasındaki - bazı makrolar kullanılıyor. █ Örnek: İlk parametresi, kaç tane variadic parametre geçildiği olan, toplam bulduran bir variadic fonksiyon yazalım. #include int sum(int n, ...) { → va_list args; → va_start(args,n); // variadic parametrelerden önceki son sabit parametre, buraya geçilmeli. In our case it is n. ────────────────────────────────── Function code & Parametrelere ulaşım int sum = 0; while(n--) { ┌ Beklediğimiz Variadic parametrelerin türünü belirtiyoruz. sum += = va_arg(args,int) } ────────────────────────────────── → va_end(args); return sum; } → Bu satırlar, variadic fonksiyon template inin bir parçası. Olmazsa olmaz. █ Örnek: #include int get_max(int n, ...) { → va_list args; → va_start(args,n); // variadic parametrelerden önceki son sabit parametre, buraya geçilmeli. In our case it is n. ────────────────────────────────── Function code & Parametrelere ulaşım int max; for(int i= 0; i max) max = ival; } ────────────────────────────────── → va_end(args); return max; } → Bu satırlar, variadic fonksiyon template inin bir parçası. Olmazsa olmaz. █ Örnek: Bir variadic func yazalım, kendine gönderilen yazıları bir dinamik dizide birleştirsin ve dizinin adresini döndürsün #include #include char* strapp(const char* p, ...) { → va_list arglist; va_list args; → va_start(arglist,p); // variadic parametrelerden önceki son sabit parametre, buraya geçilmeli. In our case it is p. va_copy(args,argslist) // Argumanları dolaşarak, ihtiyacım olacak karakter sayısını tespit edeyim size_t len = strlen(p); const char * pTemp ; while( (pTemp = va_arg(arglist, const char*))!= NULL ) { len += strlen(pTemp); } → va_end(arglist); // Dinamik bellek alanı tahsis et char * pResult = (char*) malloc(len + 1); if(!pResult) return NULL; //Gelen argumanları birleştir strcpy(pResult,p); while( (pTemp = va_arg(args, const char*))!= NULL ) { strcat(pResult,pTemp); } → va_end(args); // Neticeyi döndür return pResult; } int main(void) { char s1[] = "cinar"; char s2[] = "gursoy"; ┌ Arguman sayisini tespit etmek için seperator amacıyla kullanacağım. char *pd = strapp(s1,s2,"volkan ","gundogdu", NULL); printf("%s\n", pd); free(pd); } → Bu satırlar, variadic fonksiyon template inin bir parçası. Olmazsa olmaz. ▪ vprintf, vfprintf, vsprintf, vsnprintf, vprintf_s, vfprintf_s, vsprintf_s, vsnprintf_s ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════ Variadic fonksiyonlar içinde kullanılan giriş-çıkış fonksiyonlarıdır. █ Örnek: #include void func(const char * p, ...) { → va_list args; → va_start(args,p); // variadic parametrelerden önceki son sabit parametre, buraya geçilmeli. In our case it is n. ────────────────────────────────── Function code vprintf(args); // Ekrana yazdıralım char buffer[1000]; vsprintf(buffer, p, args); // Değişkene yazdıralım ────────────────────────────────── → va_end(args); } int main(void) { int x = 10; int y = 20; double dval = 4.5; func("%d %d %f\n",x,y,daval); } → Bu satırlar, variadic fonksiyon template inin bir parçası. Olmazsa olmaz. ■ Compound Literals - C99 ile dile eklenen bir araç. C++ dilinin standardında yok. ═══════════════════ Öyle durumlar var ki, mesela bir kez kullanmak gibi, bir isim vermeden değişken gibi değerleri ifade içinde kullanma amacıyla bir araç. Pekala isim vererek de yapabiliriz. Ancak bu araçla birlikte, isim alanına bir isim enjekte etmemiş oluyoruz. Derleyici bunun için aslında otomatik ömürlü bir değişken kullanıyor. Ömrü tanımlandığı blok içinde olan. (int){ 14 } ifadesinde int türünden değeri 14 olan isimsiz bir değişken gibi ifade içinde kullanabilirsin. █ Örnek: void func(const int * ptr ) void func(const int * ptr, size_t ) { { printf(" *ptr = %d\n", *ptr ); while(size--) printf( %d\n", *ptr++ ); } } int main(void) int main(void) { { func( &(int){ 14 } ); // okay func( (int[]){ 1,2,3,14,11 } , 5 ); // okay } } void func(int x, int y , int z ) int main(void ) { { foo ( (int[3]) {x,y,x} ); // okay print_date( & (Date) {3,5,1987} ); // okay } int main(void) Date mydate; { //code int *p = (int[]) { 1,2,3,14,11 }; //okay mydate = (Date) {3,11,2022}; // okay p[3] = 33; // okay struct tm* tp = localtime( & (time_t) {time(0)} ) ; // okay } } int main(void) int main(void) { { int *p; print_array( (int[10] ) {[3] = 3, [5]=5 } , 8 ); //designated initializer is also okay } if(1) { p = & (int) {10}; } printf("%d\n",*p); // UB , çünkü p blok içinde yaşayan bir nesneyi gösteriyor, // ancak şu an blok dışındayız ve nesne hayatta değil. // *p ile hayatta olmayan nesneye erişmeye çalışıyoruz bu yüzden UB. } struct Data { int a,b,c; double dx,dy; char str[20]; } int main(void) { struct Data *pd = & (struct Data) { .b = 12, .dx = 4.5, .str="murat" }; //designated initializer is also okay } ■ VLA - Variable Length Array - Değişken boyutlu diziler ═══════════════════════════════ C99 ile dile eklendi, ancak C11 ile opsiyonel hale geldi. Dolayısyla desteklenip desteklenmediğini sorgulamak için makro da verildi. If the compiler defines the macto constant __STDC_NO_VLA__ to ineteger constant 1 , then VLA and VM types are not supported. Dile önce koyup sonra opsiyonel hale getirerek uzaklaştırmalarının ana sebebi, VLA in güvenlik açığı oluşturabilmesi. Normalde dizinin boyutunu constant expression ile belirliyorum. VLA desteği olunca, dizi boyutunu değişkenle belirleyebiliyorum. void func(int x) { int a[x]; //When VLA , okay. } VLA dizilere ilk değer veremiyoruz! Öyle bir sentaks yok! sizeof operatörünün compile time da değer üretme özelliği de ortadan kayboluyor! █ Örnek: #include #include #include void func(int x) { int a[x]; printf(" sizeof(a) = %zu ", sizeof(a) ); printf("%02 ",x); for(int i=0; i #include #include #include int icmp(const void* vp1, const void* vp2) { //icmp dizinin elemanlarının türünün int olduğunu biliyor if ( *(const int*)vp1 > *(const int*)vp2 ) return 1; if ( *(const int*)vp1 < *(const int*)vp2 ) return -1; return 0; } double get_median(const int* p, size_t size) { int a[size]; memcpy(a,p,size*sizeof(int)); qsort(a,size,sizeof(*a),&icmp); return p[size/2]; } int main(void) { int x[10] = {2,6,-4,2,8,43, 29}; printf("%d\n", get_median(a, 7); } ■ Variably Modified Type ════════════════════════ VLA dizilere de pointer oluşturmamı sağlıyor. int x = 10; int a[x]; &a türü int(*)[x] Bu şu işe yarayabilir: █ Örnek: Mesela değişken boyutlu bir matris ele alalım. Dinamik bellek yönetimi konusunda, düzleştirme yaparak int *p = malloc(row*col*sizeof(int)) ile bellek alanı ayırıp, bununla ilerlemiştik. Ancak bu yaklaşımda p[3][5] gibi bir kullanım kolaylığından yoksun kalmıştık. int main(void) { int row, col; printf("matris satir ve sutun sayisini girin:\n"); scanf(%d%d", &row,&col); int (*p)[row][col] = malloc(sizeof(*p)); // malloc(row*col*sizeof(int)) de yazabilirdik // Üstelik bunda bir güvenlik sorunu da yok. Çünkü dinamik bellek yönetimi ile çalışıyorum. // Ancak kullanım daha şık/kolay. for(int i=0; i 9) // Bu kısım { // assertion, doğrulama kodu printf("Buraya gelen değer range dışında"); // olarak abort(); // anılıyor. } // Hatayı yakalamaya yönelik. ... } gibi yazabilirim. Eski dönemlerde, static assert in olmadığı dönemlerde çeşitli hileler kullanılıyordu. Mesela, int 'in sizeof unun 2 den büyük olduğu sistemlerde çalışabilecek bir kodunuz var. Derlemenin yapıldığı sistemin bu şekilde olup olmadığını anlamak istiyoruz diyelim. Eğer uygunsuzsa , kod derlenmesin istiyoruz. Bu durumda, int main(void) { if(sizeof(int) == 2 ) {...} } şeklinde yazabiliriz demeyin. Bu , runtime da tespit etmeye yönelik. İstenen şey, compile time da tespit etmek. Dizilerin boyutunun 0 olamayacağından faydalanabiliriz. ( gcc de 0 boyutlu dizileri kullanmaya yönelik extension var! Yani yinede dikkatli olmak lazım) █ Örnek: typedef int testIntSizeOf[sizeof(int) > 2]; Burada sizeof(int) > 2 ifadesi compile time da değerlendirilecek, yanlışsa 0 döndürecek, dolayısyla [0] yüzünden patlayacak. doğruysa herhangibir dizi tanımlamış olmayacağım, sadece bir tür eş ismim olacak. Bir diğer hile yapma unsuru, union içine 0 boyutlu bitfield eleman konamaması. █ Örnek: union testIntSizeOf { int x: (sizeof(int) > 2); } Artık hileye hurdaya gerek kalmadı, C11 ile dile _Static_assert geldi. ═══════════════════════════════════════════════════════════════════════════════════════════════════ ▪ Static Assertion : Compile time assertion. Koda bakarak, derleme zamanında demek █ Örnek: _Static_assert( (sizeof(int) > 2) , "int turunun sizeof degeri yetersiz"); ▪ Dynamic Assertion : Run time assertion, Çalışma zamanına yönelik doğrulamalar assert makrosu , başlık dosyasındadır. Debug sürümünden release sürümüne geçerken, │ etrafta yazılmış bir çok assertion kodlarını artık kaldırmak isteyebiliriz. ↓ başlık dosyası öncesi #define NDEBUG da yapabilirsin. Böylelikle elle silmeye gerek kalmaz. #ifdef NDEBUG tüm assert olan satırlar, derleyici tarafından (void 0); ile etkisiz kod haline gelir. #define assert(condition) ((void)0) #else #define assert(condition) /*implementation defined*/ #endif The definition of the macro assert depends on another macro, NDEBUG, which is not defined by the standard library. ▪ If NDEBUG is defined as a macro name at the point in the source code where is included, then assert does nothing. ▪ If NDEBUG is not defined, then assert checks if its argument (which must have scalar type) compares equal to zero. If it does, assert outputs implementation-specific diagnostic information on the standard error output and calls abort() assert makrosunun özünde (void) ( (!!expression) ) || (_wassert(....),0 ) var. expression ifadesi doğru ise akış devam eder. Short Circuit davranışından dolayı. expression ifadesi yanlış ise _wassert(....) ifadesini yürütür, ki o da mesajı basar ve abort u çağırır. █ Örnek: assert( p != NULL ); // means, make sure that p is not NULL █ Örnek: #include // uncomment to disable assert() // #define NDEBUG #include #include int main(void) output with NDEBUG not defined: { a.out: main.cpp:10: main: Assertion `x >= 0.0' failed. double x = -1.0; assert(x >= 0.0); output with NDEBUG defined: printf("sqrt(x) = %f\n", sqrt(x)); sqrt(x) = -nan return 0; } ═══════════════════════════════════════════════════════════════════════════════════════════════════ Hoca bu kitabı oku diyor. Steve Maguire - Writing Solid Code - Hoca Barrgroup blog yazılarına gözatın diyor ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 60.Ders C [ Flexible Array Member ve C99 ile eklenen araçlar ] 10 Kasım 2022 Perşembe ■ Flexible Array Member - Esnek dizi ögesi - ════════════════════════ Daha çok sistem programlama tarafında ihtiyaç duyulan bir araç. C99 ile dilin standardına kondu. Evvelden, derleyici uzantısı olarak destekleniyordu. Peki bu ne demek? Yapı nesnelerine ilişkin enterasan bir kural. struct Data { int x,y,z; int arr[]; → // Flexible Array member. Köşeli parantez içine bir şey yazılmadığına dikkat edelim. }; // Son eleman olmak zorunda ! // Yapı içinde bir tane böyle bir elemana müsaade var. ikinci bir tane daha olamaz. // Bu eleman yapı içinde yer kaplamıyor. inanmazsan printf("%zu\n", sizeof(struct Data)); ile kendin bak istersen. // Bu dizinin boyutu , programın çalışma zamanında belirlenecek. Dynamic Memory Management ile bir struct Data türünden nesne hayata getirmeye çalıştığımızı düşünelim. Ne kadar yer tahsis etmek lazım? Normalde derdik ki, malloc( sizeof(struct Data) ) İşte , bu hayata getireceğimiz struct Data türünden nesnenin, dizisinin kaç elemanlı olmasını arzu ediyorsam, bellekte yer tahsisatını ona göre yapıp, dizimizden buna göre istifade edebiliriz. Sözgelimi, bir nesne hayata getirelim ve içindeki dizi 10 elemanlı olsun istersem struct Data* p = (struct Data*) malloc( sizeof(struct Data) + 10 * sizeof(int) ); Artık p->arr[3] = 3; yapabiliriz. Yapı nesnelerinde atama operatörü çalışıyordu. Peki her bir instance inda, farklı uzunluğa sahip diziler olabilecek yapı nesnelerinde yine çalışır mı? Nasıl oluyor? Yanıt: Hayır. Flexible Array memberların, atama yoluyla kopyalanması yapılmıyor. İstiyorsa Programcı kendisi yapmalı! █ Örnek: Yapı nesneleri arası atamalarda , flexible array memberlar kopyalanmaz. struct Data mydata; mydata = *p; for(int i=0; i başlık dosyası - C99 ile eklenen, Fixed-with integer türlerini sunar. ════════════════════════════ Fixed-with integer türlerini sunar. Bilhassa embedded taraf için önemi yüksek - Portability namına - Bunlar içinde optional olan - derleyicinin sunmak zorunda olmadıkları da - var. Örn:int32_t gibi İlişkili olarak hatırlatalım: dosyasında CHAR_BIT makrosu, o platformda adreslenebilen en küçük bellek biriminin - 1 byte - kaç bit olduğunu gösteriyor, intptr_t - optional - ════════ Öyle durumlar var ki, elimizde bir adres var, ancak biz onu bir tamsayı gibi kullanmak istiyoruz. intprt_t x = (intptr_t) vptr; Bunda bir veri kaybının olmayacağı garantisi var. intptr_t yeterince, adresi tutabilecek kadar büyük. Mesela hashing işlemleri. Hash Table, constant time da bir değeri - buna anahtar deniyor - anahtarla arama yapmak (var mı yok mu diye sorgulamak ), anahtara karşılık gelen değere erişmek, için kullanılan veri yapılarından biri. Bu çok sık kullanılan bir yapı/işlem. Hepsine sırayla bakarak aramak, O(n) idi. Binary Tree de aramak O(logn) idi. Halbuki Hash table yapısı O(n) Basitçe şöyle çalışıyor. Anahtarı, vektörel veri yapısındaki bir indexe dönüştürüyor. Bu dönüşüme hashing deniyor. Bu işi yapan koda , hasher deniyor. Basitçe somutlaştırmaya çalışalım: anahtarımız 876324234 olsun. Mod 100 yapsak, yani 876324234%100 ile 0-99 arası bir sayı elde edilir. Bunu da index olarak kullanalım. Dolayısyla o indexli eleman var mı yok mu, varsa tuttuğu değere erişip kabaca hash işlemi yapmış oluyorum. Tabii, burada "ne malum başka bir anahtarın da mod 100 e göre yine aynı indexi göstermeyeceği? " sorusu var. Elbette başka bir anahtar da aynı yere nişanlayabilir. Buna da Collision deniyor. Hasher - anahtar->kod dönüştürücüsü - ne kadar iyiyse, collision o kadar az olur. Collision Hnadling içinde algoritmalar var. Düşün, bir dizi var. Her elemanında , bir bağlı liste var. Eğer collision yoksa, dizinin o elemanında 1 elemana sahip bağlı liste olur, collision varsa, birden fazla elemana sahip bağlı liste olur. Astronomi gibi çok büyük tamsayılarla işlem yapanlar için, bazı derleyicilerde - extended integer type olarak - 16 byte lık tamsayı türleri desteği var. Üçüncü parti kütüphaneler de var. Bunlar mesela 50-60 bin basamaklı sayıları ve bunlarla işlemleri destekliyorlar. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ 61.Ders C [ C99 ile eklenen araçlar cont'd ve Vector Kütüphanesi ] 17 Kasım 2022 Perşembe C99 ile dile eklenen araçlardan bahsediyorduk. Devam ediyoruz. ▪ Dizi Görünümlü parametre bildirimine C99 ile gelen ve const volatile restrict anahtar sözcüklerinin tatbikatı ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════ Bir fonksiyonun bildiriminde , pointer parametre void func (int* p) void func (int p[]) // Dizi görünümlü pointer bildirimi şekilleriyle bildirilebiliyordu. özellikle void func (int p[]) prototipinde, parametre bir dizi gibiymiş gözükse de , aslında bir pointer. Okuyanda, fonksiyon bir dizi adresi bekliyor intibaa oluşturuyor. Şimdi bu tarz bildirimlerde - void func (int p[]) - const volatile restrict anahtar sözcüklerinin tatbikinden bahsedeceğiz. ┌───────────────────────────────────────┬───────────────────────────────────────┐ │ Normal (Pointer Notasyonlu) bildirim │ Dizi Görünümlü karşılığı │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(int * p) │ void func(int p[]) │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(const int * p) │ void func(const int p[]) │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(int * const p) │ void func(int p[const]) C99 │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(volatile int * p) │ void func(volatile int p[]) │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(int * volatile p) │ void func(int p[volatile]) C99 │ ├───────────────────────────────────────┼───────────────────────────────────────┤ │ void func(int * restrict p1, ...) │ void func(int p[restrict], ...) C99 │ └───────────────────────────────────────┴───────────────────────────────────────┘ Hatırlatma: restrict, birden fazla pointer içeren arguman listesinde, bunlar arası girişim/overlapping in olmaması garantisinin talebi idi. ▪ static anahtar kelimesinin, C99 ile gelen yeni tatbikatı ══════════════════════════════════════════════════════════ static anahtar sözcüğüne bir overload daha geldi. void func(int p[static 20]); şeklinde bir bildirim yapılabilir. Bunun anlamı: Bu fonksiyon çağırandan, boyutu en az 20 olan bir dizi adresi bekliyor. Böylesi bir sentaks eklenmesinin temelde 3 sebebi var: ▪ Okuyan programcıya bu bilgiyi iletmiş oluyor ▪ Okuyan programcıya, bu fonksiyonun NULL ile çağrılmasının uygun olmayacağı bilgisini de iletmiş oluyor ▪ Derleyiciye daha iyi optimizasyon ve uyarı imkanı(uyarı vermek zorunda değil,verebilir) veriyor ▪ VLA - Variable Length Array - C99de standard iken C11 ile opsiyonel hale geldi ═════════════════════════════════════════════════════════════════════════════════ void func(int p[*]); bildirimi, bu fonksiyonun bir VLA beklediğini ifade ediyor. ▪ Optimizasyon ═════════════════ Generally speaking, ▪ Derleyicinin yaptığı optimizasyonlar - optimizer modülü ile - ▪ C++ derleyicisi kod yazabiliyor - template aracı ile - ancak C de yok. Bu templateler arasından birini tercih edebiliyor. ▪ LTO - Link Time Optimisation - Buradaki Optimizasyondan birincil kasıt, gözlenebilir davranışta değişiklik olmayacak şekilde, derleyicinin kaynak koda bakıp, daha az işlem kodu üretmesi. Derleyicilerin yaygın kullandığı birtakım teknikler: Deadcode eliminasyonu Loop reversal Loop unrolling Inline Expansion ... Bunları yaparken UB olmayacağına güvenerek hareket ediyor. int x = 10; int y; // code x/=y ; // y=0 ise UB! olur. if(y==0) // Derleyici bu kodu {...} // silebilir. Debug sürecinde, derleyicinin optimizasyon yapmadan çevirmesine ihtiyaç olabiliyor. Bir de LTO - Link Time Optimisation - var. Kısaca, öyle optimizasyon olanakları var ki, derleyicinin bu optimizasyonu yapabilmek için iki fonksiyonun ikisininde kodunu görmesi gerekiyor. Ancak bunlar farklı kaynak dosyalarda ise, derleyici adeta bir ara kod oluşturup linker birden fazla obje dosya görüp, eklenen bu ara kodları görerek, derleyicinin compile time da yapamadığı optimizasyonları yapabiliyor. ▪ Inline expansion ═══════════════════ Fonksiyon çağrılarının bir maliyeti var. Argumanların kopyalanması Geri dönüş değerinin ayarlanması stack yapısının oluşturulması ve sonra kaldırılması. Önşart: Derleyici kodu görmeli! █ Örnek: int sum_square(int x, int y) { return x*x + y*y; } int main(void) { int x = 10; int y = 20; int z = sum_square(x,y); } Derleyici sum_square tanımını/kodunu görüyor, int z = sum_square(x,y); satırı için inline expansion yapabilir. Yapabilir diyoruz Derleyici sum_square sadece bildirimini görse, tanımını/kodunu görmese idi, zaten bir şey yapma imkanı olmayacaktı. ▪ inline fonksiyon ═══════════════════ C99 öncesi inline bir anahtar sözcük değildi, ancak derleyiciler C++ dan esinlenerej extension olarak sunuyorlardı. C99 ile birlikte inline fonksiyonlar standard hale geldi. C99 C11 ile eklenen yeni keywordlei, geçmişe yönelik isimlerle çakışma olmaması için _ ile başlayacak şekilde seçmelerine rağmen, inline için böyle yapmamışlar. ilginç. Çok önemli bir nokta: C++ ve C deki inline fonksiyonlar, özde aynı amaca hizmet etmekle birlikte farklı kurallara sahipler. Bazı derleyiciler, inline konusunda, standardın içeriğinden başka eski alışkanlıklarını sürdürüyorlar. Bu hususu bir kontrol etmek gerekebilir. Inline expansion için, derleyicinin kodu görmesi gerektiğini söylemiştik. Peki ama, ben farklı kaynak dosyaların hepsinde bu fonksiyonu kullanıyorsam ve hepsinde de inline expansion imkanı olsun istiyorsam? Derleyicinin kodu görmesi için, aynı fonksiyonun tanımını birden fazla kaynak dosyaya direkt/ya da endirekt olarak include edilen başlık dosyası vasıtasıyla koysak, ODR violation olur, link zamanında hata oluşur: Multiply defined symbol :) İşte , hem her kaynak dosyada derleyicinin o kodu görmesini hem de ODR violation yüzünden hata oluşmasını önleyici bir araç lazım. Bu araç inline fonksiyon. inline int sum_square(int x, int y ) { return x*x + y*y ; } Peki başında inline var diye, derleyici inline expansion yapmak zorunda mı? Hayır! Dolayısıyla, böylesi inline bir fonksiyonu, başlık dosyasına koyabiliriz ve her kaynak dosya için derleyici bunu görebilir. Öte yandan bu fonksiyonun o kaynak dosyada inline olarak açılmaması durumunda, herhangi şekilde object module, non-inline bir kopya (yani bunun derlenmiş halini) koymuyor. Yani bu şu demek: Şöyle bir senaryo olsun. Inline bir fonksiyonum var, inline expansion yapılmadı ancak adresini kullanıyorum. myHeader.h içinde myFile.c içinde ────────── ───────── func'ın adresini alıyorum ve belki bir başka fonksiyona da göndereceğim. inline int func (int x) #include "myHeader.h" ancak link aşamasında Hata alırım. { void foo(void) Çünkü inline int func (int x) fonksiyonunun derlenmiş hali obje koda konmadı! return x+5; { Bu aynı zamanda C ve C++ arası farklılık gösterir. } int (*fp)(int) = &func; olsun. } Fonksiyonel makro ve fonksiyon un birarada kullanılmasındaki trick i andırır şekilde, bir fonksiyonun hem inline hem hem de inline olmayan bir halini projede tutarsan, derleyici inline expansion yapmak istediğinde, inline ile tanımlı şeklini, inline expansion olmaması durumunda da, extern kodu - yalnızca 1 kaynak dosyada bildirilmiş olmalı- kullanması sağlanabilir ¹ Eğer her obje kodda derlenmiş haline sahip olmak istersen, myHeader.h içinde ────────── static inline int func (int x) { return x+5; } şeklinde tanımlarsak, bunu içeren kaynak dosyalar ODR violation dan sitem etmiyorlar, ve de kod görüldüğü için Inline Expansion yapmaya uygun. Ancak inline expansion yapılmazsa, her kaynak dosya derlendiği zaman o dosyaya ait ve internal linkage da olacak. Mesela 5 ayrı kaynak dosyada tarafından include edilen bu header dan gelen func ın, bu 5 dosyada da adresinin alındığını düşünelim. Bu durumda hepsinin farklı adresi olacaktır. Çünkü her kaynak dosyada kendine ait obje kodda derlenmiş halde. Tüm projeye bakarsak da, proje kodu büyüdü, çünkü tekrarlı şekilde func her kullananda mevcut. ¹ extern kodu - yalnızca 1 kaynak dosyada bildirilmiş olmalı- kullanması (C++ da olmayan bir şey) myHeader.h içinde myFile.c içinde ────────── ───────── inline int func (int x) #include "myHeader.h" { int func(int x); --veya Bu üç bildirimde aynı şey. return x+5; --extern int func(int x); veya Bu bildirimlerden birini yaptığında, derleyiciye diyorsun ki } --extern inline int func(int x); obje koda derlenmiş bir kopyada koy. Eğer böyle yaparsak, bu extern bildirimin sadece bir kaynak dosyada olması lazım. █ Örnek: C++ daki vector sınıfına benzeyen bir kütüphane yapalım. Video 1:37 de başlıyor.