السجلات في سي " Structures in C "


بسم الله الرحمن الرحيم

السجلات في سي Structures in C

 



قبل البداء و الخوض في موضوع السجلات في لغة سي سنتطرق إلى خاصية في لغة سي و هي عبارة عن تغيير النوع ( type casting )

 عن طريق الامر typedef و هي خاصية تعريف نوع من نوع آخر, كالتالي:


typedef النوع المغير له      النوع الاصلي ;



مثلاً نريد ان تعمل الكلمة INT كالنوع int تماماً و سيكون ذلك بالامر:
 

typedef int INT;
 

هنا نستطيع ان نقول :
 

INT a;
 

و سيوكن تماماً مثل :
 

int a;
 


و لنأخذ هذا الكود على سبيل المثال و به فكرة جميلة تقريباً هي فكرة النوع string في سي++ ولو أن الامر string في سي++ عبارة عن كلاس و به الكثير من الدوال المسانده و تحميل المتغيرات و غيرها من هذه الأمور و لكن لنطالع هذا المثال:
 

#include "stdio.h"
#include "string.h"

typedef char* string;

main()
{
        string a;

        a = (string) malloc(sizeof(char));

        strcpy(a, "talal");

        printf(a);
}
 

لاحظ انه في السطر السادس عرفنا المتغير a من نوع string و قد جعلنا الــ string مكافئة للامر char* أي جعلنا string عباره عن مؤشر إلى char ( سلسلة حرفية ) و قد قمنا بعمل هذا كله في السطر الثالث, و لا يمكننا ان نستخدم مؤشر لحروف char* بدون حجز قيمة لها في الذاكرة و قد عملنا ذلك في السطر السابع, و بعد ذلك اسندنا قيمة للــ string و طبعناها في السطرين الثامن و التاسع على التوالي.

و لربما تتسائل ما فائدة هذا الشئ و ماهي علاقته في موضوع السجلات و لكننا سنتناول هذا الموضوع قريباً في هذا الدرس.


_________________________________________________________________________________
 

* كيفية تعريف السجل في لغة سي:
أولاً لابد ان نعلم ان كلمة struct كلمة محجوزه في لغة سي و سي++ , و نستطيع تعريف السجل كالتالي:
 

struct (إسم السجل)
{
    أعضاء السجل
};
 


- طبعاً هذه الطريقة هي أحدا الطرق التي تستطيع تعريف السجل بها.

فلو اردنا ان نعرف سجل إسمه data و يحتوي على إسم من نوع char* و العمر من نوع int
إذا سيكون التعريف كالتالي:
 

struct data
{
    char namr[30];
    int age;
};
 

و تبعاً لهذا التعريف سيكون السجل data نوع كأي نوع آخر مثل int, floar, char,… .
و لتعريف متغير من نوع السجل نعرفه كالتالي:
 

strcut اسم المتغير       اسم السجل ;
 

فلو اردنا ان نعرف متغير student من السجل data أعلاه فسنعرفه كالتالي:
 

struct data student ;
 


* كيفية الوصول لأعضاء السجل :
الأمر بسيط جداً و هو كالتالي :
 

(عضو السجل).(المتغير من نوع السجل)
 

فلو اخذنا السجل data و عرفنا منه متغير student كالتالي :
 

struct data student ;
 

الان المتغير student يتكون من قسمين و هما قسمي السجل name و الــ age الموجوده في السجل data و سنصل لعضوين name و age كالطريقة اعلاه هكذا:
 

student.name & student.age
 

الآن لدينا متغيرين الاول student.name من نوع char* و الثاني student.age من نوع int
إذا استطيع ان اقول :
 

student.age = 16 ;
strcpy(student.name, "Talal");
 

و لنأخذ هذا المثال على الإدخال و الإخراج في السجل :
 

#include
struct
data
{
        char namr[30];
        int age;
};

int main()
{
        struct data student;

        printf("nPlease Enter The name and the age: ");
        scanf("%s%d",student.namr, &student.age);

        printf("nName:%s, Age:%dnn",student.namr, student.age);

    return 0;
}
 


طبعاً في المثال السابق قمت بتعريف السجل خارج الــ main أي Global و هذا الذي افضله, و يمكن ان نقوم بتعريف السجل داخل الــ main أو داخل أي دالة أخرى.

- و الآن نأتي لفائدة الجملة typedef مع السجلات :
لقد ذكرنا في هذا الدرس ان السجل بعد تعريفة يكون نوع مثل أي نوع من int, char, float ...
و ذكرنا أيضاً أن الجملة typedef تعرف نوع من نوع. و تعريف متغير من سجل متعب نوعاً ما أو بالأصح غير مألوف, و أكيد أن السي لن تترك شئ كهذا بدون عملية التسهيل لمحبيها و لكن السي جعلت هناك طريقتين و لنبداء بالأولى منها:

- الطريقة الأولى:
عند تعريف السجل التالي:
 

struct data
{
        char name[30];
        int age;
};
 

الآن بإستخدام الجملة typedef سنعرف نوع نختار لإسمة من نوع السجل struct data كالتالي:
 

typedef struct data Mydata ;
 

و إذا اردت ان اعرف student من السجل اعلاه عند كتابة الجملة
 

typedef struct data Mydata ;
 

سيكون كالتالي :
 

Mydata student ;
 

بدون كلمة struct في كل مره نعرف متغير من السجل لأن Mydata أصبح نوع مثله مثل:struct data.

- الطريقة الثانية :
و هذه هي الطريقة المحبذه لي و الاسلم و هي كالتالي :
 

typedef struct
{
        (الأعضاء)
}إسم السجل ;
 

فلو اردنا ات نعرف سجلنا السابق data بهذه الطريقة سيكون كالتالي:
 

typedef struct
{
        char name[30] ;
        int age;
}data ;
 

و إذا أردنا ان نعرف المتغير student من ( النوع ) data نعرفه كالتالي :
 

data student ;
 

هل وضحت سهولت إستخدام typdef بدل من التعريف العادي ؟!
طبعاً في باقي الدرس سوف نستخدم هذه الطريقة بدل من التعريف العادي.



•السجلات المتداخلة:
في كثير من الأحيان تحتاج إلى وضع سجل داخل سجل, مثلاً في السجل السابق data كان هناك العضو name و لو اردنا ان يكون هذا العضو سجلاً بحد ذاته يحتوي على عنصرين هما الاسم الاول و الاسم الثاني سنقوم بتعريف السجلات الاصغر و الداخلية إلى أن نصل إلى السجل الأكبر فلو اردنا ان نمثل الفكرة السابقة على شكل كود للسي سنعرف السجل الاصغر و هو الذي يحتوي على الاسم الأول و الاسم الثاني هكذا :
 

typedef struct
{
        char first[15] ;
        char last[15] ;
}name ;
 

و من ثم سنعرف السجل الأكبر الذي يحوي الاسم و العمر الذي اسميناه في السابق data هكذا:
 

typedef struct
{
        name std_name ;
        int age ;
}data ;
 

لاحظوا ان العضو الأول من السجل data عبارة عن سجل إسمة std_name من نوع name .

و يبقى السؤال هنا إلى أنه كيف سنصل للأعضاء الداخلية للسجل std_name عند تعريف متغير student من نوع data ؟!
الجواب بسيط جداً و هو كالتالي :
 

strcpy(student.std_name.firsr, "Talal") ;
strcpy(student.std_name.last, "Abdullah") ;
 

إذا كلما اردنا ان نصل إلى العضو نضع إسم المتغير ثم '.' ثم العضو ( إذا كان العضو الاول سجل ) و هكذا ...


•مصفوفة السجلات :

لقد علمنا ان السجل نوع كأي نوع من انواع البيانات, لذلك من الممكن ان يكون السجل مصفوفة ايضاً و الطريقة سهله جداً كالتالي:
 

structure_name var[NUM] ;
 

فلو اخذنا السجل :
 

typedef struct
{
        char name[30];
        int age;
}data;
 

و اردنا ان نعرف مصفوفة من نوع data يسكون كالتالي:
 

data student[100] ;
 

طبعاً العدد 100 إختياري .
و نحن في السابق أخذنا نوع student من السجل data و سيكون سجل واحد و لكن هنا سيتضح اهمية السجلات فعندما عرفنا student كمصفوفة من نوع data أصبح كأنه لدينا 100 طالب و كل عنصر في المصفوفة عباره عن سجل بحد ذاته.
و للوصول إلى محتويات السجل نتبع الطريقة التاليه :
 

student[indix].name & student[indix].age …
 

و غالباً تستخدم مصفوفة السجلات إذا كان العدد محدداً أما إذا كان العدد غير محدد نستخدم طريقة من طريق الــ Data Structure منها اللنك لست درسنا القادم.


•السجلات و المؤشرات :

و نعيد و نكرر انه بعد تعريف السجل يصبح نوع كأي نوع آخر من انواع البيانات, إذا يمكن للسجل ان يكون مؤشر ( Pointer ) و العمليه كالتالي:
 

typedef struct
{
        char name[30];
        int age;
}data;
 

و سنعرف مؤشر للسجل كالتاالي :
 

data *s ;
 

فالنأخذ البرنامج التالي للتوضيح :
 

#include
#include

typedef struct
{
        char name[30];
        int age;
}data;

int main()
{
        data *s, std;

        s = &std;    // Assign std to s

        strcpy(std.name,"Talal");
        std.age = 20;

        printf("std.name = %s, std.age = %dnn",std.name, std.age);
        printf("s->name = %s, s->age = %dnn",s->name, s->age);

    return 0;
}
 

طبعاً نلاحظ الآن ظهور العلامة '->' بدل من النقطة عند إستخدام المتغير s ؟! لماذا ؟
الجواب : لأنه مؤشر لسجل و مؤشر السجل يستعمل في لغة السي و السي++ هذه العلامة بدلاً من العلامة '.' , و هذا من الاختلافات التي تميز لغة السي و السي++ عن باقي اللغات مثل الجافا و الدلفي فهي لا تفرق إذا كان مؤشر أو لا .
إذا قاعدة في لغة سي و سي++ هي إنه عند إستخدام مؤشر لسجل نستخدم -> بدلاً من '.' طبعاً هناك طريقة أخرى و هي هكذا:

(*s).name بدل s->name

طبعاً العلامة '->' أسهل :).


•السجلات و الدوال :

عند إستخدام السجلات مع الدوال إما أن يكون السجل مرسل للدالة أو إما ان يكون معاد من الدالة و إما ان يكون مستخدم في ضمن الدالة .
الحالة الأخيره معروفة و عملنا عليها في السابق داخل الدالة main و الــ main دالة اصلاً.
أما الحالتين الأولى و الثانيه فسنتطرق لها الآن.

- أولاً السجل معامل من معاملات الدالة :
أي أن نرسل السجل للدالة و الدالة تقوم بالعمليات على هذا السجل مثلاً: طباعة, معالجة, ... إلخ
و لنأخذ هذا المثال و نشرحة بعد قرائة المثال جيداً:
 

//----------------------------------------------------------

#include
#include
//----------------------------------------------------------
typedef struct
{
        char name[30];
        int age;
}data;
//----------------------------------------------------------
void display(data r);
//----------------------------------------------------------
main()
{
        data std;

        strcpy(std.name,"Talal");
        std.age = 20;

        display(std);
}

//----------------------------------------------------------
void display(data r)
{
        printf("(r.name) = %s,n(r.age) = %dnn",r.name, r.age);
}

//----------------------------------------------------------
 

و في هذا المثال لقد كتبنا رأس الدالة كالتالي:
 

void display(data r) ;
 

أي أنه يوجد دالة إسمها display تستقبل السجل r من نوع data ولا تقوم بإرجاع شئ.
و عند إستدعاينا الدالة و بعد إعطائها القيم كالتالي:
 

display( std ) ;
 

ارسلنا لها السجل كاملاً لتسقبله و تطبعه في جسم الدالة display .

و لنأخذ مثالاً آخر لإعطاء قيم السجل في الدالة و طبعاتها في الــ main :
 

#include
#include

typedef struct
{
        char name[30];
        int age;
}data;

void assign(data *r);

main()
{
        data std;

        assign(&std);
        printf("std.name = %s,nstd.age = %dnn",std.name, std.age);
}

void assign(data *r)
{
        strcpy(r->name,"Talal");
        r->age = 20;
}
 

و في هذا المثال كتبنا رأس الدالة ( التعريف ) هكذا :
 

void assign(data *r) ;
 

و جعلنا r كمؤشر لأن قيمة r ستتغير ( نحن نريد ذلك ) لإعطائها القيم.
و قمنا بإرسال السجل كالتالي :
 

assign( &std ) ;
 

لأن الدالة assign تستقبل مؤشر للسجل لذلك نرسل لها عنوان السجل و ليس السجل نفسه.
و داخل الدالة assign إستخدمنا r->name و r->age لأن r في الدالة مؤشر ( و مع المؤشرات نستخدم -> بدلاً من '.' ) .

- ثانياً إرجاع سجل من الدالة :
أي أن الدالة تقوم بإرجاع السجل عند الانتهاء من عملها و نستطيع تغيير البرنامج السابق ليرجع السجل بدلاً من إرسال السجل كعنوان و إستقباله كمؤشر.
سيتغير البرنامج ليصبح هكذا :
 

#include
#include

typedef struct
{
        char name[30];
        int age;
}data;

data assign(void);

main()
{
        data std;

        std = assign();
        printf("std.name = %s,nstd.age = %dnn",std.name, std.age);
}

data assign(void)
{
        data r;
        strcpy(r.name,"Talal");
        r.age = 20;
    return r;
}
 

و هنا عرفنا الدالة كالتالي :
 

data assign(void) ;
 

أي أن الدالة assign لا تستقبل شئ و القيمة المرجعة من الدالة هي عباره عن سجل من نوع data .
و قمنا بإستدعا الدالة هكذا :
 

std = assign() ;
 

أي أن القيمة المرجعة من الدالة ستوضع قيمتها في السجل std .
و في جسم الدالة assign عرفنا المتغير r من نوع سجل data و أعطينا لها قيم و قمنا بإرجاع هذا السجل من الدالة عن طريق الامر
 

return r ;
 


•إسناد السجلات :

نستطيع ان نسند سجلين لبعضهما البعض لكن شريطة أن يكونا من نفس النوع .
فلو أنشئنا السجل التالي :
 

typedef struct
{
char name[30];
int age;
}data;
 

و عرفنا منه متغيرين هكذا :
 

data a, b ;
 

و أعطينا المتغير a هذه القيم :
 

strcpy( a.name, "talal" ) ;
a.age = 20 ;
 

فبإمكاني ان اسند للمتغير b نفس محتويات المتغير a عن طريق هذه الجملة :
 

b = a ;
 


•إعطاء السجل أكثر من إسم أو إعطائه المتغيرات لحظة بناء السجل :

فلو كان لدينا السجل التالي :
 

typedef struct
{
char name[30];
int age;
}data, MyData ;
 

أستطيع أن اعرف المتغيرات سواء كان بــ data أو بــ MyData و كلها صحيحه.
فلو قلت :
 

MyData student ;
 

أو
 

data student ;
 

كانا سواء .
و هذا هو إعطاء السجل اكثر من إسم , أما إعطاء السجل أكثر من متغير لحظة بناء السجل و بدون تحديد إسم للسجل يكون كالتالي :
 

struct
{
    الاعضاء
}إسم المتغير ;
 

فلو اردنا ان نعمل على 100 طالب فقط و متأكيدن أن العدد لن يزيد عن 100 طالب فالأفضل
بناء السجل هكذا :
 

struct
{
    char name[30] ;
    int age ;
} student;
 

و هكذا يصبح student متغير و نقول :
 

student. name & student. age
 


طبعاً إلى الآن تعلمنا كيف ننشئ السجل بثلاثة طرق بقي الطريقة الرابعة و الاخيره و هي كالتالي:
 

struct (إسم السجل)
{
الاعضاء
}(المتغيرات) ;
 

أي نستطيع أن ننشئ سجل الطالب الذي تكرر علينا كثيراً بالطريقة الرابعه هكذا :
 

struct data
{
    char name[30] ;
    int age ;
} student;
 

هنا student سيكون متغير و data هو إسم السجل و هنا نستطيع في كل مرة نحتاج فيها لإنشاء سجل أن نشئ سجل بالطريقة :
 

struct data VAR ;
 

و إستعمال student كمتغير جاهر غير محتاج للتعيرف .


** نقطة أخيره :
في كل جزئ من أجزاء البرامج التي كتبتها و التعريفات و إنشاء المتغيرات في الدرس إستخدمت غالباً التعريف التالي :
 

typedef struct
{
    char name[30];
    int age;
}data;
 

و أنشئت المتغيرات كالتالي :
 

data VAR ;
 

ممكن تغييره إلى
 

struct data
{
char name[30] ;
int age ;
};
 

و لكن تعريف المتغير سيكون :
 

struct data VAR ;

 

و قد نوهت على ذلك من قبل و لكن الذكرى تنفع المؤمنين.


مع تحياتي ,,, و إلى اللقاء في الدرس القادم بإذن الله .

أخوكم / طلال ,
 

 

 

 


Copyright © www.kettaneh.net