الجزء 2 :¦- دورة تعليم الـC -¦: الدرس 7 -¦: القراءة و الكتابة في الملفات
ط·آ¢ط·آ®ط·آ±
ط·آ§ط¸â€‍ط·آµط¸ظ¾ط·آ­ط·آ©
EdyBel

  • ط·آ§ط¸â€‍ط¸â€¦ط·آ´ط·آ§ط·آ±ط¸ئ’ط·آ§ط·ع¾: 46565
    ط¸â€ ط¸â€ڑط·آ§ط·آ· ط·آ§ط¸â€‍ط·ع¾ط¸â€¦ط¸ظ¹ط·آ²: 15853
مشرفة سابقة
EdyBel

مشرفة سابقة
ط·آ§ط¸â€‍ط¸â€¦ط·آ´ط·آ§ط·آ±ط¸ئ’ط·آ§ط·ع¾: 46565
ط¸â€ ط¸â€ڑط·آ§ط·آ· ط·آ§ط¸â€‍ط·ع¾ط¸â€¦ط¸ظ¹ط·آ²: 15853
ط¸â€¦ط·آ¹ط·آ¯ط¸â€‍ ط·آ§ط¸â€‍ط¸â€¦ط·آ´ط·آ§ط·آ±ط¸ئ’ط·آ§ط·ع¾ ط¸ظ¹ط¸ث†ط¸â€¦ط¸ظ¹ط·آ§: 7.4
ط·آ§ط¸â€‍ط·آ£ط¸ظ¹ط·آ§ط¸â€¦ ط¸â€¦ط¸â€ ط·آ° ط·آ§ط¸â€‍ط·آ¥ط¸â€ ط·آ¶ط¸â€¦ط·آ§ط¸â€¦: 6270
  • 21:47 - 2014/07/05

 القراءة و الكتابة في الملفات
Lire et écrire dans des fichiers
Read and write in files

 
المشكل مع استعمال المتغيّرات، هو أنها موجودة فقط في الذاكرة العشوائية RAM، أي أنه بخروجنا من البرنامج، يتم حذف كل المعلومات التي عملنا عليها بالبرنامج، فكيف لنا إذا أن نحتفظ بالنتائج التي تحصّلنا عليها من خلال اللعبة التي برمجناها ؟ أو من خلال محرر النصوص الذي برمجناه ؟
 
الشيئ الجيّد أن لغة السي تدعم القراءة من خلال الملفاّت و كذا الكتابة فيها، هذه الأخيرة هي مُخزّنة في القرص الصلب Hard disk ( أو Disque dur ) من الجهاز، أي أنها تبقى محفوظة ما دمنا لم نقرر حذفها .
 
و لكي نتمكّن من القراءة من خلال الملفات، و كذا الكتابة عليها، سنستعمل كلّ ما درسناه سابقاً : المؤشرات pointeurs، الهياكل structures، النصوص Chaines de caractères ,,, الخ .
 
فتح و غلق ملف :
 
لكي نتمكن من ذلك، سنستعمل دوالاً معرّفة في المكتبة stdio التي استعملناها سابقاً . هذه الدالة تحتوي أيضاً الدالتين scanf و printf اللتان تعتبران من بديهيات البرمجة بلغة السي، كما أنها تحتوي على الكثير و الكثير من الدوال التي ستساعدنا للعمل على الملفات.
 
كل المكتبات التي سبق و استعملناها (stdlib.h, stdio.h, math.h, string.h ,,, ) تشكّل ما نسميه بالمكتبات النموذجية standard، و هي مكتبات موّزعة تلقائيا مع الـIDE الذي تستخدمونه، و هي تشتغل على كل أنظمة التشغيل، أي أنه بالإمكان أن نستعملها مع الويندوز، اللينكس أو حتى الماك ... الخ
 
و تأكّدوا قبل أن تفتحوا ملفاً، أن تقوموا باستيراد المكتبتين التاليتين على الأقل :
 
#include <stdlib.h>
#include <stdio.h>
 
و لأن هتين المكتبتين ضروريتين تقريباً في كلّ البرامج التي نكتبها، فأن أنصحكم بأن تستورداها دائماً .
حسناً و بعدما قمنا باستيراد المكتبتين، يمكننا أن نهجم على الأمور الأساسية و المهمّة، إليكم الخطوات التي يجب إتّباعها دائماً حينما تريدون العمل على ملف، سواء للقراءة منه أو للكتابة عليه :
 
- نقوم بنداء الدالة fopen التي تقوم بفتح الملف إذا وُجد و إرجاع مؤشّر نحو هذا الملف.
- نقوم باختبار بسيط حول المؤشر الذي أرجعته الدالة، و ذلك كي نعرف ما إن كان الملف موجوداً أو لا، فإن كانت يمة المؤشر = NULL، فهذا يعني وجود خطأ ما، إما خطأ في الفتح أو أن الملف لا يوجد أساسا، في هذه الحالة يجب أن نظهر للمستخدم إنذاراً .
- إذا تم فتح الملف بنجاح ( أي أن قيمة المؤشر تختلف عن NULL ) ، سنستمتع بالكتابة على الملف أو القراءة منه، و ذلك باستخدام دوال سنراها لاحقاً في هذا الدرس .
- غلق الملف، باستعمال الدالة fclose بعدما أنهينا العمل عليه .
 
 
سنتعلّم كخطوة أولى كيف نفتح و كيف نغلق ملفاً، حينما تتعوّدون على ذلك، سنتعلّم كيف نقوم نعدّل عليه.
 
fopen لفتح ملف :
 
كما رأينا مع درس النصوص chaines de caractères، كنا نستعين بنماذج prototypes الدوال من أجل أخذ فكرة عن الطريقة التي تعمل بها، على أي حال فإنه غالباً هذا ما يفعله المبرمجون، يقرؤون نموذج دالة معيّنة، يأخذون فكرة عن طريقة عملها، ثم يخوضون في التفاصيل ليفهموا كيف تعمل بالظبط، لهذا فلنقم بالتأمل قليلاً في نموذج الدالة fopen :
 
 
FILE* fopen(const char* nomDuFichier, const char* modeOuverture);
 
هذه الدالة تنتظر خاصيتين :
- اسم الملف الذي نريد فتحه .
- السبب الذي نريد فتح الملف من أجله، هل نريد أن نقرأ من خلال الملف، أو نكتب عليه، أو كلاهما.
 
هذه الدالة تقوم بإرجاع مؤشّر نحو هيكل من نمط FILE! هذا الهيكل متواجد بالمكتبة stdio.h، يمكنكم فتح الملف لتروا مما يتكوّن هذا النمط FILE لكن ليس هذا هدفنا في هذا الدرس.
 
لماذا اسم الهيكل كله majuscule ( أو Upper case )؟ اعتقد أن الأسماء بهذا الشكل حجزناها للثوابت وللمعرّفات define؟؟
هذا النوع من التسمية، أتّبعه شخصياً و يتّبعه الكثير من المبرمجون، و لكن هذا لا يعني أنها قواعد يجب التقيّد بها، و هذا ما تثبته لنا بعض الاسماء المستعملة في مكتبة stdio! لكن أعتقد ان معظم المكتبات تتبّع نفس النظام التي أتّبعه، أي أن اسم الهيكل يبتدؤ فقط بحرف واحد uppercase .
 
لنعد إلى دالتنا fopen، إنها تقوم بارجاع مؤشّر نحو ملف *FILE، إنه من المهم استرجاع هذا المؤشّر كي نعمل لاحقاً على الملف.  و لهذا سنقوم بتعريف مؤشّر نحو ملف، في الدالة الرئيسية main :
 
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    return 0;
}
 
لقد أعطينا القيمة الابتدائية NULL للمؤشّر، و هذا ما نصحتكم به سابقاً، أن تعطوا القيمة NULL لكل مؤشّر قمتم بتعريفه . لأنه إن لم تفعلوا ذلك، قد تخاطرون بوجود بعض لأخطاء لاحقاً في البرنامج .
 
إنه ليس ضرورياً، لتعريف المؤشّر، أن تكتبوا struct FILE* fichier = NULL ، لأنه تم تعريف typedef في الملف stdio.h ( راجعوا درس الهياكل ) . لاحظوا أن شكل الهيكل قد يتغيّر من نظام تشغيل إلى آخر، أي أنها ليست بالضرورة تحمل نفس المتغيرات الداخلية في كل الأنظمة. لهذا فلن نقوم بالتعامل مع الملف بالطريقة التي نتعامل بها مع الهياكل العادية أي fichier.element مثلاُ، بل سنكتفي بنداء دوال، تقوم بالعمل نيابة عناً.
 
الآن سنقوم بنداء الدالة fopen، و سنقوم باسترجاع المؤشر التي تبعثه في المؤشر الذي عرّفناه أي fichier . و قبل هذا يجب أن اشرح لكم كيف نتعامل مع الخاصية الثانية modeOuverture. فهناك من الطرق من تقرأ فقط من خلال الملف، هناك من تكتب فيه و هناك من تقوم بهما معاً، هذه هي طرق الدخول إلى الملف :
- "r" : قراءة فقط read only، يمكنكم قراءة المعلومات من الملف، و لكن لا يمكنكم التعديل عليها، يجب على الملف أن يكون موجوداً من قبل في الحاسوب.
- "w" : كتابة فقط write only، يمكنكم الكتابة في الملف، لكن لا يمكنكم قراءة محتواه، إذا لم يكن الملف موجوداً من قبل، فإنه سيتم إنشاؤه .
- "a" : إضافة add، أي أنكم ستضيفون الكتابة الجديدة في نهاية الملف، و لن تقوموا بلمس محتواه الأصلي، إن لم يكن الملف موجوداً في الجهاز من قبل، سيتم إنشاؤه.
- "r+" قراءة و كتابة read and write : يمكنكم القراءة من خلال الملف، كما بإمكانكم أن تكتبوا فيه، يجب على الملف أن يكون موجوداً من قبل .
- "w+" قراءة و كتابة مع مسح محتوى الملف الأصلي : أي أنه سيتم تفريغ الملف من محتواه أولاً، ثم بإمكانكم الكتابة فيه و قراءة محتواه لاحقاً، إن لم يكن الملف موجوداً من قبل، سيتم إنشاؤه .
- "a+" القراءة و الكتابة في آخر الملف، يمكنكم القراءة و الكتابة في الملف، إنطلاقا من نهاية محتوى الملف الأصلي ، إن لم يكن موجوداً، سيتم إنشاؤه.
 
 الحقيقة أنني قدمت لكم بعض لطرق لدخول ملف، و لم أقدّمها كلّها، لأنه يوجد ضعفها !! في كل الأساليب السابقة لدخول الملفات، لو أضفنا الحرف b بعد الحرف الأول ("rb", "wb", "ab", "rb+", "wb+", "ab+"), فإن الملف سيتم الدخول إليه بأسلوب "الثنائي" أي Binaire أو binary، و هذا أسلوب خاص قليلاً، لن ندرسه الآن. ما ندرسه الآن يكتفي بتخزين نص و هو ما نسميه بأسلوب النصوص ( تخزين مجموعة من الحروف و الرموز ) ، أما أسلوب الـbinary، يسمح بتخزين المعلومات octet بـoctet ( أرقام بشكل أساسي ) و هذا مختلف . على أي حال فطريقة العمل لن تختلف من أسلوب لآخر .
 
شخصياُ، أستعمل كثيراً الأنماط : "r" (قراءة)، "w" (كتابة)،  "r+" (قراءة و كتابة في آن) . أسلوب الدخول إلى الملفات "w+" خطر قليلاً لأنه يقوم بمسح المحتوى الأصلي للملف، بدون أن يسأل المبرمج ما إن كان متأكّدا من القيام بذلك. إن هذا الأسلوب ليس مفيداً إلا إذا أردنا أن نفرغ محتوى الملف بشكل يجعله جديد .
الأسلوب ("a") يمكنه أن يفيد في حالة ما إن كنتم تريدون إضافة محتوى جديد في نهاية الملف.
 
لو كتبتم دالة اسمها chargerNiveau ( لتوليد مستوى في لعبة مثلا )، الأسلوب "r" كافٍ، أما إن أردتم أن تكتبوا دالة enregistrerNiveau (لحفظ المستوى) فستستعملون الأسلوب "w" .
الكود التالي سيفتح الملف test.txt بأسلوب "r+" ( قراءة و كتابة ) :
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    fichier = fopen("test.txt", "r+");
    return 0;
}
 
المؤشّر fichier سيصبح إذا مؤشراً نحو الملف test .
 
أين يجب أن يكون الملف test.txt ؟
يجب أن يكون في نفس المجلّد الذي يتواجد به الملف التنفيذي .exe .
أرجوا منكم أن تقوموا بإنشاء ملف اسمه test.txt في نفس المجلّد الذي به الملف التنفيذي، بالشكل التالي :
 

 
كما ترون فأنا استعمل بيئة التطوير Code::Blocks الأمر الذي يفسّر وجود ملف المشروع  بصيغة .cbp ( على عكس الصيغة .sln إن كنتم تستعملون Visual C++مثلاً )  . حسناً الأمر المهم هو أن الملفان .txt و .exe في نفس المسار.
 
هل يجب أن يكون الملف بصيغة .txt ؟؟
لا، انتم أحرار باختيار صيغة الملف الذي تريدون، أي أنه بإمكانكم أن تختاروا الصيغة ( .niveau ) لتفتحوا الملف الذي تحفظون فيه مستويات اللعبة مثلاُ.
 
هل من الواجب أن يكون الملف الذي نريد فتحه في نفس مسار الملف التنفيذي ؟
لا، يمكنه أن يكون داخل مجلّد بذات المسار:
 
fichier = fopen("dossier/test.txt", "r+");
 
هنا، الملف .txt في مجلّد اسمه dossier، هذه اطريقة التي نسميها استعمال المسار النسبي relatif مستعملة بكثرة. و هذا ما يسمح باشتغال البرنامج أينما كان الملف محفوظاً .  أي أنه بإمكانه ألا يكون حتىّ في ملفات المشروع، و بهذا علينا أن نحدد المسار الفعلي ( نسميه المسار المطلق absolu ) :
 
fichier = fopen("C:\\Program Files\\Notepad++\\readme.txt", "r+");
 
هذا الكود يفتح الملف  readme.txt الموجود بـC:\Program Files\Notepad++.
 
ستلاحظون أنني استعملت اشارتي \ ( نسميه ضد السلاش أو antislash )، و ذلك لأنني لو كتبت اشارة واحدة، سيعتقد المترجم أنني أريد أن استخدم اشارة خاصة (مثل الـn\ أو الـt\ ) فبهذه الطريقة، أوضّح له أنني بالفعل أريد أن يطبع الإشارة \ .
 
المشكل مع المسارات المطلقة، هو أنها لا تعمل إلا مع النظام نفسه، أي أنه لو كنا نعمل على اللينكس، كنا لنكتب المسار التالي مثلا :
 
fichier = fopen("/home/eden/dossier/readme.txt", "r+");
 
لهذا فأنا أنصحكم بكتابة مسارات نسبية، و لا تستعملوا المسارات المطلقة إلا في حالة كان البرنامج مخصص لنظام تشغيل واحد، ليعدّل على خاصية معيّنة في القرص الصلب .
 
لنجرّب فتح الملف :
 
 المؤشّر fichier يجدر به أن يحتوي عنوان الهيكل من نوع FILE،  و الذي نستعمله كواصف للملف. هذا الواصف تم توليده إلى الذاكرة العشوائية من طرف الدالة () fopen. بعد هذا، هناك احتمالين :
- إذا نجحت عملية فتح الملف، سنتمكن من القراءة و الكتابة عليه .
- إذا لم تنجح، أي أن الملف ليس موجوداً بالمسار الذي حددناه أو أنه مفتوح أصلاُ من طرف برنامج آخر، سنتوقف عن العمل.
 
مباشرة بعد فتح الملف، يجب التأكد ما إن تمت العملية بنجاح، و هذا أمر لازم، ضروري و سهل في نفس الوقت، فكما قلت : إذا كانت قيمة المؤشر fichier تساوي NULL، فإن العملية فشلت، إن كانت القيمة لا تساوي NULL فقد تم فتح الملف بنجاح، كالتالي :
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    fichier = fopen("test.txt", "r+");
    if (fichier != NULL)
    {
        // On peut lire et écrire dans le fichier
    }
    else
    {
        // On affiche un message d'erreur si on veut
        printf("Impossible d'ouvrir le fichier test.txt");
    }
    return 0;
}
 
لا تنسوا أن هذا الامر ضروري، فأنتم تخاطرون بوقوع مشاكل في البرنامج إن لم تتحققوا من صحة فتح الملف.
 
fclose لغلق الملف :
 
إذا نجحت عملية فتح الملف، يمكننا الكتابة و القراءة من خلاله، هذا ما سنراه لاحقاً في الدرس، و ما إن نكمل العمل على الملف، يجب علينا غلقه، باستعمال الدالة fclose التي ستقوم بتحرير الخانات التي تم حجزها في الذاكرة . أو بشكل آخر، سيتم حذف الملف من الذاكرة العشوائية RAM ( لا تخافوا هه، فهو موجود بالذاكرة الدائمة أي بالقرص الصلب ) . نموذج الدالة :
 
 
int fclose(FILE* pointeurSurFichier);
 
هذه الدالة تأخذ خاصية واحدة، و هي المؤشر نحو الملف الذي نعمل عليه . و الدالة تقوم بإرجاع عدد طبيعي int، و الذي يأخذ القيم :
- 0 إذا تم غلق الملف ينجاح .
- EOF إذا لم يتم غلقه بنجاح، EOF تعني end of file، و هي عبارة عن معرّفة define موجودة بالمكتبة stdio و هي توافق رقماً خاصاً يُستعمل للقول أنه تم إيجاد مشكل أثناء غلق الملف، أو أننا وصلنا إلى نهاية الملف، في حالتنا هذه ، الرقم يعني تم إيجاد مشكل أثناء غلق الملف .
 
في غالب الأحيان، تنجح عملية غلق الملف، و هذا ما يدفعني إلى عدم اختبار القيمة التي تًرجعها لنا الدالة fclose. يمكنكم فعل ذلك على أي حال .
لنغلق الملف، سنكتب إذا :
 
fclose(fichier);
 
سيصبح الكود الكامل بهذا الشكل:
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    fichier = fopen("test.txt", "r+");
    if (fichier != NULL)
    {
        // On lit et on écrit dans le fichier
       
        // ...
       
        fclose(fichier); // On ferme le fichier qui a été ouvert
    }
    return 0;
}
 
لم استعمل else لأظهر رسالة خطأ في حال ما لم تنجح عملية الملف، يمكنكم فعل ذلك إن أردتم .
يجب أن ترجي العادة و تغلقوا الملف الذي فتحتموه . هذا سيسمح بتحرير الذاكرة، أي أنه إن لم نغلقه قد يأخذ حجما كبيراً و غير مستعمل في الذاكرة، قد يبدو الأمر غير خطر مع برامج بسيطة، لكن مع برامج كبيرة و معقدة سنقول مرحباً للمشاكل !
 
طرق مختلفة للقراءة و الكتابة في الملفات :
و الآن مادمنا تعلّمنا كيف نفتح و نغلق ملفا، لم يبق سوى  أن نضيف بعد الكود لنتعلم كيفية الكتابة و القراءة من خلال الملفات.
 
الكتابة في ملف:
 
توجد الكثير من الدوال التي تسمح بالكتابة في الملفات، يبقى عليكم أن تختاروا أيها الأنسب لكم لتستخدموها ، هناك ثلاث منها :
- fputc : تكتب حرفاً واحداً في كل مرة .
- fputs : تكتب نصاُ في لملف.
- fprintf : تكتب نصاً منسًقا بنفس الطريقة التي تعمل بها الدالة printf .
 
fputc :
هذه الدالة تسمح كما قلت بكتابة حرف واحد في الملف، نموذجها :
 
int fputc(int caractere, FILE* pointeurSurFichier);
 
و هي تأخذ خاصيتين :
- الحرف الذي نريد أن نكتبه، من نمط int، مثلما قلت فاستعماله يعود تقريباً إلى استعمال نمط char، إلا ان عدد الأحرف الذي يمكن أن نكتبها بنمط char أكبر. يمكنكم إذا ان تكتبوا مباشرة 'A' كمثال .
- المؤشّر نحو الملف الذي نريد أن نكتب فيه، في مثالنا، المؤشّر اسمه fichier، استعمال المؤشّر في كلّ مرة يساعدنا لأنه بإمكاننا أن نفتح العديد من الملفات في آن واحد، و نفرّق بينها باسماء المؤشّرات التي تؤشّر نحوها .
 
الدالة تقوم بإرجاع عدد طبيعي int، و هو كود الأخطاء، و هو يساوي EOF إذا فشلنا في الكتابة في الملف، أما إن نجحنا في الكتابة في الملف، فسيأخذ قيمة أخرى.
غالبا ما تنجح الكتابة في الملفات، لذا فانا لا أختبر القيمة التي تُرجعها الدالة fputc ، يمكنكم فعل ذلك إن أردتم.
 
الكود التالي يسمح بكتابة الحرف 'A' في الملف test.txt (إذا كان موجوداً من قبل فإنه سيتم التعديل عليه و إم لم يكن موجوداً سيتم إنشاؤه) ، و الكود يحتوي كل الخطوات التي تكلّمنا عليها سابقاً : فتح الملف، اختبار الفتح، كتابة ثم غلق:
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        fputc('A', fichier); // Écriture du caractère A
        fclose(fichier);
    }
 
    return 0;
}
 
 
افتحوا يدويا الملف test.txt ، ماذا ترون ؟
إن الأمر رائع، الملف يحتوي الآن على الحرف 'A' :
 

 
fputs :
 
هذه الدالة شبيهة جداً بالدالة fputc، إلا انها تسمح بكتابة نص كامل، و هذا أحسن من الكتابة حرفاً حرفاً، لكن أحيانا نحتاج إدخال حرف واحد في ملف، فنستعمل الدالة السابقة، نموذج هذه الدالة :
 
char* fputs(const char* chaine, FILE* pointeurSurFichier);
 
الخاصيتين سهلتين للفهم :
- chaine : النص الذي نريد كتابته، تلحظون أن النمط هنا هو  *const char ، الكلمة المفتاحية const تشير إلى أن النص الذي سنعطيه للدالة، ثابت و لن تقوم الدالة بتغييرةه، هذا منطقي لأننا نريد منها أن تكتب النص في الملف، و لا داعي للتعديل عليه، نستعمل الكلمة المفتاحية const من أجل أسباب حماية.
- pointeurSurFichier : مثال الدالة السابقة، تحتاج هذه الدالة إلى مؤشر نحو الملف الذي نريد أن نعمل عليه .
 
القيمة EOF سيتم ارجاعها من طرف الدالة في حالة وجود خطأ، و لأنه غالبا ما تنجح عملية ادخال النصوص في ملفات، لن أقوم باختبار القيمة التي ترجعها الدالة EOF.
 
فلنجرب الكود :
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        fputs("Salut mes amis\nComment allez vous ?", fichier);
        fclose(fichier);
    }
 
    return 0;
}
 
هذا ما يبدو عليه الملف بعد التعديل عليه من طرف البرنامج :
 

 
fprintf :
 
إليكم نوعاً آخراً من الدالة printf، و الذي يسمح بإدخال نصوص في ملف، و هذه الدالة تستعمل بنفس الطريقة التي نستعمل بها printf، إلا أنه يجب إعطاؤها المؤشر نحو الملف كخاصية أولى، الكود التالي يطلب من لمستخدم إدخال عمره، و هو يقوم بوضعه في الملف :
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int age = 0;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        // On demande l'âge
        printf("Quel age avez-vous ? ");
        scanf("%d", &age);
 
        // On l'écrit dans le fichier
        fprintf(fichier, "Le Monsieur qui utilise le programme, il a %d ans", age);
        fclose(fichier);
    }
 
    return 0;
}
 
النتيجة :
 

 
يمكنكم إذا استعمال مهاراتكم الذي تعرفونها عن printf مع هذه الدالة، على أي حال أنا غالبا ما استعمل fprintf للكتابة في الملفّات .
 
القراءة من خلال ملف :
 
 لدينا أيضاً ثلاث دوال للقراءى من خلال ملف، اسمها مختلف قليلاً فقط على دوال الكتابة :
- fgetc : قراءة حرف واحد .
- fgets : قراءة نص كامل .
-fscanf : قراءة نص منسّق.
 
إذا كنتم قد فهمتم الثلاث الدوال السابقة، فلن تجدوا أي مشكل للتعامل مع هذه الدوال، لهذا سأسرع قليلاً في شرح هذه الدوال .
 
fgetc :
النموذج اولاً :
 
int fgetc(FILE* pointeurDeFichier);
 
هذه الدالة تقوم بإرجاع int ، و هو الحرف الذي قامت بقراءته، سترجع إلينا القيمة EOF إذا لم تستطع القراءة .
 
لكن كيف لنا أن نعرف الحرف الذي نقرؤه ؟ ماذا لو أردنا قراءة الحرف الثالث ثم الرابع ؟
في الواقع، في كلّ مرة تقرؤون فيها حرفاً، فهناك مؤشر ( مثل مؤشّر محرر النصوص ) الذي يتحرّك في كلّ مرة، و هذا المؤشّر افتراضي طبعاً، لن تتمكنوا من رؤيته على الشاشة، لكنه موجود فعلاً. و هو يشير إلى أين وصلنا في قراءة الملف.
 
سنتعلم لاحقاً كيف نعرف الوضعية التي وصل إليها المؤشّر بالظبط، و ذلك لكي نقوم بتحريكه إلى بداية الملف أو إلى جانب الحرف العاشر مثلاُ .
fgetc تقوم بتحريك المؤشر بوضعية واحدة في كلّ مرة نقرؤ فيها حرفاً. أي انكم إن ناديتم fgetc مرة ثانية، فستقرؤون الحرف الثاني، ثم الثالث و هكذا ... و بهذا يمكنكم استعمال حلقة تكرارية لقراءة الملف حرفاً حرفاً .
هذا ما سنقوم به في الكود التالي، الحلقة ستتوقف حينما ترجع إلينا الدالة fgetc  القيمة EOF أي End of file أي نهاية الملف.
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int caractereActuel = 0;
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        // Boucle de lecture des caractères un à un
        do
        {
            caractereActuel = fgetc(fichier); // On lit le caractère
            printf("%c", caractereActuel); // On l'affiche
        } while (caractereActuel != EOF); // On continue tant que fgetc n'a pas retourné EOF (fin de fichier)
 
        fclose(fichier);
    }
 
    return 0;
}
 
 
الكونسول ستقوم بإظهار محتوى الملف كاملاً، كالتالي :
 
Coucou, je suis le contenu du fichier test.txt !
 
fgets :
هذه الدالة تقوم بقراءة نص من خلال ملف، و هذا لتجنب قراءة الملف حرفاً حرفاً، الدالة تقرؤ على الأكثر سطراً واحداً ( ستتوقف عن القراءة حينما تجد اول  n\ في طريقها ) ، إن أردتم قراءة العديد من الأسطر، عليكم استعمال حلقة .
هذا نموذج الدالة :
 
char* fgets(char* chaine, int nbreDeCaracteresALire, FILE*
pointeurSurFichier);
 
هذه الدالة تتطلب خاصية استثنائية نوعاً ما، هذه الخاصية هي : عدد الأحرف التي نريد قراءتها ، هذا ما يسمح للدالة fgets بالتوقف إذا تجاوزت العدد x من الأحرف.
الفائدة : هذا يسمح لنا بألا نتجاوز محتوى الملف إلى خانات أخرى في الذاكرة ! إذا كان حجم السطر أكبر من حجم المتغيرة من نمط *char ( التي ستستقبل السطر المقروء ) فهذا قد يؤدّي إلى مشاكل و بالتالي توقف عمل البرنامج .
 
سنتعلّم كيف نقرأ سطراً واحداً من ملف، ثم كيفية قراءة الملف كاملاً . و لهذا فسنقوم بتعريف متغيرة من نمط *char أي من نمط "نص"، لتتقبل السطر الذي تُرجعه الدالة، الحقيقة لا يمكننا معرفة حجم السطر، لكن سنحاول أن يكون حجم متغيرتنا كبيراً كفاية، و هنا سترون فائدة الـdefine أي المعرفات في تعريف حجم جدول :
 
#define TAILLE_MAX 1000 // Tableau de taille 1000
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    char chaine[TAILLE_MAX] = ""; // Chaîne vide de taille
TAILLE_MAX
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        fgets(chaine, TAILLE_MAX, fichier); // On lit maximum
TAILLE_MAX caractères du fichier, on stocke le tout dans "chaine"
        printf("%s", chaine); // On affiche la chaîne
 
        fclose(fichier);
    }
 
    return 0;
}
 
 
 النتيجة هي نفسها النتيجة السابقة :
 
Coucou, je suis le contenu du fichier test.txt !
 
الفرق هو أننا هنا لم نستعمل حلقة تكرارية و إنما سنقوم بإسترجاع محتوى الملف كاملا في مرّة.
و تلاحظون أيضاً فائدة استعمال المعرّفات لتعريف حجم جدول مثلاً، و حقيقة الثابتة TAILLE_MAX مستعمله في مكانين مختلفين بالكود :
- المرة الأولى لتعريف حجم الجدول الذي نريد إنشاؤه.
- مرّة اخرى في الـfgets لنقوم بحدّ عدد الأحرف التي نقرؤها .
في حال ما وجدتم أن المتغيرة من نمط نص ( أو جدول حروف ) غير كافية لاستقبال السطر، ما عليكم سوى تعديل قيمتها من الـdefine و سيتم استبدال كل تكرار لـTAILLE_MAX  بالرقم الذي يوافقها .
 
كما قلت فإن fgets تقرأ على الأكثر سطراً واحدا، و هي تتوقف عندما تصل إلى عدد الأحرف الذي سمحتم لها به بقراءته .
 
الآن و نحن نجيد قراءة سطر واحد، كيف لنا أن نقرأ كل الملف ؟
هذا بسيط، فلنستعمل حلقة تكرارية !
 
الدالة fgets تقوم بإرجاع القيمة NULL في حالة لم تستطع قراءة ما طلبته منها. أي أننا يجب أن نخرج من الحلقة في الحين الذي ترجع لنا الدالة القيمة NULL . هذا يعود إلى استعمال الحلقة while لكي نقوم بتكرار القراءة ما دامت القيمة التي ترجعها الدالة مختلفة عن NULL .
 
#define TAILLE_MAX 1000
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    char chaine[TAILLE_MAX] = "";
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        while (fgets(chaine, TAILLE_MAX, fichier) != NULL) // On lit le fichier tant qu'on ne reçoit pas d'erreur (NULL)
         {
            printf("%s", chaine); // On affiche la chaîne qu'on
vient de lire
          }
 
        fclose(fichier);
    }
 
    return 0;
}
 
هذا الكود يقوم بقراءة الملف سطراً سطراً و إظهار الأسطر .
السطر الأكثر لفتاً للانتباه في الكود هو :
 
while (fgets(chaine, TAILLE_MAX, fichier) != NULL)
 
الحلقة while تقوم بأمرين : قراءة سطر ثم التأكد ما إن كانت القيمة المُرجعة هي NULL  و كأنها تقول : إقرأ سطراً جديداً ما دمنا لم نصل إلى نهاية الملف.
 
fscanf :
هذه الدالة مشابهة تماماً لنظيرتها scanf ، فهي تقوم بقراءة نص تمت كتابته بشكل مختلف.
لنفترض أن لملف يحتيو على ثلاثة أرقام مفصولة بفراغ space ( لنقل أنها تدلّ على أكبر 3 مجموع نقاط تم التحصل عليها في اللعبة )
انتم تريدون أن تسترجعوا كلّ من هذه الأرقام في متغيرات من نمط int . إذا فالدالة fscanf ستسمح لكم بالقيام بهذا بشكل سريع :
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int score[3] = {0}; // Tableau des 3 meilleurs scores
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        fscanf(fichier, "%d %d %d", &score[0], &score[1],
&score[2]);
        printf("Les meilleurs scores sont : %d, %d et %d", score[0],
score[1], score[2]);
 
        fclose(fichier);
    }
 
    return 0;
}
 
النتيجة :
 
Les meilleurs scores sont : 15, 20 et 30
 
كما ترون فالدالة fscanf تنتظر ثلاث أرقام مفصولة بفراغ ("%d %d d%") و هي ستقوم بتخزينهم في الجدول ذو الخانات الثلاث . ثم نقوم لاحقاً بإظهار القيم على الشاشة .
 
أعتقد أنني سابقاً في الدروس لم استعمل سوى رمز d% واحد، الآن أخبركم أنه بإمكانكم أن تستعملوا العديد منها بقدر ما تشاؤون فهذ يرجع لعدد الحروف التي تتعاملون معها .
 
التحرك داخل ملف :
 
 كنت قد كلّمتكم قبل قليل عن وجود مؤشّر افتراضي Virtul cursor. سنقوم الآن بدراسته بشكل أكثر تفصيلاً .
في كلّ مرة تفتحون فيها ملفا، فهنام مؤشّر يشير إلى وضعيتكم في الملف، و لتتخيّلوه مثل مؤشر محرر النصوص Notepad++ مثلاُ .
على أي حال فنظام المؤشر يسمح لكم بالكتابة و القراءة من خلال ملف إنطلاقا من وضعية محددة. توجد ثلاث دوال لتتعرفوا عليها :
- ftell : ترجع إلينا الوضعية التي نحن بها حالياً في الملف.
- fseek : تـُموضع المؤشّر في مكان محدد .
- rewind : تقوم بإرجاع المؤشّر إلى بداية الملف، و استعمالها مكافئ لنداء الدالة fseek و تحديد البداية كموقع انطلاق.
 
 
ftell ، موضعنا في الملف :
هذه الدالة سهلة الاستعمال، غنها تقوم بتحديد الموضع الذي نتواجد به حاليا في الملف :
 
long ftell(FILE* pointeurSurFichier);
 
القيمة التي يتم ارجاعها توافق الموضع الحالي، و هي من نمط long.
 
fseek ، التحرك داخل الملف :
 
هذا نموذج الدالة :
 
int fseek(FILE* pointeurSurFichier, long deplacement, int origine);
 
 fseek تسمح بتحريك المؤشّر بـعدد من الوضعيات ( المتغيرة deplacement ) إنطلاقا من الوضعية origine .
الرقم deplacement يمكن له أن يكون رقماً موجباً ( للتقدم إلى الأمام ) أو سالباً ( للرجوع إلى الخلف ) . بينما الرقم origine يأخذ إحدى القيم التالية :
- SEEK_SET تعني بداية الملف .
- SEEK_CUR تعني الموضع الحالي نفسه .
- SEEK_END تعني نهاية الملف. 
 
إليكم بعض الأمثلة التي توضّح لكم كيف نستعمل هذه الثوابت لتغيير موضعنا في الملف :
 
هذا الكود سيسمح بتحريك المؤشر وضعيتين انطلاقا من بداية الملف :
 
fseek(fichier, 2, SEEK_SET);
 
هذا الكود يسمح بتحريك المؤشّر أربع وضعيات للخلف انطلاقا من الوضعية الحالية :
 
fseek(fichier, -4, SEEK_CUR);
 
لاحظوا أن قيمة deplacement سالبة لأننا نرجع إلى الوراء .
 
الكود التالي يحرّك المؤشّر إلى نهاية الملف :
 
fseek(fichier, 0, SEEK_END);
 
إذا حركتم المؤشر إلى نهاية الملف و كتبتم، فذلك سيضيف إلى محتوى الملف في آخره، بينما إن حركتموه إلى بداية الملف فإن محتوى الملف إنطلاقا من البداية سيتم استبداله بالنص الجديد!! هذا هو الحال، إلا إن كنتم قد برمجتم دالة تقوم بحفظ الأحرف قبل مسحها، ثم لاحقاً إعادة كتابتها !
 
لكن كيف لي أن أعرف أين يجب أن أتموضع في الملف؟
هذا يعود إليكم، فإن كان ملفاً قمتم أنتم بإنشاءه، فأنتم تعرفون كيف تتحركون بداخله، كأن قمتم بضع أحسن النتائج المسجلة في اللعبة في الوضعية 0 و أسوؤها في الوضعية 50 .. الخ .
 
في العمل التطبيقي الذي سنقوم به لاحقاً في الدورة، ستتعلمون كيف تقومون بإحضار معلومات من ملف لستم أنتم من قام بإنشاءه .
على أي حال، إن كنتم انتم من قام بإنشاء ملف، عليكم تذكّر المواضع التي وضعتم بها كلّ معلومة .
 
الدالة fseek تتعامل بشكل غريب مع الملفات المفتوحة بأسلوب النصوص Text mode، فالحقيقة أننا نستعملها بشكل أكثر مع الملفات المفتوحة بالنظام الثنائي binary . و الغلب أننا نقرأ الملفات بأسلوب النصوص Text mode حرفاً حرفاً و الشيئ الذي نسمح به غالبا مع أسلوب فتح الملفات هذا مع الدالة fseek، هو ارجاع المؤشر إلى بداية أو إلى نهاية الملف فقط.
 
rewind، الرجوع إلى البداية :
هذه الدالة مشابهة للدالة fseek من ناحية ارجاعنا إلى بداية الملف :
 
void rewind(FILE* pointeurSurFichier);
 
استعمال الدالة سهل و هذا ما يبيّنه لنا نموذج الدالة، أنتم لستم بحاجة إلى شرح هذه النقطة .
 
إعادة تسميه و مسح ملف :
 
سننتهي من هذا الدرس بتقديم دالتين سهلتين للغاية :
- rename : لإعادة تسمية ملف .
- remove : مسح ملف .
 
الشيء الاستثنائي في هاتين الدالتين هو أنهما لا تحتاجان مؤشراً نح والملف المعني و إنما فقط اسم الملف الذي نريد حذفه أو تعديل اسمه .
 
rename، إعادة تسمية ملف :
هذا نموذج الدالة :
 
int rename(const char* ancienNom, const char* nouveauNom);
 
الدالة ستقوم بإرجاع القيمة 0 إذا تجحت في اعادة تسمية لملف،  و إن لم تنجح فستعطي اي قيمة أخرى :
 
int main(int argc, char *argv[])
{
    rename("test.txt", "test_renomme.txt");
    return 0;
}
 
remove، حذف ملف :
 
هذه الدالة تقوم بحذف ملف دون ترك أي مخلّفات :
 
int remove(const char* fichierASupprimer);
 
انتبهوا جداً عند استعمالكم لهذه الدالة ! لأنها لن تطلب منكم ما إن كنتم تودّون فعلاً حذف الملف! و كما قت فهي لن تترك له أثراً في سلة المحذوفات، يمكنكم استعادته باستعمال طرق أخرى طويلة و ليس هذا ما نريده .
 
هذا الكود سيسمح لنا بمسح الملف الذي أنشأناه سابقاً، و بهذا ننهي هذا الدرس :
 
int main(int argc, char *argv[])
{
    remove("test.txt");
    return 0;
}
 الجزء 2 :¦- دورة تعليم الـC -¦: الدرس 7 -¦: القراءة و الكتابة في الملفات
ط·آ¨ط·آ¯ط·آ§ط¸ظ¹ط·آ©
ط·آ§ط¸â€‍ط·آµط¸ظ¾ط·آ­ط·آ©