Yazı dizimizin
üçüncü ve son makalesine başlamadan önce, şimdiye kadar yaptıklarımıza kısaca
bir göz atalım. İlk olarak C dilinde karakter katarlarının kullanımına ilişkin
birtakım bilgiler verdik. "char" türünden göstericilere ve dizilere
göz attık. Daha sonra uygulamamızın çatısını oluşturan listemizi oluşturduk.
Listemiz "struct record" türünden elemanlara sahipti. "record"
ve "recordList" yapılarımız için "CreateRecord" ve "CreateList"
başlangıç fonksiyonlarını oluşturduk. Listemizdeki her eleman bir isme ve o
isme ilişkin numara bilgisine sahipti. Listeye baştan ve sondan kayıt ekleyen
"AddRecordStart, AddRecordEnd" fonksiyonlarımızı yazdık. İlk makalemizin
sonunda listedeki tüm kayıtları ekrana yazdıran "DisplayList" fonksiyonunu
yazdık. İkinci makalemizde listenin herhangi bir sıra numarasına kayıt ekleyen
"InsertRecordToList" fonksiyonunu, belli bir sıra numarasındaki kaydı
silen "DeleteRecordFromList" fonksiyonunu, listede verilen bir isme
ya da numaraya ilişkin kayıtları arayıp bulan ve silen "DeleteAllRecordsFromListByNumber"
ve "DeleteRecordFromListByName" fonksiyonlarını ve listede isme göre
kayıt arayan "FindRecordInList" fonksiyonunu yazdık. İkinci makalenin
sonunda ise listede belli bir kaydı güncelleyen fonksiyonları yazdık. Üçüncü
ve bu konunun son makalesinde ise listedeki isimler üzerinde arama işlemleri
yapacağız. Bu arama işlemlerinde kullanmak üzere karakter katarları üzerinde
işlemler yapan bazı fonksiyonlar yazacağız. Son olarak da listemizdeki kayıtların veri elemanlarının değerlerini, bir dosyaya
kaydedeceğiz. Önce karakter katarları üzerinde işlem yapan fonksiyonlarımızdan
başlayalım. Daha sonra bu fonksiyonları listemize ait fonksiyonlarda kullanalım.
Öncelikle bir karakter
katarında belli bir karakterin olup olmadığını bulan, eğer var ise TRUE, yok
ise FALSE döndüren fonksiyonumuzu yazalım :
BOOL IsContainCharacter(const
char *str,char ch); /*
prototip bildirimi*/
BOOL IsContainCharacter(const
char *str,char ch)
{     int i = 0;     while (str[i] != ’\0’)
       if (str[i] == ch)
          return 1;     return 0;
}
|
"IsContainCharacter"
fonksiyonumuz (char *) türünden olan "str" isimli parametresi ile
fonksiyona geçilen karakter katarı içerisinde "ch" parametresi ile
geçilen karakteri arar. Eğer bulursa TRUE değerine, bulamazsa FALSE değerine
geri döner. Fonksiyondaki while döngüsü, str katarının sıradaki karakteri null
karakter (’\0’) olmayana dek döner. Karakter katarları ile ilgili bilgi verirken,
derleyicilerin karakter katarları sonuna null karakterini eklediğini belirtmiştik.
Buradaki while döngüsünde de, karakter katarı içerisinde belli bir karakteri
arama işlemi, katarın sonuna gelininceye dek sürdürülmektedir. Döngü içerisindeki
if ifadesinde katarın sıradaki karakterinin ch değişkenindeki değere eşit olup
olmadığı sınanıyor. Eğer eşit ise aradığımız karakter bulunmuş demektir.
Şimdi bir karakter
katarının belli bir karakter katarı ile başlayıp başlamadığını bulan fonksiyonumuzu
yazalım :
BOOL StartWith(const
char *str, const char *subStr); /*
Prototip bildirimi */
BOOL StartWith(const
char *str, const char *subStr)
{
    int index = 0;
    while (str[index]
!= ’\0’ && subStr[index] != ’\0’) {
        if (str[index] != subStr[index++])
            return FALSE;
    }
    if (subStr[index]
!= ’\0’)
       return FALSE;
    return TRUE;
}
|
"StartWith"
isimli fonksiyonumuz, parametre olarak aldığı str katarının, subStr katarı ile
başlayıp başlamadığını bulur. Fonksiyonun başında tamsayı türünden "index"
isimli bir değişken tanımlıyoruz. Bu değişkeni karakter katarlarının karakterleri
üzerinde sırasıyla dolaşmak için kullanacağız. Bu nedenle başlangıç değeri olarak
sıfır atadık. While döngüsünde koşul ifadesi olarak, her iki katarın da sonuna
gelinip gelinmediği kontrol ediliyor. İki katardan herhangi birisinin sonuna
gelinmişse arama işlemi sonlandırılmalıdır. Döngü içerisinde, "str"
katarının sıradaki karakterinin, "subStr" katarının sıradaki karakterine
eşit olup olmadığı kontrol ediliyor. Eğer eşit değil ise "return FALSE;"
ifadesi ile döngüden çıkılır ve fonksiyon sonlandırılır. Eğer döngü koşul ifadesini
başarıyla geçerek sonlanmışsa, son noktada "subStr" katarının sonuna
gelinip gelinmediği kontrol edilmelidir. Çünkü ilk katarın uzunluğu ikinci katarın
uzunluğundan daha az olabilir. Eğer karakterler birbirlerine eşit ise, ve ikinci
katar birinciden daha uzun ise, bu durumda da fonksiyon FALSE değerini döndürmelidir.
Eğer bu koşul da geçilirse fonksiyon TRUE değerine geri döner.
Şimdi fonksiyona
gönderilen karakter katarının, diğer karakter katarının içerisinde herhangi
bir sıra numarasından itibaren var olup olmadığını bulan "IsContainString"
isimli fonksiyonumuzu yazalım. Bu fonksiyona parametre değişkeni olarak sırasıyla
"CSharpNedir?" ve "Nedir" karakter katarları gönderilirse
fonksiyon TRUE değerine dönmelidir. Bu durumda yukarıda yazdığımız "StartWith"
fonksiyonumuz işimizi görmeyecektir. Bu fonksiyonda yapacağımız aramayı şekille
ifade edelim :
Bu durumda, devam
edeceğimiz her adımda, içinde arama yapılan karakter katarının sıradaki sıra
numarasından sonuna kadar olan kısmını kopyalayarak başka bir katar elde etme
yolunu seçebiliriz. İlk önce bu kopyalama işlemini yapacak olan fonksiyonumuzu
yazalım :
char *CopyString(const
char *str,char *destStr,int fromIndex); /* Prototip bildirimi */
char *CopyString(const
char *str,char *destStr,int fromIndex)
{
   
while (str[fromIndex] != ’\0’)
       destStr[j++] = str[fromIndex++];
    destStr[j] = ’\0’;
    return destStr;
}
|
"CopyString"
isimli fonksiyonumuz, "str" parametresi ile belirtilen karakter katarının
"fromIndex" parametresi ile belirtilen sıra numarasından başlayarak
sonuna kadar olan kısmını "destStr" parametre değişkenine kopyalar
ve yine bu değişkenin adresine geri döner. Fonksiyondaki while döngüsü, "str"
katarının sonuna gelinmediği müddetçe dönüyor. Döngü içerisinde de "destStr"
parametre değişkenine, "str" değişkenindeki karakterler sırayla kopyalanıyor.
Fonksiyon bu haliyle, karakter katarı aramak için tasarladığımız yapıya uygundur.
Şimdi asıl fonksiyonumuza geçelim :
BOOL IsContainString(const
char *str,const char *subStr); /* Prototip bildirimi*/
BOOL IsContainString(const
char *str,const char *subStr)
{
    int loop;
    int index = 0;
    int len = strlen(str);
    int same;
   
char *tstr = malloc(MAX_STRING_LENGTH);
   
if (strlen(subStr) < 1) {         free(tstr);
        return FALSE;     }
    while (index
< len) {
       tstr = CopyString(str,tstr,index);
       same = 1;
       loop = 0;
       while (tstr[loop]
!= ’\0’ && subStr[loop] != ’\0’)
          if (tstr[loop] != subStr[loop]) {
             same = 0;
             break;
          }
          else loop++;
       
if (subStr[loop] != ’\0’)
           same = 0;
        if (same)
{
            free(tstr);
            return TRUE;
        }
        index++;
    }
    free(tstr);
    return FALSE;
}
|
"CopyString"
isimli fonksiyonu çağırırken kullanmak üzere (char *) türünden "tstr"
isimli bir değişken tanımlıyoruz ve standart malloc fonksiyonu ile bu değişken
için dinamik alan tahsis ediyoruz. Burada malloc fonksiyonuna parametre olarak
gönderdiğimiz "MAX_STRING_LENGTH", kaynak kodun başında "#define"
önişlemci komutu kullanılarak sembolik sabit olarak belirtiliyor. Çalışma zamanında
taşma hatasıyla karşılaşmamak için bu sembolik sabitin tanımlandığı değere dikkat
edilmelidir. Daha sonra, aranacak karakter katarının uzunluğunu standart strlen
fonksiyonunu kullanarak kontrol ediyoruz. Eğer uzunluk 1’den küçük ise fonksiyon
FALSE değerine geri dönüyor. Burada "return" ifadesinden önce dinamik
olarak tahsis ettiğimiz alanı standart free fonksiyonu ile serbest bırakıyoruz.
Fonksiyondaki while döngüsü, "index" parametresinin değeri, "len"
parametresinin değerinden küçük olduğu müddetçe dönüyor. Bu şekilde her adımda,
içinde arama yapılacak katarı, ilgili sıra numarasından başlamak suretiyle "tstr"
değişkenine kopyalanıyor, ve "tstr" nin aranan karakter katarını içerip
içermediği kontrol ediliyor. Döngünün içerisindeki ikinci while döngüsü, "StartWith"
fonksiyonundaki döngüye benzer biçimde çalışıyor. Aranan karakter katarı bulunduğu
anda döngüden çıkılıyor. Dinamik olarak tahsis ettiğimiz alanı, fonksiyon sonunda
standart free fonksiyonu ile serbest bırakıyoruz. Eğer aranan karakter katarı
bulunamazsa fonksiyon FALSE değeri döndürüyor.
Şimdi listemize
ait fonksiyonlarımızı yazmaya devam edelim. Öncelikle listede belli bir isme
sahip kaydın numara değerine geri dönen "FindNumberFromName" fonksiyonunu
yazalım. Bu fonksiyon, aranan isimle, listedeki kayıtlara ait isimlerin tam
eşitliğini kontrol edecek, bu nedenle standart strcmp fonksiyonunu kullanabiliriz.
int FindNumberFromName(recordList
list, char *name); /* Prototip bildirimi*/
int FindNumberFromName(recordList
list, char *name)
{
    record *tmpRecord = list->start;
   
while (tmpRecord) {
       if (!strcmp(tmpRecord->rName,name))
          return tmpRecord->rNumber;
       tmpRecord
= tmpRecord->rNext;
    }
   
return -1;
}
|
Fonksiyonda, parametre
olarak geçilen "list" isimli liste içerisinde, "name" parametre
değişkeni ile aynı karakter katarına sahip kayıt aranıyor. Eğer böyle bir kayıt
bulunursa fonksiyon bu kaydın "rNumber" veri elemanının değerine geri
dönecek. İlk olarak listenin başındaki kaydın adresini "tmpRecord"
isimli göstericiye atıyoruz. İzleyen while döngüsü, "tmpRecord" kaydı
null olmayana dek devam ediyor. Hatırlayacağınız üzere liste sonunda bulunan
kaydın "rNext" isimli veri elemanına NULL değer atamıştık, ve liste
sonuna gelindiğini bu koşul ile sınıyorduk. While döngüsü içerisinde, "tmpRecord"
kaydının "rName" veri elemanının değeri ile "name" isimli
parametre değişkeni, standart strcmp fonksiyonuna parametre olarak gönderiliyor.
Standart strcmp fonksiyonu, ilk parametre değişkeni olarak gönderilen karakter
katarı, sözlük sırasına göre, ikinci parametre değişkeni olarak gönderilen karakter
katarından küçük ise negatif değere, ikinci karakter katarına eşit ise sıfır
değerine ve ikinci karakter katarından büyük ise pozitif değere geri dönüyordu.
If ifadesindeki "!strcmp(tmpRecord->rName,name)" koşulu da iki
karakter katarının eşit olması durumunda doğru olacak ve if ifadesindeki "return
tmpRecord->rNumber;" kodu çalışacaktır. Bu kod da, kaydın "rNumber"
veri elemanının değerini döndürüyor. Eşitsizlik durumunda ise, "tmpRecord"
göstericisine bir sonraki kaydın adresi atanıyor ve döngü dönmeye devam ediyor.
Eğer döngü içerisinde "return" ifadesi ile fonksiyondan geri dönülmemişse,
aranan isme sahip bir kayıt bulunamamıştır, bu durumda "return" ifadesi
ile fonksiyonu -1 değerine döndürdük. Burada geri dönüş değeri olarak, listede
numara değeri olarak kullanmayacağımız, özel bir değer seçmeliyiz. Daha sonra
fonksiyonu çağırdığımız yerde, geri dönüş değerinin belirlediğimiz bu değer
olup olmadığını kontrol ederek kayıt bulunup bulunmadığını anlayabiliriz.
Şimdi yazacağımız
fonksiyon, listedeki kayıtları dolaşarak, belli bir karakter katarını içeren
kayıt sayısını bulacak. Bu fonksiyonda, makalemizin başında yazmış olduğumuz
fonksiyonları kullanacağız.
int NumberOfContainString(recordList
list, char *str); /*
Prototip bildirimi */
int NumberOfContainString(recordList
list, char *str)
{
    int result = 0;
    record *tmpRecord = list->start;
   
while (tmpRecord) {
       if (IsContainString(tmpRecord->rName,str))
          result++;
       tmpRecord = tmpRecord->rNext;
    }
    return result;
}
|
Fonksiyonun geri
dönüş değeri olarak kullanacağımız tamsayı türünden "result" isimli
değişkene başlangıçta sıfır değerini atıyoruz. Bu değişken bulunan kayıtların
sayısını tutacak. Listedeki kayıtları dolaşırken kullanacağımız (record *) türünden
"tmpRecord" isimli bir gösterici tanımlayıp, bu göstericiye ilk değer
olarak listenin başında yer alan kaydın adresini atıyoruz. While döngüsü, göstericimiz
NULL değere sahip olmadığı müddetçe dönecek. Döngü içerisinde "tmpRecord"
göstericisinin gösterdiği kaydın "rName" veri elemanının değerini
ve aradığımız karakter katarını tutan "str" isimli parametre değişkenini,
daha önce yazmış olduğumuz "IsContainString" isimli fonksiyonumuza
gönderiyoruz. Eğer aranan karakter katarı, kaydın "rName" veri elemanında
bulunursa fonksiyon TRUE değeri döndürür ve bu durumda "result" değişkenimizin
değerini bir artırıyoruz. Göstericiye, bir sonraki kaydın adresini atıyoruz,
bu şekilde listedeki tüm kayıtların isim bilgisi kontrol ediliyor. Fonksiyon
"result" değişkeninin değerine geri dönüyor.
Listemizde yer
alan tüm kayıtları bir dosyaya yazdıralım. Önce bir kaydı dosyaya yazdıracak
fonksiyonumuzu oluşturalım. Bu fonksiyon, parametre değişkeni olarak, açılmış
bir dosyanın "handle" değerini ve yazılacak kaydı gösteren (record
*) türünden bir göstericiyi almalıdır.
void WriteRecordToFile(FILE
*fp,record *rp); /*
Prototip bildirimi */
void WriteRecordToFile(FILE
*fp,record *rp)
{
     fprintf(fp,"%s - %d\n",rp->rName,rp->rNumber);
}
|
Fonksiyonda standart
fprintf fonksiyonunu kullanarak, kaydın "rName" ve "rNumber"
veri elemanlarının değerlerini dosyaya yazdırdık. fprintf fonksiyonu, printf
fonksiyonu gibi çalışır; tek farkı ise yazılacak bilgiyi ekrana değil, ilk parametresinde
gönderilen dosya işaretçisinin gösterdiği dosyaya yazdırmasıdır. (Eğer ilk parametre
değişkeni olan dosya göstericisi olarak "stdout" geçerseniz, bilgi
ekrana yazdırılır; burada "stdout" stdio.h standart başlık dosyasında
tanımlı bir dosya işaretçisidir ve program başlatıldığında işletim sistemi tarafından
otomatik olarak yaratılır. "stdout", ekrana bağlıdır.) Şimdi listedeki
tüm kayıtları dosyaya yazdıracak fonksiyonumuzu yazalım :
void WriteAllRecordsToFile(const
char *fileName,recordList list); /* Prototip bildirimi */
void WriteAllRecordsToFile(const
char *fileName,recordList list)
{
    FILE *fp;
    record *tmpRecord = list->start;
    if ((fp = fopen(fileName,"w")) == NULL) {
       printf("Dosya acilamadi\n");
       exit(EXIT_FAILURE);
    }
    while (tmpRecord)
{
       WriteRecordToFile(fp,tmpRecord);
       tmpRecord = tmpRecord->rNext;
    }     fclose(fp); }
|
Fonksiyonumuz parametre
değişkeni olarak oluşturulacak dosyanın adını ve dosyaya yazılacak kayıtları
tutan listeyi alıyor. Öncelikle (FILE *) türünden bir gösterici tanımlıyoruz.
Bu gösterici, açacağımız dosyayı gösteren dosya işaretçisidir. Dosya işlemlerini
yapan diğer standart fonksiyonları çağırırken bu dosya işaretçisini kullanacağız.
Listeyi dolaşırken kullanacağımız (record *) türünden bir gösterici tanımlayarak
bu göstericiye ilk değer olarak listenin başındaki kaydın adresini atıyoruz.
Daha sonra dosyayı, fopen fonksiyonu ile yazma modunda ("w") açıyoruz.
Eğer dosya herhangi bir sebepten dolayı açılamazsa fopen fonksiyonu NULL değere
geri döner, bu durumda "Dosya açılamadı" mesajı vererek programdan
çıkışımızı sağlayacak exit fonksiyonunu çağırıyoruz. Eğer dosya açılmışsa while
döngüsü işlemeye başlar. Bu döngünün her adımında sıradaki kayıt, az önce yazmış
olduğumuz "WriteRecordToFile" fonksiyonuna gönderiliyor ve kayıt "fp"
dosya işaretçisinin gösterdiği dosyaya yazılıyor. Döngünün her adımında "tmpRecord"
isimli göstericiye listede bir sonraki sırada yer alan kaydın adresini atıyoruz
ve bu gösterici NULL değere sahip olduğunda döngüden çıkıyoruz. Bu noktada listemizdeki
tüm kayıtlar dosyaya yazılmış durumdadır. Fonksiyondan çıkmadan önce açmış olduğumuz
dosyayı, fclose fonksiyonunu çağırarak kapatıyoruz. Programı çalıştırdığınızda,
çalıştırılabilir dosyanızın (.exe) bulunduğu dizinde verdiğiniz isme sahip bir
dosya oluşturulmuş olmalıdır.
Liste içerisinde,
belli bir karakter ile başlayan kayıtları bulmak isteyebiliriz. Örneğin ismi
"a" ile başlayan kayıtları ekrana yazdırmak isteyebiliriz. Bu isteğimizi
karşılayacak bir fonksiyon yazalım. Önce, listede söz konusu karakter ile başlayan
kayıtların, liste içerisindeki sıra numaralarını bulan bir fonksiyon yazalım.
void FindRecordsStartWith(recordList
list,const char *str, int *index); /* Prototip bildirimi */
void FindRecordsStartWith(recordList
list,const char *str, int *index)
{
    int i = 0, lIndex = 0;
    record *tmpRecord = list->start;
   
while (tmpRecord) {
       if (StartWith(tmpRecord->rName,str))
           index[i++] = lIndex;
       lIndex++;
       tmpRecord = tmpRecord->rNext;
    }
    index[i] = -1; }
|
Fonksiyonumuz,
parametre değişkeni olarak gönderilen liste içerisinde dolaşarak, ismi "str"
nin belirttiği karakter ya da karakter katarı ile başlayan kayıtların liste
içerisindeki sıra numaralarını, yine parametre değişkeni olarak gönderilen (int
*) türünden "index" isimli parametre değişkenine atayacak. Bu parametre
değişkeni için fonksiyonu çağırdığımız yerde tanımlayacağımız tamsayı türünden
bir dizinin adresini gönderebiliriz. Listede dolaşırken kayıtları "tmpRecord"
isimli göstericiye atıyoruz. while döngümüz bu göstericinin değeri NULL olmadığı
müddetçe dönüyor. Döngü içerisinde, sıradaki kaydın "rName" veri elemanında
saklanan isim bilgisinin, "str" ile belirtilen karakter katarı ile
başlayıp başlamadığı kontrol ediliyor. Bu kontrol için de daha önce yazmış olduğumuz
"StartWith" isimli fonksiyonumuzu kullanıyoruz. Eğer bu fonksiyondan
TRUE değeri alırsak, "index" isimli parametre değişkenimizin sıradaki
elemanına, kaydın liste içerisindeki sıra numarasını atıyoruz. Fonksiyondan
çıkmadan önce, diziye son eleman olarak -1 değerini atıyoruz. Bu değeri dizi
sonuna gelinip gelinmediğini kontrol etmek için kullanacağız. Hiçbir elemanın
sıra numarası negatif bir değer olamayacağı için -1 değerini seçmemizde bir
sakınca yok. Şimdi bu diziyi parametre değişkeni olarak alacak ve sıra numaralarından
kayıtlara ulaşıp bilgilerini ekrana yazdıracak fonksiyonumuzu yazalım.
void WriteRecordByIndex(recordList
list,int index); /* Prototip bildirimi*/
void WriteRecordByIndex(recordList
list,int index)
{
    record *tmpRecord;
    int tmpIndex = index;
    if (index
> list->size || index < 0)
       return;
    tmpRecord
= list->start;
    if (index == 0) {
       WriteRecordToFile(stdout,tmpRecord);
       return;
    }
    while (index--)
       tmpRecord = tmpRecord->rNext;
    WriteRecordToFile(stdout,tmpRecord);
}
|
Fonksiyonun başında
sıra numarası değerinin doğruluğu kontrol ediliyor. Bu değer, listenin toplam
kayıt sayısından küçük, sıfırdan büyük veya sıfıra eşit olmalıdır. Eğer bu koşul
sağlanmıyorsa fonksiyondan çıkılıyor. Eğer sıra numarası sıfır ise listedeki
ilk kaydın bilgileri ekrana yazdırılıyor ve fonksiyondan çıkılıyor. Diğer durumlarda
ilgili kayıt "while(index--)" ifadesi ile bulunuyor. Bu döngüden çıkıldığında
"tmpRecord" göstericisi aradığımız kaydı gösteriyor olacaktır. Kaydın
bilgilerini ekrana nasıl yazdırdığımıza dikkat edin : "WriteRecordToFile(stdout,tmpRecord);"
Bunun için dosyaya kaydın bilgilerini yazmak için tanımladığımız fonksiyonu
kullandık. Bu fonksiyona dosya işaretçisi olarak "stdout" gönderdiğimizde,
işletim sistemi tarafından otomatik olarak tanımlanan bu işaretçi ekranı gösterdiği
için yazma işlemi de ekrana yapılacaktır.
Bir makalemizin
daha sonuna geldik, bir dahaki makalemizde görüşmek üzere..
Kaynak kod için tıklayın.
Makale:
Karakter Katarları Üzerinde İşlemler ve Bağlı Liste Uygulaması - 3 C ve Sistem Programlama Çiğdem Çavdaroğlu
|