Yazı dizimizin
ilk adımında karakter katarları ile ilgili genel bilgiler vermiştik ve karakter
katarları üzerinde işlemler yapabileceğimiz bir bağlı liste uygulamasına başlamıştık.
İkinci adımda, bağlı liste uygulamamıza fonksiyonlar eklemeye devam edeceğiz.
Karakter katarları ile ilgili işlemler yapan bir takım fonksiyonlar yazarak,
uygulamamızda bu fonksiyonlardan yararlanacağız. Yaptıklarımızı kısaca gözden
geçirecek olursak; öncelikle iki yapı bildiriminde bulunduk. "record"
isimli yapımız, bağlı listedeki elemanlara ilişkin bilgileri tutuyordu. "recordList"
isimli yapımız ise, elemanlara ilişkin bilgileri tutan "record" türünden
nesneleri tutuyordu. "recordList" yapısında, sadece bağlı listedeki
ilk elemana ilişkin adresin tutulması yeterli idi, diğer elemanlara bir önceki
elemanda tutuğumuz adres yoluyla erişiyorduk. Bu yapıların başlangıç fonksiyonlarını,
listeye baştan ve sonran yeni kayıt ekleyen fonksiyonlarını yazmıştık. Şimdi,
listenin herhangi bir sırasına yeni kayıt ekleyen fonksiyonumuzu yazalım :
//başlık
dosyası
#define TRUE 1
#define FALSE 1
typedef int BOOL;
BOOL InsertRecordToList(recordList list, char *name,int number, int index);
/* Prototip bildirimi */
//kaynak kod dosyası
BOOL InsertRecordToList(recordList list, char *name,int number, int index)
{
    record *tmpRecord, *newRecord;
    if (index == list->size + 1)
    {
       AddRecordEnd(list,name,number);
       return TRUE;
    }
    if (index == 1)
    {
       AddRecordStart(list,name,number);
       return TRUE;
    }
    if (index > list->size || index < 1)
       return FALSE;
    tmpRecord = list->start;
    newRecord = CreateRecord();
    SetRecord(newRecord,name,number);
    index -= 2;
    while (index--)
       tmpRecord = tmpRecord->rNext;
    newRecord->rNext = tmpRecord->rNext;
    tmpRecord->rNext = newRecord;
    return TRUE;
}
|
Fonksiyonun geri
dönüş değeri olarak kullandığımız BOOL ismi, typedef bildirimi yapılarak (int)
türünün yeni ismi olarak kullanılmıştır. TRUE ve FALSE ise sırasıyla 1 ve 0
değerlerine define edilmiştir. Fonksiyon başarı durumunda sıfır dışı bir değere
(TRUE), başarı durumunda ise sıfır değerine (FALSE) dönecektir. Eğer "index"
parametresine geçilen değer, listenin eleman sayısından fazla ise veya 1’den
küçük bir değer ise ekleme yapılmıyor; bu durumda fonksiyon sıfır değerine dönüyor.
Bu değer 1 ise, listenin başına ekleme yapan fonksiyon çağrılıyor, listenin
eleman sayısından 1 fazla ise listenin sonuna ekleme yapan fonksiyon çağrılıyor.
Diğer durumlarda araya ekleme işlemi yapılıyor. Biz listemizde sadece ilk kayda
ilişkin adresi tutuyoruz. Dolayısıyla araya ekleme yapma durumunda aradaki bir
kaydın adresine listeyi kullanarak doğrudan erişemeyiz. Bunun yerine, listedeki
elemanları "index" parametresinde belirtilen sayı kadar dolaşarak
ilgili kaydın adresine ulaşırız. Araya ekleme işlemi için yapılması gereken,
"index" değerinden bir önce sırada bulunan kaydın "rNext"
isimli veri elemanına yeni kaydın adresini atamak ve yeni kaydın "rNext"
isimli veri elemanına da "index" değerinden bir sonraki sırada bulunan
kaydın adresini aramaktır.
Bu
işlemi bir şema ile gösterelim :
Fonksiyonumuzda,
listeyi dolaşırken kullanacağımız record * türünden geçici bir nesne tanımlıyoruz.
Listeyi dolaşırken her adımda ilgili kaydı bu nesneye atayarak kaydın bilgilerine
ulaşıyoruz. (record *) türünden tanımladığımız "newRecord" isimli
nesnemizi de fonksiyona parametre olarak geçilen bilgileri atayarak oluşturuyoruz.
Döngü değişkeninin değerini önce listenin başında bulunan kaydın sıra numarasını
sıfır kabul ettiğimiz için, sonra da ilgili sıra numarasından bir önceki kayda
ulaşmak istediğimiz için birer defa azaltıyoruz. (index -= 2) Döngü (index--)
koşulu sağlandığı sürece dönüyor. Yani bu değer sıfıra ulaştığında döngüden
çıkılacak ve "tmpRecord" isimli nesne aradığımız kaydı gösteriyor
olacak. Yeni kaydımızın "rNext" veri elemanına da birinci sıradaki
kaydın "rNext" veri elemanında tutulan adresi atıyoruz. "tmpRecord"
nesnesinin gösterdiği kaydın "rNext" veri elemanına da yeni kaydımızın
adresini atıyoruz. Böylece araya ekleme işlemi gerçekleşmiş oluyor. Şimdi de
belli bir kaydı silen fonksiyonumuzu yazalım :
BOOL DeleteRecordFromList(recordList
list,int index); /*
Prototip bildirimi */
BOOL DeleteRecordFromList(recordList list,int index)
{
    record *tmpRecord, *deleteRecord;
    int tmpIndex = index;
    if (index > list->size || index < 1)
       return FALSE;
    tmpRecord = list->start;
    if (index == 1)
    {
       list->start = tmpRecord->rNext;
       free(tmpRecord);
       return TRUE;
    }
    index -= 2;
    while (index--)
       tmpRecord = tmpRecord->rNext;
    deleteRecord = tmpRecord->rNext;
    if (tmpIndex == list->size)
       tmpRecord->rNext = NULL;
    else
       tmpRecord->rNext = deleteRecord->rNext;
    free(deleteRecord);
    return TRUE;
}
|
Kayıt silen fonksiyonumuz
da başarı durumunda sıfır dışı bir değere, başarısızlık durumunda ise sıfır
değerine dönüyor. Fonksiyonumuzun başında yine listeyi dolaşırken kullanacağımız
(record *) türünden geçici bir gösterici tanımlıyoruz. Döngünün her adımında
sıradaki kaydı bu geçici nesneye atıyoruz. Kayıt silme işlemi, ilgili kayıttan
bir önceki sırada bulunan kaydın "rNext" isimli veri elemanına ilgili
kayıttan bir sonraki sırada bulunan kaydın adresinni atanması ve silinecek kaydın
"free" fonksiyonu ile serbest bırakılması yoluyla gerçekleştirilir.
Bu işlemi bir şema ile gösterelim :
while döngüsü ile
aradığımız kayda ulaştıktan sonra, silinecek kaydı tutacak olan "deleteRecord"
isimli nesneye bu kaydın "rNext" isimli veri elemanında tutulan kayıt
adresini atıyoruz. Bu değeri silme işlemini tamamladıktan sonra free etmeliyiz.
Fonksiyonun başında, silinecek kaydın ilk kayıt olup olmadığını kontrol ediyoruz.
Eğer ilk kayıt ise, listenin "start" isimli veri elemanına tmpRecord
nesnesinin "rNext" veri elemanında bulunan adresi atıyoruz ve tmpRecord
nesnesini serbest bırakarak fonksiyonu sonlandırıyoruz. Eğer silinecek kayıt
ilk kayıt değilse, kayıttan bir önceki eleman bulunana dek döngüde kalıyoruz.
Döngüden çıktığımız noktada artık elimizde aradığımız kayıt mevcut durumdadır.
Bu noktada, silinecek kaydın son kayıt olup olmadığını kontrol ediyoruz. Eğer
son kayıt ise, tmpRecord’da bulunan kaydın "rNext" isimli veri elemanına
NULL değerini atamamız gereklidir. Çünkü ilgili kayıt silindikten sonra artık
bu kayıt son kayıt durumunda olacaktır. Eğer son kayıt değilse, tmpRecord’un
"rNext" isimli veri elemanına deleteRecord’un "rNext" isimli
veri elemanında tutulan adresi atıyoruz. En son tmpRecord nesnesini serbest
bırakıyoruz. Bu fonksiyon, silinecek kaydın sıra numarasının bilindiği durumlarda
kullanılabilir. Ancak silinecek kaydın sıra numarasını değil de, değerini biliyorsak
bunun için başka bir fonksiyon yazmamız gerekecek. Bu fonksiyon, örneğin öğrencinin
adını alacak ve listeyi dolaşarak bu isme sahip bir kayıt olup olmadığını kontrol
edecek. Eğer var ise bu kaydı silecek. Bunun için öncelikle listede arama işlemini
yapacak fonksiyonumuzu tasarlayalım :
int FindRecordInList(recordList
list, char *name); /* Prototip bildirimi */
int FindRecordInList(recordList list, char *name)
{
    record *tmpRecord = list->start;
    int i = 0, size = list->size;
    while (i++ != size)
    {
       if (!strcmp(tmpRecord->rName,name))
          return i-1;
       tmpRecord = tmpRecord->rNext;
    }
    return -1;
}
|
Arama işlemini
yapan fonksiyonumuz, listeyi ve arayacağı ismi parametre olarak alıyor. Eğer
listede o isme sahip olan bir öğrenci kaydı mevcut ise kaydın sıra numarasına,
mevcut değil ise -1 değerine geri dönüyor. Burada izleyeceğimiz mantık yine
aynı. (record *) türünden "tmpRecord" isimli bir geçici gösterici
tanımlayarak fonksiyona başlıyoruz. while döngümüz (i++ != size) koşulu sağlanana
dek dönecek. Yani döngü değişkeni i, listenin eleman sayısına ulaştığında arama
işlemi bitiyor. Eğer i’nin değeri size’ın değerine ulaşmadan önce aranan kayıt
bulunursa (return i - 1;) ifadesi ile kaydın sıra numarasına geri dönülüyor.
Her adımda tmpRecord isimli geçici göstericinin gösterdiği nesnenin "rName"
isimli veri elemanının değeri ile fonksiyona gönderilen name isimli göstericinin
gösterdiği nesnenin değeri, standart strcmp fonksiyonu ile karşılaştırılıyor.
Eğer karşılaştırılan iki ifadenin değeri birbirine eşit ise bu fonksiyon sıfır
değerine döner. if koşulu içerisindeki ifade de bunu sınamaktadır. Yeni geliştirilmiş
dillerde, karakter katarları "==" eşitlik operatörü ile karşılaştırılabiliyor.
Ancak C dilinde bu yapılmamalıdır. Eğer (tmpRecord->rName == name) şeklinde
bir ifade yazılırsa, derleyici bu iki karakter katarının adres değerlerini karşılaştırır.
Çünkü şu halleri ile bu nesneler bir göstericidir ve içlerinde adres bilgisi
tutarlar. İki karakter katarı da hiçbir zaman aynı adreste bulunamayacağına
göre böyle bir ifade her zaman yanlış yorumlanacaktır. Karşılaştırma işlemleri
için standart strcmp fonksiyonu kullanılmalıdır. Şimdi silmek istediğimiz kaydın
sıra numarasını önce bu fonksiyon yardımı ile bulabiliriz. Fonksiyonumuzu şu
şekilde tasarlayabiliriz :
BOOL DeleteRecordFromListByName(recordList
list,char *name); /* Prototoip bildirimi*/
BOOL DeleteRecordFromListByName(recordList list,char *name)
{
    int index = FindRecordInList(list,name);
    if (index == -1)
       return FALSE;
    return DeleteRecordFromList(list,index);
}
|
Kaydı, isminden
bularak silen fonksiyonumuz parametre olarak kaydın ismini ve listeyi alıyor.
Fonksiyonda öncelikle söz konusu isme sahip olan kayıt "FindRecordInList"
fonksiyonu ile bulunuyor. Eğer bu fonksiyondan -1 değeri dönerse, demek ki listede
o isme ilişkin bir kayıt yoktur ve fonksiyonumuz bu durumda FALSE değerine döner.
Aksi durumda, kayıt bulunmuştur ve artık elimizde kaydın sıra numarası da olduğuna
göre daha önce yazmış olduğumuz "DeleteRecordFromList" fonksiyonumuzu
çağırabiliriz. Fonksiyonumuz, çağrılan bu fonksiyondan dönen değere geri dönüyor.
Burada, liste içerisinde, gönderilen isme ilişkin ilk kaydın bulunduğuna dikkat
etmelisiniz. Eğer aynı isme ilişkin birden fazla kayıt bulunabilecekse ve böyle
bir çağrı ile o isme sahip olan tüm kayıtların silinmesi istenirse fonksiyon
farklı bir şekilde tasarlanmalıdır. Buna örnek teşkil etmesi açısından listede
belli bir not değerine sahip olan tüm kayıtları silen bir fonksiyon yazalım.
Öncelikle listemizde ilgili not değerine sahip olan tüm kayıtları bulalım ve
bunların liste içerisindeki sıra numaralarını dinamik olarak tahsis edeceğimiz
bir dizi içerisinde tutalım. Daha sonra bu diziden silinecek kayıtların sıra
numaralarını alarak, sıra numarasına göre silme işlemi yapan fonksiyonumuzu
çağıralım :
BOOL DeleteAllRecordsFromListByNumber(recordList
list,int number); /* Prototip bildirimi */
BOOL DeleteAllRecordsFromListByNumber(recordList list,int number)
{
    int i, recordNum = 0, size = list->size;
    int *indexArray = (int *)malloc(size * sizeof(int));
    record *tmpRecord = list->start;
   
for (i = 0; i < size; ++i)
    {
       if (tmpRecord->rNumber == number)
       indexArray[recordNum++] = i;
       tmpRecord = tmpRecord->rNext;
    }
    if (recordNum == 0)
       return FALSE;
    for (i = 0; i < recordNum; ++i)
       DeleteRecordFromList(list,indexArray[i]+1-i);
    free(indexArray);
    return TRUE;
}
|
Fonksiyonumuz parametre
değişkeni olarak listeyi ve not değerini alıyor. Tanımladığımız değişkenlere
göz atalım : "i", döngü değişkeni olarak kullanılıyor. "recordNum"
isimli değişken, ilgili not değerine ilişkin kaç tane kayıt bulunduğu bilgisini,
"size" ise listedeki eleman sayısını tutuyor. "indexArray"
isimli tamsayı türünden dizide ise silinecek kayıtların sıra numaralarını tutuyoruz.
Bu diziyi dinamik olarak, listedeki kayıt sayısı kadar tahsis ediyoruz. Çünkü
listedeki tüm kayıtlara ilişkin not değeri, fonksiyona gönderilen not değerine
eşit olabilir. Taşma hatası ile karşılaşmamak için diziyi maksimum boyutta tahsis
ediyoruz. (Burada dizi için önce küçük bir miktar tahsis edip, dizide tutulması
gereken eleman sayısı arttığı sürece yeniden tahsisat yapılması yoluna da gidilebilir.
Listedeki kayıt sayısı çok fazla olduğunda bu yöntem daha etkin olacaktır.)
"tmpRecord" isimli değişkeni yine listeyi dolaşırken kullanıyoruz.
Bu göstericiye öncelikle listenin başındaki kaydın adresini atıyoruz. Her adımda,
tmpRecord göstericisinin gösterdiği yerdeki nesnenin "rNumber" isimli
veri elemanının değeri ile fonksiyona gönderilen "number" isimli parametre
değişkeninin değerini karşılaştırıyoruz. Eğer bu iki değer eşit ise tmpRecord
göstericisi şu anda aradığımız kayıtlardan birisini göstermektedir. Bu noktada
"indexArray" dizimizin sıradaki elemanına i döngü değişkeninin değerini
atıyoruz. Bu noktada i döngü değişkeni aradığımız kaydın liste içerisindeki
sıra numarasını göstermektedir. Döngüden çıkıldığında eğer "recordNum"
değişkeninin değeri sıfıra eşit ise demek ki listede söz konusu nota sahip hiçbir
kayıt bulunmamaktadır. Bu duurmda fonksiyon FALSE değerine geri dönüyor. Aksi
durumda yine bir for döngüsüne giriyoruz. Bu döngü "recordNum" değişkeninin
değeri kadar dönecek. Döngü içerisinde, "DeleteRecordFromList" isimli
fonksiyonumuzu çağırıyoruz. Bu fonksiyon, sıra numarasına göre listeden kayıt
siliyordu. Sıra numarası olarak da "indexArray" dizisinde sakladığımız
değerleri kullanacağız. Ancak bu noktada bir şeye dikkat etmeliyiz : Örneğin
sileceğimiz kayıtların sıra numaraları sırasıyla 0, 4, 5 olsun. Bu durumda sıfır
sıra numaralı kayıt silindikten sonra, döngünün diğer adımında silme işlemini
yapan fonksiyona sıra numarası olarak dört gönderirsek yanlış kaydı silmiş oluruz.
Çünkü listeden bir kaydın silinmesi ile artık listemizin sıralaması değişmiş
durumdadır. Doğru kayda erişmek için (indexArray[i]+1-i) ifadesini kullanmalıyız.
Burada bir eklenmesinin sebebi, silme işlemi yapan fonksiyonun sıra numarasının
başlangıcı olarak 0’ı değil 1’i baz almasıdır. (-i) yazılmasının sebebi ise
kayıt silindikçe listedeki sıralama değişikliğinden kaynaklanan farkı gidermektir.
İlk adımda 0 numaralı kayıt silindiğinde, 4 numaralı kayıt artık 3 numaralı
kayıt olacaktır. Bu ifade ile bu şekilde doğru kayda ulaşım sağlanır. Fonksiyondan
çıkmadan önce dinamik tahsis ettiğimiz alanı boşaltıyoruz ve TRUE değerine dönüyoruz.
Şimdi de listemizdeki kayıtlara sıra numaraları ile ve isimleri ile ulaşarak
not değerlerini güncelleyelim. Üç tane fonksiyon yazacağız. Bu üç fonksiyonumuzun
prototip bildirimleri şu şekilde :
BOOL UpdateRecordByIndex(recordList
list,int index,int newNumber);
BOOL UpdateRecordByName(recordList list,char *name,int newNumber);
void UpdateRecordByNumber(recordList list,int number,int newNumber);
|
Parametre değişkeni
olarak, ilk fonksiyonumuz listeyi, sıra numarasını ve yeni not değerini; ikinci
fonksiyonumuz listeyi kaydın isim bilgisini ve yeni not değerini ; üçüncü fonksiyonumuz
ise listeyi, not değerini ve yeni not değerini alıyor. İlk fonksiyonumuzu yazalım
:
BOOL UpdateRecordByIndex(recordList
list,int index,int newNumber)
{
    record *tmpRecord;
    int tmpIndex = index;
    if (index > list->size
|| index < 1)
       return FALSE;
    tmpRecord =
list->start;
    if (index == 1)
    {
       tmpRecord->rNumber = newNumber;
       return TRUE;
    }
    index -= 1;
    while (index--)
       tmpRecord = tmpRecord->rNext;
   
tmpRecord->rNumber = newNumber;     return TRUE;
} |
Fonksiyonumuz önce
sıra numarası girilen kayda ulaşıyor, ardından kaydın "rNumber" isimli
veri elemanına "newNumber" parametre değişkeninin değerini atıyor.
Fonksiyonu başında, gönderilen sıra numarasının liste sınırlarını taşıp taşmadığı
kontrol ediliyor. Taşma durumunda FALSE değerine dönülüyor. İkinci fonksiyonumuzu
yazalım :
BOOL
UpdateRecordByName(recordList list,char *name,int newNumber)
{
    int index = FindRecordInList(list,name);
   
if (index == -1)
       return FALSE;
    return UpdateRecordByIndex(list,index+1,newNumber);
} |
İkinci fonksiyonumuz
önce gönderilen isme sahip bir kayıt olup olmadığını araştırmak için "FindRecordInList"
isimli fonksiyonu çağırıyor. Eğer o isme sahip bir kayıt yok ise bu fonksiyon
-1 değerine dönüyordu. Burada geri dönüş değerinin -1 olup olmadığı kontrol
ediliyor ve -1 değerine dönülmüşse fonksiyon FALSE değerine dönüyor. Aksi durumda
geri dönen sıra numarası değeri, ilk fonksiyonumuza gönderiliyor. Üçüncü fonksiyonumuzu
yazalım :
void
UpdateRecordByNumber(recordList list,int number,int newNumber)
{
    record *tmpRecord = list->start;
    while
(tmpRecord->rNext)
    {
       if (tmpRecord->rNumber == number)
       tmpRecord->rNumber = newNumber;
       tmpRecord = tmpRecord->rNext;
    }
    if (tmpRecord->rNumber == number)
       tmpRecord->rNumber = newNumber;
   
return TRUE;
} |
Üçüncü fonksiyonumuz,
gönderilen not değerine sahip olan tüm kayıtları güncelliyor. Bunun için liste
sırayla dolaşılıyor, her adımda sıradaki kaydın söz konusu not değerine sahip
olup olmadığı kontrol ediliyor ve eğer sahip ise kaydın değeri güncelleniyor.
Döngüden çıkıldığı noktada "tmpRecord" isimli göstericimiz hala son
kaydı göstermektedir. Bu nedenle ikinci bir if ifadesi ile bu kaydın değeri
de kontrol ediliyor.
Yazı dizimizin
ikinci makalesinin de sonuna geldik. Üçüncü makalemizde listemize yeni fonksiyonlar
eklemeye devam edeceğiz. Makaleye ilişkin uygulamayı
indirmek için tıklayınız.
Mutlu ve huzurlu
günler...
Makale:
Karakter Katarları Üzerinde İşlemler ve Bağlı Liste Uygulaması - 2 C ve Sistem Programlama Çiğdem Çavdaroğlu
|