بسم الله الرحمن
الرحيم
السجلات في سي
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 ;
و قد نوهت على ذلك من قبل و لكن
الذكرى تنفع المؤمنين.
مع تحياتي ,,, و إلى اللقاء في الدرس القادم بإذن الله .
أخوكم / طلال ,