STRINGLER C programa dilinde iki tırnak içerisindeki ifadelere string ifadeleri ya da kısaca stringler denir. Örneğin: "sifir zero" "x = %d\n" "lütfen bir sayı giriniz : " ifadelerinin hepsi birer stringdir. Stringlerin tek bir atom olarak ele alındığını önceki derslerden hatırlıyoruz. C'de stringler aslında char türden bir adres olarak ele alınmaktadır. C derleyicileri, derleme aşamasında bir stringle karşılaştığında, önce bu stringi belleğin güvenli bir bölgesine yerleştirir, sonuna NULL karakteri ekler ve daha sonra string yerine yerleştirildiği yerin başlangıç adresini koyar. Bu durumda string ifadeleri aslında stringlerin bellekteki başlangıç yerini gösteren char türden birer adrestir. Örneğin: char *p; ... p = "sifir zero"; gibi bir kodun derlenmesi sırasında, derleyici önce "sifir zero" stringini belleğin güvenli bir bölgesine yerleştirir; daha sonra yerleştirdiği yerin başlangıç adresini string ifadesi ile değiştirir. Stringler char türden bir adres olarak ele alındığına göre char türden göstericilere atanmalarında error ya da uyarı gerektiren bir durum söz konusu değildir. Stringlerin Fonksiyonlara Arguman Olarak Gönderilmesi Parametre değişkeni char türden bir gösterici olan fonksiyonu char türden bir adres ile çağırmak gerektiğini biliyoruz. Çünkü doğal olarak char türden bir göstericiye char türden bir adres atanmalıdır. Derleyiciler açısından stringler de char türden bir adres belirttiklerine göre, parametre değişkeni char türden gösterici olan bir fonksiyonu bir string ile çağırmak son derece doğal bir durumdur, ve C dilinde bu yapı çok kullanılır: puts("sifir zero"); Burada derleyici "sifir zero" stringini belleğe yerleştirip sonuna NULL karakteri koyduktan sonra artık bu stringi, karakterlerini yerleştirdiği bellek bloğunun başlangıç adresi olarak görecektir. puts fonksiyonunun parametre değişkenine de artık char türden bir adres kopyalanacaktır. puts fonksiyonu parametre değişkeninde tutulan adresten başlayarak NULL karakteri görene kadar tüm karakterleri ekrana yazmaktadır. Bu durumda ekranda Merhaba yazısı çıkacaktır. Başka bir örnek: char str[20]; ... strcpy(str, "sifir zero"); Bu örnekte de "sifir zero" stringi str adresinden başlayarak kopyalanmaktadır. String ifadelerinin bulunduğu yerde char türden bir adresin bulunduğu düşünülmelidir. Şimdi aşağıdaki ifadeyi yorumlayalım: strcpy("sifir", "zero"); Derleyici derleme aşamasında iki stringi de bellekte güvenli bir bölgeye yerleştirir. Çalışma zamanı sırasında strcpy fonksiyonu "sifir" stringini gösteren adresten başlayarak "zero" stringini gösteren adrese NULL karakter görene kadar kopyalama yapacağına göre, bu ifadenin faydalı hiçbir anlamı olmayacaktır. Üstelik bu ifade, ikinci string birinciden uzun olduğu için bir gösterici hatasına da neden olur. Şimdi aşağıdaki ifadeyi inceleyelim: p = "zero" + "sifir"; Yukarıdaki örnekte aslında "sifir" stringinin başlangıç adresi ile "zero" stringinin başlangıç adresi toplanmaktadır. Peki bu toplamın yararlı bir sonucu olabilir mi? Derleyiciler anlamsızlığından dolayı iki adresin toplanmasına izin vermezler. Fakat bir göstericiden başka bir göstericinin değerini çıkarmanın yararlı gerekçeleri olabilir. Bu yüzden göstericilerin birbirinden çıkartılması derleyicilerin hepsinde geçerli bir işlemdir. Gösterici aritmetiğine göre bir adresten aynı türden bir başka bir adres çıkartıldığı zaman elde edilen sonuç bir tamsayıdır, ve bu sayı iki adresin sayısal bileşenlerinin farkı değerinin adresin tür uzunluğuna bölünmesiyle elde edilir: #include <stdio.h> int main() { char *p = (char *) 0x1AA8; char *q = (char *) 0x1AA0; long *lp = (long *) 0x1AA8; long *lq = (long *) 0x1AA0; printf("fark1 = %d\n", p - q); printf("fark2 = %d\n", lp - lq); return 0; } Yukarıdaki programın çalıştırılmasıyla ekrana: fark1 = 8 fark2 = 2 yazılacaktır. C derleyicileri kaynak kodun çeşitli yerlerinde tamamen özdeş stringlere rastlasa bile bunlar için farklı yerler ayırabilirler. Derleyici özdeş stringlerin sadece bir kopyasını da bellekte saklayabilir. Özdeş stringlerin nasıl saklanacağı bazı derleyicilerde programcının seçimine bırakılmıştır. Derleyicinin ayarları arasında bu konuyla ilgili bir seçenek bulunur. Örneğin bir programın içerisinde: printf("Dosya açılamıyor!..\n"); ... printf("Dosya açılamıyor!..\n"); ... printf("Dosya açılamıyor!..\n"); ifadelerinin bulunduğunu varsayalım. Farklı yerlerde "Dosya açılamıyor!..\n" gibi özdeş stringler bulunsa bile derleyici bunlar için tekrar ve farklı yerler ayırabilir. Bu da büyük uygulamalar için belleğin verimsiz kullanılmasına yol açabilir. Bellekte yer ayırma işlemi derleme aşamasında yapılmaktadır. Aşağıdaki kod parçasının çalıştırılmasıyla ekrana hep aynı adres basılacaktır : char *p; int k; ... for (k = 0; k < 10; ++k) { p = "sifir zero"; printf("%p\n", p); } ... Eğer yer ayırma işlemi çalışma zamanı sırasında yapılsaydı, o zaman farklı adresler basılabilirdi. Stringlerin doğrudan karşılaştırılması yanlış bir işlemdir. ... if (“izmir” == “izmir”) printf(“dogru\n”); else printf(“yanlış”\n”); ... Yukarıdaki kodun çalıştırılması durumunda büyük bir olasılıkla ekrana “yanlış” yazdırılacaktır. Çünkü derleyicinin if parantezi içindeki karşılaştırma ifadesindeki iki “Ankara” stringinin bellekte ayrı yerlere yerleştirilmesi durumunda, bu iki string birbirinden farklı iki ayrı char türden adres olarak değerlendirilir. Benzer bir yanlışlık aşağıdaki kod parçasında da yapılmıştır. char *str = “Mavi ay”; char s[20]; printf(“parolayı giriniz : “); gets(s); if (str == s) printf(“dogru parola\n”); else printf(“yanlış parola\n”); s bir dizi ismidir ve derleyici tarafından dizinin yerleştirildiği bloğun başlangıcı olan char türden bir adres sabiti gibi ele alınır. str ise char türden bir göstericidir. str göstericisine “Mavi ay” stringi atandığında, derleyici önce “Mavi ay” stringini bellekte güvenli bir yere yerleştirir daha sonra stringin yerleştirildiği yerin başlangıç adresini str göstericisine atar. programı kullananın parola olarak “Mavi ay” girdiğini varsayalım. Bu durumda if deyimi içinde yalnızca s adresiyle str göstericisinin içindeki adresin eşit olup olmadığı kontrol edilmektedir. Bu adresler de eşit olmadıkları için ekrana "yanlış parola" yazılacaktır. İki yazının birbirine eşit olup olmadığı strcmp fonksiyonu ile kontrol edilmeliydi: char *str = “Mavi ay”; char s[20]; printf(“parolayı giriniz : “); gets(s); if (!strcmp(str, s)) printf(“dogru parola\n”); else printf(“yanlış parola\n”); Stringlerin Ömürleri Stringler statik ömürlü nesnelerdir. Tıpkı global değişkenler gibi programın yüklenmesiyle bellekte yer kaplamaya başlarlar, programın sonuna kadar bellekte kalırlar. Dolayısıyla stringler çalışabilen kodu büyütür. Birçok sistemde statik verilerin toplam uzunluğunda belli bir sınırlama söz konusudur. Stringler derleyici tarafından .obj modüle linker tarafından da .exe dosyasına yazılırlar. Programın yüklenmesiyle hayat kazanırlar. Bir exe program 3 bölümden oluşmaktadır: kod data stack Kod bölümünde, fonksiyonların makine kodları vardır. Data bölümünde, statik ömürlü nesneler bulunmaktadır, global değişkenler ve stringler bu bölümde bulunurlar. Stack bölümü yerel değişkenlerin saklandığı bellek alanıdır. Stack bölümü her sistemde sınırlı bir alandır. Örneğin DOS işletim sisteminde 64K büyüklüğünde bir stack söz konusudur. Yani hiç bir zaman yerel değişkenlerin bellekte kapladığı alan 64K değerini geçemez. WINDOWS işletim sisteminde default stack sınırı 1 MB’dır. Ancak bu sınır istenildiği kadar büyütülebilir. Daha önceki derslerimizde, bir fonksiyonun yerel bir değişkenin adresi ile geri dönmemesi gerektiğini söylemiştik. Çünkü fonksiyon sonlandığında yerel değişkenler bellekten boşaltılacağı için, fonksiyonun geri döndürdüğü adres güvenli bir adres olmayacaktır. Örneğin aşağıdaki program hatalıdır: char *getname(void) { char s[100]; printf(“adı ve soyadı giriniz : “); gets(s); return s; } int main() { char *ptr; ptr = getname(); puts(ptr); return 0; } Fonksiyon bir stringin adresiyle geri dönebilir, bu durumda bir yanlışlık söz konusu olmayacaktır. Çünkü stringler programın çalışma süresi boyunca bellektedir. Örneğin aşağıdaki programda hatasız olarak çalışır: char *getday(int day) { char *ptr; switch (day) { case 0: p = “Sunday”; break; case 1: p = “Monday”; break; case 2: p = “Tuesday”; break; case 3: p = “Wednesday”; break; case 4: p = “Thursday”; break; case 5: p = “Friday”; break; case 6: p = “Saturday”; } return p; } Stringlerin Birleştirilmesi Daha önce söylendiği gibi stringler tek bir atom olarak ele alınmaktadır. Bir string aşağıdaki gibi parçalanamaz: char *ptr; ... ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz"; /* error */ Ancak string ifadeleri büyüdükçe bunu tek bir satırda yazmak zorlaşabilir. Ekrandaki bir satırlık görüntüye sığmayan satırlar kaynak kodun okunabilirlirliğini bozar. Uzun stringlerin parçalanmasına olanak vermek amacıyla C derleyicileri yanyana yazılan string ifadelerini birleştirirler. Örneğin: ptr = "sifir zero'nun C Dersi" "Notlarını okuyorsunuz"; geçerli bir ifadedir. Bu durumda iki string birleştirilerecek ve aşağıdaki biçime getirilecektir. ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz"; Derleyicinin iki stringi birleştirmesi için, stringlerin arasında hiçbir operatörün bulunmaması gerekmektedir. : p = "sifir " "zero"; ifadesi ile p = "sifir zero"; ifadesi eşdeğerdir. Birleştirmenin yanı sıra, bir ters bölü karakteri ile sonlandırılarak sonraki satıra geçiş sağlanabilir. Örneğin: ptr = "sifir zero'nun C Dersi \ Notlarını okuyorsunuz"; ifadesi ile ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz"; ifadesi eşdeğerdir. Tersbölü işaretinden sonra string aşağıdaki satırın başından itibaren devam etmelidir. Söz konusu string aşağıdaki şekilde yazılırsa: ptr = "sifir zero'nun C Dersi \ Notlarını okuyorsunuz"; satır başındaki boşluk karakterleride stringe katılır ve sonuç aşağıdaki ifadeye eşdeğer olur: ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz"; Ancak ters bölü karakteri ile sonraki satırın başından devam etme standart olarak her C derleyicisinde geçerli olmayabilir. Çatışmalı bir durumla karşılaştığınızda çalıştığınız derleyicilerin referans kitaplarına başvurmalısınız. Stringlerde Ters Bölü Karakter Sabitlerinin Kullanılması Stringler içerisinde ters bölü karakter sabitleri de (escape sequence) kullanılabilir. Derleyiciler stringler içerisinde bir ters bölü karakteri gördüklerinde, onu yanındaki karakter ile birlikte tek bir karakter olarak ele alırlar. Örneğin: char *p; p = "sifir\tzero"; ifadesinde \t bir karakterdir. (9 numaralı ASCII carakteri olan tab karakteri) yani printf("stringdeki karakter sayısı = %d\n", strlen(p)); ifadesi ile ekrana stringdeki karakter sayısı = 11" yazdırılacaktır. String ifadelerinde doğrudan çift tırnak ya da ters bölü karakterleri kullanılamaz, çünkü bu karakterlerin özek işlevi var. Çift tırnak karakter sabitinin kendisini ifade etmek için çift tirnak karakterinden önce bir ters bölü karakteri kullanılır. Örneğin: p = "\"sifir zero\""; gibi bir string ifadesi geçerlidir. Bu durumda puts(p); ile ekrana "sifir zero" yazısı basılacaktır. Stringlerle Göstericilere İlkdeğer Verilmesi Stringler kullanılarak char türden göstericilere ilkdeğer verilebilir. Örneğin: char *p = "İstanbul"; char *err = "Bellek yetersiz"; char *s = "Devam etmek için bir tuşa basınız"; ... String ifadeleri aslında char türden bir adres olduğuna göre ilk değer verilen göstericilerin de char türden göstericiler olması gerekir. Dizilere iki tırnak içerisinde ilk değer vermeyle göstericilere stringlerle ilk değer verme arasındaki ayırıma dikkat etmek gerekir. char *p = "Deneme"; char s[10] = "Deneme"; ifadeleri tamamen farklıdır. Göstericilere ilkdeğer verildiğinde derleyici bunu bir string ifadesi olarak ele almaktadır. Yani string belleğe yerleştirildikten sonra başlangıç adresi göstericiye atanır. Oysa dizilerde önce dizi için yer ayrılır, daha sonra karakterler tek tek dizi elemanlarına yerleştirilir. Dizilere ilkdeğer verirken kullandığımız iki tırnak ifadeleri adres belirtmezler (string değildireler). Dizi elemanlarına tek tek char türden sabitlerle ilk değer verme işlemi zahmetli olduğu için, programcının işini kolaylaştırmak amacı ile dile bu ilk değer verme sentaksı eklenmiştir. Yani char s[10] = "Deneme"; aslında char s[10] = {'D', 'e', 'n', 'e', 'm', 'e', '\0'}; ile aynı anlamdadır. İcinde Yazı Tutan char Türden Dizilerle Bir Stringi Gösteren char Türden Göstericilerin Karşılaştırılması Şimdiye kadar almış olduğumuz bilgileri değerlendirdiğimizde, C dilinde bir yazıyı saklamak iki ayrı şekilde sağlanabilir: 1. Yazıyı char türden bir dizi içinde saklamak: ... char s1[100] = “sifir zero”; char s2[100]; char s3[100]; gets(s2); / * Diziye yazının karakterleri klavyeden alınıyor */ strcpy(s2, s3); /* s3 dizisinin elemanları s2 dizisi içine kopyalanıyor */ 2. Yazıyı bir string olarak saklayarak char türden bir göstericinin bu stringi göstermesini sağlamak . char *str = “sifir zero”; İki yöntem birbirinin tamamen alternatifi değildir ve aşağıdaki noktalara dikkat edilmesi gerekmektedir: stringler statik nesneler oldukları için programın sonlanmasına kadar bellekte yer kaplarlar. Yani bir göstericinin bir stringi gösterirken daha sonra başka bir stringi gösterir duruma getirilmesi , daha önceki stringin bellekten silinmesi anlamına gelmeyecektir. char *s = “Bu string programın sonuna kadar bellekte kalacak.”; s = “artık yukarıdaki string ile bir bağlantımız kalmayacak...”; Yazının char türden bir dizi içinde tutulması durumunda bu yazıyı değiştirmemiz mümkündür. Dizi elemanlarına yeniden atamalar yaparak yazıyı istediğimiz gibi değiştirebiliriz ama stringlerin değiştirilmesi taşınabilir bir özellik değildir. Stringler içinde bulunan yazılar değiştirilmemelidir. Aşağıdaki kod parçaları taşınabilir değildir: char *s = “Metin”; char *str = “Ankara”; s[1] = ‘Ç’; strcpy(str, “Bolu”); Derleyicilerin özdeş stringleri aynı adreste saklamaları konusunda bir garanti yok. Özdeş stringler aynı adreste tek kopya olarak ya da farklı adreslerde tutuluyor olabilir. Daha önce söylediğimiz gibi derleyiciler bu konuda birbirinden farklı davranabilirler. Yani yukarıdaki örneklerde biz “Metin” ya da “Ankara” stringini değiştirdiğimizde, bu stringleri program içinde baska yerlerde de kullanmışsak, bunların hepsinin değiştirilmiş olması gibi bir tehlike söz konusudur. char *s = "dosya açılamıyor"; /* birinci string */ char a[] = "dosya açılamıyor"; ... printf("%s", s); ... printf("dosya açılamıyor"); /* ikinci string */ ... strcpy(s, "Dosya Açıldı"); ... "dosya açılamıyor" stringinin derleyici tarafından aynı adreste tek kopya olarak saklandığını farsayalım. s adresinde bulunan stringe yeni bir yazı kopyalanınca hem birinci string hemde ikinci string değişmiş olur.
29 Temmuz 2020 Çarşamba
C programlama stringler
Kaydol:
Kayıt Yorumları (Atom)
Hiç yorum yok:
Yorum Gönder
Her yorum bilgidir. Araştırmaya devam...