السجلات في سي 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:%d\n\n",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 = %d\n\n",std.name, std.age);
printf("s->name = %s, s->age = %d\n\n",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) = %d\n\n",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 = %d\n\n",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 = %d\n\n",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 ;





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


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