الأربعاء، 27 فبراير 2013

C++


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

لقد تعلمتم المصفوفات وكيفية التعامل معها أي التعامل مع البيانات داخلها وقد ذكر الإخوان بعض طرق البحث في المصفوفة مثل البحث الثنائي ولكن بالنسبة للطريقة التي سوف ندرسها ستكون أسهل في الفهم من البحث الثنائي ولفهم الطريقة أكثر سوف نضع هذا الخوارزم (عدد من الخطوات القريبة من شكل البرنامج)
خوارزم البحث عن قيمة ما في مصفوفة باستخدام مفهوم البحث المتتالي:
  1. البدابة
  2. أدخل س(وهي القيمة المراد البحث عنها)
  3. ص=صفر (عداد تكراري يبدا من أول قيمة في المصفوفة ويتحرك إلى نهاية المصفوفة)
  4. ع=صفر (عدد مرات وجود تلك القيمة المختارة داخل المصفوفة)
  5. إذا كان  س  يساوي العنصر الذي رقمه  ص اجعل
                                                                    ع=ع+1
  6. ص=ص+1
  7. إذا كان ص أقل من عدد عناصر المصفوفة إذهب إلى الخطوة خمسة
  8. إذا كان ع =صفر
                                    إطبع  (قيمة س  غير موجودة في المصفوفة)
  9. إذا كان ع لايساوي صفر
                                    إطبع  (قيمة س موجودة في المصفوفة ع من المرات)
  10. النهاية

       لتطبيق هذا الخوارزم بلغة C :
#include "IOSTREAM.H"
void main (void)
{
 int array[7];
 int counter;
 int value;
 int isfound = 0;
 for (counter = 0 ; counter < 7 ; counter ++)
 {
  cout << " array ["<< counter<<"]=";
  cin >> array [counter];
 }
 cout << " nnENTER THE VALUE YOU WHONT TO SEARCH :  ";
 cin >> value ;
 for (counter = 0 ; counter < 7 ; counter ++)
  if ( value == array[counter])
  {
   cout << value << "  FOUND "<      isfound++;
  }
  if ( isfound == 0)
   cout << value << "  NOT FOUND"<< endl;
}
 
والآن يمكنم البحث داخل المصفوفة..
تعلمنا في هذا الدرس كيفية تطبيق البحث المتتالي، أرجو أيها القارئ الكريم أن تكون قد استفدت من هذا الدرس البسيط، والسلام عليكم..


الملفات مع السي
 السلام عليكم و رحمة الله و بركاته
الملفات مع السي
 
هل فكرت يوماً بعمل برنامج ولنقل مثلاً لمكتب عقاري ؟
هل فكرت يوماً بعمل برنامج يقوم بعمل معين ولكنك تحتاج إلى البيانات الموجوده فيه مسبقاً و لا تريدها ان تضيع بمجرد إقفال البرنامج ؟

الحل سيكون إستخدام الملفات بالتأكيد ...فهي المعنى الحقيقي للبرمجة و هي التي تقوم بجعل البرامج العديمة الفائدة مفيدة جداً , فتخيل برنامج Word مثلاً بدون الملفات ؟!!! لن يكون له اي فائدة عدى القليل جداً .

طبعاً اقصد بالملفات هي الحفظ و الفتح التي تقوم بها يوميا تقريباً في برامجك .

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



الملفات في أي لغة لن تتعامل معها مباشرة ( أي بإسم الملف ) و لكنك ستتعامل مع مؤشر إلى الملف و لنبدأ:

 أولاً كيف يمكننا أن نعرف مؤشر إلى ملف ؟
________________________________________________________________________________

1. التعريف :
 

FILE *file_pointer;
 مع العلم ان كلمة FILE لابد ان تكون بالحروف الكبيره و file_pointer هي كلمة إختيارية تختارها أنت و لتكن مثلاً fp لسهولة الإستعمال و لكن لابد أن تسبق بالعلامة *
فقط في التعريف ( كما هو موضح في الأعلى ) أي أن المتغير سيكون مؤشر للملف.
 

__________________________________________________________________________________

2. فتح الملف :
لابد من فتح الملف قبل إستخدامة مثلة مثل فتح ( تشغيل ) الكمبيوتر قبل إستخدامة
ولفتح الملف سنستعمل الدالة التالية :


FILE *fopen ( const char *filename, const char *mode ) ;

الـ filename هو إسم الملف الذي يكون ظاهر للمستخدم مثلاً talal.txt
و الـ mode هو طبيعة فتح الملف فهناك اكثر من صفة لفتح الملف سأذكرها بعد قليل
فلو كان لدينا هذا التعريف :
 

FILE *fp;
 
و نريد ان نفتح ملف إسمة talal.txt فسنفتحه بالطريقة التالية :
 

fp = fopen( "talal.txt", "w" ) ;
 
إذا لم يتمكن النظام من فتح الملف سنعيد في الـ fp قيمة NULL .
و لنرى الصفات التي تفتح عليها الملفات :



الطريقةالوصف
ropen a text file for reading   أي فتح ملف للقراءة
wopen a text file for writing   أي فتح ملف للكتابة و إذا كان الملف موجود يقوم بمسح محتوياته
a(open a text file for appending (adding info to the END of file    فتح للإضافة إلى النهاية
+ropening a text file for update ( reading and writing  فتح ملف للتحديث ( قرائة و كتابة )
+wopen a text file for update. If the file exists it is truncated to zero length. If it does not exists it is created يفتح ملف للتعديل ( للقرائة و الكتابة ) إذا كان الملف موجود فإنة تلغى محتوياته
أما إن لم يكن موجود فإن النظام ينشأة
+aopen a text file for update ( reading and writing ). Writing is only allowed at the end of file. But any part of the file can be readed. If the file does not exists the system create itفتح الملف للتعديل و التحديث و لكن الإضافة تكون في نهاية الملف و بينما القرائة تكون من أي مكان في الملف. إذا لم يكن الملف موجود اصلاً فإن النظام تقوم بإنشاء هذا الملف.
Adding a 'b' changes the file type from text to binary. Apart from that the string will have the same effect.و عند إضافة الحرف 'b' إلى نوع الفتح سيتغير من فتح ملف من نوع text إلى فتح من نوع binary. و سيكون لملف الـ Binary نفس خصائص الحروف بالـ text .






إذاً إذا أردنا أن نفتح ملف للقرائة منه ماذا نفعل ؟ 
الجواب هو كالتالي :


fp = fopen("talal.txt", "r");

و إذا أردنا فتح الملف للقراءة و الكتابة معاً يمكننا ذلك بطرق أحدها:


fp = fopen("talal.txt", "r+");   // OR by using w+ OR using a+

________________________________________________________________________________

3. إغلاق الملف :

كما فتحنا الملف في البداية لابد من اإغلاقة عند الإنتهاء منه أليس كذلك ؟!!
بالتأكيد سنغلقة في حال إنتهينا من إستعمال الملف و الدالة المسؤلة عن إغلاق الملف هي:
 
int fclose(FILE *file_pointer );
 
هكذا فقط :) .
فلو كان عندنا التعريف التالي :
 
FILE *fp ;
fp = fopen("talal.txt", "w") ;
 
سنغلق هذا الملف هكذا :
 
fclose ( fp ) ;
 
فقط, هذا كل ما في الأمر. أليس هذا سهلاً ؟!!

وإذا لم ينفتح الملف لأي سبب من الأسباب كأمتلاء الذاكرة, ستعيد الدالة القيمة NULL.
_________________________________________________________________________________

4. الإدخال و الإخراج من الملفات ( I/O with files ) :
الآن قد انتهينا من  كيفية فتح و إغلاق الملفات إلى هذه النقطة لم نتعلم كيف نحفظ ما قمنا بعمله, للقيام بذلك هناك عدة دوال مسؤالة عن هذا الشيء منها:

الدالة الأولى هي :
 
int putc( char ch, FILE *file_pointer );
 
و هي موجوده ضمن ملف stdio.h .
و لنأخذ مثال على هذه الدالة :
 
#include "stdio.h"
main()
{
    FILE *fp ;
    char ch ;
        fp = fopen("talal.txt", "w");
        while( ( ch = getche() ) != 'r' )
            putc(ch, fp);

        fclose(fp) ;
    return 0 ;
}
 
لقد قمت بتنفيذ هذا البرنامج فوجدت ملف إسمة talal.txt في نفس المجلد الذي يوجد به الكود الذي نفذتة فوجدت بداخلة الجملة التي كتبتها و هي :
 
Hi this is Talal

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

 
#include "conio.h"
#include "stdio.h"
main()
{
    FILE *fp ;
    char ch ;
        fp = fopen("talal.txt", "r");
        while( (ch = getc(fp) ) != EOF )
            printf("%c",ch);

        fclose(fp) ;
    return 0 ;
}
 
هذا الكود يقوم بقرائة الملف و طباعة محتواه على الشاشة , و لكن ما هي و ظيفة الدالة getc ؟ هذه دالة للقرائة من ملف و هي على الصيغة التالية :
 
char getc( FILE *file_pointer ) ;
 
و هي توجد في الملف stdio.h , ولشرح هذا البرنامج بالتفصيل :
عرفنا مؤشر إلى ملف بإسم fp , ثم قمنا بفتح الملف بإستخدام الدالة fopen و فتحناه على صيغة قرائة من ملف 'w' , الان مؤشر الملف على اول حرف في الملف , ثم دخلنا الدوارة while فنقراء أول حرف و نضع قيمتة في الـ ch و نقارنة بالقيمة EOF و هي تعني
End Of File ( نهاية الملف ) فنستمر في القرائة من الملف و نطبع على الشاشة قيمة الـ ch إذا لم تكن EOF .
________________________________________________________________________________

الدالة : 
 
fprintf (file_pointer, " ",,,) ;
 
وهي مثل الدالة printf تماماً ولكنها تختلف في أن البراميتر الأول هو مؤشر الملف و مثلها
أيضاً الدالة :
 
fscanf (file_pointer, " " ,,,) ;
 
وهي مثل الدالة scanf تماماً و لكن البراميتر الأول مؤشر للملف الذي نريد أن نقرأ منه .

لنأخذ هذا البرنامج على الدالتين السابقتين :
 
#include "stdio.h"
main()
{
    FILE *fp;
    char name[40];
    int id;
    float gpa;

    fp = fopen("std.txt","w");

    if(fp == NULL)
        printf("nCan't Open File...");
    else
    {
        do
        {
            printf("nEnter Name, id, gpa: ");
            scanf("%s %d %f",name, &id, &gpa);
            fprintf(fp,"%s %d %.2fn",name, id, gpa);
        }while( id != 0 );
    }
    fclose(fp);
return 0;
}
 
في هذا المثال فتحنا الملف STD.txt في الأول, و تأكدنا أن الملف لم يرجع NULL  (أي أن الملف قد فتح)
بالشرط أعلاه :
 
if(fp == NULL)
printf("nCan't Open File...");
 
و هذا الشرط مهم جداً لعمل برنامج كامل يعمل في جميع الأحوال
بعد ذلك عملنا دواره do…..While حتى يدخل المستخدم رقم صفر لأي طالب.
بعد ذلك قمنا بالطلب من المستخدم أن يدخل الإسم و الرقم و المعدل GPA, و إستقبلنا المدخلات بالأمر scanf, و حتى هنا لا يوجد شئ جديد, بعدها قمنا بالطباعة في الملف بالأمر fprintf
و بعدها قمنا بإغلاق الملف.

ثم بعد ذلك سنكتب البرنامج التالي:
 
#include "stdio.h"
main()
{
        FILE *fp;
        char name[40], ch;
        int id;
        float gpa;
        fp = fopen("std.txt","r");

        if(fp == NULL)
            printf("nCan't Open File...");
        else
        {
            fscanf(fp, "%s %d %f",name, &id, &gpa);
            fscanf(fp, "%c", &ch);

            while( id != 0 )
            {
                printf("Name:%s, ID:%d, GPA:%.2fn",name, id, gpa);
                fscanf(fp,"%s %d %f",name, &id, &gpa);
                fscanf(fp, "%c", &ch);
            }
        }
        fclose(fp);
    return 0;
}
 
ماذا سنجد ؟!؟
سنجد المعلومات التي كتبناها في البرنامج الذي سبق هذا :) .
و لكن قد يسئل البعض لماذا وضعنا هذه الجملة :
 
fscanf(fp, "%c", &ch);
 
اقول أولاً لنرجع للبرنامج الذي سبق هذا و في الجملة التالية بالذات :
 
fprintf(fp,"%s %d %.2fn",name, id, gpa);


أقول اننا طبعا سطر جديد في الملف بالأمر n و السطر الجديد هو عبارة عن char
فكما طبعنا في الملف سطر جديد لا بد أن نقرأ هذا الحرف بالأمر:
 
fscanf(fp, "%c", &ch);
 
فجرب ان تزيل هذه الجملة و سترى النتيجة :) .

و هناك ايضاً بعض الدوال التي سنكتفي بذكر تعريفها فقط :
 
char *fgets ( char *s, int n, FILE *File_pointer) ;
 
وهذه الدالة تقراء في المتغير s حتى الحرف n-1 أو حتى نهاية السطر .
 

int fputs ( const char *s, FILE *FILE_pointer ) ;


وهاتان الدالتان مثل الدوال gets و puts تماماً غير انها للملفات .

والدوال السبقة هي دوال للملفات المنتظمة أي تقراء و تطبع سطر بسطر بالترتيب .
________________________________________________________________________________


أما الآن فسندخل في الملفات العشوائية Random Files : و لملفات العشوائية يوجد عدد من الدوال لذلك وهي غالباً للكاتبة و القرائة بالسجلات Structures
و من هذه الدوال :
 
size_t fread ( void *ptr, size_t size, size_t n, FILE *file_pointer ) ;
 
فالباراميتر الأول يأخذ السجل المراد القرائة فيه و المتغير الثاني يأخذ حجم السجل و المتغير
الثالث يأخذ غالباً الرقم ( واحد ) أي عدد السجلات في كل مرة و المتغير الرابع يأخذ مؤشر
الملف , و سنأخذ مثال على هذه الدالة و الدوال الباقية مجتمعه في مثال واحد .

الدالة الثانية : 
 
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *file_pointer ) ;
 
وهي نفس الدالة السابقة تماماً ولكن هذه الدالة للكتابة فقط .

الدالة الثالثة :
 
int fseek (FILE *File_pointer, long offset, int whence );
 
وهذه الدالة تستخدم في التنقل عبر الملف, فالمتغير الأول عبارة عن مؤشر للملف المراد التنقل فيه , و المتغير الثاني عبارة عن عدد الحروف المراد قفزها, و المتغير الثالث عبارة عن مكان القفز و هو أما ان يكون من بداية الملف SEEK_SETأو من المكان الحالي SEEK_CUR
او من النهاية SEEK_END .

فلو كان عندنا السجل التالي :
 
typedef struct {
     datatype data;
}RECORD;
 
,ومن ثم عرنا متغير R من نوع RECORD كالتالي :
 
RECORD R ;
 
و كتبنا في الملف و لنقل 100 سجل ...
و اردنا أن نذهب للسجل الأخيرفي الملف فكيف يكون ذلك ؟!
الحل سيكون بالأمر fseek ولكن هل سيكون كالتالي : ؟!
 
fseek ( fp, 0, SEEK_END ) ;
 
و لنفرض جدلاً ان هذا الكلام صحيح !! لنترجم هذا الامر لنعرف صحتة من خطأه ...
الامر يقول اقفز من نهاية الملف الذي يؤشر عليه fp بمقدار صفر بايت .
إذاً سيكون المؤشر عندها على نهاية الملف و ليس على آخر سجل :)
و لنجعله يقف على آخر سجل سنغير الامر كالتالي :
 
fseek ( fp, - sizeof(RECORD) , SEEK_END ) ;
 
يعني علمنا الآن ان امقدار السالب يعمل على الإرجاع للخلف :) .

أما الان سنأخذ الدالة : 
 
long ftell ( FILE *File_pointer ) ;
 
و هي تعطينا عدد البايتات من بداية الملف حتى موضع مؤشر الملف, ومن اهم إستخداماتها هي
معرفة عدد السجلات في الملف و لنأخذ هذه الدالة التي أنشأناها كمثال لهذه العملية :
 
int size ( FILE *fp)
{
        int c;

        fseek( fp, 0, SEEK_END) ;
        c = ftell(fp) ;
        c = c / sizeof(RECORD) ;
    return c ;
}
 
وهذه الدالة تعيد لنا عدد السجلات في الملف و اعتقد انها قد اوضحت جيداً كيفية إستعمال الدالة ftell .

والان لنأخذ مثال عام على الملفات العشوائية و تبين كيفية القرائة و الكتابة من الملف
و تبين كيفية إستخدام الدالة size التي كتبناها أعلاه و أليكم هذا المثال:
 
#include "stdio.h"
#include "stdlib.h"
//------------------------------------- 
struct data
{
    int id;
    float gpa;
};
//------------------------------------- 
int size(FILE *fp)
{
        int c;

        fseek(fp, 0, SEEK_END);
        c = ftell(fp);
        c = c / sizeof(struct data);
    return c ;
}
//------------------------------------- 
void readrec(struct data *r)
{
    printf("n Enter the ID No: ");
    scanf("%d",&r->id);
    printf("n Enter the GPA: ");
    scanf("%f",&r->gpa);
}
//------------------------------------- 
main()
{
        FILE *fp;
        struct data r;
        int ch=0 ;

        fp=fopen("struct.txt","w");

        printf("n Enter (1)to write. (0)to EXIT.");
        scanf("%d",&ch);

        while(ch!=0)
        {
            readrec(&r);

            fwrite(&r,sizeof(struct data),1,fp);
            system("cls");
            printf("n Enter (1)to write. (0)to EXIT.");
            scanf("%d",&ch);
        }


        fclose(fp);

        fp=fopen("struct.txt","r");

        while((fread(&r,sizeof(struct data),1,fp)) != 0)
        {
            printf("nID:%dttGPA:%.2f",r.id,r.gpa);
        }

        printf("nnnThe File Contane: %d Record..",size(fp));

        fclose(fp);
        printf("nn");

}

 

و للزيادة أيضاً هناك الدالة : 
 
remove(char *file_name) ;
 

و تعطى هذه الدالة String الذي هو إسم الملف أو مكانه و تقوم هذه الدالة
بحذف الملف من الجهاز .

ملاحظه:
أي دالة يكون أحد البراميتر التي ترسل لها هو إسم ملف مثل fopen أو remove نستطيع إرسال اسم الملف كــ Path أي  هكذا:
remove("C:\talal\talal.txt");
بشطر أن نكتب بدل العلامة '' ---->  '\' كما هو موضح في الأعلى.
 



آمل أني قد وفقت في الشرح و لا تنسونها من دعائكم ,,,
مع تحياتي أخوكم / طلال .
 

السجلات في سي " 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 نوع كأي نوع آخر مثل intfloarchar,… .
و لتعريف متغير من نوع السجل نعرفه كالتالي:
 
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 مع السجلات :
لقد ذكرنا في هذا الدرس ان السجل بعد تعريفة يكون نوع مثل أي نوع من intcharfloat ...
و ذكرنا أيضاً أن الجملة 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 ;

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


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

أخوكم / طلال ,
 

المؤشرات في سي "Pointers In C"

 
( بسم الله الرحمن الرحيم )
المؤشرات في لغة السي و السي++
Pointers

المؤشرات في لغة السي هي من أقوى ما يميز لغة السي, و لكن في نفس الوقت فهي تعتبر من أخطر الخصائص التي توفرها لغة السي و سنعرف لماذا بعد قليل.

أولاً: ماذا هو المؤشر ؟
المؤشر هو متغير يحتوي على عنوان متغير آخر.
و لكن ماذا يعني هذا الكلام ؟
عندما نعرف متغير من نوع int هكذا:
int a;
إذاً عند تنفيذ البرنامج سوف يحجز نظام التشغيل مقدار 2 أو 4 بايت لهذا المتغير في الذاكرة الرئيسية Main Memory يضع فيها المتغير معلوماته من قيمت المتغير و ما إلى ذلك و لكن كيف سيصل لها البرنامج عندما يحتاج أن يعرف قيمة هذا المتغير ؟
إذاً لا بد أن يكون لهذا المكان عنوان لا سيما إذا عرفنا ان الذاكرة الرئيسية مقسمة إلى عناوين.
ففي الأنترنت مثلاً لا تستطيع أن ترسل لي شيئاً إلا إذا كنت تعرف بريدي الإلكتروني (عنواني) و لكن لماذا ؟
لأنه يوجد في الانترنت آلاف الأشخاص فكيف تفرق بيني و بينهم ؟ بالعنوان طبعاً, و هذا الشيئ ينطبق على أجزاء الذاكرة الرئيسية.
إذاً المؤشر هم متغير يوجد بداخلة العنوان الذي يؤشر عليه.

ثانياً: كيف نعرف مؤشر ؟
ببساطة نستطيع تعريف المؤشر بإضافة العلامة قبل إسم المتغير.
بالشكل التالي:
type *variable;
حيث أن type من الممكن أن يكون أي نوع نت أنواع البينات مثل:
intcharfloatdouble, ...
أو من الممكن حتى أن يكون Structure أو Class .
فمثلاً عندما نريد تعريف مؤشر من نوع int يكون كالتالي:
int *p;
و يفضل أن يكون إسم المؤشر محتوي على الحرف أو ptr بحيث يدل على أنه مؤشر.
ثالثاً: العاملان * و & :
لقد بينا عمل العامل * و لكننا لم نتطرق إلى العامل &, العامل & يفيد إيجاد عنوان متغير, فلو كان عندنا متغير من نوع int مثلاً هكذا:
int a;
و أسندنا له القيمة 10 مثلاً:
a = 10;
و أردنا أن نعرف عنوان هذا المتغير و طبعاته سيكون الامر كالتالي:
printf("%dnn", &a);
في هذه الجملة سوف يطبع البرنامج عنوان المتغير a .
و لنتعرف على قاعدة مهمه جداً و هي أنه إذا عرفنا مؤشر px من نوع int هكذا:
int *px;
و اردنا أن نعرف عنوان المؤشر كيف ؟
نحن قلنا نعرف عنوان المتغير العادي بالعلامة & و بالتالي نفس الشيئ سيكون للمؤشرات و ستكتب هكذا:
&*px;
هذه الجملة تعطينا عنوان المؤشر px و لكن أليس هذا طويلاً ؟ و مقعد ؟
نعم هو كذلك و لغة السي وفرت علينا هذا الجهد بأن وضعت قاعدة تقول عنوان المؤشر سيعرف هكذا:
px;
أي أن علامة & تلغي علامة *.

رابعاً: إسناد المؤشرات:
في إسناد المؤشرات لن يخرج الإسناد عن حالتين ثنتين هما:
1.إسناد مؤشر لمتغير أو العكس.
2.إسناد مؤشر لمؤشر.

سنتعرف الآن على كيفية إسناد متغير لمؤشر:
لو كان لدينا التعريف التالي:
int *px;
int a = 3;
هنا عرفنا px من نوع مؤشر إلى int و عرفنا أيضاً a متغير من نوع int و أسندنا لـ a القيمة 3.
السؤال هنا: كيف سنجعل px يؤشر على a ?
الجواب بسيط جداً ولكن أكثرنا سيظن أن الجواب هو:
*px = a;
و لكن لتجربو هذه الطريقة ستجون أنها خاطئة لماذا ؟!؟
نحن عرفنا المؤشر أنه مؤشر إلى عنوان في الذاكرة, إذا px لابد أن يحوي عنوان في الذاكرة الرئيسية وسوف نحجز لها هذا العنوان عن طريق الدالة :
malloc و بالنسبة لمستخدمي السي++ سوف يستخدمون المعامل new .
إذا سوف نضيف هذا السطر أولاً:
px = (int *)malloc(sizeof(int));
وهذه الدالة موجوده في المكتبة stdlib.h .
و بالنسبة لمستخدمي السي++:
px = new int;

و الان قد حجزنا للمتغير px مكان في الذاكرة مخصص لها و الان فقط نستطيع أن نقول:
*px = a;
أو:
*px = 10;
و من الممكن أن أجعل المؤشر px يؤشر على المتغير a كالتالي:
px = &a;
هنا لن نحتاج للدالة malloc أو المعامل new لماذا ؟
لأننا في الإسناد السابق قمنا بإسناد عنوان المتغير a إلى المؤشر px أي بعبارة أوضح:
" جعلنا px يؤشر على ". و هنا عند أي تغيير يحدث في px سوف يحدث في عنوان px الذي هو عنوان a أي سيتغير a لنأخذ هذا البرنامج كدليل على هذا الكلام:
#include "stdio.h"

int main ()
{
int *px;
int a;

01: px = &a; 
/* 'px' will point on 'a' */

02: *px = 10; /*
 Changes on 'px' will effect on 'a' */

03: printf("px = %d
 nn", *px);
04: printf("a = %d
 nn", a); /* 'a' and 'px' will have the same value that is 10 */


05: a = 20; /*
 Changes on 'a' will effect on 'px' */

06: printf("px = %d
 nn", *px);
07: printf("a = %d
 nn", a); /* 'a' and 'px' will have the same value that is 20 */


return 0;
}

 
لاحظ عزيزي أنه في السطر 1 جعلنا px يؤشر على a أي أن px سيحتوي على عنوان a .
في السطر 2 جلعا غيرنا px و جعلناها تساوي 10 و لكن في الحقيقة نحن لم نغير px و لكننا قمنا بتغيير العنوان الذي يوجد بداخل px و المعنى الحرفي لهذا السطر هو:
" إذهب إلى العنوان الموجود داخل px و إجعل هذا العنوان يحتوي على القيمة 10 " و نحن نعلم أن هذا العنوان هو عنوان a ( من السطر 1 ) إذا سوف يغيير هذا السطر المتغير و سنلاحظ هذا التغيير في السطرين 3 و 4 في السطر 3 قلنا للدالة printf إطبعي محتوى العنوان الموجود داخل px و في السطر 4 قلنا لها إطبعي قيمة المتغير a و سنلاحظ أن هذين القيمتين هما 10 إذاً سيطبع على الشاشة القيمة 10.
أما في السطر 5 أسندنا للمتغير a القيمة 20.
و في السطر 6 قلنا للدالة printf إطبعي القيمة الموجوده في العنوان الموجود داخل px و نحن نعرف أن العنوان الموجود داخل px هو عنوان a ( من السطر 1 ), ثم في السطر 7 طبعنا قيمة a و سيبطع على الشاشة القيمة 20 مرتين, مره من المؤشر px و مرة أخرى من المتغير a .

طبعاً هذا المثال سهل و لكن الذي يفهم هذا المثال سيفهم %90 من موضوع المؤشرات, و هذا الموضوع ليس صعب كما يعتقده الكثيرون :).
الآن قد تعرفنا على كيفية جعل المؤشر يؤشر على متغير و على كيفية حجز عنوان في الذاكرة للمؤشر و كيفية إسناد القيم للمؤشر و لكن بقي علينا أن نتعرف على كيفية إسناد المؤشرات لبعضها البعض و على كيفية جعل المؤشر يؤشر على مؤشر آخر.

ففرضاً لو كان لدينا التعريق التالي:
int *p1, *p2;
int a = 5;
و جعلنا p1 يؤشر على بكتابة هذا السطر:
p1 = &a;
أي جعلنا p1 يحوي عنوان )
الآن نريد أن نجعل p2 يؤشر على p1 كيف سيكون ذلك ؟!؟
الحل ببساطة نجعل في p2 عنوان p1 الذي هو في الأصل عنوان a, إذاً يكون السطر الذي يجعل p2 يؤشر على P1 هو:
p2 = p1;
أي عنوان p2 يساوي عنوان p1 .
الآن لو طبعاً قيم:
*p1, *p2, a
ستحتوي جميع هذه القيم على القيمة 5 الموجوده في a.
و لو غيرنا قيمة واحده منها فقط سيتغير الكل تبعاً لذلك, لأن p1 يؤشر على و p2 يؤشر على p1 و p1 يؤشر على a إذاً منطقياً أن سؤشر p2 إلى و الفي الحقيقة أي تغيير في p1, p2 سيكون التغيير في لأنهما لا يحملان في الأصل إلى عنوان المنتغير a.
خامساً: التعابير الرياضية على المؤشرات:
لقد تطرقنا فيما سبق إلى العامل الرياضي = (الإسناد) و هو شبية جداً مع إسناد المتغيرات.
و لكن في هذا الجزء سوف نتطرق إلى العوامل الرياضيه مثل:
++, --, +, -,,,,
و قبل ان نسترسل في هذا الجزء لنلقي أولاً نظره على جدول الأولوية في التنفيذ في جمل لغة سي و سي++:

Highest() [] -> .
 ! ~ ++ -- - (type) * & sizeof
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:
= += -= *= /= etc
Lowest,

سنلاحظ أن للمؤشرات أولوية مرتفعة جداً مقارنة بباقي المعاملات.
فلو كان لدينا هذا التعريف:
int x, y, *px;
و كتبنا هذه الجملة بعد هذا التعريف:
x = 3;
px = &x;
y = *px + 1;
لو لاحظنا الجملة الأخيره سنلاحظ أنه لو إستبدلناها بالجملة:
y = x + 1;
ستكون مكافئة لها تماماً لماذا ؟
لأنه كما قلنا مراراً و تكراراً أن px يؤشر إلى x فلو كتبنا px* أو x في مكان سيكون لهما نفس التأثير.
و لو قلنا:
printf("%d", *px);
فهذا السطر سيطبع لنا قيمة x لأن px تشير إلى x .
ولو قلنا :
*px = 8;
سيكون هذا الشرط مكافئ لهذا:
x = 8;
لأن px يشير إلى x.
و لكن ماذا لو أردنا أن نزيد المؤشر px بواحد بواسطة العامل (++) أو إنقاصة بواسطة العامل (--) ؟
الجواب المتوقع هو:
*px++;
و لكن هذا خطأ. لماذا ؟
أولاً بالرجوع إلى جدول الأولويات ستجد أن علامة (++ و * (للمؤشر)) لهما نفس الأولويه و لكن عندما تتساوى الأولويات فالكمبايلر ينفذ من اليمين لليسار أي سيزيد واحد على عنوان المؤشر أي سيؤشر على الخانه التاليه للعنوان القديم.
و لكن كيف ممكن أن نتغلب على مشكلة الأولوية ؟!
الحل هو أن تستخدم عامل له اولوية أكبر من جدول العوامل أعلاه ألا وهو الأقواس الدائريه ( و ) عندها نقول:
(*px)++;
في هذه الحالة سيزيد واحد على المتغير الذي يؤشر عليه px وهو x, و نفس الشيئ ينطبق على (--).
و لكن لو قلت:
++*px;
في هذه الحالة سيزيد واحد على المتغير الذي يؤشر عليه px وهو x, و نفس الشيئ ينطبق على (--). بدون الحاجة إلى الأقواس لأنه سينفذ الجملة من اليمين لليسار أي سيؤشر أولاً ثم سيزيد.
ملاحظة:
لنرى هذا البرنامج:
#include "stdio.h"
int main ()
{
int *ptr, i = 10;
ptr = &i; /* ptr Will point to (i) */
printf("ptr Points to: %un", ptr);

ptr++;

printf("ptr Points to: %un", ptr);

return 0;
}
نفذ البرنامج الآن و لترى على أي عنوان يؤشر ptr بالنسبة لي ظهر:
ptr Points to: 1245048
ptr Points to: 1245052
Press any key to continue

و لكن نحن زدنا العنوان واحد فقط و لكن لماذا ؟
السبب في ذلك أن حجم size للـ int في جهازي هو 4 كيلو بايت لذلك زاد المشر من عنده 4.
أما لو كان الـ int في جهازك بحجم 2 كيلو بايت فسيزيد العنوان بمقدار 2.
و تستطيع ان تعرف حجم أي منغير بواسطة الـدالة sizeof .

سادساً: المؤشرات و المصفوفات:
المؤشرات و المصفوفات في لغة سي و سي++ متقاربين جداً, لذلك تجدون في أغلب الكتب أنهما يكونان في فصل واحد, و في الحقيقة كمبايلر السي و السي++ يجعل إسم المصفوفة مؤشر إلى أول عنصر فيها, لذلك الجملتين التاليتين متماثله تماماً:
r = x[1]; تساوي r = *(x+1);
و لأن اسم المصفوفة مؤشر إلى أول عنصر بداخالها فإنه من الممكن إسناد مؤشر إلى المصفوفة عن طريق هذه الجملة:
ptr = x;
أو:
ptr = &x[0];
هاتين الطريقتين لهما نفس الفاعليه و لكن الفرق هو انه في الأول عاملنا المصفوفه كمؤشر إلى أول عنصر و لكن في الطريقة الثانية عاملنا المصفوفه x كمصفوفه أي أن [x[0 هو متغير عادي مثل أي متغير.

و لنأخذ هذا المثال:
#include "stdio.h"

int main ()
{
int x[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};int i;

printf("Arrays As Pointers:n");
for( i = 0 ; i < 10 ; i++)
{
printf("*(x + %d) = %dn", i, *(x + i));
}

printf("nnArrays As Arrays:n");
for( i = 0 ; i < 10 ; i++)
{
printf("x[%d] = %dn", i, x[i]);
}

printf("nnn");
return 0;
}

 

في هذا المثال يتبين لنا كيف إستخدمنا المصفوفات كمؤشرات تاره و كمصفوفات تارة أخرى و أن لهما نفس النتائج.
سابعاً السلاسل الحرفيه:
لقد تحدثت عن هذا الموضوع في درس مستقل راجع الدروس في قسم البرمجة في قسم السي بإسم:
دوال التعامل مع الحروف و السلاسل الحرفية

ثامناً:المؤشرات ضمن المتغيرات المرسلة للدالة:
من المؤسف أن الدوال في لغة السي و كل لغات البرمجة التي أعرفها لا تستطيع إلا إرجاع متغير واحد فقط !!
سواء كان هذا المتغير من الانواع القياسيه كـ int, float, char ... أو من نوع سجلات كما أخذنا في درس
السجلات مع السي Structures in C
إذا السؤال اللذي يطرح نفسه هو كيف أستطيع أن أرجع أكثر من قيمه من الدالة ؟!
الطريقة سهله جداً و هي عن طريق ما يسمى بـ Call By Reference أي المناداة بالمرجع.
و سنأخذ هذا المثال و بالتالي سنشرح عليه هذه العمليه:
فلو كان أردنا أن نكتب داله نرسل لها رقمين num1 و num2 و تعيد لنا حاصل الجمع و الضرب و القسمه أي sum و pro و div
عندئذ لابد من إستخدام ما يسمى بـ Call By Reference و سيكون رأس الدالة هكذا:
void function1( int num1, int num2, int *sum, int *pro, float *div);
و عندما نريد أن نرسل للدالة المتغيرات سيكون هكذا:
function( n1, n2, &s, &p, &d);
السؤال لماذا إستبدلنا كل * في تعريف الدالة بـ& في الإرسال للدالة ؟!!!
من المعروف أنه عند إرسال متغيرات للداله سوف يقوم الكمبايلر بالآتي على مثالنا السابق:
سوف يساوي num1 بـ n1 و num2 بـ n2 و s بـ sum و هكذا و لكن في تعريف الدالة sum و pro و div عبارة عن مؤشرات إذا عن الإسناد إلى مؤشر كما تعلمنا أعلاه سوف نسند له العنوان فقط و ليس المتغير هنا عرفنا لمذا قلنا في الإرسال & و ليس * .
و لكن ربما سئل سائل كيف سنرجع القيمه ؟
أقول له بأن sum مؤشر و إذا أرسلنا له عنوان s فقط فسيكون sum و s شيئ واحد أي يشيران إلى نفس المكان لذلك عند الخروج من الدالة سيتكون القيمة متغيره و ليست كما كانت عليه قبل الارسال.
لنكتب هذه الدالة كامله الآن:
void function1( int num1, int num2, int *sum, int *pro, float *div)
{
*sum = num1 + num2;
*pro = num1 * num2;
if( num2 == 0 )
*div = num1 / num2;
else *div = 0;
}
فلو كتبنا هذا البرنامج الكامل:
#include "stdio.h"
void function1( int num1, int num2, int *sum, int *pro, float *div)
{
*sum = num1 + num2;
*pro = num1 * num2;
if( num2 == 0 )
*div = num1 / num2;
else *div = 0;
}
int main()
{
int n1, n2, s, p;
float d;
n1 = 12;
n2 = 2;
function(n1, n2, &s, &p, &d);
printf("Sum = %dn", s);
printf("Pro = %dn", p);
printf("Div = %.2fn", d);
return 0;
}

الآن لننفذ البرنامج !!! سنجد أن المخرجات هي:
Sum = 14
Pro = 24
Div = 6.00
إذا لو تأملنا المخرجات لوجدنا أن قيمة و و d قد أعدناها من الداله بدون الأمر return .
تاسعاً: مصفوفة المؤشرات:
المصفوفات من الممكن أن تكون مؤشر إلى نوع ما بحيث كل عنصر من المصفوفة يصبح مؤشر إلى نوع معين.
من أكثر الأمثله لهذا التركيب هو مصوفة مؤشرات إلى char أي كل عنصر عبارة عن string أي كالتالي:
char *names[4];

أي أن المصوفه name تحتوي على أربع عناصر كل منها هو string .
لنأخذ مثال بارسم على ذلك:
لنفرض انه لدينا هذا التعريف:
char *name[4] = { "Talal", "Abdullah", "Thamer", "Mohammad" };
name[0] ===> "Talal"
name[1] ===> "Abdullah"
name[2] ===> "Thamer"
name[3] ===> "Mohammad"
سيكون تمثيل العناصر كما هو مبين أعلاه.
و لكن ربما قال أحدهم لماذا لا أستخدم مصفوفة ذات بعدين للآتي ؟!!
سنرد عليه بسهوله و نقول:
أن المصفوفة ذات البعدين لها حجم ثابت لا نستطيع أن نتعداه مهما كبر الإسم, و لكن الامر مختلف مع مصفوفة المؤشرات لأنه كما نلاحظ أن Talal و Abdullah و Thamer و Mohammad مختلفت الأطول لذلك نجد أن المؤشرات تعطي مرونه و قوة أكثر من المصفوفات.

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

أخوكم / طلال. talal*c .

المصفوفة المتناثرة أو مصفوفة الأصفار [ Sparse Matrix(1) ]
بسم الله الرحمن الرحيم

المصفوفة المتناثرة أو مصفوفة الأصفار ( Sparse Matrix ) :

لو ألقينا نظرة على المصفوفة التالية التي تحوي 6 صفوف و6 أعمدة وتتكون من : 6x 6 = 36 عنصر  :

سيتضح لنا من الوهلة الأولى أن أكثر عناصر هذه المصفوفة عبارة عن " أصفار " ؛ تسمى المصفوفة  التي أكثر عناصرها أصفار  بـمصفوفة الأصفار أو المصفوفة المتناثرة " Sparse Matrix " .. ومن الصعب علينا تحديد ما إذا كانت المصفوفة عبارة عن Sparse Matrix أو لا .. ولكن يتضح لنا ذلك عن طريق النظر ؛ ففي المصفوفة السابقة يوجد فقط 8 عناصر لا تساوي الصفر من أصل  36 عنصر ؛  بينما البقية كلها أصفار ..
تستخدم الـ Sparse Matrix لتوفير المساحة في الذاكرة حيث نستطيع تخزين العناصر الغير مساوية للصفر فيها ففقط ؛ وذلك من خلال استخدام مصفوفة وحيدة لكل عنصر من عناصرها يوجد 3 صفات هي : الصف والعمود والقيمة الخاصة به  ؛ ويتم ذلك عن طريق استخدامنا لـStructure   يحوي هذه الصفات  ؛ كالتالي :
 
#define MAX_TERMS 10 /* maximum number of terms + 1 */
typedef struct {
  int row ;
  int col ;
  int value ;
  } sparse;
sparse a[MAX_TERMS] ;

ولكن يجب أن نراعي هنا أن ترتيب العناصر في هذه المصفوفة الوحيدة سيكون تابع لأحد هذه الصفات وهو الصف " row  " و لابد من أن يكون  تصاعدياً ..
ملاحظة : لتتعلم أكثر عن الـ Structures راجع هذا الدرس للأخ طلال : السجلات في سي " Structures in C "

إذن .. يتضح لدينا أن تعريف الـ Sparse Matrix كما هو موجود في قاموسنا كالتالي :
مصفوفة ذات بعد واحد تحوي الكثير من العناصر المتشابهة ، والتي غالباً ما تساوي صفر. ، لكل عنصر فيها ثلاث صفات : الصف ، العمود ، والقيمة التي تسند إليه .
* Sparse_Matrix : a set of triples < row , col , value > , where row & column are integers & from a unique combination , & value comes from the set item .
* Sparse_Matrix Create(max_row , max_col) ::= return a Sparse_Matrix that can hold up to max_item = max_row X max_col ,& whose maximum row size is max_row , & whose maximum column size is max_col.
ومن هنا نستطيع إعادة رسم الـ Sparse Matrix كما في الشكل التالي :
حيث أن a[0].row تحتوي عدد الأسطر الكلي للمصفوفة الأصلية ( في هذا المثال = 6 ) , كذلك a[0].col فهي تحوي عدد الأعمدة الكلي للمصفوفة الأصلية ( في هذا المثال = 6 ) , وأيضاً a[0].value عدد العناصر الغير مساوية للصفر فقط ( في هذا المثال = 8 ) .
ولكي نعرف رقم الصف لأي عنصر ننظر لـ Field row , وبالمثل إذا أردنا أن نعرف رقم العمود فننظر لـ Field col وستكون قيمة هذا العنصر مخزنة في Field value . والثلاثي < row , col , value > سيكون مرتب في المصفوفة على حسب الصفوف " تصاعدياً " كما ذكرنا سابقاً ..
ولكن كيف نستطيع كتابة شيفرة لإنشاء هذه المصفوفة بلغة السي ؟ هذا ما سنعرفه ان شاء الله خلال الاسطر التالية : سنقوم بالبداية بإنشاء Structure مشابه للمذكور أعلاه .. وسنفترض في مثالنا الحالي أن المصفوفة مكوّنة من 3 صفوف و3 أعمدة ..
وبعد ذلك نسمح للمستخدم بإدخال العناصر كمصفوفة عادية شريطة أن تكون أكثرها مساوية للصفر ونطبع المصفوفة بالطريقة التقليدية العادية :
 Part1:

//------------------((( Sparse Matrix )))---------------#include 
#define MAX_TERM 10
typedef struct {
   int row ;
   int col ;
   int val ;
   } sparse;
int main()
{
    sparse a[MAX_TERM]; //  تعريف المصفوفة الصفرية 
    
int b[3][3] ;  //  تعريف مصفوفة عادية 
    int i, j, count ;
    printf("Plz. Enter the element one by one with press Enter :n");
    for (i=0 ; i <3 ; i++ ) {
       for (j=0 ; j <3 ; j++ ) {
          scanf("%i",&b[i][j]) ;
        } // end j loop
    
} // end i loop
//------------>--------------- -
    printf("n**********************************nnNormal_Matrix is : n") ;
    for (i=0 ; i <3 ; i++ ) {
        for (j=0 ; j <3 ; j++ ) {
           printf("%i t",b[i][j]) ;
        } // end j loop
        
printf("n") ;
    } // end i loop


بعد ذلك .. نقوم بإنشاء الـ Sparse Matrix ؛ نخزّن في البداية عدد الصفوف الكلي وعدد الأعمدة الكلي  في كل من  a[0].row  و a[0].col .. ثم نضع عداداً  = 1  كفهرس لكي نبدأ التخزين ..
نقوم بعمل Loop يمّر على كل عناصر المصفوفة العادية ويسأل ما إذا كان هذا العنصر مساوياً للصفر أما لا ؟
إذا اتضح أن العنصر لا يساوي الصفر .. نقوم بتخزين رقم الصف الموجود فيه هذا العنصر وكذلك رقم العمود ثم نخزّن قيمة العنصر باستخدام  العداد الذي جعلنا قيمته =1 كفهرس  لأول عنصر يقابلنا غير مساوي للصفر  .. ثم نزيد قيمة العداد بواحد لكي يفهرس العنصر المخزن الجديد .. وهكذا إلى أن ننتهي من جميع عناصر المصفوفة الأصلية .
الآن قمنا بتخزين جميع القيم الغير مساوية للصفر في الـ Sparse Matrix  ولكن يتبقى Field واحد لم نخزّن به شئ .. أتعلمون ما هو ؟
إنه الـ Field  الخاص بعدد العناصر الغير مساوية للصفر في المصفوفة الأصلية (a[0].val ) .. ونستطيع معرفة عدد العناصر الغير مساوية للصفر من خلال العداد الذي فهرس العناصر ..
 ولكن نلاحظ هنا أن هذا العداد داخل Loop عندما انتهى التخزين قد زادت قيمته بواحد على عدد العناصر الغير مساوية للصفر ؛ فيجب علينا أن نقوم بانقاص قيمته بمقدار واحد ثم نخزنها في a[0].val ...
 إذن ؛ نستطيع طباعة المصفوفة المتناثرة الناتجة لدينا ..  وذلك بإضافة  الكود التالي للكود السابق  :  
 
 Part2:

//------------>----------------
a[0].row=3 ;
a[0].col=3 ;
count=1;
for (i=0 ; i <3 ; i++ ){
 for (j=0 ; j <3 ; j++ ){
  if ( b[i][j]!= 0 ){
   a[count].row=i ;
   a[count].col=j ;
   a[count].val=b[i][j] ;
   count++;
  } // end if
 
} // end j loop} // end i loop 
a[0].val=count-1 ;
printf("n*******************nnSparse_Matrix is : nrowtcoltvaluen---------------------n") ;
 for ( i=0 ; i    printf("%it",a[i].row) ;
    printf("%it",a[i].col) ;
    printf("%in",a[i].val) ;
 }
 return 0;
}// end of main


وهكذا .. نكون قد وفرنا الذاكرة لدينا عند استخدام مثل هذه المصفوفات في حل المشكلات الكبيرة ..

أتمنى أن أكون وفقت في الشرح .. وسنتعرض لعملية تدوير المصفوفة المتناثرة أو مصفوفة الأصفار (Sparse Matrix  Transpose ) في الدرس القادم إن شاء الله ..
تحياتي، وأشركونا في صالح الدعاء :)

تدوير المصفوفة المتناثرة [ Sparse Matrix(2) ]
بسم الله الرحمن الرحيم

تدوير المصفوفة المتناثرة ( Transposing Sparse Matrix )


بعد أن تعرفنا على المصفوفة المتناثرة ( مصفوفة الأصفار ) [ Sparse_Matrix ] نتعرف الآن على طريقة تدويرها ؛ ونعني بتدويرها:  تبديل الصفوف بالأعمدة في السجل.
بمعنى أن كل عنصر a[i][j] في المصفوفة المتناثرة سيؤول إلى العنصر b[j][i]  في مصفوفة التدوير المقابلة لها. والشكل التالي يوضح التدوير للمصفوفة المتناثرة التي اعتمدناها في شرح الدرس السابق :


لنفكر قليلاً كيف يمكننا فعل ذلك بمصفوفة ذات بعد واحد ؟

أول ما سنستنجد به هو ترتيب المصفوفة المتناثرة ؛ حيث أنها مرتبة ترتيباً تصاعدياً عن طريق الصفوف ..

وهذا صحيح ولكن لنفكر للحظة كيف لنا استخدام هذا الترتيب التصاعدي في الصفوف كي نصل إلى ترتيب تصاعدي آخر ولكن عن طريق الأعمدة ؟


لنرى معاً :

لو قلنا أنه بما أننا فهرسنا مصفوفة الأصفار تصاعدياً باستخدام الصفوف ؛ فإننا سنستفيد من هذا الترتيب التصاعدي في عملية تدوير المصفوفة كما توضح الخوارزمية التالية :

For each row i

Take element < i , j , value > and store it as element < j , i , value > of the transpose ;


لو جدنا في هذه الطريقة أن الترتيب سيبقى تصاعدياً ولكن من ناحية الأعمدة - حيث أننا في مصفوفة التدوير بدلنا الصفوف بالأعمدة - وبذلك لن نحصل على مصفوفة مرتبة ترتيباً تصاعدياً من ناحية الصفوف !؛ ولو طبقنا الخوارزمية السابقة على المثال الذي شرحنا عليه مصفوفة الأصفار ؛ فسنحصل على التالي :
(0 , 0 , 15 ) ستصبح (0 , 0 , 15 )
( 0 , 3 , 22 ) ستصبح (3 , 0 , 22 )
(0 , 5 , -15 ) ستصبح ( 5 , 0 , -15 )
(4 , 0 , 91 ) ستصبح (0 , 4 , 91 )
(5 , 2 , 28 ) ستصبح (2 , 5 , 28 )
وهكذا


النتيجة: مصفوفة مرتبة تصاعدياً من ناحية الأعمدة ، بينما نحتاج إلى مصفوفة التدوير مرتبة تصاعدياً من ناحية الصفوف كما ذكرنا ذلك !!

الطريقة التي ستوصلنا إلى ترتيب تصاعدي جديد عن طريق الصفوف ، هي تثبيت العمود إبتداءاً من أصغر قيمة (صفر) والدوران حوله بجميع القيم الممكنة للصفوف (ابتداءاً من صفر إلى عدد الصفوف الكلي المخزن في a[0].row من مصفوفة الأصفار) ، وهكذا  لكل عمود على حدة ..

ربما تساعدك الخوارزمية التالية لمعرفة ديناميكية الحل :

For all elements in column j

 Place element < i , j , value > in element < j , i , value >  ;
 
لعمل ذلك ، سنستخدم Tow For Loops & If statement ولا ننسى أن نستخدم عداد جديد "currentb مثلاً " للفهرسة ، كما توضح الشيفرة التالية:
//----->------
void Transpose (sparse m[MAX_TERM] , sparse n[MAX_TERM] )

{

int i , j , k , currentb ;

k = m[0].val ; // total number of elements.

n[0].row = m[0].row ; // rows in n = columns in m .

n[0].col = m[0].col ; // columns in n = rows in m .  

n[0].val = k ;

if( k > 0 ){ /* non zero matrix */ 

 currentb = 1 ;

 for (i=0 ; i <= m[0].col ; i++ ) {

 /* transpose by the columns in m */ 
  for (j=1 ; j <= k ; j++ ) {

  /* find elements from the current column */ 

   if(m[j].col == i){

   /* element in curret column , add it to n */ 

    n[currentb].row = m[j].col ;

    n[currentb].col = m[j].row ;

    n[currentb].val = m[j].val ;

    currentb++ ;

   } // end if 

  } // end j loop

 } // end i loop 



 printf( "n**************nnTranspose of a Sparse_Matrix is : nrowtcoltvaluen------------n" );

 for ( i=0 ; i<= n[0].val ; i++ ){

  printf("%it",n[i].row );

  printf("%it",n[i].col );

  printf("%in",n[i].val );

  } // end i loop  

 } // end if  

// end of function : Transpose.
 

الزمن الكلي لتنفيذ هذه الـ For Loops هو ببساطة ناتج ضرب عدد أعمدة مصفوفة الأصفار في عدد عناصرها ، وهو وقت قليل بالمقارنة مع وقت تدوير المصفوفة بطريقة تقليدية بدون استخدام الـ For Loops .
جرب هذه الشيفرة التي تعتبر تطبيق كلي لما تعلمناه في درسي مصفوفة الأصفار :

//------------------((( Sparse Matrix )))---------------

#include "stdio.h"
#define MAX_TERM 10

typedef struct {

   int row ;

   int col ;

   int val ;

   } sparse;
void Transpose (sparse m[MAX_TERM] ,sparse n[MAX_TERM] ) ;
void main()

{

sparse a[MAX_TERM] ,trans_a[MAX_TERM];

int b[3][3] ;

int i, j, count ;

printf("Plz. Enter the element one by one with press Enter :n");

for (i=0 ; i <3 ; i++ ) {

 for (j=0 ; j <3 ; j++ ) {

 scanf("%i",&b[i][j]) ;

 } // end j loop 
// end i loop
//------------>----------------

printf("n***************************nnNormal_Matrix is : n") ;

for (i=0 ; i <3 ; i++ ) {

 for (j=0 ; j <3 ; j++ ) {

 printf("%i t",b[i][j]) ;

 } // end j loop

printf("n") ;

// end i loop
//------------>----------------
a[0].row=3 ;

a[0].col=3 ;

count=1;

for (i=0 ; i <3 ; i++ ){

 for (j=0 ; j <3 ; j++ ){

  if ( b[i][j]!= 0 ){

   a[count].row=i ;

   a[count].col=j ;

   a[count].val=b[i][j] ;

   count++;

  } // end if 

 } // end j loop

// end i loop 

a[0].val=count-1 ;

printf("n***************nnSparse_Matrix is : nrowtcoltvaluen-----------n")
;

for ( i=0 ; i
 printf("%it",a[i].row) ;

 printf("%it",a[i].col) ;

    printf("%in",a[i].val) ;

}

//-----------<To Transpose Sparse_Matrix>------------
Transpose( a , trans_a);



//----->------
void Transpose (sparse m[MAX_TERM] , sparse n[MAX_TERM] )

{

int i , j , k , currentb ;

k = m[0].val ; // total number of elements.

n[0].row = m[0].row ; // rows in n = columns in m .

n[0].col = m[0].col ; // columns in n = rows in m .  

n[0].val = k ;

if( k > 0 ){ /* non zero matrix */ 

 currentb = 1 ;

 for (i=0 ; i <= m[0].col ; i++ ) {

 /* transpose by the columns in m */ 
  for (j=1 ; j <= k ; j++ ) {

  /* find elements from the current column */ 

   if(m[j].col == i){

   /* element in curret column , add it to n */ 

    n[currentb].row = m[j].col ;

    n[currentb].col = m[j].row ;

    n[currentb].val = m[j].val ;

    currentb++ ;

   } // end if

  } // end j loop

 } // end i loop 



 printf( "n************nnTranspose of a Sparse_Matrix is : nrowtcoltvaluen---------n" );

 for ( i=0 ; i<= n[0].val ; i++ ){

  printf("%it",n[i].row );

  printf("%it",n[i].col );

  printf("%in",n[i].val );

  } // end i loop  

 } // end if  

// end of function : Transpose.


 
في  نهاية حديثنا عن المصفوفة المتناثرة (مصفوفة الأصفار) وطريقة تدويرها ، أتمنى أن نكون قد تعلمنا أساليب برمجية جديدة توفر مكان تخزيني كبير في الذاكرة وتحافظ على الوقت .. وأرجو من الله أن يكون هذان الدراسان بداية فهم منطق برامج الضغط لدى القارئ الكريم ..

السلام عليكم ورحمة الله وبركاته :)


ليست هناك تعليقات:

إرسال تعليق